Item 1: 使用静态工厂方法而不是构造函数
优势:
- 有名字
比如BigInteger.probablePrime()
一看就知道是返回一个BigInteger素数.
比较不好的做法是构造函数的重载, 通过修改参数的顺序达到不同的功能 - 不需要每次都创建新的对象. 比如如下代码:
1 2 3 |
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; } |
如果创建类的开销比较大, 那么对性能的提升就更明显了
- 能返回的类型选择更多, 不一定是当前类的类型 这一点非常好理解~
- 在创建参数化类型实例的时候, 代码会更简洁 主要原理是类型推导(type inference), 例如 :
1 |
Map<String, List<String>> m = new HashMap<String, List<String>>(); |
如果需要支持各种类型, 那么代码量级就海了去了, 同时也是很枯燥无聊的事情. 利用类型推导, 可以使用下面的方式:
1 2 3 4 5 6 |
public static <K, V> HashMap<K, V> newInstance() { return new HashMap<K, V>(); } // 真正的创建一个对象 Map<String, List<String>> m = HashMap.newInstance(); |
这种方式在Hadoop与Spark之中的应用非常非常广泛.
注意: 这种方式在当时的JDK1.6 可能会比较好用, 但是现在(JDK 1.8)可以更加方便的进行创建了, 有下面两种方式:
1 2 3 4 5 6 |
// 方式1: 让JDK自行推导, 不特意声明. 但是会有warning Map<String, String> map = new HashMap(); // 方式2: 使用Guava Map<String, String> map = Maps.newHashMap(); |
劣势
- 除非类含有
public
或者protected
构造函数, 否则这些静态方法不能被子类化
好像这也没什么 JavaDoc 上面没有特殊显示, 不能直接区分出跟其他普通方法的区别
因为现在的IDE已经可以很完美的显示出不同了
本文原创,原文链接:http://www.flyml.net/2017/02/05/effective-java-chapter-1/
Item 2: 使用builder替代多个参数的constructor
有时候,我们不得不面对一个对象需要很多参数.
作者不太推荐的做法是构造函数重载的方式, 如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat) { this(servingSize, servings, calories, fat, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) { this(servingSize, servings, calories, fat, sodium, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) { this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbohydrate = carbohydrate; } |
如果我们需要新建一个对象, 可能需要如下代码:
1 2 |
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27); |
如果我们想知道每个参数的意义, 只能去构造函数的定义之中一个个的看.
如果参数列表更长, 这个过程可能会非常痛苦而且容易出错
可以使用JavaBean 的getters / setters 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class NutritionFacts { // Parameters initialized to default values (if any) private int servingSize = -1; // Required; no default value private int servings = -1; // " " " " private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public NutritionFacts() { } // Setters public void setServingSize(int val) { servingSize = val; } public void setServings(int val) { servings = val; } public void setCalories(int val) { calories = val; } public void setFat(int val) { fat = val; } public void setSodium(int val) { sodium = val; } public void setCarbohydrate(int val) { carbohydrate = val; } } |
但是这种方法的不足在于: JavaBean 不是不可变的(immutable Item 15), 可能在创建过程之中状态不一致【这一点笔者还不够理解, 是因为线程安全?】.
这里应该使用Builder
模式(Gamma95, p. 97)
先来看看结果:
1 2 3 4 5 6 |
NutritionFacts cocaCola = new NutritionFacts .Builder(240, 8) .calories(100) .sodium(35) .carbohydrate(27) .build(); |
这种方法, 首先需要创建一个builder 对象(上面第二行), 有非常轻微的性能损失.
可能要到Item 39/63 才能知道Builder模式相对setter 模式的优势, 相比构造函数, 优势就是非常灵活了.
比如在构造函数进行参数调整的时候, 不会牵一发而动全身.
只不过, 跟多setter方法类似, 个人认为这种方式不知道有多少个参数要全部实现才行. 如果是构造函数, 看一眼就知道了.
本文原创,原文链接:http://www.flyml.net/2017/02/05/effective-java-chapter-1/
Item 3: 使用私有构造函数/枚举类型强化单例属性(Singleton Property)
关于单例, 这里可能多说一些.
单例就是为了能够在整个环境之中,只有一份实例存在. 常见的应用场景, 比如FileSystem, WindowManager 等等.
在Java之中, 可以有至少7种单例的实现方法, 可以参考《Java:单例模式的七种写法》
在看的时候, 可以多看看下面各种大神的评论, 感觉并没有完全统一的定论, 不过此书作者的推荐跟StackOverflow上面推荐的做法都是一致的: 枚举类型
1 2 3 4 5 6 |
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); |
在使用的时候, 如下所示:
1 2 |
Elvis e = Elvis.INSTANCE; e.printFavorites(); |
这种做法的好处:
- 有效避免多线程同步问题
- 防止反序列化重新创建新的对象
这里有一些更详细的说明: http://www.importnew.com/6461.html
PS: 只不过这种方式, 在实际工作之中确实太少见了. 可能是我孤陋寡闻~
PSS : 这里面好像跟私有构造函数没有什么关系? 但是标题里面确实提到了私有构造函数. 不明所以~
本文原创,原文链接:http://www.flyml.net/2017/02/05/effective-java-chapter-1/
Item 4: 通过私有构造函数使工具类无法被实例化
原始英文: Enforce noninstantiability with a private constructor
直译感觉比较绕: 使用私有构造函数强化不可实例化的能力
因此, 自行对全文内容作了一个片面的小结, 使得标题更加容易理解一些
我们在日常工作之中, 一般会生成各种各样的工具类, 在JDK之中, 比如java.util.Math.
还有一些并不是那么纯的工具类, 比如 java.util.Array.
对于这些工具类, 如果不显示的声明一个使用构造函数, 使用者可能就会无意识的将这个类new一下, 即实例化。 这种做法不是正确的使用方法, 而不是会造成什么性能问题吧。
注意: 在class之前使用final ,只是说不能继承, 而不是不能被实例化!
可以参考下面的做法:
1 2 3 4 5 6 7 8 |
// Noninstantiable utility class public class UtilityClass { // Suppress default constructor for noninstantiability private UtilityClass() { throw new AssertionError(); } // Remainder omitted } |
本文原创,原文链接:http://www.flyml.net/2017/02/05/effective-java-chapter-1/
Item 5: 避免创建不必要的对象
文中列举了下面的对比例子:
1 2 3 4 5 |
// BAD, 不好的例子 String s = new String("abc"); // GOOD String s = "abc"; |
文中认为, 在实际执行的时候, 每次都要new String() 会造成不必要的性能开销.
在现代编译器之中, 这两种方式是一样的, 因为在编译的时候会自动识别.
不过如果还有类似的其他情况, 如果有不需要变化的对象, 就省着点用. 可以对比下面两段代码:
不推荐的做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Person { private final Date birthDate; // Other fields, methods, and constructor omitted // DON'T DO THIS! public boolean isBabyBoomer() { // Unnecessary allocation of expensive object Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0); Date boomStart = gmtCal.getTime(); gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0); Date boomEnd = gmtCal.getTime(); return (birthDate.compareTo(boomStart) >= 0) && (birthDate.compareTo(boomEnd) < 0); } } |
推荐做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class Person { // Other fields, methods, and constructor omitted /** * The starting and ending dates of the baby boom. */ private static final Date BOOM_START; private static final Date BOOM_END; static { Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0); BOOM_START = gmtCal.getTime(); gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0); BOOM_END = gmtCal.getTime(); } private final Date birthDate; public boolean isBabyBoomer() { return (birthDate.compareTo(BOOM_START) >= 0) && (birthDate.compareTo(BOOM_END) < 0); } } |
【注意】文中也强调了几点:
- 不应该维护自己的对象池, 除了数据库连接(这个用开源的库好了, 除非您也是大神)
- 普通对象的创建与销毁是非常轻量级的, 鼓励多创建对象
本文原创,原文链接:http://www.flyml.net/2017/02/05/effective-java-chapter-1/
Item 6: 消除过期的引用
这一章主要谈了内存泄露
首先文章里面用了一个自己写的Stack类做例子来说明内存泄露的情况。 核心在于: Stack类自己维护了一个数组来存储对象(对象存储池), 当pop之后, 不再需要的那个对象, 并没有手动置空。 从GC/JVM 的角度来说, 这个对象依然是被需要的(因为还被引用的状态)
置空很简单, 如下面第5行
1 2 3 4 5 6 7 |
public Object pop() { if (size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; // 【重点是这一行】Eliminate obsolete reference return result; } |
Item 7: 避免使用finalizer
具体不是非常理解, 但是结论还是比较简单的:
不要自行使用System.gc()
or super.finalize()
或者类似的方法 不要去覆盖finalize()
方法
本文为原创文章,转载请注明出处
原文链接:http://www.flyml.net/2017/02/05/effective-java-chapter-1/

文章评论