AKSTIANYE

Oct 19, 2018

Effective Java 笔记(一)

以下的所有条例仅仅作为一种大多数情况下的选择,但是编写代码仍需以实际情况[权衡]

1: 考虑用静态方法替代构造器

指用静态方法返回某个类的实例
eg:

1
2
3
public static Test getInstance() {
return new Test();
}

优点:

  • 可命名,更容易使调用者理解
  • 实例化更加可控与可定制化
  • 可用于单例模式,避免实例化重复对象
  • 可返回原来类型的任何子类
  • 创建参数化类型实例时更加简洁

2: 遇到多个构造器参数时,考虑使用构建器

eg:

1
Student student = new Student.Builder("kevin", 23).grade("1年级").build();

优点:

  • 链式调用易于编写和理解
  • 避免编写过多的构造函数
  • 避免使用set方法分步实例化,会有线程安全问题

3: 用枚举类型强化Singleton属性

eg:

1
2
3
public enum Instance {
INSTANCE
}

优点:

  • 无需实现readResolve方法即可保证反序列化无法破坏单例

4: 使用私有构造器强化不可实例化的能力

一些没有必要实例化的类[如工具类],应当私有其构造函数

优点:避免无意识[不小心]的实例化
缺点:导致其无法被继承


5: 避免创造不必要的对象

当你应该重用现有对象时,请不要创建新对象,这里的[应该]指创建新对象代价较高时

一些公用的,不变的实例,可以提出来只构建一次,而不是放在方法内部去每次创建新实例

自动装箱会导致实例化其包装对象,因此在基本类型满足需求的情况下,不要使用其包装类型


6: 消除过期的对象引用

过期的对象引用会在极端情况下导致内存泄漏,内存泄漏常见于三个来源

  • 自己管理内存

    如果一个容器中对象引用的删除是逻辑管理[类似逻辑删除]的,即其没有被真正移除,而用户在逻辑上认为已被移除.那么应该警惕内存泄漏问题,一旦元素被释放掉,应当同时移除该对象的引用

    eg:

    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
    27
    28
    29
    30
    public class Strack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Strack() {
    elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
    ensureCapacity();
    elements[size++] = e;
    }

    public Object pop() {
    if (size == 0) {
    throw new EmptyStackException();
    }
    Object result = return elements[--size];
    // 移除该对象的引用
    elements[size] = null;
    return result;
    }

    private void ensureCapacity() {
    if (elements.length == size) {
    elements = Arrays.copyOf(elements, 2 * size + 1);
    }
    }
    }

    例如eg中被弹出的对象,哪怕在别的地方已经失去引用,但在elements中其位置未被别的对象引用替换前,elements仍保持了其过期的引用,会导致该对象以及该对象引用的对象无法被垃圾回收

  • 缓存

    当对象的引用被放入缓存中后,很容易被遗忘或忽略

    在某些情况下,如缓存的存在是否必要仅有外部引用而不是值来决定时,可以考虑使用WeakHashMap来处理

    更常见的做法是使用某种策略定期清理不用的缓存,正如许多框架所做的那样

  • 监听器与其他回调


7: 避免使用终结方法

finalizer方法通常是不可预测、不稳定的,java规范也并不保证其一定会执行。在java中,内存资源的回收通常交由垃圾回收器处理,而其他资源[如文件流]的回收通常用try-finally来完成,因此应该尽量[避免]使用finalizer来做这些事。


8: 覆盖equals时遵守通用约定

通常情况下,我们认为每一个对象都是独一无二的,因此它应该只等于其自身,Object类的equals方法也确实是这么做的
但equals方法的结果应当取决于用户当前期望什么样是相等的,即逻辑相等。所以在某些情况下我们需要重写equals方法以达到目的。
很多已有的类中也重写了equals方法,他们都遵循了一些通用的约定。若我们重写方法时没有遵循这些约定,会造成某些不可预知的错误。

  • 自反性
    对于任何非null的引用值x,x.equals(x)必须返回true
  • 对称性
    对于任何非null的引用值x、y,当且仅当y.equals(x)为true时,x.equals(y)必须为true
  • 传递性
    对于任何非null的引用值x、y、z,如果x.equals(y)为true,并且y.equals(z)也为true,那么x.equals(z)也必须为true
  • 一致性
    对于任何非null的引用值x、y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用equals方法返回的结果必须一致
  • 对于任何非null的引用值x,x.equals(null)必须为false

9: 覆盖equals时总要覆盖hashCode

重写equals方法时应当重写hashCode方法,否则会导致该类无法结合所有基于散列的集合一起正常工作

  • 若一个类的equals方法返回true,则hashCode方法应当返回相同的整数
  • 若一个类的equals方法返回false,hashCode方法不一定要返回不同的整数,但返回不同的整数有利于提高性能

10: 使用要覆盖toString

应当尽可能的覆盖toString方法,返回该对象的内容摘要

有助于在任何可能输出该类信息的地方
使得使用者得到易于理解的信息

OLDER > < NEWER