Effective.Java第23-33条(泛型相关)
23. 类结构层次优于标签类
有时你会碰到一个类,它的实例有一个或多个风格,并且包含一个tag属性表示实例的风格。例如,如下面的类表示一个圆或者矩形:
public class Figure { /** * 标签: circle表示圆 rectangle表示矩形 */ private String tag; private double length; private double width; private double radis; public Figure(double radis) { super(); this.radis = radis; } public Figure(double length, double width) { super(); this.length = length; this.width = width; } public double area() { // 返回圆的面积 if ("circle".equals(tag)) { return Math.PI * radis * radis; } if ("rectangle".equals(tag)) { return length * width; } return 0; }
这样的类有很多缺点,代码可读性查,将来增加一个三角形又得重写计算面积的方式,而且需要增加三角形属性。
解决办法,用类层次结构代替:
package cn.qlq; public abstract class Figure { abstract double area(); } class Circle extends Figure { private double radis; @Override double area() { return Math.PI * radis * radis; } } class Rectangle extends Figure { private double length; private double width; @Override double area() { return length * width; } }
24. 支持使用静态成员类而不是非静态类
有四种嵌套类:静态成员类、非静态成员类、匿名内部类和局部类。
静态成员类是最简单的嵌套类。最好把它看做是一个普通的类,恰好在另一个类中声明,并且可以访问宿主类的成员,甚至是那些被声明为私有类的成员。其一个主要用途是作为公共帮助类,例如Calculator可以使用Calculator.Operation.PLUS。
在语法上,静态成员和非静态成员类之间的唯一区别是静态成员类在其宿主类中用static修饰。
如果声明了一个不需要访问宿主实例的成员类,不如用static修饰使之成为静态成员类。
25. 将源文件限制为单个顶级类
虽然Java编译器允许在单个Java文件中定义多个顶级类,只需要文件名与public修饰的类一致即可,但是不建议这么做。
26. 不要使用原始类型
也就是泛型尽量指明类型,虽然存在泛型擦除,但是至少编译期间会减少一些潜在的错误。比如你很容易将BigInteger和BigDecimal存入同一个集合。
比如下面就是正确的用法:(右边直接用<>即可,编译器会自动推断出正确的实际类型参数,不写<>会编译警告)
Map<String, Object> result = new HashMap<>();
泛型的术语如下:
27. 消除非检查警告
尽可能的消除每一个未经检查的警告。
如果不能消除警告,但你确保证明引发警告的代码是类型安全的,那么只能用@SuppressWarnings("unused")来消除警告(注意使用注解压制警告的时候写明原因)。
总之,未经检查的警告是重要的,在运行时可能会出现ClassCaseException异常。
比如:
List<String> list = new ArrayList();
会有一个警告:ArrayList is a raw type. References to generic type ArrayList<E> should be parameterized
写成下面这样则不会出现警告,虽然右边只是一个<>符号,并没有指定真正的实际类型参数,但编译器会根据前面的自动推断。
List<String> list = new ArrayList<>();
28. 列表优于数组
列表与数组在两方面不同。
首先,数组是斜变的,也就是如果Sub是Super的子类,则数组类型Sub[]是Super[]的子类型;集合是不变的,对于任何两种不同的类型Tyepe1和Type2,List<Type1>既不是List<Type2>的子类型也不是父类型。
也就是说:如下编译不通过
// 正确编译 Object[] objects = new Long[11];
如下会报编译错误:
// 编译不通过 List<Object> lists = new ArrayList<Long>();
第二个差异就是数组被具体化了,也就是数组在运行时知道并强制执行它们的元素类型。如果尝试将String放入一个Long型数组是做不到的,但是通过集合不指定具体的实际类型却可以做到。
由于上面的差异,数组和泛型不能很好的结合在一起。例如,创建泛型类型的数组,参数化类型的数组以及类型参数的数组都是非法的。因此,这些数组创建表达式都不合法:
new List<E>(); new List<String>(); new E<String>();
补充:Java语言并不支持列表,所以一些泛型类型(如ArrayList)必须在数组上实现,其他的泛型类型,比如HashMap是为了提高性能而实现的。
29. 优先考虑泛型
泛型类型比需要在客户端代码中强制转换的类型更安全,更易于使用。当你设计新的类型时,确保它们可以在没有这种强制转换的情况下使用。这通常意味着使类型泛型化。
如果有任何现有的类型应该是泛型化但实际上却不是,考虑把它们泛型化。这使得这些类型的新用户的使用更容易,而不会破坏现有的客户端。
30. 优先使用泛型方法
泛型类和泛型方法需要选择的时候优先使用泛型方法。
31. 使用限定通配符来增加API的灵活性(重要)
参数化类型是不变的。比如List<String> 你就只能存放String类型的数据。
相对于提供的不可变的类型,有时候使用限定通配符可以获得更高的灵活性。
在API中使用通配符类型很棘手,但使得API更加灵活。一条基本的原则就是producer-extends,consumer-super(PECS)。还需要记住,所有Comparable和Comparator都是消费者。如果一个输入参数同时是生产者和消费者那么考虑使用精确类型。
如下:
package cn.qlq; import java.util.ArrayList; import java.util.List; public class Client { public static void main(String[] args) { List<Integer> set1 = new ArrayList<>(); set1.add(1); List<Float> set2 = new ArrayList<>(); set2.add(1F); List<Number> unionMember = unionMember(set1, set2); System.out.println(unionMember); } private static <E> List<E> unionMember(List<? extends E> set1, List<? extends E> set2) { List<E> result = new ArrayList<>(); result.addAll(set1); result.addAll(set2); return result; } }
上面的unionMember方法的参数声明,两个参数set1和set2都是E的生产者所以PCES告诉我们应该用extends。
请注意:返回类型仍然是E,不要使用限定通配符作为返回类型。
32. 合理地结合泛型和可变参数
一般不要同时使用可变的泛型参数。
33. 优先考虑类型安全的异构容器