第一章 创建和销毁对象

1.1 考虑用静态工厂方法替换构造器

1.2 用私有构造器或者枚举类型强化Singleton属性

1.3 避免创建不必要的对象

1.4 消除过期的对象引用

import java.util.Arrays;
import java.util.EmptyStackException;

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){
        ensureCapaciy();
        elements[size++] = e;
    }

    public Object pop(){
        if(size == 0){
            throw new EmptyStackException();
        }
        return elements[--size];
    }

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

}

在本例中,凡是数组下标小于size的那些元素都是过期引用

在支持垃圾回收的语言中,内存泄露是很隐蔽的

这类问题的修复方法很简单:一旦对象引用以及过期,只需要清空这些引用即可

public Object pop(){
    if(size == 0){
        throw new EmptyStackException();
    }
    Object result = elements[--size];
    elements[size] = null;
    return result;
}

一般而言,只要类是自己管理内存,程序员就应该警惕内存泄露的问题

内存泄露的另一个常见的来源是缓存

内存泄露的第三个常见来源是监听器和其他回调.如果你实现了一个API,客户端在这个API中注册回调,却没有显式地取消注册,那么除非你采取某些动作,否则他们就会积聚.确保回调立即被当成垃圾回收的最佳方法是只保存它们的弱引用(weak reference),例如,只将它们保存成WeakHashMap中的键.

第二章 对于所有对象都通用的方法

2.1 覆盖equals时总要覆盖hashCode

2.2 始终覆盖toString

2.3 考虑实现Compareable接口

第三章 类和接口

3.1 使类和成员的可访问行最小化

3.2 使可变性最小化

3.3 组合、继承

组 合 关 系 继 承 关 系
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立 缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
优点:具有较好的可扩展性 缺点:支持扩展,但是往往以增加系统结构的复杂度为代价
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象 缺点:不支持动态继承。在运行时,子类无法选择不同的父类
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 缺点:子类不能改变父类的接口
缺点:整体类不能自动获得和局部类同样的接口 优点:子类能自动继承父类的接口
缺点:创建整体类的对象时,需要创建所有局部类的对象 优点:创建子类的对象时,无须创建父类的对象

请记住下面两句话:

继承要慎用,其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是,问一问自己是否需要从新类向基类进行向上转型。如果是必须的,则继承是必要的。反之则应该好好考虑是否需要继承。《Java编程思想》

只有当子类真正是超类的子类型时,才适合用继承。换句话说,对于两个类A和B,只有当两者之间确实存在is-a关系的时候,类B才应该继续类A。《Effective Java》

3.4.接口只用于定义类型。

3.5 常量接口不值得效仿,接口应该只被用来定义类型,不应该被用来导出常量。

3.6 对于常量的使用我们应该遵循如下规则:如果常量与某个现有的类或者接口紧密相关,就应该把这些常量添加到这个类或者接口中;如果常量最好被看做枚举类型的成员,则应该用枚举类型来导出这些常量;否则应该使用不可实例化的工具类来导出这些常量。

第四章 泛型

推荐使用泛型,虽然他在运行时会被擦除。

尽可能地消除每一个非受检警告。如果无法消除警告,同时可以证明引起警告的代码是类型安全的,则可以使用@SuppressWarnings("unchecked")注解来禁止这条警告,同时我们必须要竟可能小的范围使用该注解,禁止在整个类上使用该注解。

第五章 枚举和注解

5.1 使用enum代替int常量

5.2 坚持使用Override注解

使用Override注解IDE会自动检查是否真的覆盖了超类的方法

第六章 方法

6.1 必要时,对不可变对象的构造器和访问方法进行检查性保护

6.2 慎用重载

方法重载是在编译时做出决定的。

永远都不要导出两个具有相同参数数目的重载方法,这是一个安全保守的方法。

任何一组给定的实际参数将应用于那个重载方法上

同一组参数只需要经过类型转换就可以被传递给不同的重载方法,如果不能避免这种情况,就应该保证:当传递同样的参数是,所有重载方法的行为必须一致

6.3 慎用可变参数

可变参数接受0个或者多个指定类型的参数。其机制是先创建一个数组,数组的大小为在调用位置所传递参数的数量,然后将参数值传递给数组,最后将数组传递给方法。

6.4 返回零长度的数组或者集合,而不应该是null。不重要但是值得注意

第七章 通用程序设计

7.1 局部变量作用域最小化

将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性。

要是局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方声明。一般来说每个局部变量都应该有一个初始化,如果没有足够的信息来对该变量进行有意义的初始化,则需要推迟该变量的声明,直到可以被初始化为止。

对于方法而言,应该尽可能地使方法小而集中,这样就可以将局部变量的作用域尽可能地小化。

7.2 优先使用for-each循环

for循环相比于while循环它可以使局部变量更小化,同时程序更加简短增强了可读性。

for-each循环在简洁性和预防bug方面有着传统for循环无法比拟的优势,并且没有性能损失,所以我们应该尽可能使用for-each循环,但是在以下三种情况是否无法使用for-each循环的:

  1. 过滤:如果需要遍历某个集合,删除特定的元素,则需要使用显示的迭代器。

  2. 转换:如果需要遍历列表或者数组,并取代它部分或者全部元素值,这时就需要迭代器来进行设置元素的值。

  3. 平行迭代

8.3 优先使用基本类型

基本类型相当于其引用类型有如下几个区别:

  1. 基本类型只有值,而引用类型则具有他们值不同的同一性,也就说new Integer(1) != new Integer(1);

  2. 基本类型具有功能完备的值,而引用还有一个null值 3).基本类型通常比引用类型更加节省空间和时间。

当在一项操作中混合使用基本类型和其引用类型时,其引用类型会自动进行拆箱动作。

在进行迭代时需要格外注意拆箱装箱机制,因为频繁的拆箱装箱会导致性能的下降。

其他

8.4 如果需要精确的答案,请使用BigDecimal而不是double和float。

8.5 如果有合适的接口类型存在,那么对于参数、返回值、变量和域来说,就都应该使用接口类型进行声明。

第八章 异常

异常应该只用于异常的情况下,他们永远不应该用于正常的控制流。

异常机制设计是用于不正常的情况,所以很少会有JVM会对他进行优化,所以异常一般运行都比较慢。

我们应该尽量让异常代码块小,因为try..catch会阻碍JVM实现本来可能要执行的某些特定的优化。

第九章 并发

9.1 同步访问共享的可变数据

9.2 避免过度同步

9.3 executor和task优先于线程

9.4 并发工具优先于wait和notify

9.5 慎用延迟初始化

第十章 序列化

10.1 谨慎地实现Serializable接口

实现Serializable接口付出的最大代价是,一实现旦一个类被发布就大大降低了"改变这个类的实现"的灵活性

实现Serializable的第二个代价是,它增加了出现bug和安全漏洞的可能性(反序列化创建对象)

为了继承而设计的类应该尽可能少的去实现Serializable