概述
阅读Effective Java 3rd,对本书一些要点进行总结与自己的一些见解,方便查阅。
1.考虑使用静态工厂方法替代构造方法
类中提供一个创建某个类的静态方法。
优点:
1、不像构造方法,它们是有名字的
有多个构造方法可以考虑使用,突出创建该对象的用途。
2、它们不需要每次调用时都创建一个新对象
可以定义成静态成员变量缓存
3、可以返回其返回类型的任何子类型的对象
返回类型定义为父类型或者接口,方便扩展
4、返回对象的类可以根据输入参数的不同而不同
跟第三点相关,根据传入类型,返回不同的对象(可以是内部类等),调用方无需感知有这么个对象。
5、返回的对象的类不需要存在
第三、四点相关,其实就是返回接口,传参也是接口
缺点
1、没有公共或受保护构造方法的类不能被子类化
2、程序员很难找到它们
说的是通过javadoc生成的api,没有分多一栏显示,可能会忽略
2.当构造方法参数过多时使用 builder 模式
构造方法中参数过多,一般的编码方法有三种:
1、提供多个构造方法,设置不同值
优点:无
缺点:阅读性不好,有些参数需要构造器中强制设置默认值
2、提供javabean来封装
优点:相对第一种方法较好,但不是最优
缺点:set方法可能分散在代码中,难以i封装成不可变对象
3、使用builder模式
优点:
1、解决方法一,方法二中的缺点
2、可以封装成链式风格
3、提供一个抽象构造类,可实现多层次创建多个builder
缺点:
1、需要构造一个Builder对象
2、相对构造器直接创建对象,代码比较冗长
3、在构造方法参数很多,需要改成该模式,且客户端代码无法修改的情况下,旧方法无法去除,旧代码的处境比较尴尬
3.使用私有构造方法或枚举类实现 Singleton 属性
1、私有构造方法创建单例:
1.1、懒汉方式:
优点:延迟加载,对象大时开销没那么大,需要用时才创建
缺点:需要同步
1.2、饿汉方式:
优缺点与懒汉相反
2、该章节中用的是饿汉方式
两种方式相同点:
2.1.1、都需要定义无参私有构造器
2.1.2、提供方法或者公开属性获得类实例
缺点:
2.2.1、会被反射攻击。构造器中可用类属性标记是否第二次创建
2.2.2、序列化后的对象不唯一。重写readResolve方法
3、使用枚举类创建单例则无用 私有构造方法创建单例的所有缺点,且简单方便
4. 使用私有构造方法执行非实例化
一些工具类的实例化是无意义的,例如java.util.Collections,java.util.Arrays等,都是提供静态方法给外部使用。
做法:
定义一个私有无参构造器(可选:在该构造器中抛出runtime exception,可防止类中不小心实例化),因为无任何构造器的情况下编译器会定义一个公有无参构造器。
1、优点:
可防止实例化,且杜绝子类继承后创建实例。
2、缺点:
无法扩展子类,后期方法太多过于臃肿。
5. 依赖注入优于硬连接资源(hardwiringresources)
某些类需要依赖于另外的类,会硬编码到类中:
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} // Noninstantiable
public static boolean isValid(String word) { ... }
public static List<String> suggestions(String typo) { ... }
}
或者实现为单例模式,这样会失去灵活性,建议用依赖注入
// Dependency injection provides flexibility and testability
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
可以结合工厂方法模式、builder模式、Java 8 中引入的 Supplier 接口使用
优点:灵活
缺点,多个依赖会导致代码混乱,使用spring等依赖注入框架可以消除这些混乱
6. 避免创建不必要的对象
在每次需要时重用一个对象而不是创建一个新的相同功能对象通常是恰当的。
String s = new String("bikini");//创建了两个对象
String s2 = "bikini"; //正确的做法
Boolean b = Boolean(String); //创建了两个对象
Boolean b1 = Boolean.valueOf(String); //正确的做法
//在使用多次匹配的情况下很耗性能,因为底层是用正则匹配,会创建有限状态机
s.matches("^(?=.)M*(C[MD]|D?C{0,3})")
?
private static long sum() {
Long sum = 0L;//应该改成 ->long sum = 0L; 避免自动装箱
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
7. 消除过期的对象引用
缓存或者一个类管理内存时要注意内存泄漏。
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object leakpop() {
if (size == 0)
throw new EmptyStackException();
//这里引起内存泄漏,因为数组仍然持有该下标的元素
return elements[--size];
}
//这个方法不会有内存泄漏问题
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
/
**
1. Ensure space for at least one more element, roughly
2. doubling the capacity each time the array needs to grow.
*/
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
出现这个问题的原因是数 组中的每个元素都是强引用,除了直接将引用元素置空之外还有两种方法:
1.启动定时任务定时清除(ScheduledThreadPoolExecutor )
2.WeakHashMap等弱引用(在gc之后清除)
注意,以下两个put方法的数据永远无法回收,因为Integer跟字符串常量池中有引用:
WeakHashMap<Object,Object whm = new WeakHashMap<>();
whm.put(1,2);//whm.put(new Integer(1),2); 能回收
whm.put("3",2);//whm.put(new String("1"),2); 能回收
System.gc();
while(!whm.isEmpty())
System.out.println(whm);
8. 避免使用 Finalizer 和 Cleaner 机制
Finalizer 在JDK9中已经被标记为弃用;Cleaner为JDK9中的新机制代替Finalizer。
避免使用的原因:
1.性能和移植问题(有的机器不会执行)
2.不能及时执行
3.Finalizer 机制的另一个问题是在执行 Finalizer 机制过程中,未捕获的异常会被忽略(该方法被声明为抛出Throwable)
4.finalizer攻击(将要死亡的对象重新引用),编写一个 final 的 finalize 方法,它什么都不做,可以避免子类的finalize攻击
9. 使用 try-with-resources 语句替代 try-finally 语句
jdk7开始新增的语法(其实就是语法糖),当类实现了AutoCloseable接口之后,就可以用
try(XXX xx = new XX){
........
}
超出代码块的资源自动关闭。
优点:
1、更简洁
2、finally中的异常不会覆盖try中的异常,用getSuppressed可以获取。
10. 重写 equals 方法时遵守通用约定
自反性(Reflexivity): 对于任何非空引用 x, x.equals(x) 必须返回 true。
对称性(Symmetry): 对于任何非空引用 x 和 y,如果且仅当 y.equals(x) 返回 true 时 x.equals(y) 必须返回 true。
import java.util.Objects;
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
this.s = Objects.requireNonNull(s);
}
// Broken - violates symmetry!
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
if (o instanceof String) return s.equalsIgnoreCase((String) o);// 这句删掉,违法对称性
return false;
}
// Remainder omitted
}
测试代码:
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
System.out.println(cis.equals(s)); // true
System.out.println(s.equals(cis)); // false
传递性(Transitivity): 对于任何非空引用 x、y、z,如果 x.equals(y) 返回 true, y.equals(z) 返回 true,则x.equals(z) 必须返回 true。
考虑子类的情况
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
}
// Remainder omitted
}
public class ColorPoint extends Point {
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
// Remainder omitted
}
执行代码会发现违反对称性:
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp)); // true
System.out.println(cp.equals(p)); // false
把ColorPoint的equals改为
@Override
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
// If o is a normal Point, do a color-blind comparison
if (!(o instanceof ColorPoint))
return o.equals(this);
// o is a ColorPoint; do a full comparison
return super.equals(o) && ((ColorPoint) o).color == color;
}
能解决对称性,但却失去传递性:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.println(p1.equals(p2)); // true
System.out.println(p2.equals(p3)); // true
System.out.println(p1.equals(p3)); // false
所以,继承没办法做到这对称与一致性,但是有一个方法,组合:
public class ColorPoint {
private final Point point;
private final Color color;
public ColorPoint(int x, int y, Color color) {
point = new Point(x, y);
this.color = Objects.requireNonNull(color);
}
/
**
* Returns the point-view of this color point.
*/
public Point asPoint() {
return point;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint) o;
return cp.point.equals(point) && cp.color.equals(color);
}
... // Remainder omitted
}
一致性(Consistent): 对于任何非空引用 x 和 y,如果在 equals 比较中使用的信息没有修改,则 x.equals(y) 的多次调用必须始终返回 true 或始终返回 false。对于任何非空引用 x,x.equals(null) 必须返回 false
结论:
手写equals方法会大概率犯错,建议使用IDE生成或者用谷歌 AutoValue 开源框架。
尽量让容易不相等的属性放在前面,提高性能.