Effective Java学习笔记(五) 避免建立没必要要的对象

本篇笔记对应原书条目6,介绍几个避免建立没必要要对象的方法。java

1. 采用更合适的API或工具类减小对象的建立

若是一个方法中的一些对象,每次调用都起到相同的做用,那么就能够被重用。程序员

好比,咱们不该该用以下的方式来建立一个String对象:数据库

String str = new String("aaa");

由于当咱们往构造方法里传入aaa的时候,其实这个aaa就是一个String实例了。咱们等因而建立了两个String实例。数组

咱们应该直接这么写:安全

String str = "aaa";

根据jdk文档,上述方式实际上等同于:app

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

传入一个字符数组来建立String,避免了建立重复对象。工具

再举一个常见的例子[2],咱们有时但愿遍历一个list,将其中的元素存到一个字符串里,并用逗号分隔。咱们可能会用下面这种最low的办法:性能

public static String listToString(List<String> list) {
    String str = "";
    for (int i = 0; i < list.size(); i++) {
        str += list.get(i);
        if (i < list.size() - 1) {
            str += ",";
        }
    }
    return str;
}

这样其实在每次+=的时候都会从新建立String对象,极大地影响了性能。学习

咱们能够修改一下,采用StringBuilder的方式来拼接list:优化

public static String listToString(List<String> list) {
    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < list.size(); i++) {
        stringBuilder.append(list.get(i));
        if (i < list.size() - 1) {
            stringBuilder.append(",");
        }
    }
    return stringBuilder.toString();
}

这种方式每次只会生成两个实例——StringBuilder和最后返回的String

那有没有更好的方法呢?咱们能够采用Google Guava的Joiner,这样每次只用生成一个实例,以下所示:

public static String listToString(List<String> list) {
    return Joiner.on(",).join(list);
}

2. 重用相同功能的对象

有时候咱们提供的API中有一些每次调用都具有相同功能的对象,那么就能够把这些对象变成静态的不可变对象,只需实例化一次便可。好比下面这个相似书中的例子[1]:

public static boolean isNumeral(String s) {
    return s.matches("^[0-9]*$");
}

这个例子用String.matches()方法来判断字符串是否为数字。每次调用matches()方法,里面都会建立一个Pattern对象,这会对性能形成影响。

由于每次调用isNumeral实际上都会生成一个功能彻底相同的Pattern对象,因此咱们能够把它抽出来,变成一个静态不可变对象,以下所示:

public static final Pattern NUMBER = Pattern.compile("^[0-9]*$");

public static boolean isNumeral(String s) {
    return NUMBER.matcher(s).matches();
}

上面咱们谈到了一个不可变对象的重用,接下来咱们再看看可变对象的重用。可变对象的重用能够经过视图(views)来实现。好比,Map的keySet()方法就会返回Map对象全部key的Set视图。这个视图是可变的,可是当Map对象不变时,在任何地方返回的任何一个keySet都是同样的,当Map对象改变时,全部的keySet也会相应的发生改变。[1]

3. 当心自动装箱(auto boxing)

自动装箱容许程序员混用基本类型和包装类型[1],在二者相计算时,程序会构造出基本类型的包装类型实例。例如,咱们看书中的这个例子:

private static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++) 
        sum += i;
    return sum;    
}

这个例子里的sum += i处用Long型和long型相加,这样每次都会实例化一个值为i的包装类型,一共实例化了约231个没必要要的实例,极大地下降了系统的性能。因此咱们在平常开发中,方法内尽可能用基本类型,只在入出参的地方用包装类型。多留心,切忌无心识地使用到自动装箱。[1]

其余

若是涉及到对象池的应用,除非池中的对象很是重,相似数据库链接,不然最好不要去本身维护一个对象池,由于这样会很复杂。另外,有时考虑到系统的安全性,那么咱们须要进行防护性复制,这个在后面会讲到。此时,重复建立对象就是有意义的,由于比起隐含错误和安全漏洞,重复建立对象带来的性能损失是能够接受的。[1]

总结

这个条目要求咱们平时多审视咱们的代码,在能不重复建立对象的地方,就不要重复建立。不要无脑写代码,而是要多留点心。这是一种很是好的编码习惯,也能够为系统带来性能上的优点。

声明

本文仅用于学习交流,请勿用于商业用途。转载请注明出处,若是涉及任何版权问题,请及时与我联系,谢谢!

参考资料

  1. 《Effective Java(第3版)》
  2. Java之避免建立没必要要的对象 https://www.jianshu.com/p/420...
  3. java代码优化——避免建立没必要要的对象 https://www.jianshu.com/p/83a...
相关文章
相关标签/搜索