Effective|Effective java 读书笔记(2)

第四条:通过私有构造器强化不可实例化的能力
有时可能需要编写只包含静态方法和静态域的类,这样的工具类不希望被实例化,因为实例化对它来说没有意义。
然而,在缺少显式构造器的情况下,系统会自动提供一个缺省构造,但这种类又不能设计为抽象类,因为它不希望被继承,也不希望它的子类能实例化。
所以,可以为其提供一个显式的私有构造器,保证不能从类的外部调用构造,代码如下:

public class UtilityClass{ private UtilityClass(){ throw new AssertionError(); //AssertionError可以避免不小心在类内调用构造

} ...... }

这种习惯做法也有副作用,它使得一个类不能被子类化。

第五条:优先考虑依赖注入来引用资源

第六条:避免创建不必要的对象
当你应该重用现有对象的时候,请不要创建新的对象。
一般来说,最好能重用单个对象,而不是在每次需要的时候就创建一个新对象,如果对象是不可变的(详见17条),它就始终可以被重用。
反面例子如下:
String s = new String("wuhu");

该语句每次执行的时候都创建一个新的String实例,但这是不必要的,若该语句处于循环中,就会创造出成千上万不必要的String实例。
所以,正确的应该这样做:
String s = "wuhu";

这个版本只用了一个String实例,而且可以保证,对于所有在同一台虚拟机中运行的代码,只要字符串相同,该对象就会被重用。
对于同时提供了静态工厂方法和构造器的不可变类,通常优先使用静态工厂方法而不是构造器,以避免创建不必要的对象。例如,静态工厂方法Boolean valueOf(String)几乎总是优先于构造器Boolean(String)(该构造在java9中已被废弃)。构造器每次调用都会创建一个对象,而静态工厂方法不会。除了重用不可变的对象之外,也可以重用那些已知不会被修改的可变对象。
静态工厂方法详见此处关于 Java 的静态工厂方法,看这一篇就够了! - 简书 (jianshu.com)
有些对象创建成本很高,如果需要重复使用,建议将其缓存下来重用。如以下实例:
static boolean isRomanNumeral(String s){ return s,matches(....); //此处为正则表达式 }

String.matches方法虽然最易于查询一个字符串是否与正则表达式相匹配,但并不适合在注重性能的情形下重复使用。原因在于,它在内部为正则表达式创建了一个Pattern实例,却只用了一次,之后就可以进行垃圾回收了。创建Pattern实例的成本很高,因为需要将正则表达式编译为一个有限状态机。
为了提升性能,应显式的将正则表达式编译成一个Pattern实例(不可变),让它成为类初始化的一部分,并将其缓存起来,每当调用isRomanNumeral方法时就重用同一个实例,如下:
public class RomanNumerals{ private static final Pattern ROMAN = Pattern.compile(...); static boolean isRomanNumeral(String s){ return ROMAN.matcher(s).matches(); } }

如果一个对象是不变的,那么它显然能够被安全的重用,但其他有些情形则并不这么明显。考虑适配器的情形,有时也叫做视图。
适配器模式详见适配器模式 | 菜鸟教程 (runoob.com)
例如,Map接口的keyset方法返回Map对象的Set视图,其中包含该Map中所有的键,对于给定的Map对象,每次调用keySet实际上都返回同样的Set实例,虽然返回的Set实例一般是可改变的,但是所有返回的对象在功能上都是等同的:其中一个返回对象发生变化时,所有其他的返回对象也要发生变化,因为它们是由同一个Map实例支撑的。
另一种创建多余对象的方法,称为自动装箱。请看如下代码:
private static long sum(){ Long sum = 0L; for(long i = 0; i <= Integer.MAX_VALUE; i++) sum += i; return sum; }

【Effective|Effective java 读书笔记(2)】这段程序算出的答案是正确的,但是比实际情况要更慢一点,因为变量sum被声明为Long而不是long,意味着每次先Long sum中增加一个long时都要构造一个实例,所以:要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。

    推荐阅读