Effective_java读书笔记

Chapter 01.

Item 1.Consider static factory methods instead of constructors

使用静态工厂方法替换构造方法的好处:
  1. 每一个构造方法都有它对应的名字,不像构造方法,名字相同参数不同,可读性不高。
  2. 静态工厂方法不一定要每次构造新的对象,他们可以调用,这样可以作用再不可变类中。类似于享元模式(flyweight)
  3. 返回类型可以是其返回类型的任何一个子类并且可以要求客户端进行面向接口编程
  4. 减少创建参数化实例如创建
Map<String, List<String>> m = new HashMap<String, List<String>>();

创建实例对象方法

public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}

hashMap无需参数化

 Map<String, List<String>> m = HashMap.newInstance();
使用静态工厂方法替换构造方法的坏处:
  1. 没有public或protected的构造方法让他无法派生出子类。也就是无法被继承
  2. 无法从其他静态方法中轻松区别出来。

###Item 2.Consider a builder when faced with many constructor parameters

在面对又大量参数的构造方法的时候我们可以采用以下三种方式

  1. telescoping constructor 就是使用各种各样的构造方法。
    the telescoping constructor pattern works, but it is hard to write
    client code when there are many parameters, and harder still to read it.
class NutritionFacts {
    private int servingSize = 0; // (mL) required
    private int servings = 0; // (per container) required
    private int calories = 0; // optional
    ...

    public NutritionFacts(int servingSize, int servings,
                          int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }
    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }
    public NutritionFacts(int servingSize, int servings,
                          int calories) {
        this(servingSize, servings, calories, 0);
    }
    ...
}
  1. 使用javabean
    就是在类中定义set然后初始化对象的时候set,具体如下:
public class NutritionFacts {
    // Parameters initialized to default values (if any)
    private int servingSize = -1; // Required; no default value
    private int servings = -1; // " " " "
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;
    public NutritionFacts() { }
    // Setters
    public void setServingSize(int val) { servingSize = val; }
    public void setServings(int val) { servings = val; }
    public void setCalories(int val) { calories = val; }
    public void setFat(int val) { fat = val; }
    public void setSodium(int val) { sodium = val; }
    public void setCarbohydrate(int val) { carbohydrate = val; }
}

使用时:

NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);

缺点:

  1. a JavaBean may be in an inconsistent state partway through its construction

  2. the JavaBeans pattern precludes the possibility of making class immutable
    两者结合讲的主要是线程安全方面的问题,javabean的方式无法 " freezing the object "。

  3. builder object
    构造静态内部类,用于创建对象。实现如下:

// Builder Pattern
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 {
        // Required parameters
        private final int servingSize;
        private final int servings;
        // Optional parameters - initialized to default values 这里的初始化如果为0可有可无
        private int calories = 0;
        private int fat = 0;
        private int carbohydrate = 0;
        private int sodium = 0;
        //1.使用时先到这一句
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }
        //2.添加可选参数到这一句
        public Builder calories(int val)
        { calories = val; return this; }
        public Builder fat(int val)
        { fat = val; return this; }
        public Builder carbohydrate(int val)
        { carbohydrate = val; return this; }
        public Builder sodium(int val)
        { sodium = val; return this; }
        //3.最后到这一行构建NutritionFacts对象
        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 cocaCola = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();

Item 3.Enforce the singleton property with a private constructor or an enum type

1.5之前有两种,都是基于私有构造方法的,然后暴露一个公共的成员属性来使用这个单例类。

// Singleton with public final field
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public void leaveTheBuilding() { ... }
}
// Singleton with static factory
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() { ... }
}

1.5之后发布枚举类型,可使用枚举作为单例

a single-element enum type is the best way to implement a singleton.

public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}

Item 4.Enforce noninstantiability with a private constructor

如果这个类为工具类只有静态的方法和字段,那么就要让这个类不可实例化。

class UtilClass{
    private UtilClass(){
        throw new AssertionError();
    }
    //只有静态方法
    public static int power(int a){
        return a*a;
    }
}

就像Math类设置的privae的构造方法一样,1.8有声明Don’t let anyone instantiate this class.

public final class Math {
    /**
     * Don't let anyone instantiate this class.
     */
    private Math() {}
	//...
}

Item 5.Avoid creating unnecessary objects

几个点:

  1. String s=new String("String")不要使用这种String s="String"使用这种,这样可以避免重复创建新的对象特别实在循环中。
  2. 对于生成候不会再改变的变量,我们可以在初始化对象的时候直接将其初始化(使用static final),如下例子:
class Person{
    private final Date birthDate;
	//...
	//DON'T DO THIS
    public boolean isBabyBoomer(){
        Calendar gmtCal=Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
        Date boomStart = gmtCal.getTime();
        gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
        Date boomEnd=gmtCal.getTime();
        return birthDate.compareTo(boomStart)>=0 && birthDate.compareTo(boomEnd) < 0;
    }
}

以上的形式如果多次循环中会产生多组Calendar,TimeZone,和两个Date对象,这些在生成之后都是没有发生过改变的,所以可以提取出来,如下:

class Person{
	private Date birthDate;
	//将所需用到的起始时间和结束时间用final static修饰,在直接初始化
	private final static Date boomStart;
    private final static Date boomEnd;
    static {
        Calendar gmtCal=Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
        boomStart = gmtCal.getTime();
        gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
        boomEnd=gmtCal.getTime();
    }

    public boolean isBabyBoomer(){
        return birthDate.compareTo(boomStart)>=0 && birthDate.compareTo(boomEnd) < 0;
    }
}
  1. Prefer primitives to boxed primitives, and watch out for unintentional autoboxing. 自动装箱会生成多余的对象,如果可以用原始类型就用原始类型。如下例子sum为Long,在叠加的时候会生成多个多余对象,而sum1为long在累加的时候只会生成一个对象。
public class Main {
    public static void main(String[] args) {
        Long t=System.currentTimeMillis();
        Long sum=0L;
        for (long i=0;i<Integer.MAX_VALUE;i++){
            sum+=i;
        }
        System.out.println(sum);
        Long t1=System.currentTimeMillis();
        System.out.println(t1-t);
        long sum1=0L;
        for (long i=0;i<Integer.MAX_VALUE;i++){
            sum1+=i;
        }
        System.out.println(sum1);
        Long t2=System.currentTimeMillis();
        System.out.println(t2-t1);
    }
}

Item 6.Eliminate obsolete object references

  1. 在java.util.Stack中,我们可以发现push有确保大小足够的扩容操作,而在pop不单单把指针前移返回当前位置上的值,而是在最后添加了一步将pop的元素位上置空。这样可以触发gc来清理这个对象,消除过期的对象引用,而不会因为扩容然后缩小(a stack grows and then shrinks)导致内存泄露(memory leak)。
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */

Nulling out object references should be the exception rather than the norm.

  1. caches.
  2. listeners and other callbacks
    2,3两部分主要描述了对于对象长时间不用的那些,可以使用弱引用(weakhashmap

Item 7.Avoid finalizers

关于finalizer,并没有使用过,finalize()可以简单了解

this is called before the garbage collection for a particular object.

Chapter 02.

Item 8.Obey the general contract when overriding equals

如果满足以下条件不需要重写equals,而不重写equals是避免错误的最好的办法。

  1. Each instance of the class is inherently unique(谷歌翻译天生独特).
    例如thread表示的是活动的主体二(active entities)而不是值(values),继承下来的obect类中的equals以及可以准确表达了。
  2. You don’t care whether the class provides a “logical equality” test. 该类不需要提供逻辑上的相同。
    书中例子是random的equals提供了比较两个random对象是否生成了一样的串,但是如果我们觉得client用不到这个功能,就可以直接去使用object中的equals。
  3. A superclass has already overridden equals, and the superclass behavior
    is appropriate for this class.父类重写过equals并且对子类任然有效。比如说大多数的Set、map、list的equals都沿用他们的抽象父类,AbstractSet、AbstractMap、AbstractList。
  4. The class is private or package-private, and you are certain that its equals
    method will never be invoked.私有的或者protect的并且equals不会被调用的。
    这种情况可以用以下这种情况来重写,防止被调用。但是在不会被调用的前提下。
@Override
public boolean equals(Object o) {
	throw new AssertionError(); // Method is never called
}

适合重写的时候
value class(例如Integer、Date这种只关心他的值,而不关心两者是否为同一对象的类,同时父类还没有重写过他的equals方法),这个时候为了满足这个要求,我们不仅需要覆盖equals还需要保证他可以被用来作为map的key或用在set中。
但是其中有一种单例( uses instance control (Item 1) to ensure that at most one object
exists with each value.)不需要重写。枚举类型在这种类型中。对于这些类逻辑上的相对都等同于两个object identity是否相同。
重写equals需要满足下面几个contracts(在x,y,z都non-null情况下)

  1. Reflexive: x.equals(x) == true (如果违反会出现在一个collection中加了一个instance,但是调用contains的时候会找不到)
  2. Symmetric(对称): x.equals(y) == y.equals(x)
  3. Transitive: x.equals(y) == true&&y.equals(z)==true ==> x.equals(z)==true
  4. Consistent(一致): 如果没有改变x,y,多次调用不改变x.equals(y)的结果
  5. x.equals(null)==false
    高质量的写equals
  6. Use the == operator to check if the argument is a reference to this object.
  7. Use the instanceof operator to check if the argument has the correct type.
  8. Cast the argument to the correct type.
  9. For each “significant” field in the class, check if that field of the argument
    matches the corresponding field of this object. 比较两个对象中关键字段是否相等。
  10. When you are finished writing your equals method, ask yourself three
    questions: Is it symmetric? Is it transitive? Is it consistent?

注意事项

  1. Always override hashCode when you override equals.
  2. Don’t try to be too clever. 不要将数据分析语句放进来,不要搞得太智能。
  3. Don’t substitute another type for Object in the equals declaration. 不要换形参的类型,要和父类一样。可以使用@Override注解来防止出现这种问题

Item 9.Always override hashCode when you override equals

几条contracts:

  1. 在一个application执行期间,无论多少次调用同一个object的hashcode返回的Integer相同。而本次执行结束到下一次执行,返回的值一致性没有要求。
  2. 如果两个对象的equals方法返回true,那么他们返回的hashcode值也要相同。
  3. 不要求如果两个对象equals不同,他们hashcode就一定要不同,但是我们要知道unequals的object产生不同的hashcode可以提高hash表的性能。

关键是第二句;两个不同的对象在逻辑上相同使他们的equals方法相同,但是他们没有什么相同在hashcode方法上。因此会返回两个随机的数而不是相同的数。

考虑为以下程序设计hashcode:

final class PhoneNumber{
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;


    PhoneNumber(int areaCode, int prefix, int lineNumber) {
        rangeChack(areaCode,999,"areaCode");
        rangeChack(prefix,999,"prefix");
        rangeChack(lineNumber,9999,"lineNumber");
        this.areaCode = (short) areaCode;
        this.prefix = (short) prefix;
        this.lineNumber = (short) lineNumber;
    }

    private static void rangeChack(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 p=(PhoneNumber)obj;
        return p.areaCode==areaCode
                && p.prefix==prefix
                && p.lineNumber==lineNumber;
    }
}

一种方式直接使用常数@Override public int hashcode(){return 42;}这种可行,但是使所有的对象都在同一个hash桶中,大大减少了hashmap的性能,时间复杂度从quadratic time变为了line time。

一个好的hashcode应该要尝试尽量去把不同(unequals)的对象产生不同的hashcode,here is a simple recipe:

  1. 随便选一个非零常量在作为hashcode的初始result,如17
  2. 对于在equals中用于比较的field,进行如下操作:
    1. Compute a int hash code c for the field:
      1. 如果field是boolean,计算 f?1:0
      2. 如果是byte、char、short、int,计算(int)f
      3. 如果是long,计算(int)(f^(f>>>32))
      4. 如果是float,计算Float.floatToIntBits(f)
      5. 如果是double,先计算Double.doubleToLongBits(f),然后对返回的long计算(int)(f^(f>>>32))
      6. 如果是对象引用,如果他是递归比较各个field,那么也递归的调用。如果更复杂的话就用一个cononical representation。如果field是null,return 0(或其他常数)
      7. 如果是array,就遍历每一个。1.5以上的可以使用Arrays.hashcode。
    2. 把result结合到以上算出来的cresult=31 * reslt + c;
  3. 返回结果
  4. 测试equals的instance是否hashcode也相同。

hashcode如下:

@Override
    public int hashCode(){
        int result=17;
        /**
         * 1.用result=31 * reslt + c;可以让结果根据fields的顺序来生成,
         * 不用担心如String 中"add"和"dda"出现相同hashcode的情况
         * 2.用31是因为31是一个奇素数,而且31*i ==(i<<5)-i,这种优化很多虚拟机可以自动完成
         **/
        result = 31*result+areaCode;
        result = 31*result+prefix;
        result = 31*result+lineNumber;
        return result;
    }

如果需要多次调用不变的对象的hashcode,可以将他的值存起来,使用lazily initialize在第一次调用的时候计算。

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

下面是1.8的String的hashcode,hash没有用volatile在多线程中会有点问题(由于是用于缓存的,问题也不大)。

private int hash; // Default to 0
public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

Do not be tempted to exclude significant parts of an objects from the hash code computation to improve performance.不要在hashcode中去掉equals中比较了的fields来提高性能。Do not be tempted to exclude significant parts of an objects from the hash code computation to improve performance.不要在hashcode中去掉equals中比较了的fields来提高性能。

Item 10.Always override toString

Object中的toString方法是采用classname+@+对象hashcode的16进制组成的。建议每一个子类都重写。
Providing a good toString implementation makes your class much more pleasant to use.
When practical,the toString method should return all of the interesting information contained in the object.
Whether or not you decide to specify the format,you should clearly document your intentions.
Provide programmatic access to all of the information contained in the value returned by toString.
上述例子的toString:

@Override
public String toString() {
	return String.format("(%03d)%03d-%4d",areaCode,prefix,lineNumber);
}

Item 11.Override clone judiciously

Clone的general contract:
一般情况下(x.clone() != x) == true,以及(x.clone().getClass() == x.getClass())==true,x.clone().equals(x)==true,这些都不是绝对的要求,都是一般情况下。

If you override the clone method in a nonfinal class, you should return an object obtained by invoking super.clone. 返回的实例应该通过调用父类的super.clone()获得

In practie, a class that implements Cloneable is expected to provide a properly functioning public clone method. 可以拷贝的对象期望有一个public的clone方法。

如果被克隆对象中有非基本类型对象,而非基本类型对象又没有继承Cloneable接口,那么克隆时只会将引用复制过去,即克隆前后的两个对象共享这个非基本类型的变量,这也被称为浅拷贝(Shallow)。

如果要给上面的PhoneNumber类加clone方法,首先需要在类上加上implemennts cloneable

	@Override
    public PhoneNumber clone(){
        try{
		//注意这里运行直接转换类型,但需要是子类。
            return (PhoneNumber)super.clone();
        }catch (CloneNotSupportedException e){
                throw new AssertionError();
        }
    }

但是如果对象包含引用可变对象的字段会出现问题。比如说Stack里面有Object[]以及size,那么单纯使用super.clone()会导致新旧两个实例中的Object[]存在指向同一个数组的问题。也就是上面说的浅拷贝的问题。

In effect, the clone method functions as a constructor;you must ensure that it does no harm to the original object and that it properly establishes invariants on the clone.
为了达到深拷贝的效果,最简单的方法就是递归的调用cloe方法。

	@Override
    public Stack clone() {
        try {
            Stack result=(Stack) super.clone();
			//1.5以后返回的数组编译时候的类型和原数组一样,所以可以不用强制转换类型。
			//如果elements类型是final的话这种clone不可行,因为the clone architecture is incompatible with normal use of final fields referring to mutable objects
            result.elements=elements.clone();
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

而在以下这种hash结构中不可行,如果我们单单clonebuckets,但是里面的next会指向和原来同一个位置。

public class HashTable implements Cloneable {
    private Entry[] buckets = ...;

    private static class Entry {
        final Object key;
        Object value;
        Entry next;

        Entry(Object key, Object value, Entry next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
    } ... // Remainder omitted
}

常见的可以使用以下这种方式clone:

class HashTable implements Cloneable {
    private Entry[] buckets ;

    private static class Entry {
        final Object key;
        Object value;
        Entry next;

        Entry(Object key, Object value, Entry next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
		//提供深拷贝方法。使用递归的方法,从头到尾深拷贝。
        Entry deepCopy(){
            return new Entry(key,value,
                    next==null?null:next.deepCopy());
        }
    }
    
    @Override
    public HashTable clone(){
        try {
            HashTable result=(HashTable)super.clone();
            result.buckets=new Entry[buckets.length];
			//新创建桶数组,然后遍历桶,把每一个entry都深拷贝
            for(int i=0;i<buckets.length;i++){
                result.buckets[i]=buckets[i].deepCopy();
            }
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

当链表过长时递归有可能产生栈溢出。可以改进使用Iteration,不用递归。

Entry deepCopy(){
	Entry result=new Entry(key,value,next);
	for(Entry p=result;p.next!=null;p=p.next){
		p.next=new Entry(p.next.key,p.next.value,p.next.next);
	}
	return result;
}

还有一种方法是先调用super.clone(),把所有fields初始化。然后用更高级的方法给他们赋值。以hashtable为例,可以先初始化buckets的arrays,然后调用put方法将他们一个一个加入。这种方法简洁,优雅,但是速度没有直接赋值内部结构快。这里说的应该是final或private的put。

public方法不用throw CloneNotSupportedException,这样更好用,但是如果你的类是用来继承的,那么就应该像Object中一样,用protected并且throw CloneNotSupportedException,并且不用implements Cloneable,这样就给子类实现或不实现的自由。线程安全的类需要加上synchronized。

A fine approach to object copying is to provide a copy constructor or copy factory.
就像如果我们需要clone一个TreeSet s我们只需要new TreeSet(s)

总结一下,虽然 final 类实现 Cloneable 的危害要小一些,但这应该被视为一种性能优化,仅在极少数情况下是合理的。通常,复制功能最好由构造函数或工厂提供。这个规则的一个明显的例外是数组,最好使用 clone 方法来复制数组。

Item 12.Consider implementing Comparable

compareTo是Comparable接口中唯一的一个方法。可以Arrays.sort(a);对实现Comparable接口的对象数组排序。

Contract与equals的类似,如下:
A和一个指定的B比较,返回一个负数,零或整数,分别表示A小于等于大于B。抛出ClassCastException如果B的类型不能转换为A,这里与equals不同,equals直接返回false。
以下内容中sgn(expression)是signum,就是使用-1,0,1来表示结果的符号。例如,sgn(-5)=-1。

  1. sgn(x.compareTo(y))==-sgn(y.compareTo(x)) ps.如果x.compareTo(y)抛出异常y.compareTo(x)也要抛出异常。
  2. 传递性:x.compareTo(y)>0&&y.compareTo(z)>0 ==> x.compareTo(z)>0
  3. sgn(x.compareTo(y))==0 ==> sgn(x.compareTo(z))==sgn(x.compareTo(z))
  4. 强烈建议。(x.compareTo(y)0)(x.equals(y))。如果不一致推荐注释:Note:This class has a natural ordering that is inconsistent with equals.

如果需要在一个类中添加一个field,不要继承,写另外一个类,包含第一个类的实例。然后提供一个方法返回包含实例。这可以让我们随意实现任何compareTo方法在第二个类中,同时可以使用被包含类去获取包含类(通过提供的视图方法)。

compareTo和equals两这如果入参是空前者直接返回exception后者返回false,为不同type前者抛出异常后者返回false。

PhoneNumber类设计的compareTo表示如下:

@Override
public int compareTo(PhoneNumber pn) {
	if(areaCode<pn.areaCode){ return -1; }
	if(areaCode>pn.areaCode){ return 1; }

	if(prefix<pn.prefix){ return -1; }
	if(prefix>pn.prefix){ return 1; }

	if(lineNumber<pn.lineNumber){ return -1; }
	if(lineNumber>pn.lineNumber){ return 1; }
	return 0;
}

又由于compareTo只看符号,不看数字大小,因此可以改进为:

@Override
public int compareTo(PhoneNumber pn) {
	int areaCodeDiff=areaCode-pn.areaCode;
	if(areaCodeDiff!=0){ return areaCodeDiff; }

	int prefixDiff=prefix-pn.prefix;
	if(prefixDiff!=0){return prefixDiff;}

	return lineNumber-pn.lineNumber;
}

Item 13.Minimize the accessibility of classes and members

Information hiding:也就是说各个module之间只能通过APIs,而不能知道他的内部工作。
Information hiding的好处:可以解耦各个模块(可以加快开发,debug一个module对其他无影响等)。
Make each class or member as inaccessible as possible.

如果包级私有顶级类或接口只被一个类使用,那么可以考虑:在使用它的这个类中,将顶级类设置为私有静态嵌套类。

对于members的四种访问级别:

  • private – 成员只能从声明它的顶级类内部访问。
  • package-private – 成员声明的包中的任何一个类(default),默认的等级。
  • protected – 成员声明所在的类的子类或成员声明的包中的任何一个类。
  • public – 哪儿都可以

子类的访问等级要比父类宽。
Instance fields should never be public.对不是final的对象或引用的可变对象公开就等于放弃了限制字段中可以存储的值的能力。这意味着你放弃了强制包含字段的不变量的能力。此外,你还放弃了在修改字段时采取任何操作的能力,因此带有公共可变字段的类通常不是线程安全的(classes with public mutable fields are not thread-safe)。

Note that a nonzero-length array is always mutable, so it is wrong for a class to have a public static final array field, or an accessor that returns such a field.如下例子:

public static final Thing[] VALUES = { ... };

很多ide会生成返回私有数组字段引用的访问器,这恰恰会导致这个问题。两种解决方法如下:

  1. 你可以将公共数组设置为私有,并添加一个公共不可变 List:
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
  1. 你可以将数组设置为私有,并添加一个返回私有数组副本的公共方法:
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values(){
	return PRIVATE_VALUES.clone();
}

Item 14.In public classes,use accessor methods,not public fields

public类不要暴露可变的fields(用getter和setter)。暴露final修饰的不可变fields,危害不大,但是还是可能有问题。不过嵌套的内部类,有时可以暴露无论可变的或不可变的fields。

Item 15.Minimize mutability

immutable class – 例如String,the boxed primitive classes,BigInteger,BigDecimal。
遵循以下五个rules:

  1. 不提供任何方法去修改对象的状态(object’s state)。比如说setter方法。
  2. 确保类不能被继承。一般使用final定义类。
  3. 用final修饰所有的fields。
  4. 用private修饰所有的fields。
  5. 确保只有我可以访问(exclusive access)任何可变组件。 ps:如果fields中有指向可变对象,确保其他类不能获取到这个类的引用。

不可变类的好处:

  1. Immutable objects are simple.不同于可变的对象,他的状态就是创建时候的状态,之后不会再发生改变。
  2. Immutable objects are inherntly(固有的) thread-safe;they require no synchronization.
  3. Immutable objects can be shared freely. 由于不可变的对象线程安全,所以可以自由的共享。那就要尽可能多的去复用存在的实例,比如说一些平凡使用的值,可以用public static final修饰。如下例子:
public static final Complex ZERO = new Complex(0,0);
public static final Complex ONE = new Complex(1,0);
public static final Complex I = new Complex(0,1);
  1. You can share their internals.
  2. Immutable Objects make great building blocks for other objects.
    唯一的缺点:
    They require a separate object for each distinct value.特别是对于特别大的对象,每次都需要创建一个新的。

除非有很好的理由,不然都让类final,不可变。

Item 16.Favor composition over inheritance

Unlike method invocation, inheritance violates encapsulation.

如下例子,addCount纪录添加的元素的大小(包括已经移除的)。

class InstrumentedHashSet<E> extends HashSet<E>{
    private int addCount=0;
    public InstrumentedHashSet(){
    }
    public InstrumentedHashSet(int initCap ,float loadFactor){
        super(initCap,loadFactor);
    }
    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection c) {
        addCount+=c.size();
        return super.addAll(c);
    }
    public int getAddCount(){
        return addCount;
    }
}

方法存在问题,我们发现如果使用下面的实例:

InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("aa", "bb", "cc"));
System.out.println(s.getAddCount());

期待的输出结果为3,但是结果为6。因为调用addAll(c)的时候将addCount+3,HashSet的addAll方法基于add方法,遍历c中的每个元素,那么每次就又会调用InstrumentedHashSet.add方法,最终又将addCount+3,结果为6。可以通过composition的方法进行改进,如下:

class InstrumentedHashSet<E> extends ForwardingSet<E>{
    private int addCount=0;
    public InstrumentedHashSet(Set<E> s){
        super(s);
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }
	//super.addAll(c),是调用了下面的s的addAll方法,不会影响到this。
    @Override
    public boolean addAll(Collection c) {
        addCount+=c.size();
        return super.addAll(c);
    }
    public int getAddCount(){
        return addCount;
    }
}

//实现Set主要是为了获取set中需要使用的方法
class ForwardingSet<E> implements Set<E>{
	//类中有一个引用的s,而不使用继承。
    private final Set<E> s;
    ForwardingSet(Set<E> s) { this.s = s; }
    @Override public int size() { return s.size(); }
    @Override public boolean isEmpty() { return s.isEmpty(); }
    @Override public boolean contains(Object o) { return s.contains(o); }
    @Override public Iterator<E> iterator() { return s.iterator(); }
    @Override public Object[] toArray() { return s.toArray(); }
    @Override public <T> T[] toArray(T[] a) { return s.toArray(a); }
    @Override public boolean add(E e) { return s.add(e); }
    @Override public boolean remove(Object o) { return s.remove(o); }
    @Override public boolean containsAll(Collection<?> c) { return s.containsAll(c); }
    @Override public boolean addAll(Collection<? extends E> c) { return s.addAll(c); }
    @Override public boolean retainAll(Collection<?> c) { return s.retainAll(c); }
    @Override public boolean removeAll(Collection<?> c) { return s.removeAll(c); }
    @Override public void clear() { s.clear(); }
}
InstrumentedHashSet<String> s = new InstrumentedHashSet(new HashSet<>());
s.addAll(Arrays.asList("aa", "bb", "cc"));
System.out.println(s.getAddCount());

最终结果为3

Item 17.Design and document for inheritance or else prohibit it

一些限制:

  1. Constructors must not invoke override methods. 父类的Constructor会比子类先构造,所以override method会在子类构造之前被调用,如果子类的构造器中对某一个field初始化,而父类构造器中调用的方法中用到了这个field,他将没有机会初始化,从而出现错误。
  2. 尽量不要让继承类去实现CloneableSerializable可以让子类去实现。如果真的要实现,由于clonereadObject方法有点像constuctor,所以也要注意第一条。最后如果要实现Serializable,让readResolvewriteResolveprotected而不是private。

但是对于具体的类,如果继承它(类似于上面的hashset),就有可能出现问题。The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed.
具体有两种方法限制他们:

  1. 使用final修饰。
  2. private修饰所有构造器,然后用public static的工厂类代替他们。
    而限制的类也可以使用Item 16中介绍的wrapper方法来继承。

Item 18.Prefer interfaces to abstract classes

两者之间的区别:

  1. abstract classes中可以有是具体的实现(不实现要用abstract修饰方法),但是interfaces不可以。
  2. abstract classes只能被子类继承,而且只能有一个父类,interfaces可以有很多个。
    Existing classes can be easily retrofitted to implement a new interface.

对于标题有几个好处:

  1. Interfaces are ideal for defining mixins.
  2. Interfaces allow the construction of nonhierarchical type frameworks.实际中,有很多情况非分层类型的结构,如下:
//表示歌手
interface Singer{
    AudioClip sing(String s);
}
//表示作家
interface SongWriter{
    String compose(boolean hit);
}
//当需要表示一个人是歌手又是作家的时候,interface就十分灵活
interface SingerSongWriter extends Singer,SongWriter{
    AudioClip strum();
    void actSensitive();
}
  1. Interfaces enable safe,powerful functionality enhancements.如果使用抽象类,想要添加功能,需要继承这个类,但是用接口可以使用wrapper的方法,像Item16中介绍的。

面对interface没办法实现的情况,You can combine the virtues of interfaces and
abstract classes by providing an abstract skeletal implementation class to go
with each nontrivial interface that you export.例如AbstractList,我们可以随意的构建自己需要的AbstractList,如下:

static List<Integer> intArrayAsList(final int[] a) {
	if (a == null)
		throw new NullPointerException();
	return new AbstractList<Integer>() {
		public Integer get(int i) {
			return a[i]; // Autoboxing (Item 5)
		}
		@Override public Integer set(int i, Integer val) {
			int oldVal = a[i];
			a[i] = val; // Auto-unboxing
			return oldVal; // Autoboxing
		}
		public int size() {
			return a.length;
		}
	};
}

下面是构建的抽象类:

First you must study the interface and decide which methods are the
primitives in terms of which the others can be implemented. These primitives will
be the abstract methods in your skeletal implementation. Then you must provide
concrete implementations of all the other methods in the interface.

// Skeletal Implementation
public abstract class AbstractMapEntry<K,V>
	implements Map.Entry<K,V> {
	// Primitive operations
	public abstract K getKey();
	public abstract V getValue();
	// Entries in modifiable maps must override this method
	public V setValue(V value) {
		throw new UnsupportedOperationException();
	}
	// Implements the general contract of Map.Entry.equals
	@Override public boolean equals(Object o) {
		if (o == this)
		return true;
		if (! (o instanceof Map.Entry))
			return false;
		Map.Entry<?,?> arg = (Map.Entry) o;
		return equals(getKey(), arg.getKey()) &&
			equals(getValue(), arg.getValue());
		}
	private static boolean equals(Object o1, Object o2) {
		return o1 == null ? o2 == null : o1.equals(o2);
	}
	// Implements the general contract of Map.Entry.hashCode
	@Override public int hashCode() {
		return hashCode(getKey()) ^ hashCode(getValue());
	}
	private static int hashCode(Object obj) {
		return obj == null ? 0 : obj.hashCode();
	}
}

An interface is generally the best way to define a type that
permits multiple implementations. An exception to this rule is the case where ease
of evolution is deemed more important than flexibility and power. Under these
circumstances, you should use an abstract class to define the type, but only if you
understand and can accept the limitations. If you export a nontrivial interface, you
should strongly consider providing a skeletal implementation to go with it.
Finally, you should design all of your public interfaces with the utmost care and
test them thoroughly by writing multiple implementations.

Item 19.Use interfaces only to define types

主要说了接口不要用来定义常数。主要原因如下:

The constant interface pattern is a poor use of interfaces. That a class uses
some constants internally is an implementation detail. Implementing a constant
interface causes this implementation detail to leak into the class’s exported API. It
is of no consequence to the users of a class that the class implements a constant
interface. In fact, it may even confuse them. Worse, it represents a commitment: if
in a future release the class is modified so that it no longer needs to use the constants, it still must implement the interface to ensure binary compatibility. If a
nonfinal class implements a constant interface, all of its subclasses will have their
namespaces polluted by the constants in the interface.

可以使用枚举类型或者Item5中描述的没有实现方法的工具类如下:

// Constant utility class
package com.effectivejava.science;
public class PhysicalConstants {
	private PhysicalConstants() { } // Prevents instantiation
	public static final double AVOGADROS_NUMBER = 6.02214199e23;
	public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
	public static final double ELECTRON_MASS = 9.10938188e-31;
}

但是每次使用都需要PhysicalConstants.BOLTZMANN_CONSTANT,如果使用的多了比较麻烦,可以使用如下办法直接获取。

// Use of static import to avoid qualifying constants
import static com.effectivejava.science.PhysicalConstants.*;
public class Test {
	double atoms(double mols) {
	return AVOGADROS_NUMBER * mols;
}
...
// Many more uses of PhysicalConstants justify static import
}

Item 20.Prefer class hierarchies to tagged classes

考虑以下类,有一个field代表类型,并且有两个及以上的构造方法来区别Figure到底是circle还是rectangle。

// Tagged class - vastly inferior to a class hierarchy!
class Figure {
	enum Shape { RECTANGLE, CIRCLE };
	// Tag field - the shape of this figure
	final Shape shape;
	// These fields are used only if shape is RECTANGLE
	double length;
	double width;
	// This field is used only if shape is CIRCLE
	double radius;
	// Constructor for circle
	Figure(double radius) {
		shape = Shape.CIRCLE;
		this.radius = radius;
	}
	// Constructor for rectangle
	Figure(double length, double width) {
		shape = Shape.RECTANGLE;
		this.length = length;
		this.width = width;
	}
	double area() {
		switch(shape) {
			case RECTANGLE:
				return length * width;
			case CIRCLE:
				return Math.PI * (radius * radius);
			default:
				throw new AssertionError();
		}
	}
}

以上类有一些缺点:tagged classes are verbose, error-prone, and inefficient.里面不同类型的东西太多了,有一些在一个实例中甚至是没有用的,这会导致field不能final修饰,而且会导致内存的增加。实例生成报错后很难修改。
可以修改为:

// Class hierarchy replacement for a tagged class
abstract class Figure {
	abstract double area();
}
class Circle extends Figure {
	final double radius;
	Circle(double radius) { this.radius = radius; }
	double area() { return Math.PI * (radius * radius); }
}
class Rectangle extends Figure {
	final double length;
	final double width;
	Rectangle(double length, double width) {
		this.length = length;
		this.width = width;
	}
	double area() { return length * width; }
}

除了弥补了上面的缺点之外,还可以对子类进行很方便的扩展,

Another advantage of class hierarchies is that they can be made to reflect natural hierarchical relationships among types, allowing for increased flexibility and
better compile-time type checking. Suppose the tagged class in the original example also allowed for squares. The class hierarchy could be made to reflect the fact
that a square is a special kind of rectangle (assuming both are immutable):

class Square extends Rectangle {
	Square(double side) {
		super(side, side);
	}
}

Item 21.Use function objects to represent strategies

Strategy pattern

下面的方法用于比较两个String的大小,但是只能用于String的比较,而且只能用于比较字符串长短,不能按字典序比较。

class StringLengthComparator {
	public int compare(String s1, String s2) {
		return s1.length() - s2.length();
	}
}

首先 the StringLengthComparator class is stateless.可以使用他应该是一个单例。修改如下:

class StringLengthComparator{
    private StringLengthComparator(){};
    public static final StringLengthComparator INSTANCE=new StringLengthComparator();
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
}

为了让实例传递到method,参数需要一个适当的类型(要使用于多种类型的参数,使用泛型)。为了让其他的比较类也可以被客户端在一个方法上使用,我们创建strategy interface来让StringLengthComparator(具体策略类)继承。

interface Comparator<T>{
    int compare(T t1,T t2);
}
class StringLengthComparator implements Comparator<String>, Serializable {
    private StringLengthComparator(){};
    public static final StringLengthComparator INSTANCE=new StringLengthComparator();

    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
}

Concrete strategy classes 经常使用在匿名内部类中:

String[] strings=new String[]{"aa", "bb", "cc"};
Arrays.sort(strings, new java.util.Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length() - o2.length();
            }
        });

使用lamba表达式简化:Arrays.sort(strings, ((o1, o2) -> o1.length()-o2.length()));
但是考虑到匿名内部类会重复创建,可以用一个private static final field复用。

class Host{
    /**
     * Because the strategy interface serves as a type for all of its concrete strategy
     * instances, a concrete strategy class needn’t be made public to export a concrete
     * strategy. 
     */
    private static class StringLengthComparator implements Comparator<String>, Serializable {
        @Override
        public int compare(String s1, String s2) {
            return s1.length() - s2.length();
        }
    }

    /**
     * Instead, a “host class” can export a public static field (or static factory
     * method) whose type is the strategy interface, and the concrete strategy class can
     * be a private nested class of the host
     */
    public static final StringLengthComparator INSTANCE=new StringLengthComparator();
    //...Bulk of class omitted
}

String包中的CASE_INSENSITIVE_ORDER就是使用这种方法来暴露

To summarize, a primary use of function pointers is to implement the Strategy
pattern. To implement this pattern in Java, declare an interface to represent the
strategy, and a class that implements this interface for each concrete strategy.
When a concrete strategy is used only once, it is typically declared and instantiated as an anonymous class. When a concrete strategy is designed for repeated
use, it is generally implemented as a private static member class and exported in a
public static final field whose type is the strategy interface.

Item 22.Favor static member classes over nonstatic

四种嵌套类:

  1. static member classes(静态成员类):最典型的嵌套类,定义在另一个类中并且可以获取这个类中的成员(包括私有的)。与其他的static的成员类似,遵循相同的accessibility rules。常用作public helper class,用户类似于Calculator.Operation.PLUS 这样使用。

If you declare a member class that does not require access to an enclosinginstance, always put the static modifier in its declaration

  1. nonstatic member classes(非静态成员类):比1少了一个static。每一个非静态成员类的实例与包含他们的封闭类实例(enclosing instance)联系。如果一个非静态成员类可以独立于他的封闭类实例存在(也就是说在某些地方只需要用到类的嵌套类,而不用封闭类本身),那么就应该把非静态成员类定义成静态的,因为非静态成员类在创建之前一定需要先创建他的封闭类,不能像1那样,如下是非静态成员类创建实例的过程:
Calculator a=new Calculator();
Calculator.Operation b=a.new Calculator.Operation();

可以看出来在b创建出来之后两者就已经确立关系,后面无法修改。
**经常用在适配器上。**如下:

// Typical use of a nonstatic member class
public class MySet<E> extends AbstractSet<E> {
		// Bulk of the class omitted
		public Iterator<E> iterator() {
			return new MyIterator();
		}
		private class MyIterator implements Iterator<E> {
			...
		}
}
  1. anonymous classes(匿名类)
  2. local classes(局部类):

If a nested class needs to be visible outside of a single method or is too long
to fit comfortably inside a method, use a member class. If each instance of the
member class needs a reference to its enclosing instance, make it nonstatic; otherwise, make it static. Assuming the class belongs inside a method, if you need to
create instances from only one location and there is a preexisting type that characterizes the class, make it an anonymous class; otherwise, make it a local class.

##Chapter 10.
###Synchronize access to shared mutable data

下面这段代码会一直运行下去,因为我们修改的stopRequested值后另一个线程并没去主存里面取而是再缓存里面,所以一直无法取到true,加入输出之后,println中有同比代码,此时线程就会有空闲时间,线程就会更新数据到主存里面取,而原来会因为进程一直再运行而没有空闲时间更新线程里面的值而无法停止。

public class StopThread {
	private static boolean stopRequested;
	public static void main(String[] args) throws InterruptedException {
		Thread backgroundThread = new Thread(new Runnable() {
			public void run() {
				int i = 0;
				while (!stopRequested)
				i++;
				//System.out.println(i);
			}
		});
		backgroundThread.start();
		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}
}

可以使用synchronized对读写都同步的办法来使程序停止,他会确保可见性

private static synchronized void requestStop() {
	stopRequested = true;
}
private static synchronized boolean stopRequested() {
	return stopRequested;
}

使用volatile关键字,可以保证可见性,但是无法保证原子性,例如当i++操作的时候,可能会出错。

private static volatile boolean stopRequested;

此时若要保证原子性可以使用AtomicLong,内部使用cas来确保原子性

private static final AtomicLong nextSerialNum = new AtomicLong();
public static long generateSerialNumber() {
return nextSerialNum.getAndIncrement();
}

##Chapter 5.
###Item 23.Don’t use raw types in new code
raw type就是对于用了泛型的类在声明时没有使用具体的类,例如List<E>使用时候直接使用List,在添加一个错误类型进去的时候不会出错,但是在获取的时候会出问题,如果加上了具体的类型就会正在编译时就会提醒,而且不需要进行强制类型转换。

If you use raw types, you lose all the safety and expressiveness benefits of generics.

List是List的子类型,但是不是List的子类型(具体的看ITEM25)。

unbounded wildcard types(无限通配符):
看如下例子(获取两个Set中相同的元素的个数):

// Use of raw type for unknown element type - don't do this!
static int numElementsInCommon(Set s1, Set s2) {
	int result = 0;
	for (Object o1 : s1)
		if (s2.contains(o1))
			result++;
	return result;
}

可以达到效果,但是很危险,在不知道参数是什么具体类型的情况下,可以用问好代替,例如Set<E>可以用Set<?>(read “set of some type”)表示

// Unbounded wildcard type - typesafe and flexible
static int numElementsInCommon(Set<?> s1, Set<?> s2) {
	int result = 0;
	for (Object o1 : s1)
		if (s2.contains(o1))
			result++;
	return result;
}

有两个例外点:

  1. You must use raw types in class literals.也就是说使用List.classString[].class,但是不能使用List<String>.class
  2. Because generic type information is erased at runtime, it is illegal to use the instanceof operator on parameterized types other than unbounded wildcard types.
    所以如果要使用可以如下:
// Legitimate use of raw type - instanceof operator
if (o instanceof Set) { // Raw type
Set<?> m = (Set<?>) o; // Wildcard type
...
}

###Item 24.Eliminate unchecked warnings
如果无法消除一个unchecked warning,在保证类型安全(typesafe)的情况下,可以使用一个@SuppressWarnings("unchecked")来使这个warning消失。
作用域可以从单个局部声明变量到整个类都可以,但是最好尽可能的小。当发现SuppressWarnings作用在多于一行的方法或者constructor之上,最好把它移到一个局部变量上,例如:

public <T> T[] toArray(T[] a) {
	if (a.length < size)
		return (T[]) Arrays.copyOf(elements, size, a.getClass());
	System.arraycopy(elements, 0, a, 0, size);
	if (a.length > size)
		a[size] = null;
	return a;
}

编译后,return (T[]) Arrays.copyOf(elements, size, a.getClass());这一行会报错,但是return上不可以加@SuppressWarnings("unchecked")那么就只能加在方法上。那么注解的作用就多余一行,那么可以新建一个局部变量。如下:

// Adding local variable to reduce scope of @SuppressWarnings
public <T> T[] toArray(T[] a) {
	if (a.length < size) {
		// 这个cast是成立的,因为我们创建的类型和传入的类型是一致的都是T[]
		@SuppressWarnings("unchecked") T[] result =
			(T[]) Arrays.copyOf(elements, size, a.getClass());
		return result;
	}
	System.arraycopy(elements, 0, a, 0, size);
	if (a.length > size)
		a[size] = null;
	return a;
}

当使用@SuppressWarnings("unchecked")之后我们应该加上注释,为什么这里是安全的。

###Item 25.Prefer lists to arrays
Arrays和Generic有两个很大的不同:

  1. Arrays are covariant. Generics are invariant.比如说Sub是Super的子类,那么Sub[]就是Super[]的子类,但是List就不会是List的子类。例如书中的例子:
// Fails at runtime!
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in"; // Throws ArrayStoreException
// Won't compile!
List<Object> ol = new ArrayList<Long>(); // Incompatible types
ol.add("I don't fit in");

前者会在运行时出现错误,而后者则直接无法编译,那么我们肯定更加偏向于后者,在运行之前就找到问题。

  1. Arrays are reified. Generics are implemented by erasure.也就是说Arrays会一直知道数组的类型,所以如上,在运行的时候如果添加了String到Long类型中会报错,而泛型会在编译的时候检查,而在运行的时候消除。Erasure是实现版本兼容的方式。

因为两者的不同,他们不能很好的结合,例如,不能创建泛型的数组(new E[]),带有泛型的List的数组(new List[] 或new List[])。如下:

// Why generic array creation is illegal - won't compile!
List<String>[] stringLists = new List<String>[1]; 	// (1)
List<Integer> intList = Arrays.asList(42); 			// (2)
Object[] objects = stringLists; 					// (3)
objects[0] = intList; 								// (4)
String s = stringLists[0].get(0); 					// (5)

假设1可以编译通过,那么2创建并初始化了一个List,3由于Arrays are covariant可以编译通过,那么objects就存储了一个List[],而4因为Generics are implemented by erasure,List和List在运行时都表示为List,所以objects[0]中就存入了一个intList,但是问题出现了,objects是List类型的但是0位置上存的是List所以5在读取的时候会出现ClassCastException

最好使用List 而不是E[],如下例子:
假设有一个同步的List和一个方法(将两个传入的参数进行操作后返回第三个数,方法会随参数类型的改变而改变),我们需要设计一个reduce方法,根据function,把List中的数进行操作。reduce方法还带一个默认值。

// Reduction without generics, and with concurrency flaw!
static Object reduce(List list, Function f, Object initVal) {
	synchronized(list) {
		Object result = initVal;
		for (Object o : list)
			result = f.apply(result, o);
		return result;
	}
}
interface Function {
	Object apply(Object arg1, Object arg2);
}

toArray()方法内部加锁可以修改为如下:

// Reduction without generics or concurrency flaw
static Object reduce(List list, Function f, Object initVal) {
	Object[] snapshot = list.toArray(); // Locks list internally
	Object result = initVal;
	for (Object o : list)
		result = f.apply(result, o);
	return result;
}

如果我们将Function和reduce改写为泛型就会出现问题,如下所示:

interface Function<T> {
	T apply(T arg1, T arg2);
}
static <E> E reduce(List<E> list,Function<E> f,E initVal){
	E[] snapshot = list.toArray();
	E result=initVal;
	for(E e : snapshot){
		result=f.apply(result,e);
	}
	return result;
}

编译方法的时候会出现编译不通过,我们需要将lists.toArray()后的内容强制转换为E[]类型

E[] snapshot = (E[]) list.toArray();

但是这样会出现一个warning: [unchecked] unchecked cast因为泛型E的元素类型信息在运行的时候是被删除的,这样可以运行,但是不安全,E[]在运行是可以是String[],也可以是Integer[]或者其他任何的类型,所以可以能出现转换的错误。因此我们需要做的就是把Arrays转换为List

// List-based generic reduction
static <E> E reduce(List<E> list, Function<E> f, E initVal) {
	List<E> snapshot;
	synchronized(list) {
		snapshot = new ArrayList<E>(list);
	}
	E result = initVal;
	for (E e : snapshot)
		result = f.apply(result, e);
	return result;
}

###Item 26.Favor generic types
改写下面的stack类为泛型

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; // Eliminate obsolete reference
        return result;
    }
    public boolean isEmpty() {
        return size == 0;
    }
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}
  1. 为类添加一个或两个泛型的参数,Stack类中主要是一个elements数组,所以需要一个参数,Stack。
  2. 将所有的Object改为E:
class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    public Stack() {
		//ERROR - Type parameter 'E' cannot be instantiated directly,直接创建泛型数组。
        elements = new E[DEFAULT_INITIAL_CAPACITY];
    }
    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }
    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        E result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }
    public boolean isEmpty() {
        return size == 0;
    }
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}
  1. 修改错误,有两种解决方式
    1. elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY];生成Object数组然后强制转换为E[],但是会报错,由于elements是私有的,客户端不能直接使用,而唯一可以修改的地方是push中而这里传入的参数类型是固定的是E,所以这里可行,使用@SuppressWarnings("unchecked")消除警告。
    2. private Object[] elements;然后修改popE result =(E) elements[--size];可以消除Warning的原因类似。
      ps. 这里不使用List的方式去修改elements主要是因为,List也是在Arrays的基础上搭建的,所以就像HashMap一样使用Arrays来提高性能。
      当代码中用在pop的点少是,使用第二种,只需要强制转换个别类型,但是第一种需要转换整个数组,建议第二中,但是当使用pop的点多的时候,建议第一种。

###Item 27. Favor generic methods
如果不使用泛型而是使用raw type 经常会出现警告,如下:

// Uses raw types - unacceptable! (Item 23)
public static Set union(Set s1, Set s2) {
	Set result = new HashSet(s1);
	result.addAll(s2);
	return result;
}
Union.java:5: warning: [unchecked] unchecked call to
HashSet(Collection<? extends E>) as a member of raw type HashSet
Set result = new HashSet(s1);
			 ^
Union.java:6: warning: [unchecked] unchecked call to
addAll(Collection<? extends E>) as a member of raw type Set
result.addAll(s2);
			  ^

改写为泛型后不会出现警告和错误:

// Generic method
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
	Set<E> result = new HashSet<E>(s1);
	result.addAll(s2);
	return result;
}

但是有一个限制,参数和返回类型必须是一样的,可以用有界通配符类型(bounded wildcard types)来解决。

泛型类型的创建通常要两边都声明类型,Map<String, List<String>> anagrams = new HashMap<String, List<String>>(); (现在右边的似乎不需要。)
为了减少这个冗余,我们可以用generic static factory method。例如给HashMap增加一个:

public static <K,V> HashMap<K,V> newHashMap(){
	return new HashMap<K,V>();
} 

调用时就不许要右侧的类型声明,Map<String, List<String>> anagrams = newHashMap();

generic singleton factory较多的用在function object。如下例子:

public interface UnaryFunction<T> {
	T apply(T arg);
}

在每次使用的时候创建会很浪费内存,所以可以有一个静态的,但是需要满足任何类型都适用,因此:

private static UnaryFunction<Object> IDENTITY_FUNCTION =new UnaryFunction<Object>() {
	@Override
	public Object apply(Object arg) {
		return arg;
	}
};
@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T> identityFunction(){
	return (UnaryFunction<T>)IDENTITY_FUNCTION;
}

使用的时候可以像上面的newHashMap一样UnaryFunction<String> sameString = identityFunction();

其他类似<E extends Comparable<E>>可以被理解为可以与自身进行比较的任何类型 E,也就是说方法中如果使用了这个作为类型,那么只有实现类可比性的类型才可以,这种就是递归类型限定(Recursive type bound),实例如下:

// Returns the maximum value in a list - uses recursive type bound
public static <T extends Comparable<T>> T max(List<T> list) {
	Iterator<T> i = list.iterator();
	T result = i.next();
	while (i.hasNext()) {
		T t = i.next();
		if (t.compareTo(result) > 0)
		result = t;
	}
	return result;
}

###Item 28.Use bounded wildcards to increase API flexibility
由于参数化类型(parameterized types)是不可变的,因此两个不同的类型Type1和Type2即使是继承关系,他们在List和List中也是毫不相关的,比如说List和List不是子类与父类的关系,所以当我们将类定义为List就不可以在其中加入Object类型的数据,为了使这种参数化类型更加灵活,我们可以使用有界通配符类型(bounded wildcard type)例如:

public class Stack<E> {
	public Stack();
	public void push(E e);
	public E pop();
	public boolean isEmpty();
}

向其中加入方法pushAll

public void pushAll(Iterable<E> src) {
	for (E e : src)
		push(e);
}

当想要加入一个Iterable到Stack中的时候:

Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);

虽然Integer是Number的子类,但是由于参数化类型的不可变,会无法通过编译。这个时候需要加入一个有界通配符类型:

// Wildcard type for parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
	for (E e : src)
		push(e);
}

类似的,添加一个popAll方法,并将结果集合加入到传入的参数中:

public void popAll(Collection<E> dst) {
	while (!isEmpty())
		dst.add(pop());
}

当想要定义一个Object的集合用来当Number类型的数时候:

Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ... ;
numberStack.popAll(objects);

依然会出现问题。加入有界通配符类型之后:

// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
	while (!isEmpty())
		dst.add(pop());
}

popAll使用了super,pushAll使用了extends。因为Collection消费了Stack中的值,Iterable生产了Stack中的值。(PECS:producer-extends, consumer-super.)
三个例子:

  1. static <E> E reduce(List<E> list, Function<E> f, E initVal)list生产出了用于reduce的数,所以是extends,static <E> E reduce(List<? extends E> list, Function<E> f,E initVal)
  2. public static <E> Set<E> union(Set<E> s1, Set<E> s2)Set生产了用于union的值,所以也是extends,但是要主要不要再返回类型中使用通配符号类型,public static <E> Set<E> union(Set<? extends E> s1,Set<? extends E> s2)
  3. public static <T extends Comparable<T>> T max(List<T> list) list是生产者,Comparable消费一个T,然后生产一个int的数,对于Comparable 或Comparator都是用<? super T> public static <T extends Comparable<? super T>> T max(List<? extends T> list)

两种swap方法的定义形式:

public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);

if a type parameter appears only once in a method declaration, replace it with a wildcard<?>.
有一个问题,直接实现,会出现 set(int,capture#282 of ?) in List<capture#282 of ?>

public static void swap(List<?> list, int i, int j) {
	list.set(i, list.set(j, list.get(i)));
}

因为list.get(i)读取的元素是?类型,解决办法是写一个私有的helper方法来捕获通配符的类型

public static void swap(List<?> list, int i, int j) {
	swapHelper(list, i, j);
}
// Private helper method for wildcard capture
//list.get(i)是E类型的,所以可以直接添加到list中
private static <E> void swapHelper(List<E> list, int i, int j) {
	list.set(i, list.set(j, list.get(i)));
}

这样不仅客户端可以方便的获取,也可以消除上面的问题。1.8的包中采用rawtype并且用SuppressWarnings,但是1.8推荐使用上面这个:

@SuppressWarnings({"rawtypes", "unchecked"})
public static void swap(List<?> list, int i, int j) {
	// instead of using a raw type here, it's possible to capture
	// the wildcard but it will require a call to a supplementary
	// private method
	final List l = list;
	l.set(i, l.set(j, l.get(i)));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值