《Effective Java中文版第二版》读书笔记

说明

这里是阅读《Effective Java中文版第二版》的读书笔记,这里会记录一些我的感受稍微有些重要的内容,方便之后查阅,可能会由于我的实力缘由致使理解有误,如有发现欢迎指出。一些我的还不理解的会用斜线标注。java

第一章是引言,因此跳过。设计模式

第二章 建立和销毁对象

第1条:考虑用静态工厂方法代替构造器

含义

静态工厂方法是指一个返回类的实例的静态方法,例如:数组

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean,FALSE;
}

优势

相对于一个类的构造器,静态工厂方法的名称没有限制。

众所周知,构造器的方法名是必须和类名同样的,所以对于有多个参数类型相同的构造方法,一种方法是更改参数的顺序,另外一种是增长一个flag来判断执行哪一个构造方法。可是这样对于使用者是不友好的,他必须熟悉API或者查阅开发文档。假若使用静态工厂方法,那么能够经过方法名来给予使用者良好的提示与说明。框架

不用再每次调用的时候建立一个新的对象。

这句话的典型应用是在设计模式的单例模式中,静态工厂方法可以为重复的调用返回相同的对象。函数

静态工厂方法能够返回原返回类型的任何子类型的对象。

构造方法是不能使用return语句的,它在使用时也只能产生自身这个类的一个对象,而静态工厂方法可使用return语句,所以在选择返回对象时就有了更大的灵活性。这个优点的应用不少,好比服务提供者框架模式工具

小结

应当熟悉静态工厂方法和构造器的各自的长处,在合适的场景使用合适的方法。性能

第2条:遇到多个构造器参数时要考虑用构建器

在面对一个拥有多个属性的类且构造方法拥有多个可选参数时,一个常见的方法是使用重叠构造器模式(建立多个构造方法,每一个构造方法比前一个构造方法有新的参数)。例如,第一个构造方法有两个必须参数,第二个构造方法有两个必须参数和一个可选参数,第三个构造方法有两个必须参数和两个可选参数,以此类推。可是当有许多参数的时候,代码会变得很难编写,也很难阅读,甚至会容易出错。测试

另外一个方法是使用javabean模式。由于构造过程被分到了多个调用中(为每一个属性的赋值调用该属性的set方法),在构造过程当中,javabean可能处于不一致的状态,这种问题难以发现。ui

第三种方法就是构建器模式(Builder模式)的一种形式。this

public class NutritionFacts {

    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // 必须属性
        private final int servingSize;
        private final int servings;
        // 可选属性
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder setCalories(int calories) {
            this.calories = calories;
            return this;
        }

        public Builder setFat(int fat) {
            this.fat = fat;
            return this;
        }

        public Builder setSodium(int sodium) {
            this.sodium = sodium;
            return this;
        }

        public Builder setCarbohydrate(int carbohydrate) {
            this.carbohydrate = carbohydrate;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}


// 使用方法
NutritionFacts n = new NutritionFacts.Builder(200,10).setCalories(20).setFat(30).build();

Builder模式十分灵活,能够利用一个builder来建立多个相同的对象,而且对必须参数和可变参数的实现符合人类的正常思惟。另外,对于使用者而言,使用时的代码更容易阅读和编写。

这种方法我在google的protobuf的java实现中见到过。

第3条:用私有构造器或者枚举类型强化Singleton属性

私有构造方法就不提了,这里记录一下第二个:

public enum A {
    INSTANCE;

    public void leaveTheBuilding() {...}
}

第4条:经过私有构造器强化不可实例化的能力

对于一些只包含静态方法或者静态属性的类(好比工具类),咱们不但愿他们被实例化。众所周知,在缺乏显式构造方法的时候,编译器会默认添加一个无参的构造方法。若是为了严谨,咱们能够添加一个私有的构造方法,更能够在这个构造方法中throw异常来停止程序。

第5条:避免建立没必要要的对象

通常来讲,最好能重用对象而不是在每次须要的时候就建立一个相同功能的新对象。

除了重用不可变的对象以外,也能够重用那些已知不会被修改的可变对象。

能使用基本数据类型,就尽可能不要用对应的封装类。

第6条:消除过时的对象引用

不能觉得有了垃圾回收机制后,就不须要考虑内存管理的事情了。

例如用数组来实现栈,当实现出栈操做,size-1后,栈顶坐标后的元素对使用者来讲就已是无效部分了,可是数组仍然拥有对它们的引用,所以垃圾回收机制不会将它们回收。解决办法是在出栈时,将引用置空。

第7条:避免使用终结方法

除了特定状况,不要使用终结方法(finalize)。

子类覆盖了父类的终结方法后,子类的终结方法不会自动调用父类的终结方法,须要手动调用。

第三章 对于全部对象都通用的方法

第8条:覆盖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的比较操做在对象中所用的信息没有被修改,屡次调用x.equals(y)就会一致地返回true,或者一致地返回false。
  • 非空性:对于任何非null的引用值x,x.equals(null)必须返回false。

实现高质量equals方法的诀窍:

  1. 使用==操做符检查“参数是否为这个对象的引用”。若是是,则返回true。
  2. 使用instanceof操做符检查“参数是否为正确的类型”。若是不是,则返回false。
  3. 把参数转化为正确的类型。由于转换前进行过instanceof测试,因此确保会成功。
  4. 对于该类中的每一个“关键”字段,检查参数中的字段是否与该对象中对应的字段相匹配。若是这些测试所有成功,则返回true;不然返回false。
  5. 当你编写完成了equals方法以后,应该质问本身而且测试这三个问题:它是不是对称的、传递的、一致的?固然,equals方法也必须知足自反性和非空性,不过一般都会自动知足。

一个简单的列子:

public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof MyClass))
        return false;
    MyClass obj = (MyClass) o;
    return obj.field0 == this.field0 && obj.field1 == this.field1;
}

告诫:

  • 覆盖equals时总要覆盖hashCode。
  • 不要企图让equals方法过于智能。
  • 不要将equals声明中的Object对象替换为其余的类型。
public boolean equals(MyClass o); // Don't do this!

第9条:覆盖equals时总要覆盖hashCode

若是没有共同覆盖equals方法和hashCode方法,那么该类将没法结合全部基于散列的集合一块儿正常运做,这样的集合包括HashMap、HashSet和HashTable。

约定:相等的对象必须具备相等的散列码(HashCode)。

在散列码的计算过程当中,必须排除equals比较计算中没有用到的任何字段,能够把冗余字段(它的值能够根据参与计算的其余字段计算出来)排除在外。

不要试图从散列码计算中排除掉一个对象的关键部分来提升性能。

第10条:始终要覆盖toString

提供好的toString实现可使类用起来更加温馨。

第11条:谨慎地覆盖clone

若是你继承了一个实现了Cloneable接口的类,那么你除了实现一个行为良好的clone方法外,没有别的选择。不然,最好提供某些其余的途径来代替对象拷贝,或者干脆不提供这样的功能。

另外一个实现对象拷贝的好方法是提供一个拷贝构造方法或者拷贝工厂。

// 拷贝构造方法
public MyClass(MyClass mc);

// 拷贝工厂
public static MyClass newInstance(MyClass mc);

第12条:考虑实现Comparable接口

类实现了Comparable接口,就代表它的实例具备天然顺序关系(natural ordering)。

约定:(符号sgn(表达式)表示数学中的signum函数,根据表达式的值为负值、零和正值,分别返回-一、0和1)

  • 必须确保全部的x和y都知足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。(这也意味着,当且仅当y.compareTo(x)抛出异常时,x.compareTo(y)才必须抛出异常)
  • 必须确保这个比较关系是可传递的。x.compareTo(y) > 0 && y.compareTo(z) > 0成立意味着x.compareTo(z) > 0
  • 必须确保x.compareTo(y) == 0意味着全部的z都知足sgn(x.compareTo(z)) == sgn(y.compareTo(z))
  • 强烈建议(x.compareTo(y) == 0) == (x.equals(y)),但这绝非必要。若违反了这个条件,应当给予说明。

比较浮点字段用Double.compare或者Float.compare。

若是一个类有多个关键字段,按照什么样的顺序来比较是很是重要的。

compareTo方法中,若是两个对应字段不相等,可使用该类的字段与传入参数的字段的差值做为返回值,但应确保差值是绝对正确的。

相关文章
相关标签/搜索