《Effective Java》读书笔记

一、对象
  1. 考虑静态工厂方法代替构造器
    类可以提供一个公有的静态工厂方法,它只是一个返回类的实例的静态方法。

    public static Boolean valueOf(boolean b){
        return b ? Boolean.TRUE : Boolean.FALSE;
    }
    静态工厂方法的优势:      
    1. 有名称,构造器只是在参数上不同。        
    2. 不必每次调用时都创建一个新对象。
    3. 可以返回原返回类型的任何子类型的对象。
    4. 在创建参数化类型实例的时候,它们使代码变得更加简洁。
    
    静态工厂方法的缺点:
    1. 类如果不含公有的或者受保护的构造器,就不能被子类化。
    2. 它们与其他的静态方法实际上没有任何区别。
    

    静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不必存在。静态工厂方法有一些惯用名称,valueOf、of、getInstance、newInstance、getType、newType等。

  2. 遇到多个构造器参数时要考虑用构造器

    重叠构造器模式:提供一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推,最后一个构造器包含所有可选参数。

    这种方式调用构造器时通常需要许多本不想设置的参数,但还是不得不为它们传递值。当有许多参数时,代码会比较难写并且难读。

    Builder模式:让客户端利用所有必要的参数调用构造器(或静态工厂),得到一个builder对象,然后在builder对象上来设置可选参数。

    public class NutritionFacts {
    
        private final int servingSize;
        private final int servings;
        private final int calories;
        private final int fat;
        private final int carbodyrate;
        private final int sodium;
    
        public static class Builder{
            private final int servingSize;
            private final int servings;
    
            private int calories = 0;
            private int fat = 0;
            private int carbodyrate = 0;
            private int sodium = 0;
    
            public Builder(int servingSize,int servings){
                this.servingSize = servingSize;
                this.servings = servings;
            }
    
            public Builder calories(int val){
                calories = val;
                return this;
            }
    
            public Builder fat(int val){
                fat = val;
                return this;
            }
    
            public Builder carbodyrate(int val){
                carbodyrate = val;
                return this;
            }
    
            public Builder sodium(int val){
                sodium = val;
                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;
            carbodyrate = builder.carbodyrate;
        }
    }
    

    调用方式:

    NutritionFacts build = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).calories(27).build();
    //对可选参数进行选择性调用
    NutritionFacts nutritionFacts = new NutritionFacts.Builder(20, 20).calories(10).build();

    与构造器相比,builder可以利用单独的方法来设置每个参数。如果类的构造器或者静态工厂中具有多个参数,设计这种类时用Builder模式是不错的选择。

  3. 用私有构造器或者枚举类型强化Singleton属性
    单例模式有懒汉、饿汉、静态内部类等方式。为了维护并保证Singleton,必须声明所有实例域都是瞬时的,并提供一个readResolve方法,否则每次反序列化一个序列化的实例时,都会创建一个新的实例。枚举已经成为实现单例的最佳方法。

  4. 通过私有构造器强化不可实例化的能力
    工具类不希望被实例化,实例对它没有任何意义。通过私有构造器,所以不可用在该类的外部实例化。
  5. 避免创建不必要的对象
    通常可以使用静态工厂方法而不是构造器,以避免创建不必要的对象。最好能重用对象而不是在每次需要的时候就创建一个功能相同的新对象。
    优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。
  6. 消除过期的对象引用

    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 pop(){
            if (size == 0) {
                throw new EmptyStackException();
            }
            Object result = elements[--size];
            elements[size] = null;//清除过期引用
            return result;
        }
    
        private void ensureCapacity() {
            if (elements.length == size) {
                elements = Arrays.copyOf(elements, 2 * size + 1);
            }
        }
    
        @Override
        public Object clone() {
            try {
                Stack result = (Stack) super.clone();
                result.elements = elements.clone();
                return result;
            } catch (CloneNotSupportedException e) {
                throw new AssertionError();
            }
    
        }
    }

    如果一个栈先是增长,然后再收缩,那么从栈中弹出来的镀锡将不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。因为栈内部维护者对这些对象的过期引用(永远不会被解除的引用)。
    清空对象引用应该是一种例外,而不是一种规范行为。

    存储池包含了elements数组(对象引用单元,而不是对象本身)的元素。数组活动区域中的元素是已分配的,而数组其余部分的元素是自由的。但是垃圾回收器并不知道,对于垃圾回收器而言,elements数组中的所有对象引用都同等有效。

  7. 避免使用终结方法
    终结方法的缺点在于不能保证会被及时地执行,从一个对象变得不可到达开始,到它的终结方法被执行,所花费的这段时间是任意长的。
    不应该依赖终结方法来更新重要的持久状态。只需提供一个显示的终止方法,通常与try-finally结构结合起来使用,以确保及时终止。
    子类的终结方法必须手工调用超类的终结方法。即使子类的终结过程抛出异常,超类的终结方法也会得到执行。

  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。
  9. 覆盖equals时总要覆盖hashCode
    在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一的返回同一个整数。
    相等的对象必须有相等的散列码。不同的对象是不同的散列码。

    public class PhoneNumber {
        private final short areaCode;
        private final short prefix;
        private final short lineNumber;
        public PhoneNumber(int areaCode, int prefix, int lineNumber) {
            rangeCheck(areaCode,999,"area code");
            rangeCheck(prefix,999,"prefix");
            rangeCheck(lineNumber,999,"line number");
            this.areaCode = (short) areaCode;
            this.prefix = (short) prefix;
            this.lineNumber = (short) lineNumber;
        }
    
        private void rangeCheck(int arg, int max, String name) {
            if (arg < 0 || arg > max) {
                throw new IllegalArgumentException(name + ":" + arg);
            }
        }
    
        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }
            if (!(obj instanceof PhoneNumber)) {
                return false;
            }
            PhoneNumber pn = (PhoneNumber) obj;
            return pn.lineNumber == lineNumber && pn.areaCode == areaCode && pn.prefix == prefix;
        }
    
        @Override
        public int hashCode() {
            int result = 17;
            result = 31 * result + areaCode;
            result = 31 * result + prefix;
            result = 31 * result + lineNumber;
            return result;
        }
    }
    
  10. 始终要覆盖toString

    Object的toString,包含类的名称,以及一个“@”符号,接着是散列码的无符号十六进制表示法。建议子类覆盖这个方法,toString方法应该返回对象中包含的所有值得关注的信息。

  11. 谨慎地覆盖clone
    Cloneable接口的目的是作为对象的一个mixin接口,表明这样的对象允许克隆。如果不借助反射,就不能仅仅因为一个对象实现了Cloneable,就可以调用clone方法。
    如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象。
    clone方法就是另一个构造器;你必须确保它不会伤害到原始的对象,并确保正确的创建被克隆对象中的约束条件。

12.考虑实现Comarable接口

二、类和接口
  1. 使类和成员的可访问性最小化
    模块之间只通过它们的API进行通信,一个模块不需要知道其他模块的内部工作情况。
    尽可能地使每个类或者成员不被外界访问。可以使数组变成私有的,并添加一个公有方法,它返回私有数组的一个备份。
  2. 在公有类中使用访问方法而非公有域
  3. 使可变性最小化
    不可变类只是其实例不能被修改的类。
    为了使类成为不可变,要遵循5条规则:
    1. 不要提供任何会修改对象状态的方法。
    2. 保证类不会被扩展。一般做法使这个类成为final。
    3. 使所有的域都是final的。
    4. 使所有的域都成为私有的。
    5. 确保对应任何可变组件的互斥访问。

    public final class Complex {
        private final double re;
        private final double im;
        public Complex(double re, double im) {
            this.re = re;
            this.im = im;
        }
        public double realPart(){
            return re;
        }
        public double imaginaryPart(){
            return im;
        }
        public Complex subtract(Complex c){
            return new Complex(re - c.re, im - c.im);
        }
        public Complex add(Complex c){
            return new Complex(re + c.re, im + c.im);
        }
        public Complex multiply(Complex c){
            return new Complex(re * c.re - im * c.im, re * c.im + im * c.re);
        }
        public Complex divide(Complex c){
            double tmp = c.re * c.re + c.im * c.im;
            return new Complex((re * c.re + im * c.im)/tmp, (im * c.re - re * c.im) /tmp);
        }
        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }
            if (!(obj instanceof Complex)) {
                return false;
            }
            Complex c = (Complex) obj;
            return Double.compare(re, c.re) == 0 && Double.compare(im, c.im) == 0;
        }
        @Override
        public int hashCode() {
            int result = 17 + hashDouble(re);
            result = 31 * result + hashDouble(im);
            return result;
        }
    
        private int hashDouble(double val){
            long longBits = Double.doubleToLongBits(re);
            return (int)(longBits ^ (longBits >>> 32));
        }
    
        @Override
        public String toString() {
            return "(" + re + " + " + im + "i)";
        }
    }
    
  4. 复合优先于继承
    与方法调用不同的是,继承打破了封装性。子类依赖于其超类中特定功能的实现细节。超类的实现有可能会随着发行版本的不同而有所变化,如果发生变化,子类可能会遭到破坏。
    在新的类中增加一个私有域,它引用现有类的一个实例,这种设计被称作“复合”。

  5. 为继承设计提供文档说明
    文档指明该方法或者构造器调用了哪些可覆盖的方法。

  6. 接口优于抽象类
    为了实现由抽象类定义的类型,类必须成为抽象类的一个子类。

  7. 接口只用于定义类型
    常量接口模式是对接口的不良使用,会把实现细节泄露到API中。在类或接口中,应该使用枚举或者工具类来导出常量。

  8. 类层次优于标签类

  9. 用函数对象表示策略

    public class Host {
        private static class StrlenCmp implements Comparator<String>,Serializable{
    
            @Override
            public int compare(String o1, String o2) {
                return o1.length() - o2.length();
            }
    
        }
    
        //返回的比较器是系列化的
        public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrlenCmp();
    }
  10. 优先考虑静态成员类

三、泛型
  1. 请不要在新代码中使用原生态类型

  2. 消除非受检警告

  3. 列表优先于数组

  4. 优先考虑泛型

  5. 优先考虑泛型方法

  6. 利用有限制通配符来提升API的灵活性

  7. 优先来了类型安全的异构容器

四、枚举和注解
  1. 用enum代替int常量

  2. 用实例域代替序数

  3. 用EnumSet代替位域

  4. 用EnumMap代替序数索引

  5. 用接口模拟可伸缩的枚举

  6. 注解优先于命名模式

  7. 坚持使用Override注解

  8. 用标记接口定义类型

五、方法
  1. 检查参数的有效性

  2. 必要时进行保护性拷贝

  3. 谨慎设计方法签名

  4. 谨慎重载

  5. 慎用可变参数

  6. 返回零长度的数组或者集合,而不是null

  7. 为所有导出的API元素编写文档注释

六、通用程序设计
  1. 将局部变量的作用域最小化

  2. for-each循环优先于传统的for循环

  3. 了解和使用类库

  4. 如果需要精确的答案,避免使用float和double

  5. 基本类型优先于装箱基本类型

  6. 如果其他类型更合适,则尽量避免使用字符串

  7. 当心字符串连接的性能

  8. 通过接口引用对象

  9. 接口优先于反射机制

  10. 谨慎地使用本地方法

  11. 谨慎地进行优化

  12. 遵守普遍接受的命名惯例

七、异常
  1. 只针对异常的情况才使用异常

  2. 对可恢复的情况使用受检异常,对编程错误使用运行时异常

  3. 避免不必要的使用受检的异常

  4. 优先使用标准的异常

  5. 抛出与抽象相对应的异常

  6. 每个方法抛出的异常都要有文档

  7. 在细节消息中包含能捕获失败的信息

  8. 努力使失败保持原子性

  9. 不要忽略异常

八、并发
  1. 同步访问共享的可变数据

  2. 避免过度同步

  3. executor和task优先于线程

  4. 并发工具优先于wait和notify

  5. 线程安全性的文档化

  6. 慎用延迟初始化

  7. 不要依赖于线程调度器

  8. 避免使用线程组

九、序列化
  1. 谨慎地实现Serializable接口

  2. 考虑使用自定义的序列化形式

  3. 保护性地编写readObject方法

  4. 对于实例控制,枚举类型优先于readResolve

  5. 考虑用序列化代理代替序列化实例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值