Effective Java -- 1

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

优点:

1.静态工厂方法与构造器不同的第一大优势在于,它们有名称。

getInstance:返回唯一的实例。
newInstance:返回不同的实例。

2.静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新对象。
3.静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。
4.静态工厂方法与构造器不同的第四大优势在于,在创建参数化类型实例的时候,它们使代码变得更加简洁。

缺点:

1.静态工厂方法的主要缺点在于,类如果不含公有的或者受保护的构造器,就不能被子类化。
2.静态工厂方法的第二个缺点在于,它们与其他的静态方法实际上没有任何区别。

public class OttoBus extends Bus {
    private volatile static OttoBus mBus;

    private OttoBus() { 
    }

    public static OttoBus getInstance() { //静态工厂方法
        if (mBus == null) {
            synchronized(OttoBus.class){
                if(mBus == null){
                    mBus = new OttoBus();
                }
            }
        }
        return mBus;
    }
}

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

重叠构造器模式:第一个构造器只有必要参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推。
重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。

如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是种不错的选择,特别是当大多数参数都是可选的时候。与使用传统的重叠构造器模式相比,使用Builder模式的客户端代码将更易于阅读和编写,构建器也比JavaBeans更加安全。

Builder模式:

public class Car {
    public static final int COLOR_WHITE = 0;
    public static final int COLOR_OTHER = 1;
    private final String company;
    private final String model;
    private final int color;
    private final float price;

    public static class Builder {
        //Required parameters
        private final String company;
        private final String model;

        //Optional parameters - initialized to default values
        private int color = COLOR_OTHER;
        private float price = 0F;

        public Builder(String company, String model) {
            this.company = company;
            this.model = model;
        }

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

        public Builder price(float price) {
            this.price = price;
            return this;
        }

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

    private Car(Builder builder) {
        company = builder.company;
        model = builder.model;
        color = builder.color;
        price = builder.price;
    }

    @Override
    public String toString() {
        return "company: " + company + ", model: " + model + ", color: " + color + ", price: " + price;
    }
}

//代码测试
Car.Builder builder = new Car.Builder("Honda", "XRV");
builder.color(Car.COLOR_WHITE);
builder.price(138000F);
Car car = builder.build();
Log.d(TAG, "zwm, car: " + car);

//输出
03-02 17:07:25.919 zwm, car: company: Honda, model: XRV, color: 0, price: 138000.0

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

实现Singleton有三种常用方法:
方法1:

public class Car {
    public static final Car INSTANCE = new Car();

    private Car() {
    }

    public void doSomething() {
    }
}

//测试代码
Car.INSTANCE.doSomething();

方法2:

public class Car {
    private static final Car INSTANCE = new Car();

    private Car() {
    }

    public static Car getInstance() {
        return INSTANCE;
    }
    
    public void doSomething() {
    }
}

//测试代码
Car.getInstance().doSomething();

方法3:

public enum Car {
    INSTANCE;

    public void doSomething() {
    }
}

//测试代码
Car.INSTANCE.doSomething();

这种方法在功能上与公有域方法相近,但是它更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。

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

所有的构造器都必须显式或隐式地调用超类构造器,当将构造器私有化之后,子类就没有可访问的超类构造器可调用了,因此将构造器私有化的类无法被子类化。

第5条:避免创建不必要的对象

String:

String s = new String("string"); //每次都创建一个新的String实例,错误使用
String s = "string"; //只用了一个String实例,正确使用

自动拆箱:

Long sum = 0L; //变量sum声明为Long类型
for(long i=0; i

要优先使用基本类型而不是装箱基本类型,要当心无意识的自动拆箱。

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

内存泄露场景:
1.无意识的对象保持。
2.缓存。
3.监听器和其他回调。

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

Java语言规范不仅不保证终结方法会被及时地执行,而且根本就不保证它们会被执行,不应该依赖终结方法来更新重要的持久状态。例如,依赖终结方法来释放共享资源(比如数据库)上的永久锁,很容易让整个分布式系统垮掉。

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

覆盖equals时请遵守通用约定:
1.自反性
对象必须等于其自身。
2.对称性
任何两个对象对于"它们是否相等"的问题都必须保持一致。
3.传递性
如果一个对象等于第二个对象,并且第二个对象又等于第三个对象,则第一个对象一定等于第三个对象。
4.一致性
如果两个对象相等,它们就必须始终保持相等,除非它们中有一个对象(或者两个都)被修改了。
5.非空性
所有的对象都必须不等于null。

实现高质量equals方法的诀窍:
1.使用==操作符检查"参数是否为这个对象的引用"。如果是,则返回true。
2.使用instanceof操作符检查"参数是否为正确的类型"。如果不是,则返回false。
3.把参数转换成正确的类型。
4.对于该类中的每个"关键"域,检查参数中的域是否与该对象中对应的域相匹配。如果这些测试全部成功,则返回true,否则返回false。
对于既不是float也不是double类型的基本类型域,可以使用==操作符进行比较;对于对象引用域,可以递归地调用equals方法;对于float域,可以使用Float.compare方法;对于double域,则使用Double.compare。对float和double域进行特殊的处理是很有必要的,因为存在着Float.NaN、-0.0F以及类似的double常量;对于数组域,则要把以上这些指导原则应用到每个元素上。
5.当编写完equals方法,应该确认五个特性:对称性、传递性、一致性、自反性(通常自动满足)、非空性(通常自动满足)。

public final class CaseInsensitiveString {
    private final String s;

    public CaseInsensitiveString(String s) {
        if(s == null) {
            throw new NullPointerException();
        }
        this.s = s;
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj) {
            return true;
        }
        if(obj instanceof CaseInsensitiveString) {
            return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
        }
        return false;
    }
}

//测试代码
CaseInsensitiveString testString1 = new CaseInsensitiveString("ABC");
CaseInsensitiveString testString2 = new CaseInsensitiveString("Abc");
Log.d(TAG, "zwm, testString1.equals(testString1): " + testString1.equals(testString1));
Log.d(TAG, "zwm, testString1.equals(testString2): " + testString1.equals(testString2));

//输出
03-03 15:40:47.979 zwm, testString1.equals(testString1): true
03-03 15:40:47.979 zwm, testString1.equals(testString2): true

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

在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常工作,这样的集合包括HashMap、HashSet和Hashtable。

约定内容如下:
1.在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。
2.如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
3.如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但是给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。

public class PhoneNumber {
    private final int areaCode;
    private final int prefix;
    private final int lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == this) {
            return true;
        }
        if(!(obj instanceof PhoneNumber)) {
            return false;
        }
        PhoneNumber pn = (PhoneNumber)obj;
        return pn.areaCode == areaCode
                && pn.prefix == prefix
                && pn.lineNumber == lineNumber;
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + areaCode;
        result = 31 * result + prefix;
        result = 31 * result + lineNumber;
        return result;
    }
}

//测试代码
PhoneNumber phoneNumber1 = new PhoneNumber(9, 150, 11);
PhoneNumber phoneNumber2 = new PhoneNumber(5, 137, 11);
Log.d(TAG, "zwm, phoneNumber1 hashCode: " + phoneNumber1.hashCode());
Log.d(TAG, "zwm, phoneNumber2 hashCode: " + phoneNumber2.hashCode());
Log.d(TAG, "zwm, phoneNumber1.equals(phoneNumber2): " + phoneNumber1.equals(phoneNumber2));

//输出
03-03 23:31:00.905 zwm, phoneNumber1 hashCode: 519757
03-03 23:31:00.906 zwm, phoneNumber2 hashCode: 515510
03-03 23:31:00.906 zwm, phoneNumber1.equals(phoneNumber2): false

第10条:始终要覆盖toString

虽然java.lang.Object提供了toString方法的一个实现,但它返回的字符串通常并不是类的用户所期望看到的。它包含类的名称,以及一个"@"符号,接着是散列码的无符号十六进制表示法,例如"PhoneNumber@163b91"。

toString的通用约定指出,被返回的字符串应该是一个"简洁的,但信息丰富,并且易于阅读的表达形式",建议所有的子类都覆盖这个方法。

public class PhoneNumber {
    private final int areaCode;
    private final int prefix;
    private final int lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == this) {
            return true;
        }
        if(!(obj instanceof PhoneNumber)) {
            return false;
        }
        PhoneNumber pn = (PhoneNumber)obj;
        return pn.areaCode == areaCode
                && pn.prefix == prefix
                && pn.lineNumber == lineNumber;
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + areaCode;
        result = 31 * result + prefix;
        result = 31 * result + lineNumber;
        return result;
    }

    @Override
    public String toString() {
        return "areaCode: " + areaCode + ", prefix: " + prefix + ", lineNumber: " + lineNumber;
    }
}

//测试代码
PhoneNumber phoneNumber1 = new PhoneNumber(9, 150, 11);
Log.d(TAG, "zwm, phoneNumber1: " + phoneNumber1);

//输出
03-04 22:50:50.878 zwm, phoneNumber1: areaCode: 9, prefix: 150, lineNumber: 11

第11条:谨慎地覆盖clone

Object类提供了Clone方法用于复制对象,一个类如果要支持Clone,需要覆盖Clone方法,并实现Cloneable接口。Object类的Clone实现为浅复制,为了达到深复制,对象的引用以及其引用的引用(依次类推)都需要实现深复制。

浅复制

public class PhoneNumber implements Cloneable{
    public int areaCode;
    public int prefix;
    public int lineNumber;
    public Address address;

    public PhoneNumber(int areaCode, int prefix, int lineNumber, Address address) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "areaCode: " + areaCode + ", prefix: " + prefix + ", lineNumber: " + lineNumber + ", address: " + address;
    }

    public static class Address {
        public String country;
        public String city;

        public Address(String country, String city) {
            this.country = country;
            this.city = city;
        }
    }
}

//测试代码
PhoneNumber.Address address = new PhoneNumber.Address("China", "Guangzhou");
PhoneNumber phoneNumber1 = new PhoneNumber(9, 150, 11, address);
PhoneNumber phoneNumber2 = null;
try {
    phoneNumber2 = (PhoneNumber)phoneNumber1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
Log.d(TAG, "zwm, phoneNumber1 == phoneNumber2: " + (phoneNumber1 == phoneNumber2));
Log.d(TAG, "zwm, phoneNumber1.address == phoneNumber2.address: " + (phoneNumber1.address == phoneNumber2.address));

//输出
2019-03-11 17:16:00.590 zwm, phoneNumber1 == phoneNumber2: false
2019-03-11 17:16:00.590 zwm, phoneNumber1.address == phoneNumber2.address: true

深复制

public class PhoneNumber implements Cloneable{
    public int areaCode;
    public int prefix;
    public int lineNumber;
    public Address address;

    public PhoneNumber(int areaCode, int prefix, int lineNumber, Address address) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        PhoneNumber phoneNumber = (PhoneNumber)super.clone(); //浅复制
        phoneNumber.address = (PhoneNumber.Address)address.clone(); //深复制
        return phoneNumber;
    }

    @Override
    public String toString() {
        return "areaCode: " + areaCode + ", prefix: " + prefix + ", lineNumber: " + lineNumber + ", address: " + address;
    }

    public static class Address implements Cloneable {
        public String country;
        public String city;

        public Address(String country, String city) {
            this.country = country;
            this.city = city;
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
}

//测试代码
PhoneNumber.Address address = new PhoneNumber.Address("China", "Guangzhou");
PhoneNumber phoneNumber1 = new PhoneNumber(9, 150, 11, address);
PhoneNumber phoneNumber2 = null;
try {
    phoneNumber2 = (PhoneNumber)phoneNumber1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
Log.d(TAG, "zwm, phoneNumber1 == phoneNumber2: " + (phoneNumber1 == phoneNumber2));
Log.d(TAG, "zwm, phoneNumber1.address == phoneNumber2.address: " + (phoneNumber1.address == phoneNumber2.address));

//输出
2019-03-11 17:29:43.010 zwm, phoneNumber1 == phoneNumber2: false
2019-03-11 17:29:43.011 zwm, phoneNumber1.address == phoneNumber2.address: false

第12条:考虑实现Comparable接口

Java平台类库中的所有值类都实现了Comparable接口,如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序、按数值顺序或者按年代顺序,那你就应该坚决考虑实现这个接口:

public interface Comparable {
    int compareTo(T t);
}

compareTo方法的通用约定与equals方法的相似:
将这个对象与指定的对象进行比较。当该对象小于、等于或大于指定对象的时候,分别返回一个负整数、零或者正整数。如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException。

public class PhoneNumber implements Comparable{
    public int areaCode;
    public int prefix;
    public int lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }

    @Override
    public int compareTo(PhoneNumber o) {
        int areaCodeDiff = areaCode - o.areaCode;
        if(areaCodeDiff != 0) {
            return areaCodeDiff;
        }
        int prefixDiff = prefix - o.prefix;
        if(prefixDiff != 0) {
            return prefixDiff;
        }
        int lineNumberDiff = lineNumber - o.lineNumber;
        return lineNumberDiff;
    }
}

//测试代码
PhoneNumber phoneNumber1 = new PhoneNumber(9, 150, 1501753);
PhoneNumber phoneNumber2 = new PhoneNumber(9, 137, 1375186);
Log.d(TAG, "zwm, phoneNumber1.compareTo(phoneNumber2): " + phoneNumber1.compareTo(phoneNumber2));

//输出
2019-03-11 18:36:20.252 zwm, phoneNumber1.compareTo(phoneNumber2): 13

第13条:使类和成员的可访问性最小化

对于成员(域、方法、嵌套类和嵌套接口)有四种可能的访问级别:
私有的(private):只有在声明该成员的顶层类内部才可以访问这个成员。
包级私有的(package-private):声明该成员的包内部的任何类都可以访问这个成员。从技术上讲,它被称为"缺省(default)访问级别",如果没有为成员指定访问修饰符,就采用这个访问级别。
受保护的(protected):声明该成员的类的子类可以访问这个成员,并且该成员的包内部的任何类也可以访问这个成员。
公有的(public):在任何地方都可以访问该成员。
如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。这样可以确保任何可使用超类的实例的地方也都可以使用子类的实例。
如果一个类实现了一个接口,那么接口中所有的类方法在这个类中也都必须被声明为公有的。这是因为接口中的所有方法都隐含着公有访问级别。
实例域决不能是公有的,包含公有可变域的类并不是线程安全的。同样的建议也适用于静态域,当然公有静态final域除外,但是要确保公有静态final域所引用的对象都是不可变的。

第14条:在公有类中使用访问方法而非公有域

公有类永远都不应该暴露可变的域。但是,有时候会需要用包级私有的或者私有的嵌套类来暴露域,无论这个类是可变还是不可变的。

第15条:使可变性最小化

不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。Java平台类库中包含许多不可变的类,其中有String、基本类型的包装类、BigInteger和BigDecimal。

为了使类成为不可变,要遵循下面五个规则:
1.不要提供任何会修改对象状态的方法。
2.保证类不会被扩展。
一般做法是使这个类成为final的。或者让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂来代替公有的构造器。
3.使所有的域都是final的。
4.使所有的域都成为私有的。
5.确保对于任何可变组件的互斥访问。

public class Complex {
    private final double re;
    private final double im;

    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public static Complex valueOf(double re, double im) {
        return new Complex(re, im);
    }
}

第16条:复合优先于继承

继承是实现代码重用的有力手段,但它并非永远是完成这项工作的最佳工具。继承使得子类依赖于其超类中特定功能的实现细节。超类的实现有可能会随着发行版本的不同而有所改变,如果真的发生了变化,子类可能会遭到破坏,即使它的代码完全没有变化。因而,子类必须要跟着其超类的更新而演变,除非超类是专门为了扩展而设计的,并且具有很好的文档说明。

不用扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例,这种设计被称做复合。它不依赖于现有类的实现细节,即使现有的类添加了新的方法,也不会影响新的类。

第17条:要么为继承而设计,并提供文档说明,要么就禁止继承

对于为了继承而设计的类,唯一的测试方法就是编写子类,必须在发布类之前先编写子类对类进行测试。

对于为了继承而设计的类,其构造器决不能调用可被覆盖的方法,无论是直接调用还是间接调用。如果违反了这条规则,很有可能导致程序失败。超类的构造器在子类的构造器之前运行,所以,子类中覆盖版本的方法将会在子类的构造器运行之前就先被调用。如果该覆盖版本的方法依赖于子类构造器所执行的任何初始化工作,该方法将不会如预期般地执行。

public class Super {
    private static final String TAG = "Super";

    public Super() {
        Log.d(TAG, "zwm, Super construct method");
        overrideMe(); //超类构造器调用了可被覆盖的方法
    }

    public void overrideMe() {
        Log.d(TAG, "zwm, Super overrideMe method");
    }
}

public class Sub extends Super {
    private static final String TAG = "Sub";
    private final Date date;

    public Sub() {
        Log.d(TAG, "zwm, Sub construct method");
        date = new Date();
    }

    @Override
    public void overrideMe() {
        Log.d(TAG, "zwm, Sub overrideMe method, date: " + date);
    }
}

//测试代码 
Sub sub = new Sub();
sub.overrideMe();

//输出Log
2019-03-14 17:29:55.298 zwm, Super construct method //超类构造器
2019-03-14 17:29:55.299 zwm, Sub overrideMe method, date: null //子类覆盖的方法
2019-03-14 17:29:55.299 zwm, Sub construct method //子类构造器
2019-03-14 17:29:55.356 zwm, Sub overrideMe method, date: Thu Mar 14 17:29:55 GMT+08:00 2019 //子类覆盖的方法

第18条:接口优于抽象类

抽象类允许包含某些方法的实现,但是接口则不允许,为了实现由抽象类定义的类型,类必须成为抽象类的一个子类。任何一个类,只要它定义了所有必要的方法,并且遵守通用约定,它就被允许实现一个接口,而不管这个类是处于类层次的哪个位置。因为Java只允许单继承,所以抽象类作为类型定义受到了极大的限制。

第19条:接口只用于定义类型

当类实现接口时,接口就充当可以引用这个类的实例的类型。因此,类实现了接口,就表明客户端可以对这个类的实例实施某些动作,为了任何其他目的而定义接口是不恰当的。

第20条:类层次优于标签类

标签类正是类层次的一种简单的仿效。为了将标签类转变成类层次,首先要为标签类中的每个方法都定义一个包含抽象方法的抽象类,这每个方法的行为都依赖于标签值。
标签类:

class Figure {
    enum Shape{RECTANGLE, CIRCLE};

    //Tag field
    final Shape shape;

    //Fields for RECTANGLE
    double length;
    double width;

    //Field for or CIRCLE
    double radius;

    //Construct for RECTANGLE
    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }
    
    //Construct for CIRCLE
    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius;
    }

    double area() {
        switch (shape) {
            case RECTANGLE:
                return length * width;
            case CIRCLE:
                return Math.PI * (radius * radius);
            default:
                throw new AssertionError();
        }
    }
}

类层次:

abstract class Figure {
    abstract double area();
}

class Rectangle extends Figure {
    final double length;
    final double width;

    Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    double area() {
        return length * width;
    }
}

class Circle extends Figure {
    final double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    @Override
    double area() {
        return Math.PI * (radius * radius);
    }
}

第21条:用函数对象表示策略

为了在Java中实现策略模式,需要声明一个接口来表示该策略,并且为每个具体策略声明一个实现了该接口的类。当一个具体策略只被使用一次时,通常使用匿名类来声明和实例化这个具体策略类。当一个具体策略是设计用来重复使用的时候,它的类通常就要被实现为私有的静态成员类,并通过公有的静态final域被导出,其类型为该策略接口。

第22条:优先考虑静态成员类

嵌套类是指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为了它的外围类提供服务。如果嵌套类将来可能会用于其他的某个环境中,它就应该是顶层类。嵌套类有四种:静态成员类、非静态成员类、匿名类和局部类,除了第一种外,其他三种都被称为内部类。

静态成员类是最简单的一种嵌套类,最好把它看作是普通的类,只是碰巧被声明在另一个类的内部而已,它可以访问外围类的所有成员,包括那些声明为私有的成员。静态成员类是外围类的一个静态成员,与其他的静态成员一样,也遵守同样的可访问性规则。如果它被声明为私有的,它就只能在外围类的内部才可以被访问,等等。

非静态成员类的每个实例都隐含着与外围类的一个外围实例相关联。在非静态成员类的实例方法内部,可以调用外围实例上的方法,或者利用修饰过的this构造获得外围实例的引用。如果嵌套类的实例可以在它的外围类的实例之外独立存在,这个嵌套类就必须是静态成员类:在没有外围实例的情况下,要想创建非静态成员类的实例是不可能的。

匿名类没有名字,它不是外围类的一个成员,它并不与其他的成员一起被声明,而是在使用的同时被声明和实例化。匿名类可以出现在代码中任何允许存在表达式的地方。当且仅当匿名类出现在非静态的环境中时,它才有外围实例。但是即使它们出现在静态的环境中,也不可能拥有任何静态成员。

局部类是四种嵌套类中使用得最少的类。在任何可以声明局部变量的地方,都可以声明局部类,并且局部类也遵守同样的作用域规则。局部类与其他三种嵌套类中的每一种都有一些共同的属性。与成员类一样,局部类有名字,可以被重复地使用。与匿名类一样,只有当局部类是在非静态环境中定义的时候,才有外围实例,它们也不能包含静态成员。与匿名类一样,它们必须非常简短,以便不会影响到可读性。

第23条:请不要在新代码中使用原生态类型

声明中具有一个或者多个类型参数的类或者接口,就是泛型类或者接口。每个泛型都定义一个原生态类型,即不带任何实际类型参数的泛型名称,例如,与List相对应的原生态类型是List。

原生态类型List和参数化的类型List之间的区别:前者逃避了泛型检查,后者则明确告知编译器,它能够持有任意类型的对象。虽然你可以将List传递给类型List的参数,但是不能将它传给类型List的参数。泛型有子类型化的规则,List是原生态类型List的一个子类型,而不是参数化类型List的子类型。因此,如果使用像List这样的原生态类型,就会失掉类型安全性,但是如果使用像List这样的参数化类型则不会。

List strings = new ArrayList<>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0); //编译通过,运行时抛异常,Caused by: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Log.d(TAG, "zwm, s: " + s);

void unsafeAdd(List list, Object object) { //使用List
    list.add(object);
}
List strings = new ArrayList<>();
unsafeAdd(strings, new Integer(42)); //编译不通过,strings类型不对
String s = strings.get(0);
Log.d(TAG, "zwm, s: " + s);

void unsafeAdd(List list, Object object) { //使用List
    list.add(object);
}
 
 

如果要使用泛型,但不确定或者不关心实例的类型参数,就可以使用一个问号代替,即无限制的通配符类型。
无限制通配类型List和原生态类型List的区别:通配符类型是安全的,原生态类型则不安全。

List list = new ArrayList<>();
list.add("abc");
list.add("def");
test(list);

List list2 = new ArrayList<>();
list2.add(123);
list2.add(456);
test(list2);

void test(List list) { //使用List
    Log.d(TAG, "zwm, get(0): " + list.get(0));
}

//输出Log
2019-03-14 21:09:08.277 zwm, get(0): abc
2019-03-14 21:09:08.277 zwm, get(0): 123

不要在新代码中使用原生态类型,这条规则有两个小小的例外,两者都源于泛型信息可以在运行时被擦除这一事实。
1.在类文字中必须使用原生态类型,如List.class,String[].class和int.class都合法,但是List.class和List.class则不合法。
2.由于泛型信息可以在运行时被擦除,因此在参数化类型而非无限制通配符类型上使用instanceof操作符是非法的,用无限制通配符类型代替原生态类型,对instanceof操作符的行为不会产生任何影响。在这种情况下,尖括号和问号就显得多余了,下面是利用泛型来使用instanceof操作符的首选方法:

if(o instanceof List) {
    List m =  o; //一旦确定这个o是个List,就必须将它转换成通配符类型List,而不是转换成原生态类型List。
}

第24条:消除非受检警告

用泛型编程时,要尽可能地消除每一个非受检警告。如果消除了所有警告,就可以确保代码是类型安全的,这是一件很好的事情,这就意味着不会在运行时出现ClassCastException异常。

如果无法消除警告,同时可以证明引起警告的代码是类型安全的,只有在这种情况下,可以用一个@SuppressWarnings("unchecked")注解来禁止这条警告。SuppressWarnings注解可以用在任何粒度的级别中,从单独的局部变量声明到整个类都可以,应该始终在尽可能小的范围中使用SuppressWarnings注解。

第25条:列表优先于数组

数组会在运行时发现所犯的错误,而列表可以在编译时发现错误。泛型只在编译时强化它们的类型信息,并在运行时丢弃(或者擦除)它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码随意进行互用。

数组和泛型不能很好地混合使用,创建泛型、参数化类型或者类型参数的数组是非法的。例如,new List[]、new List[]和new E[],会报编译时错误。

Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in"; //编译通过,运行时抛异常,Caused by: java.lang.ArrayStoreException: java.lang.String cannot be stored in an array of type java.lang.Long[]
List list = new ArrayList(); //编译不通过
list.add("I don't fit in");
 
 

第26条:优先考虑泛型

使用泛型比使用需要在客户端代码中进行转换的类型来得更加安全。

第27条:优先考虑泛型方法

就如类可以从泛型中收益一般,方法也一样,静态工具方法尤其适合于泛型化。

public  T genericMethod(Class tClass) {
    T instance = null;
    try {
        instance = tClass.newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return instance;
}

//测试代码
Circle circle = genericMethod(Circle.class);
Log.d(TAG, "zwm, circle: " + circle);
Rectangle rectangle = genericMethod(Rectangle.class);
Log.d(TAG, "zwm, rectangle: " + rectangle);

//输出Log
2019-03-15 10:29:15.560 zwm, circle: com.tomorrow.test2019_3.Circle@6831f23
2019-03-15 10:29:15.560 zwm, rectangle: com.tomorrow.test2019_3.Rectangle@1631c20

第28条:利用有限制通配符来提升API的灵活性

Java提供了有限制的通配符类型:
表示E的某个子类型,表示T的父类型。

第29条:优先考虑类型安全的异构容器

泛型最常用于集合,如Set和Map,以及单元素的容器,如ThreadLocal和AtomicReference。

public class Favorites {
    private Map, Object> favorites = new HashMap<>();

    public  void putFavorite(Class type, T instance) {
        if(type == null) {
            throw new NullPointerException("Type is null");
        }
        favorites.put(type, instance);
    }

    public  T getFavorite(Class type) {
        return type.cast(favorites.get(type));
    }
}

//测试代码
Favorites favorites = new Favorites();
favorites.putFavorite(String.class, "abc");
favorites.putFavorite(Integer.class, 123);
Log.d(TAG, "zwm, getFavorite, Integer: " + favorites.getFavorite(Integer.class));
Log.d(TAG, "zwm, getFavorite, String: " + favorites.getFavorite(String.class));

//输出Log
2019-03-15 10:57:33.460 zwm, getFavorite, Integer: 123
2019-03-15 10:57:33.460 zwm, getFavorite, String: abc

第30条:用enum代替int常量

enum Week {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}

第31条:用实例域代替序数

所有的枚举都有一个ordinal方法,它返回每个枚举常量在类型中的数字位置,但是我们永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中。

enum Week {
    MONDAY(11),
    TUESDAY(12),
    WEDNESDAY(13),
    THURSDAY(14),
    FRIDAY(15),
    SATURDAY(16),
    SUNDAY(17);

    private final int number;
    Week(int number) {
        this.number = number;
    }

    public int number() {
        return number;
    }
}

//测试代码
Log.d(TAG, "zwm, MONDAY ordinal: " + Week.MONDAY.ordinal());
Log.d(TAG, "zwm, MONDAY number: " + Week.MONDAY.number());

//输出Log
2019-03-15 13:46:33.664 zwm, MONDAY ordinal: 0
2019-03-15 13:46:33.664 zwm, MONDAY number: 11

第32条:用EnumSet代替位域

enum Week {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}

//测试代码
EnumSet enumSet = EnumSet.of(Week.MONDAY, Week.WEDNESDAY, Week.FRIDAY);
Log.d(TAG, "zwm, enumSet: " + enumSet);

//输出Log
2019-03-15 14:06:02.714 zwm, enumSet: [MONDAY, WEDNESDAY, FRIDAY]

第33条:用EnumMap代替序数索引

enum Week {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}

//测试代码
EnumMap enumMap = new EnumMap<>(Week.class);
enumMap.put(Week.MONDAY, "星期一");
enumMap.put(Week.WEDNESDAY, "星期三");
enumMap.put(Week.FRIDAY, "星期五");
Log.d(TAG, "zwm, enumMap: " + enumMap);

//输出Log
2019-03-15 14:19:19.007 zwm, enumMap: {MONDAY=星期一, WEDNESDAY=星期三, FRIDAY=星期五}

第34条:用接口模拟可伸缩的枚举

public interface Operation {
    double apply(double x, double y);
}

public enum BasicOperation implements Operation {
    PLUS("+") {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        @Override
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        @Override
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        @Override
        public double apply(double x, double y) {
            return x / y;
        }
    };
    
    private final String symbol;
    BasicOperation(String symbol) {
        this.symbol = symbol;
    }
    @Override
    public String toString() {
        return symbol;
    }
}

public enum ExtendedOperation implements Operation {
    EXP("^") {
        @Override
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
    REMAINDER("%") {
        @Override
        public double apply(double x, double y) {
            return x % y;
        }
    };
    
    private final String symbol;
    BasicOperation(String symbol) {
        this.symbol = symbol;
    }
    @Override
    public String toString() {
        return symbol;
    }
}

第35条:注解优先于命名模式

Java 1.5发行版本之前,一般使用命名模式表明有些程序元素需要通过某种工具或者框架进行特殊处理。例如,JUnit测试框架原本要求它的用户一定要用test作为测试方法名称的开头。这种方法可行,但是有几个很严重的缺点:
1.文字拼写错误会导致失败,且没有任何提示;
2.无法确保它们只用于相应的程序元素上;
3.它们没有提供将参数值与程序元素关联起来的好方法。
注解很好地解决了所有这些问题,有了注解,就完全没有理由再使用命名模式了。除了特定程序员外,大多数程序员都不必定义注解类型,但是所有的程序员都应该使用Java平台所提供的预定义的注解类型。

第36条:坚持使用Override注解

随着Java 1.5发行版本中增加注解,类库中也增加了几种注解类型,对于传统的程序员而言,这里面最重要的就是Override注解了。这个注解只能用在方法声明中,它表示被注解的方法声明覆盖了超类型中的一个声明。

第37条:用标记接口定义类型

标记接口是没有包含方法声明的接口,而只是指明一个类实现了具有某种属性的接口。例如,考虑Serializable接口,通过实现这个接口,类表明它的实例可以被写到ObjectOutputStream(或者"被序列化")。

标记接口的优点:
1.标记接口定义的类型是由被标记类的实例实现的,标记注解则没有定义这样的类型;
2.标记接口可以被更加精确地进行锁定。

标记注解的优点:
1.可以通过默认的方式添加一个或者多个注解类型元素,给已被使用的注解类型添加更多的信息;
2.标记注解是更大的注解机制的一部分。

如果你发现自己在编写的是目标为ElementType.TYPE的标记注解类型,就要花点时间考虑清楚,它是否真的应该为注解类型,想想标记接口是否会更加合适呢。

第38条:检查参数的有效性

每当编写方法或者构造器的时候,应该考虑他的参数有哪些限制。应该把这些限制写到文档中,并且在这个方法体的开头处,通过显示的检查来实施这些限制。养成这样的习惯是非常重要的,只要有效性检查有一次失败,你为必要的有效性检查所付出的努力便都可以连本带利地得到偿还了。

第39条:必要时进行保护性拷贝

对于构造器的每个可变参数进行保护性拷贝是必要的。保护性拷贝是在检查参数的有效性之前进行的,并且有效性检查时针对拷贝之后的对象,而不是针对原始的对象。一般情况下,最好使用构造器或者静态工厂进行保护性拷贝。

第40条:谨慎设计方法签名

方法:
1.谨慎地选择方法的名称,方法的名称应该始终遵循标准的命名习惯。
2.不要过于追求提供便利的方法,每个方法都应该尽其所能。
3.避免过长的参数列表,目标是四个参数或者更少;
4.对于参数类型,要优先使用接口而不是类;
5.对于boolean参数,要优先使用两个元素的枚举类型。

第41条:慎用重载

private void testMethod(int arg) {
    Log.d(TAG, "zwm, testMethod int, arg: " + arg);
}

private void testMethod(Integer arg) {
    Log.d(TAG, "zwm, testMethod int, arg: " + arg);
}

第42条:慎用可变参数

在定义参数数目不定的方法时,可变参数方法是一种很方便的方式,但是它们不应该被过度滥用,如果使用不当,会产生混乱的结果。

private void testMethod(int arg, int... otherArgs) {
    Log.d(TAG, "zwm, testMethod int, arg: " + arg);
    for(int a : otherArgs) {
        Log.d(TAG, "zwm, a: " + a);
    }
}

//测试代码
testMethod(9, 1, 2, 3);

//输出Log
03-16 10:40:45.660 zwm, testMethod int, arg: 9
03-16 10:40:45.660 zwm, a: 1
03-16 10:40:45.660 zwm, a: 2
03-16 10:40:45.660 zwm, a: 3

第43条:返回零长度的数组或者集合,而不是null

第44条:为所有导出的API元素编写文档注释

要为API编写文档,文档注释是最好、最有效的途径。

第45条:将局部变量的作用域最小化

将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性。Java允许在任何可以出现语句的地方声明变量。要使局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方声明。

第46条:for-each循环优先于传统的for循环

for-each循环不仅让你遍历集合和数组,还让你遍历任何实现Iterable接口的对象。

有三种常见的情况无法使用for-each循环:
1.过滤
如果需要遍历集合,并删除选定的元素,就需要使用显示的迭代器,以便可以调用它的remove方法。
2.转换
如果需要遍历列表或数组,并取代它部分或者全部的元素值,就需要列表迭代器或者数组索引,以便设定元素的值。
3.平行迭代
如果需要并行地遍历多个集合,就需要显示地控制迭代器或者索引变量,以便所有的迭代器或者索引变量都可以得到同步前移。
在以上任何一种情况下,就要使用普通的for循环。

第47条:了解和使用类库

不要重新发明轮子。如果你要做的事情看起来是十分常见的,有可能类库中已经有个类完成了这样的工作。如果确实是这样,就使用现成的,如果还不清楚是否存在这样的类,就去查一查。一般而言,类库的代码可能比你自己编写的代码更好一些,并且会随着时间的推移而不断改进。

第48条:如果需要精确的答案,请避免使用float和double

float和double类型主要是为了科学计算和工程计算而设计的。它们执行二进制浮点运算,这是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们并没有提供完全精确的结果,所以不应该被用于需要精确结果的场合。float和double类型尤其不适合用于货币计算,因为要让一个float或者double精确地表示0.1(或者10的任何其他负数次方值)是不可能的。

float a = 1.03F;
float b = 0.1F;
float c = a - b;
Log.d(TAG, "zwm, c: " + c);

//输出Log
03-16 11:24:23.975 zwm, c: 0.92999995

解决这个问题的正确办法是使用BitDecimal、int(整型,数值范围没有超过9位十进制数字)或者long(长整型,数值范围没有超过18位十进制数字)进行货币计算。

BigDecimal a = new BigDecimal("1.03");
BigDecimal b = new BigDecimal("0.1");
BigDecimal c = a.subtract(b);
Log.d(TAG, "zwm, c: " + c);

//输出Log
03-16 15:39:26.152 zwm, c: 0.93

第49条:基本类型优先于装箱基本类型

Java每个基本类型都有一个对应的引用类型,称为装箱基本类型。如int、double和boolean对应的是Integer、Double和Boolean。

当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱,这种情况无一例外。

Long sum = 0L; //装箱基本类型
for(long i=0; i

第50条:如果其他类型更适合,则尽量避免使用字符串

不应该使用字符串的情形:
1.字符串不适合代替其他的值类型;
2.字符串不适合代替枚举类型;
3.字符串不适合代替聚集类型;
4.字符串不适合代替能力表。

第51条:当心字符串连接符的性能

字符串连接操作符(+)是把多个字符串合并为一个字符串的便利途径。要想产生单独一行的输出,或者构造一个字符串来表示一个较小的、大小固定的对象,使用连接操作符是非常适合的,但是它不适合运用在大规模的场景中。为了连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间。这是由于字符串不可变而导致的不幸结果。当两个字符串被连接在一起时,它们的内容都要被拷贝。

为了获得可以接受的性能,请使用StringBuilder代替String。

StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("This is StringBuilder:")
        .append("append A, ")
        .append("append B.");
Log.d(TAG, "zwm, string: " + stringBuilder.toString());

//输出Log
03-16 16:10:34.886 zwm, string: This is StringBuilder:append A, append B.

第52条:通过接口引用对象

如果有合适的接口类型存在,那么对于参数、返回值、变量和域来说,就都应该使用接口类型进行声明。如果你养成了用接口作为类型的习惯,你的程序将会更加灵活。

不存在适当接口类型的情形:
1.如果没有合适的接口存在,完全可以用类而不是接口来引用对象;
2.如果对象属于这种基于类的框架,就应该用相关的基类(往往是抽象类)来引用这个对象,而不是用它的实现类;
3.类实现了接口,但是它提供了接口中不存在的额外方法。如果程序依赖于这些额外的方法,这种类就应该只被用来引用它的实例,它很少应该被用作参数类型。

第53条:接口优先于反射机制

核心反射机制提供了通过程序来访问关于已装载的类的信息的能力。给定一个Class实例,你可以获得Constructor、Method和Field实例,分别代表了该Class实例所表示的类的构造器、方法和域,这些对象提供了通过程序来访问类的成员名称、域类型、方法签名等信息的能力。

反射机制的代价:
1.丧失了编译时类型检查的好处,包括异常检查;
2.执行反射访问所需要的代码非常笨拙和冗长;
3.性能损失。

第54条:谨慎地使用本地方法

Java Native Interface(JNI)允许Java应用程序可以调用本地方法,所谓本地方法是指本地程序设计语言(比如C或者C++)来编写特殊方法。本地方法在本地语言中可以执行任意的计算任务,并返回到Java程序设计语言。

极少数情况下会需要使用本地方法来提高性能,如果你必须要使用本地方法来访问底层的资源,或者遗留代码库,也要尽可能少用本地代码,并且要全面进行测试。本地代码中的一个BUG就有可能破坏整个应用程序。

第55条:谨慎地进行优化

不要费力去编写快速的程序,应该努力编写好的程序,速度自然会随之而来。在设计系统的时候,特别是在设计API、线路层协议和永久数据格式的时候,一定要考虑性能的因素。当构建完系统之后,要测量它的性能,如果它足够快,你的任务就完成了。如果不够快,则可以在性能剖析器的帮助下,找到问题的根源,然后设法优化系统中相关的部分。第一个步骤是检查所选择的算法,再多的底层优化也无法弥补算法的选择不当。必要时重复这个过程,在每次改变之后都要测量性能,直到满意为止。

第56条:遵守普遍接受的命名惯例

第57条:只针对异常的情况才使用异常

异常是为了在异常情况下使用而设计的,不要将他们用于普通的控制流,也不要编写迫使它们这么做的API。

第58条:对可恢复的情况使用受检异常,对编程错误使用运行时异常

Java程序设计语言提供了三种可抛出结构:受检的异常、运行时异常和错误。

第59条:避免不必要地使用受检的异常

受检的异常是Java程序设计语言的一项很好的特性,与返回代码不同,它们强迫程序员处理异常的条件,大大增强了可靠性。也就是说,过分使用受检的异常会使API使用起来非常不方便。如果方法抛出一个或者多个受检的异常,调用该方法的代码就必须在一个或者多个catch块中处理这些异常,或者它必须声明它抛出这些异常,并让他们传播出去。无论哪种方法,都给程序员增添了不可忽视的负担。

第60条:优先使用标准的异常

常用的异常:
IllegalArgumentException:非null的参数值不正确。
IllegalStateException:对于方法调用而言,对象状态不合适。
NullPointerException:在禁止使用null的情况下参数值为null。
IndexOutofBoundsException:下表参数值越界。
ConcurrentModificationException:在禁止并发修改的情况下,检测到对象的并发修改。
UnsupportedOperationException:对象不支持用户请求的方法。

第61条:抛出与抽象相对应的异常

更高层的实现应该捕获底层的异常,同时抛出可以按照高层抽象进行解释的异常,这种做法被称为异常转译。

try {
    ...
} catch(LowerLevelException e) {
    throw new HigherLevelException(...);
}

第62条:每个方法抛出的异常都要有文档

要为你编写的每个方法所能抛出的每个异常建立文档。对于未受检和受检的异常,以及对于抽象的和具体的方法也都一样。要为每个受检异常提供单独的throws子句,不要为未受检的异常提供throws子句。如果没有为可以抛出的异常建立文档,其他人就很难或者根本不可能有效地使用你的类和接口。

第63条:在细节消息中包含能捕获失败的信息

异常的细节消息应该捕获住失败,便于以后分析。

第64条:努力使失败保持原子性

一般而言,失败的方法调用应该使对象保持在被调用之前的状态,具有这种属性的方法被称为具有失败原子性。

第65条:不要忽略异常

当API的设计者声明一个方法将抛出某个异常的时候,他们等于正在试图说明某些事情,所以,请不要忽略它。要忽略一个异常非常容易,只需将方法调用通过try语句包围起来,并包含一个空的catch块。

try {
    ...
} catch (SomeException e) {

}

每当见到空的catch块时,应该警钟长鸣,至少,catch块应该包含一条说明,解释为什么可以忽略这个异常。

第66条:同步访问共享的可变数据

关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法,或者某一个代码块。

Java语言规范保证读或者写一个变量是原子的,除非这个变量的类型为long或者double。换句话说,读取一个非long或者double类型的变量,可以保证返回的值是某个线程保存在该变量中的,即使多个线程在没有同步情况下并发地修改这个变量也是如此。虽然语言规范保证了线程在读取原子数据的时候,不会看到任意的数值,但是它并不保证一个线程写入的值对于另一个线程将是可见的。为了在线程之间进行可靠的通信,也为了互斥访问,同步是必要的。

虽然volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该域的时候都将看到最近刚刚被写入的值,不过在使用volatile的时候务必小心。

private static volatile int nextSerialNumber = 0;

public static int generateSerialNumber() {
    return nextSerialNumber++;
}

如果没有同步,generateSerialNumber方法仍然无法正常工作。问题在于增量操作符(++)不是原子的。它包含三个操作:读取值、加1、写回值。如果第二个线程在第一个线程读取旧值和写回新值期间读取这个域,第二个线程就会与第一个线程一起看到同一个值,并返回相同的序列号。这就是安全性失败,即这个程序会计算出错误的结果。

修正方法1:使用synchronized修饰符

private static volatile int nextSerialNumber = 0;

public synchronized static int generateSerialNumber() {
    return nextSerialNumber++;
}

修正方法2:使用AtomicInteger

private static volatile AtomicInteger nextSerialNumber = new AtomicInteger(0);

public static int generateSerialNumber() {
    return nextSerialNumber.getAndIncrement();
}

第67条:避免过度同步

为了避免活性失败(一个线程做修改,另一个线程无法获知)和安全性失败(一个线程做修改但是未完成,另一个线程获得旧值,如加了volatile关键字的自增操作),在一个被同步的方法或者代码块中,永远不要放弃对客户端的控制。

为了避免死锁和数据破坏,千万不要从同步区域内部调用外来方法。更为一般地讲,要尽量限制同步区域内部的工作量。当你在设计一个可变类的时候,要考虑一下它们是否应该自己完成同步操作。

第68条:executor和task优先于线程

java.util.concurrent.Executors类包含了静态工厂,能提供所需的大多数executor。也可以使用ThreadPoolExecutor类。

你不仅应该尽量不要编写自己的工作队列,而且还应该尽量不要直接使用线程。现在关键的抽象是工作单元,称作任务。任务有两种,Runnable及其近亲Callable(它与Runnable类似,但它会返回值)。

第69条:并发工具优先于wait和notify

虽然你始终应该优先使用并发工具,而不是使用wait和notify,但可能必须维护使用了wait和notify的遗留代码。wait方法被用来使线程等待某个条件,它必须在同步区域内部被调用,这个同步区域将对象锁定在了调用wait方法的对象上。

使用应该使用wait循环模式来调用wait方法,永远不要在循环之外调用wait方法,循环会在等待之前和之后测试条件。

一般情况下应该优先使用notifyAll(唤醒所有正在等待的线程),而不是notify(唤醒一个正在等待的线程)。

第70条:线程安全性的文档化

一个类为了可被多个线程安全地使用,必须在文档中清楚地说明它所支持的线程安全级别。

第71条:慎用延迟初始化

延迟初始化是延迟到需要域的值时才将它初始化的这种行为。除非绝对必要,否则就不要这么做。

第72条:不要依赖于线程调度器

不要让应用程序的正确性依赖于线程调度器,否则,结果得到的应用程序将既不健壮,也不具有可移植性。作为推论,不要依赖Thread.yield或者线程优先级。这些设施仅对调度器做些暗示。线程优先级可以用来提高一个已经能够正常工作的程序的服务质量,但永远不应该用来"修正"一个原本并不能工作的程序。

第73条:避免使用线程组

我们最好把线程组看作是一个不成功的试验,你可以忽略掉它们,就当它们根本不存在一样。

你可能感兴趣的:(Effective Java -- 1)