[关闭]
@gnudennis 2015-05-09T22:51:38.000000Z 字数 4761 阅读 3869

Core Java笔记 8.泛型(II) - 高级语法与最佳实践

CoreJava


本章重点:

  • 高级语法:通配符
  • 最佳实践

本文介绍泛型的高级语法已经最佳实践。Java 泛型的很多限制都可以从上篇中的原理(Java 泛型机制) 来解释。

通配符

通配符是使用的角度而言的,在编译器期间执行更加严格的检查。

子类限定通配符

语法:
Pair<? extends Employee>

继承关系:
corejava-generic-pic01
限制:
只需要从编译器如何翻译代码块的角度思考这些限制。

  1. Employee emp = new Employee();
  2. Manager ceo = new Manager();
  3. Manager cfo = new Manager();
  4. Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo);
  5. Pair<? extends Employee> wildcardBuddies = managerBuddies; // OK
  6. // 编译器只知道是某个Employee的子类,编译器推断不出具体的类型
  7. // 限制: 编译器拒绝任何特定类型
  8. // void setFirst(? extends Employee)
  9. wildcardBuddies.setFirst(ceo); // compile-time error
  10. wildcardBuddies.setFirst(emp); // compile-time error
  11. wildcardBuddies.setFirst(new Object()); // compile-time error
  12. // ? extends Employee getFirst()
  13. Employee ret = wildcardBuddies.getFirst(); // OK

超类限定通配符

语法:
Pair<? super Manager>

继承关系:
corejava-generic-pic02

限制:

  1. Employee e1 = new Employee();
  2. Employee e2 = new Employee();
  3. Manager ceo = new Manager();
  4. Pair<Employee> employeeBuddies = new Pair<Employee>();
  5. Pair<? super Manager> wildcardBuddies = employeeBuddies; // OK
  6. // 编译器值知道是某个Manager的父类
  7. // 限制: 编译器拒绝任何特定类型
  8. // ? super Manager
  9. Manager ret = wildcardBuddies.getFirst(); // compile-time error
  10. // void setFirst(? super Manager)
  11. wildcardBuddies.setFirst(e1); // compile-time error
  12. wildcardBuddies.setFirst(ceo); // OK

更加严格的写法:

  1. public static <T extends Comparable> T min(T[] a);
  2. // 更加严格的写法
  3. public static <T extends Comparable<? super T>> T min(T[] a);

解释:
该泛型方法使用Comparable<? super T>进行擦除,现在 compareTo 写成:
int compareTo(? super T). 表明只需要 T的父类有 compareTo() 方法即可.
例如:GregorianCalendar 是 Calendar 的子类,Calendar实现了Comparable, 但是 GregorianCalendar 并没有重写Comparable, 所以:
public static > T min(T[] a); 是不够的。

补充:无限定通配符

语法:
Pair

限制:

  1. ? getFirst(); // 只能赋给一个Object
  2. void setFirst(?); // 无法调用, Object也不行

存在的理由:
对于简单的操作,代替泛型方法,更加具有可读性。
比如:

  1. public static <T> boolean hasNulls(Pair<T> p)
  2. // 可以写成:
  3. public static boolean hasNull(Pair<?> p) {
  4. return p.getFirst() == null || p.getSecond() == null;
  5. }

局限:
? 不能作为一种类型。? t = xx 非法。
这样导致只能代替操作简单的泛型方法。比如swap都写不了.

  1. public static void swap(Pair<?> p) {
  2. ? t = p.getFirst; // ERROR
  3. p.setFirst(p.getSecond);
  4. p.setSecond(t);
  5. }
  6. // 这时候只能:
  7. public static <T> void swapHelper(Pair<T> p) {
  8. T t = p.getFirst; // ERROR
  9. p.setFirst(p.getSecond);
  10. p.setSecond(t);
  11. }
  12. public static void swap(Pair<?> p) {
  13. swapHelper(p);
  14. }

最佳实践

1.不能使用基本类型实例化类型参数. 才有 wrapper 即可.

2.类型查询只适用于原始类型. 即类型检查直接使用原始类型即可.

  1. if (a instanceof Pair<String>) // same as "a instanceof Pair"
  2. if (a instanceof Pair<T>) // T is ignored
  3. Pair<String> p = (Pair<String>)a; // can only test that a is a Pair
  4. Pair<String> stringPair = ...;
  5. Pair<Employee> employeePair = ...;
  6. if (stringPair.getClass() == employeePair.getClass()) // they are equal, Pair.class

3.泛型类无法扩展 Throwable,也无法抛出泛型类.

  1. // 1. 泛型类无法扩展 Throwable
  2. public class Problem<T> extends Exception {/*...*/} // ERROR -- can't extend Throwable(编译无法通过)
  3. // 2. 不能抛出泛型类示例
  4. public static <T extends Throwable> void doWork(Class<T> t) {
  5. try {
  6. do work
  7. } catch (T e) {// ERROR -- cant't catch type variable
  8. Logger.global.info(...)
  9. }
  10. }
  11. // 正确时间:
  12. public static <T extends Throwable> void doWork(Class<T> t) {
  13. try {
  14. do work
  15. } catch (Throwable realCause) {
  16. t.intCause(realCause);
  17. throw t;
  18. }
  19. }

4.参数化类型的数组不合法,而是采用容器类存储.

  1. Pair<String>[] table = new Pair<String>[10]; // ERROR

5.可以声明类型参数,但不能实例化类型参数

new T(...), new T[...], T.class 非法. 但是可以声明.

问题1.

  1. public Pair() { first = new T(); second = new T(); } // ERROR
  2. first = T.class.newInstance(); // ERROR
  3. // 最佳实践: 指定T的类型
  4. public static <T> Pair<T> makePair(Class<T> clazz) {
  5. try {
  6. return new Pair(clazz.newInstance(), clazz.newInstance());
  7. } catch (Exception e) {
  8. return null;
  9. }
  10. }
  11. Pair<String> stringPair = Pair.<String>makePair(String.class);// OK

问题2.

  1. public static <T extends Comparable<? super T>> T[] minmax(T... a) {
  2. // T[] mm = new T[2]; // ERROR, Type parameter 'T' cannot be instantiated directly
  3. Object[] mm = new Object[2];
  4. mm[0] = a[0];
  5. mm[1] = a[1];
  6. return (T[])mm; // T 被擦成Comparable, 运行时会ClassCastException, 而Object不是Comparable
  7. }
  8. public static void main(String[] args) {
  9. String[] ss = minmax("Tom", "Dick", "Harry"); // ClassCastException!!!
  10. }
  11. // Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Comparable
  12. }
  13. // 最佳实践:
  14. public static <T extends Comparable<? super T>> T[] minmax(T... a) {
  15. T[] mm = (T[])Array.newInstance(a.getClass().getComponentType(), 2);
  16. ...
  17. return mm;
  18. }

问题3.

  1. public class ArrayList<T> {
  2. private T[] elements;
  3. public ArrayList() { elements = (T[]) new Object[10]; }
  4. public T get(int n) { return elements[n]; }
  5. public void set(int n, T e) { elements[n] = e; }
  6. public static void main(String[] args) {
  7. ArrayList<String> src = new ArrayList<String>();
  8. src.set(0, "hello");
  9. src.set(1, "world");
  10. // String[]和Object[]是不同的类型.
  11. // String[] copy = src.toArray(); // Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
  12. String[] copy2 = new String[100];
  13. src.toArray(copy2);
  14. System.out.println(Arrays.toString(copy2)); // OK
  15. }
  16. public T[] toArray() {
  17. // 可以分析:elements.getClass().getComponentType()实质是Object, 所以返回的类型实质是:Object[],
  18. T[] result = (T[]) Array.newInstance(elements.getClass().getComponentType(), elements.length);
  19. return result;
  20. }
  21. public T[] toArray(T[] src) {
  22. for (int i = 0; i < elements.length; ++i) {
  23. src[i] = elements[i];
  24. }
  25. return src;
  26. }
  27. }

6.不能在静态域或者方法中使用类型变量. 因为静态域是类共享的.

7. 当T和S有继承关系时,Pair<T>Pair<S> 没有继承关系.

8. Java SE5.0 增加了对泛型的反射API.

Java SE5.0 增加了 java.lang.reflect.Type 来支持类型的反射信息.
public void > minmax(T... a);

corejava-generic-pic03

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注