java 编码规范总结

sancaiodm Java 2021-09-14 2007 0


1、把 Array 转成 ArrayList

说实在的,很多 Java 程序员喜欢把 Array 转成 ArrayList:

List  <String > list  = Arrays . asList (arr ) ;

   但实际上, Arrays.asList() 返回的 ArrayList 并不是  java.util.ArrayList,而是 Arrays 的内部私有类  java.util.Arrays.ArrayList。虽然名字完全相同,都是  ArrayList,但两个类有着很大的不同。         Arrays.ArrayList 虽然有  set()、 get() 和  contains() 等方法,但却没有一个方法用来添加元素,因此它的大小是固定的。

  如果想创建一个真正的  ArrayList,需要这样做:    

List  <String > list  =  new  ArrayList  <String > (Arrays . asList (arr ) ) ;

ArrayList 的构造方法可以接收一个 Collection 类型的参数,而  Arrays.ArrayList 是其子类,所以可以这样转化。

方法2:

    List<String> list = Arrays.asList(arr);

    List<String> newList = new ArrayList<>();

    newList.addAll(list);

    newList.add(key,value);

02、通过 Set 检查数组中是否包含某个值

之前我在写一篇文章《如何检查Java数组中是否包含某个值 》中曾提到一种方法:    

Set  <String > set  =  new  HashSet  <String > (Arrays . asList (arr ) ) ;
return set . contains (targetValue ) ;

这种方法确实可行,但却忽视了性能问题;为了能够尽快完成检查,可以这样做:    

Arrays . asList (arr ) . contains (targetValue ) ;

或者使用普通的 for 循环或者 for-each。

03、通过 for 循环删除列表中的元素

新手特列喜欢使用 for 循环删除列表中的元素,就像这样:    

List  <String > list  =  new  ArrayList  <String > (Arrays . asList ( "沉" ,  "默" ,  "王" ,  "二" ) ) ;
for  ( int i  =  0 ; i  < list . size ( ) ; i ++ )  {
    list . remove (i ) ;
}
System .out . println (list ) ;

   上面这段代码的目的是把列表中的元素全部删除,但结果呢:   [默 , 二 ]
   竟然还有两个元素没删除,why?
   当 List 的元素被删除时,其  size() 会减小,元素的下标也会改变,所以想通过 for 循环删除元素是行不通的。
    那 for-each 呢?    

  for (String s  : list )  {
     if  ( "沉" . equals (s ) )  {
       list . remove (s ) ;
     }
}
System .out . println (list ) ;

竟然还抛出异常了:    

Exception in thread  "main" java .util .ConcurrentModificationException
at java .util .ArrayList$Itr . checkForComodification (ArrayList .java : 909 )
at java .util .ArrayList$Itr . next (ArrayList .java : 859 )
at com .cmower .java_demo .programcreek .Top10Mistake . main (Top10Mistake .java : 15 )

 抛出异常的原因,可以查看我之前写的文章《Java,你告诉我 fail-fast 是什么鬼?》。

  有经验的程序员应该已经知道答案了,使用 Iterator:    

Iterator  <String > iter  = list . iterator ( ) ;
while  (iter . hasNext ( ) )  {
    String s  = iter . next ( ) ;

     if  (s . equals ( "沉" ) )  {
        iter . remove ( ) ;
     }
}
System .out . println (list ) ;

程序输出的结果如下:[默 , 王 , 二 ]

04、使用 Hashtable 而不是 HashMap

通常来说,哈希表应该是 Hashtable,但在 Java 中,哈希表通常指的是 HashMap。两者之间的区别之一是 Hashtable 是线程安全的。如果没有特殊要求的话,哈希表应该使用 HashMap 而不是 Hashtable。

05、使用原始类型

在 Java 中,新手很容易混淆无限通配符和原始类型之间的差别。举例来说, List<?> list 为无限通配符, List  list 为原始类型。
来看下面这段代码:

  public  static  void  add (List list , Object o ) {
    list . add (o ) ;
}
public  static  void  main (String [ ] args ) {
    List  <String > list  =  new  ArrayList  <String > ( ) ;
     add (list ,  18 ) ;
     add (list ,  "沉默王二" ) ;
    String s  = list . get ( 0 ) ;
}

这段代码在运行时会抛出异常:  

Exception in thread  "main" java .lang .ClassCastException : java .lang .Integer cannot be cast to java .lang .String
at com .cmower .java_demo .programcreek .Top10Mistake . main (Top10Mistake .java : 38 )

 使用原始类型非常的危险,因为跳过了泛型的检查。至于  List<Object> 和  List 之间的区别,查看我写的另外一篇文章:《为什么不应该使用Java的原始类型》。

06、使用 public 修饰字段

有些新手喜欢使用 public 修饰字段,因为不需要  getter/setter 方法就可以访问字段。但实际上,这是一个非常糟糕的设计;有经验的程序员更习惯于提供尽可能低的访问级别。

07、使用 ArrayList 而不是 LinkedList

新手往往搞不清楚 ArrayList 和 LinkedList 之间的区别,因此更倾向于使用 ArrayList,因为比较面熟。但是呢,它们之间存在巨大的性能差异。简单的说吧,如果“添加/删除”的操作比较多,而“获取”的操作比较少,则应该首选 LinkedList。

08、使用过多的不可变对象

不可变对象有着不少的优点,比如说简单性和安全性。但是呢,如你所料,它也有一些难以抗拒的弊端:对于每一个不同的值,它都需要一个单独的对象来表示,这样的对象太多的话,很可能会导致大量的垃圾,回收的成本就变得特别高。

     为了在可变与不可变之间保持平衡,通常会使用可变对象来避免产生太多中间对象。一个经典的例子就是使用 StringBuilder(可变对象) 来连接大量的字符串,否则的话,String(不可变对象)会产生很多要回收的垃圾。
反例:    

  String result = "" ;
for (String s : arr ) {
result  = result  + s ;
}

正例:    

  StringBuilder result  =  new  StringBuilder ( ) ;
for  (String s : strs )  {
result . append (s ) ;
}

参考文章:为什么 Java 字符串是不可变的?

09、父类没有默认的无参构造方法

在 Java 中,如果父类没有定义构造方法,则编译器会默认插入一个无参的构造方法;但如果在父类中定义了构造方法,则编译器不会再插入无参构造方法。所以下面的代码会在编译时出错。
子类中的无参构造方法试图调用父类的无参构造方法,但父类中并未定义,因此编译出错了。解决方案就是在父类中定义无参构造方法。

10、使用构造方法创建字符串

创建字符串有两种方法:
     1)使用双引号    

String er  =  "沉默王二" ;

  2)使用构造方法    

String san  =  new  String ( "沉默王三" ) ;


  但是它们之间有着很大的不同(可以参照创建 Java 字符串,用""还是构造函数),双引号被称为字符串常量,可以避免重复内容的字符串在内存中创建。
原文链接:https://blog.csdn.net/qing_gee/article/details/103867740


【0】删除未使用的局部变量、方法参数、私有方法、字段和多余的括号。  

【1】当循环中只需要获取Map 的主键key时,迭代keySet() 是正确的;但是,当需要主键key 和取值value 时,迭代entrySet() 才是更高效的做法,其比先迭代keySet() 后再去通过get 取值性能更佳。

          //Map 获取value 反例:

          HashMap<String, String> map = new HashMap<>();

          for (String key : map.keySet()){

                 String value = map.get(key);

          }

          //Map 获取key & value 正例:

          HashMap<String, String> map = new HashMap<>();

          for (Map.Entry<String,String> entry : map.entrySet()){

                  String key = entry.getKey();

                  String value = entry.getValue();

          }

【2】使用Collection.isEmpty() 检测空

    使用Collection.size() 来检测是否为空在逻辑上没有问题,但是使用Collection.isEmpty() 使得代码更易读,并且可以获得更好的性能;除此之外,任何Collection.isEmpty() 实现的时间复杂度都是O(1) ,不需要多次循环遍历,但是某些通过Collection.size() 方法实现的时间复杂度可能是O(n)。O(1)纬度减少循环次数 例子

反例:

          LinkedList<Object> collection = new LinkedList<>();

          if (collection.size() == 0){

              System.out.println("collection is empty.");

          }

正例:

      LinkedList<Object> collection = new LinkedList<>();

        if (collection.isEmpty()){

            System.out.println("collection is empty.");

        }

        //检测是否为null 可以使用CollectionUtils.isEmpty()

        if (CollectionUtils.isEmpty(collection)){

            System.out.println("collection is null.");


        }

【3】初始化集合时尽量指定其大小

      尽量在初始化时指定集合的大小,能有效减少集合的扩容次数,因为集合每次扩容的时间复杂度很可能时O(n),耗费时间和性能。

反例: 

        //初始化list,往list 中添加元素反例:

          int[] arr = new int[]{1,2,3,4};

          List<Integer> list = new ArrayList<>();

          for (int i : arr){

              list.add(i);

          }

正例:

       //初始化list,往list 中添加元素正例:

        int[] arr = new int[]{1,2,3,4};

        //指定集合list 的容量大小

        List<Integer> list = new ArrayList<>(arr.length);

        for (int i : arr){

            list.add(i);

        }

【4】使用StringBuilder 拼接字符串

      一般的字符串拼接在编译期Java 会对其进行优化,但是在循环中字符串的拼接Java 编译期无法执行优化,所以需要使用StringBuilder 进行替换。

反例:

   //在循环中拼接字符串反例

         String str = "";

         for (int i = 0; i < 10; i++){

             //在循环中字符串拼接Java 不会对其进行优化

             str += i;

         }

正例:

    //在循环中拼接字符串正例

         String str1 = "Love";

         String str2 = "Courage";

         String strConcat = str1 + str2;  //Java 编译器会对该普通模式的字符串拼接进行优化

          StringBuilder sb = new StringBuilder();

          for (int i = 0; i < 10; i++){

             //在循环中,Java 编译器无法进行优化,所以要手动使用StringBuilder

              sb.append(i);

          }

 【5】若需频繁调用Collection.contains 方法则使用Set

      在Java 集合类库中,List的contains 方法普遍时间复杂度为O(n),若代码中需要频繁调用contains 方法查找数据则先将集合list 转换成HashSet 实现,将O(n) 的时间复杂度将为O(1)。

反例:

          //频繁调用Collection.contains() 反例

          List<Object> list = new ArrayList<>();

          for (int i = 0; i <= Integer.MAX_VALUE; i++){

              //时间复杂度为O(n)

              if (list.contains(i))

              System.out.println("list contains "+ i);

          }

正例:

     //频繁调用Collection.contains() 正例

          List<Object> list = new ArrayList<>();

         Set<Object> set = new HashSet<>();

          for (int i = 0; i <= Integer.MAX_VALUE; i++){

              //时间复杂度为O(1)

              if (set.contains(i)){

                  System.out.println("list contains "+ i);

              }

          }

【6】使用静态代码块实现赋值静态成员变量

    对于集合类型的静态成员变量,应该使用静态代码块赋值,而不是使用集合实现来赋值。

反例:

 1 //赋值静态成员变量反例

 2     private static Map<String, Integer> map = new HashMap<String, Integer>(){

 3         {

 4             map.put("Leo",1);

 5             map.put("Family-loving",2);

 6             map.put("Cold on the out side passionate on the inside",3);

 7         }

 8     };

 9     private static List<String> list = new ArrayList<>(){

10         {

11             list.add("Sagittarius");

12             list.add("Charming");

13             list.add("Perfectionist");

14         }

15     };

正例:

 1  //赋值静态成员变量正例

 2     private static Map<String, Integer> map = new HashMap<String, Integer>();

 3         static {

 4             map.put("Leo",1);

 5             map.put("Family-loving",2);

 6             map.put("Cold on the out side passionate on the inside",3);

 7         }

 8         

 9     private static List<String> list = new ArrayList<>();

10         static {

11             list.add("Sagittarius");

12             list.add("Charming");

13             list.add("Perfectionist");

14         }


【7】工具类中屏蔽构造函数

      工具类是一堆静态字段和函数的集合,其不应该被实例化;但是,Java 为每个没有明确定义构造函数的类添加了一个隐式公有构造函数,为了避免不必要的实例化,应该显式定义私有构造函数来屏蔽这个隐式公有构造函数。

反例:

1 public class PasswordUtils {

2     //工具类构造函数反例

3     private static final Logger LOG = LoggerFactory.getLogger(PasswordUtils.class);

5     public static final String DEFAULT_CRYPT_ALGO = "PBEWithMD5AndDES";

7     public static String encryptPassword(String aPassword) throws IOException {

8         return new PasswordUtils(aPassword).encrypt();

9     }

正例:

 1 public class PasswordUtils {

 2     //工具类构造函数正例

 3     private static final Logger LOG = LoggerFactory.getLogger(PasswordUtils.class);

 4 

 5     //定义私有构造函数来屏蔽这个隐式公有构造函数

 6     private PasswordUtils(){}

 7     

 8     public static final String DEFAULT_CRYPT_ALGO = "PBEWithMD5AndDES";

 9 

10     public static String encryptPassword(String aPassword) throws IOException {

11         return new PasswordUtils(aPassword).encrypt();

12     }


【8】删除多余的异常捕获并抛出

     用catch 语句捕获异常后,若什么也不进行处理,就只是让异常重新抛出,这跟不捕获异常的效果一样,可以删除这块代码或添加别的处理。

反例:

 2     private static String fileReader(String fileName)throws IOException{

 4         try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {

 5             String line;

 6             StringBuilder builder = new StringBuilder();

 7             while ((line = reader.readLine()) != null) {

 8                 builder.append(line);

 9             }

10             return builder.toString();

11         } catch (Exception e) {

12             //仅仅是重复抛异常 未作任何处理

13             throw e;

14         }

15     }

正例:

 2     private static String fileReader(String fileName)throws IOException{

 4         try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {

 5             String line;

 6             StringBuilder builder = new StringBuilder();

 7             while ((line = reader.readLine()) != null) {

 8                 builder.append(line);

 9             }

10             return builder.toString();

11             //删除多余的抛异常,或增加其他处理:

12             /*catch (Exception e) {

13                 return "fileReader exception";

14             }*/

15         }

16     }


【9】字符串转化使用String.valueOf(value) 代替 " " + value

      把其它对象或类型转化为字符串时,使用String.valueOf(value) 比 ""+value 的效率更高。

反例:

1 //把其它对象或类型转化为字符串反例:

2             int num = 520;

3             // "" + value

4             String strLove = "" + num;

正例:

1 //把其它对象或类型转化为字符串正例:

2             int num = 520;

3             // String.valueOf() 效率更高

4             String strLove = String.valueOf(num);


【5】避免使用BigDecimal(double)

      BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。

反例: 

2         BigDecimal bigDecimal = new BigDecimal(0.11D);

正例:

2         BigDecimal bigDecimal1 = bigDecimal.valueOf(0.11D);


【10】返回空数组和集合而非 null

      若程序运行返回null,需要调用方强制检测null,否则就会抛出空指针异常;返回空数组或空集合,有效地避免了调用方因为未检测null 而抛出空指针异常的情况,还可以删除调用方检测null 的语句使代码更简洁。

反例:

 1  //返回null 反例

 2     public static Result[] getResults() {

 3         return null;

 4     }

 5 

 6     public static List<Result> getResultList() {

 7         return null;

 8     }

 9 

10     public static Map<String, Result> getResultMap() {

11         return null;

12     }

正例:

 1  //返回空数组和空集正例

 2     public static Result[] getResults() {

 3         return new Result[0];

 4     }

 5 

 6     public static List<Result> getResultList() {

 7         return Collections.emptyList();

 8     }

 9 

10     public static Map<String, Result> getResultMap() {

11         return Collections.emptyMap();

12     }


【11】优先使用常量或确定值调用equals 方法

      对象的equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals 方法。

反例:

1 //调用 equals 方法反例

2     private static boolean fileReader(String fileName)throws IOException{

4         // 可能抛空指针异常

5         return fileName.equals("Charming");

6  }

正例:

1 //调用 equals 方法正例

2     private static boolean fileReader(String fileName)throws IOException{

4         // 使用常量或确定有值的对象来调用 equals 方法

5         return "Charming".equals(fileName);

6         

7         //或使用: java.util.Objects.equals() 方法

8        return Objects.equals("Charming",fileName);

9  }

转载声明 上文出自:https://www.cnblogs.com/taojietaoge/p/11575376.html


为什么要代码优化

      一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是,吃的小虾米一多之后,鲸鱼就被喂饱了。

        代码优化也是一样,如果项目着眼于尽快无BUG上线,那么此时可以抓大放小,代码的细节可以不精打细磨;但是如果有足够的时间开发、维护代码,这时候就必须考虑每个可以优化的细节了,一个一个细小的优化点累积起来,对于代码的运行效率绝对是有提升的。      

代码优化的目标是

  • 减小代码的体积

  • 提高代码运行的效率

代码优化细节

1、尽量指定类、方法的final修饰符

      带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String,整个类都是final的。为类指定final修饰符可以让类不可以被继承,为方法指定final修饰符可以让方法不可以被重写。如果指定了一个类为final,则该类所有的方法都是final的。Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大,具体参见Java运行期优化。此举能够使性能平均提高50%。      

2、尽量重用对象

      特别是String对象的使用,出现字符串连接时应该使用StringBuilder/StringBuffer代替。由于Java虚拟机不仅要花时间生成对象,以后可能还需要花时间对这些对象进行垃圾回收和处理,因此,生成过多的对象将会给程序的性能带来很大的影响。      

3、尽可能使用局部变量

      调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。      

4、及时关闭流

      Java编程过程中,进行数据库连接、I/O流操作时务必小心,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,将会导致严重的后果。      

5、尽量减少对变量的重复计算

      明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的,包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。所以例如下面的操作:
               for( int i = 0; i < list.size(); i++){...}
            建议替换为:
              for(int i =0, int length = list.size(); i < length; i++){...}
                      这样,在list.size()很大的时候,就减少了很多的消耗      

6、尽量采用懒加载的策略,即在需要的时候才创建

      例如:
              Stringstr="aaa"; if(i ==1){list.add(str);}
            建议替换为:
               if(i ==1){Stringstr="aaa";list.add(str);}          

7、慎用异常

      异常对性能不利。抛出异常首先要创建一个新的对象,Throwable接口的构造函数调用名为fillInStackTrace()的本地同步方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。      

8、不要在循环中使用try…catch…,应该把其放在最外层

      除非不得已。如果毫无理由地这么写了,只要你的领导资深一点、有强迫症一点,八成就要骂你为什么写出这种垃圾代码来了。      

9、如果能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度

       比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder为例:
     (1)StringBuilder() // 默认分配16个字符的空间
     (2)StringBuilder(int size) // 默认分配size个字符的空间
     (3)StringBuilder(String str) // 默认分配16个字符+str.length()个字符空间
    可以通过类(这里指的不仅仅是上面的StringBuilder)的来设定它的初始化容量,这样可以明显地提升性能。比如StringBuilder吧,length表示当前的StringBuilder能保持的字符数量。因为当StringBuilder达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,无论何时只要StringBuilder达到它的最大容量,它就不得不创建一个新的字符数组然后将旧的字符数组内容拷贝到新字符数组中—-这是十分耗费性能的一个操作。试想,如果能预估到字符数组中大概要存放5000个字符而不指定长度,最接近5000的2次幂是4096,每次扩容加的2不管,那么:
    (1)在4096 的基础上,再申请8194个大小的字符数组,加起来相当于一次申请了12290个大小的字符数组,如果一开始能指定5000个大小的字符数组,就节省了一倍以上的空间;
    (2)把原来的4096个字符拷贝到新的的字符数组中去。

        这样,既浪费内存空间又降低代码运行效率。所以,给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的,这会带来立竿见影的效果。但是,注意,像HashMap这种是以数组+链表实现的集合,别把初始大小和你估计的大小设置得一样,因为一个table上只连接一个对象的可能性几乎为0。初始大小建议设置为2的N次幂,如果能估计到有2000个元素,设置成new HashMap(128)、new HashMap(256)都可以。      

10、当复制大量数据时,使用System.arraycopy()命令

11、乘法和除法使用移位操作

      例如:
               for(val=0;val<100000;val+=5){a =val*8;b =val/2;}
            用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的,因此建议修改为:
               for(val=0;val<100000;val+=5){a =val<<3;b =val>>1;}
            移位操作虽然快,但是可能会使代码不太好理解,因此最好加上相应的注释。      

12、循环内不要不断创建对象引用

      例如:
               for(inti =1; i <= count; i++ )
               {Object obj =new Object();}
            这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为:
              Object obj = null; for(inti =0; i <= count; i++) { obj =new Object(); }
            这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了。      

13、基于效率和类型检查的考虑,应该尽可能使用array,无法确定数组大小时才使用ArrayList

14、尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销

15、不要将数组声明为public static final

      因为这毫无意义,这样只是定义了引用为static final,数组的内容还是可以随意改变的,将数组声明为public更是一个安全漏洞,这意味着这个数组可以被外部类所改变。      

16、尽量在合适的场合使用单例

      使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:
        (1)控制资源的使用,通过线程同步来控制资源的并发访问
        (2)控制实例的产生,以达到节约资源的目的
        (3)控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信      

17、尽量避免随意使用静态变量

      要知道,当某个对象被定义为static的变量所引用,那么gc通常是不会回收这个对象所占有的堆内存的,如:
       public class A{
       private static B b =new B();
      }
       此时静态变量b的生命周期与A类相同,如果A类不被卸载,那么引用B指向的B对象会常驻内存,直到程序终止      

18、及时清除不再需要的会话

      为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为30分钟。当应用服务器需要保存更多的会话时,如果内存不足,那么操作系统会把部分数据转移到磁盘,应用服务器也可能根据MRU(最近最频繁使用)算法把部分不活跃的会话转储到磁盘,甚至可能抛出内存不足的异常。如果会话要被转储到磁盘,那么必须要先被序列化,在大规模集群中,对对象进行序列化的代价是很昂贵的。因此,当会话不再需要时,应当及时调用HttpSession的invalidate()方法清除会话。      

19、实现RandomAccess接口的集合比如ArrayList,应当使用最普通的for循环而不是foreach循环来遍历

      这是JDK推荐给用户的。JDK API对于RandomAccess接口的解释是:实现RandomAccess接口用来表明其支持快速随机访问,此接口的主要目的是允许一般的算法更改其行为,从而将其应用到随机或连续访问列表时能提供良好的性能。实际经验表明,实现RandomAccess接口的类实例,假如是随机访问的,使用普通for循环效率将高于使用foreach循环;反过来,如果是顺序访问的,则使用Iterator会效率更高。可以使用类似如下的代码作判断:
               if(list instanceof RandomAccess){ 

                      for( int i =0; i iterator =list.iterable(); while(iterator.hasNext() ){

                               iterator.next()

                      }

               }
            foreach循环的底层实现原理就是迭代器Iterator,参见Java语法糖1:可变长度参数以及foreach循环原理。所以后半句”反过来,如果是顺序访问的,则使用Iterator会效率更高”的意思就是顺序访问的那些类实例,使用foreach循环去遍历。      

20、使用同步代码块替代同步方法

      这点在多线程模块中的synchronized锁方法块一文中已经讲得很清楚了,除非能确定一整个方法都是需要进行同步的,否则尽量使用同步代码块,避免对那些不需要进行同步的代码也进行了同步,影响了代码执行效率。      

21、将常量声明为static final,并以大写命名

      这样在编译期间就可以把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字以大写命名也可以方便区分出常量与变量      

22、不要创建一些不使用的对象,不要导入一些不使用的类

      这毫无意义,如果代码中出现”The value of the local variable i is not used”、”The import java.util is never used”,那么请删除这些无用的内容      

23、程序运行过程中避免使用反射

      关于,请参见反射。反射是Java提供给用户一个很强大的功能,功能强大往往意味着效率不高。不建议在程序运行过程中使用尤其是频繁使用反射机制,特别是Method的invoke方法,如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存—-用户只关心和对端交互的时候获取最快的响应速度,并不关心对端的项目启动花多久时间。      

24、使用数据库连接池和线程池

  这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程      

25、使用带缓冲的输入输出流进行IO操作

      带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升IO效率      

26、顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的场景使用LinkedList

      这个,理解ArrayList和LinkedList的原理就知道了      

27、不要让public方法中有太多的形参

      public方法即对外提供的方法,如果给这些方法太多形参的话主要有两点坏处:
        1、违反了面向对象的编程思想,Java讲求一切都是对象,太多的形参,和面向对象的编程思想并不契合
        2、参数太多势必导致方法调用的出错概率增加
        至于这个”太多”指的是多少个,3、4个吧。比如我们用JDBC写一个insertStudentInfo方法,有10个学生信息字段要插如Student表中,可以把这10个参数封装在一个实体类中,作为insert方法的形参。      

28、字符串变量和字符串常量equals的时候将字符串常量写在前面

      这是一个比较常见的小技巧了,如果有以下代码:
        String str ="123" ;
        if(str.equals("123")) {...}
            建议修改为:
        String str ="123" ;
         if("123" .equals(str)){
             ...
         }
      这么做主要是可以避免空指针异常                      

29、请知道,在java中if (i == 1)和if (1 == i)是没有区别的,但从阅读习惯上讲,建议使用前者

30、不要对数组使用toString()方法           

     public  static  void  main(String[] args){

           int[] is = new int[]{1,2,3 };

           System.out.println(is.toString());

    }

    结果是:[I@18a992f

            本意是想打印出数组内容,却有可能因为数组引用is为空而导致空指针异常。不过虽然对数组toString()没有意义,但是对集合toString()是可以打印出集合里面的内容的,因为集合的父类AbstractCollections重写了Object的toString()方法。      

 所以数组转为字符串应写成:

Arrays.toString(a) 

  这种方法的toString()是带格式的,也就是说输出的是[a, b, c],如果仅仅想输出abc则需用以下两种方法:

  方法1:直接在构造String时转换。

char[] data = {'a', 'b', 'c'};
String str = new String(data);

  方法2:调用String类的方法转换。

String.valueOf(char[] ch)

31、不要对超出范围的基本数据类型做向下强制转型

     public  static  void  main(String[] args){
          long l =12345678901234L ;
          int i = ( int ) l;
          System.out.println(i);

    }
     我们可能期望得到其中的某几位,但是结果却是:  1942892530          

32、公用的集合类中不使用的数据一定要及时remove掉

      如果一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们。所以,如果公用集合里面的某些数据不使用而不去remove掉它们,那么将会造成这个公用集合不断增大,使得系统有内存泄露的隐患。      

33、把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据+””最慢

34、使用最有效率的方式去遍历Map

      遍历Map的方式有很多,通常场景下我们需要的是遍历Map中的Key和Value,那么推荐使用的、效率最高的方式是:
               public  static  void  main(String[] args){
HashMap hm = new HashMap();
hm.put("111","222" );
Set<Map.Entry<String, String>> entrySet =  hm.entrySet();
         Iterator<Map.Entry<String, String>> iter =  entrySet.iterator();
  while  (iter.hasNext()) {
            Map.Entry<String, String> entry =  iter.next();
          System.out.println(entry.getKey() + "    " +  entry.getValue());

  }
}
            如果你只是想遍历一下这个Map的key值,那用”Set keySet = hm.keySet();”会比较合适一些      

35、对资源的close()建议分开操作

               try{

                    XXX.close();YYY.close();

              } catch (Exception e){...}
            建议修改为:
               try{ 

                      XXX.close();

               } catch (Exception e) {

                          ...

                } 

                  

                try{

                        YYY.close();

                  } catch (Exception e) {

                        ...

                 }
            虽然有些麻烦,却能避免资源泄露。我想,如果没有修改过的代码,万一XXX.close()抛异常了,那么就进入了cath块中了,YYY.close()不会执行,YYY这块资源就不会回收了,一直占用着,这样的代码一多,是可能引起资源句柄泄露的。而改为上面的写法之后,就保证了无论如何XXX和YYY都会被close掉。

转载声明  上方出自:http://www.javathinker.net/bbs_topic.do?postID=1207


尽量使用字符替换字符串

字符串的长度不确定,而字符的长度固定为1,查找和匹配的效率自然提高了。

反例:

String source = "a:1,b:2,c:3,d:4";

int index = source.indexOf(":");

String target = source.replace(":", "=");

正例:

String source = "a:1,b:2,c:3,d:4";

int index = source.indexOf(':');

String target = source.replace(':', '=');


4.3.尽量使用移位来代替正整数乘除

用移位操作可以极大地提高性能。对于乘除2^n(n为正整数)的正整数计算,可以用移位操作来代替。

反例:

int num1 = a * 4;

int num2 = a / 4;

正例:

int num1 = a << 2;

int num2 = a >> 2;


5.4.不要使用""+转化字符串

使用""+进行字符串转化,使用方便但是效率低,建议使用String.valueOf.

反例:

int i = 12345;

String s = "" + i;

正例:

int i = 12345;

String s = String.valueOf(i);


6.数组

6.1.不要使用循环拷贝数组,尽量使用System.arraycopy拷贝数组

推荐使用System.arraycopy拷贝数组,也可以使用Arrays.copyOf拷贝数组。

反例:

int[] sources = new int[] {1, 2, 3, 4, 5};

int[] targets = new int[sources.length];

for (int i = 0; i < targets.length; i++) {

    targets[i] = sources[i];

}

正例:

int[] sources = new int[] {1, 2, 3, 4, 5};

int[] targets = new int[sources.length];

System.arraycopy(sources, 0, targets, 0, targets.length);


6.2.集合转数组时,尽量传入空数组T[0]

将集合转换为数组有2种形式:toArray(new T[n])和toArray(new T[0])。在旧的Java版本中,建议使用toArray(new T[n]),因为创建数组时所需的反射调用非常慢。在OpenJDK6后,反射调用是内在的,使得性能得以提高,toArray(new T[0])比toArray(new T[n])效率更高。此外,toArray(new T[n])比toArray(new T[0])多获取一次列表大小,如果计算列表大小耗时过长,也会导致toArray(new T[n])效率降低。

反例:

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, ...);

Integer[] integers = integerList.toArray(new Integer[integerList.size()]);

正例:

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, ...);

Integer[] integers = integerList.toArray(new Integer[0]); // 勿用new Inte


10.3.尽量减少同步代码块范围

在一个方法中,可能只有一小部分的逻辑是需要同步控制的,如果同步控制了整个方法会影响执行效率。所以,尽量减少同步代码块的范围,只对需要进行同步的代码进行同步。 

反例:

private volatile int counter = 0;

public synchronized void access(Long userId) {

  counter++;

    ... // 非同步操作

}

正例:

private volatile int counter = 0;

public void access(Long userId) {

    synchronized (this) {

        counter++;

    }

    ... // 非同步操作

}


10.5.尽量使用线程池减少线程开销

多线程中两个必要的开销:线程的创建和上下文切换。采用线程池,可以尽量地避免这些开销。

反例:

public void executeTask(Runnable runnable) {

    new Thread(runnable).start();

}

正例:

private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(10);

public void executeTask(Runnable runnable) {

    executorService.execute(runnable);

}

转载声明 出处:  Java编码技巧之高效代码50例

评论