[关闭]
@lemonguge 2015-06-23T02:33:05.000000Z 字数 8165 阅读 354

泛型(二)

JAVA


边界

之前曾经简单的介绍过边界,只是大家没有发觉而已。边界使得你可以在用于泛型的类型参数上设置限制条件,尽管这使得你可以强制规定泛型可以应用的类型,但是其潜在的一个更重要的效果是你可以按照自己的边界类型来调用方法。

重用extends关键字

因为擦除移除了类型信息,所以,可以用无界泛型参数调用的方法只是那个可以用Object调用的方法。但是,如果能将这个参数限制为某个类型子集,那么你就可以调用这些类型子集来调用方法。

Java泛型重用了extends关键字来表示上界。

  1. interface Swim{
  2. public abstract void swim(); // 游泳的功能
  3. }
  4. abstract class Animal{
  5. public abstract void eat(); // 动物的吃方法
  6. }
  7. class Person implements Swim{
  8. @Override
  9. public void swim() {
  10. System.out.println("Person swim.."); // 人可以游泳
  11. }
  12. }
  13. abstract class Fish extends Animal implements Swim{
  14. @Override
  15. public void swim(){
  16. System.out.println("Fish swim..");
  17. }
  18. }
  19. class GoldenFish extends Fish{
  20. @Override
  21. public void eat() {
  22. System.out.println("GoldenFish eat rice");
  23. }
  24. public void bubble() {
  25. System.out.println("Oo."); // 金鱼吐泡泡
  26. }
  27. }

上面这段代码描述了这么一些信息:人和鱼都有游泳的功能,金鱼可以吐泡泡,还有一个动物的继承体系(动物<--鱼<--金鱼)。

  1. // 无界泛型参数
  2. class HoldAny<T> {
  3. T item;
  4. HoldAny(T item) { this.item = item; }
  5. }
  6. // 上界是鱼,可以接受鱼和鱼的子类
  7. class HoldFish<T extends Fish> {
  8. T item;
  9. HoldFish(T item) { this.item = item; }
  10. void feed(){ item.eat(); }
  11. void show(){ item.swim(); }
  12. }
  13. // 可以接受实现游泳功能的类型
  14. class HoldSwim<T extends Swim> {
  15. T item;
  16. HoldSwim(T item) { this.item = item; }
  17. void show() { item.swim(); }
  18. }
  19. // 可以继承一个类,实现多个接口,此时类必须放左边
  20. // ! class HoldSwimAnimal<T extends Swim & Animal> { }
  21. class HoldSwimAnimal<T extends Animal & Swim> { // 会游泳的动物
  22. T item;
  23. HoldSwimAnimal(T item) { this.item = item; }
  24. void feed(){ item.eat(); }
  25. void show() { item.swim(); }
  26. }
  27. public class ExtendsBound {
  28. public static void main(String[] args) {
  29. new HoldAny<Person>(new Person()); // OK
  30. new HoldAny<Fish>(new GoldenFish()); // OK
  31. // ! new HoldAny<Fish>(new Person());
  32. // ! new HoldSwimAnimal<Person>(new Person());
  33. new HoldAny<Fish>(new GoldenFish()); // OK
  34. new HoldSwim<Swim>(new Person()).show();
  35. new HoldSwim<Swim>(new GoldenFish()).show();
  36. // ! new HoldFish<Person>(new Person()); // Bound mismatch
  37. new HoldFish<Fish>(new GoldenFish()).show();
  38. new HoldFish<Fish>(new GoldenFish()).feed();
  39. new HoldFish<Fish>(new Fish(){
  40. @Override
  41. public void eat() {
  42. System.out.println("Fish eat..");
  43. }
  44. }).feed();
  45. }
  46. } /* Output:
  47. Person swim..
  48. Fish swim..
  49. Fish swim..
  50. GoldenFish eat rice
  51. Fish eat..
  52. *///:~

通配符

通配符:在泛型参数表达式中的问号?

  1. class Fruit {}
  2. class Apple extends Fruit {}
  3. class Jonathan extends Apple {}
  4. class Orange extends Fruit {}
  5. public class CovariantArrays {
  6. public static void main(String[] args) {
  7. Fruit[] fruit = new Apple[10];
  8. fruit[0] = new Apple(); // OK
  9. fruit[1] = new Jonathan(); // OK
  10. // Runtime type is Apple[], not Fruit[] or Orange[]:
  11. try {
  12. // Compiler allows you to add Fruit:
  13. fruit[0] = new Fruit(); // ArrayStoreException
  14. } catch (Exception e) {
  15. System.out.println(e);
  16. }
  17. try {
  18. // Compiler allows you to add Oranges:
  19. fruit[0] = new Orange(); // ArrayStoreException
  20. } catch (Exception e) {
  21. System.out.println(e);
  22. }
  23. }
  24. } /* Output:
  25. java.lang.ArrayStoreException: Fruit
  26. java.lang.ArrayStoreException: Orange
  27. *///:~

我们可以向导出类型的的数组赋予基类型的数组引用,但是存放元素时,只能存放导出类型或导出类型的子类。如果存放了基类型的元素,虽然在编译期,这是允许的。但是,运行时的数组机制知道它处理的实际类型,所以会抛出异常。

我们知道,泛型将错误检测从运行期移到编译期。我们使用泛型容器来代替数组,会发生什么呢?

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. public class NonCovariantGenerics {
  4. // Compile Error: incompatible types:
  5. // ! List<Fruit> flist = new ArrayList()<Apple>();
  6. } ///:~

根本就不能通过编译器的检查,直接报错了。其实很简单:假如这样做能通过编译,当我们给flist存放一个Fruit对象时,而在运行期,泛型的类型参数被擦除为Object,所以整个过程中不会产生任何异常,而这样的结果与我们所期望的不同,因为不可以往导出类型存放基类对象。假设不成立,即在编译期时,就不能通过检查。

希望大家明白,这根本就不是向上转型——AppleList不是FruitList。当我们讨论容器的类型,而不是容器持有的类型,这和我们讨论数组的类型和数组元素的类型是一样的意思。正确的理解上面代码的说法是:“不能把一个涉及Apple的泛型赋给一个涉及Fruit的泛型”。

但是,有时你想要在两个类型之间建立某种关系的向上转型,这正是通配符所允许的:

  1. import java.util.*;
  2. public class GenericsAndCovariance {
  3. public static void main(String[] args) {
  4. // Wildcards allow covariance:
  5. List<? extends Fruit> flist = new ArrayList<Apple>();
  6. // Compile Error: can't add any type of object:
  7. // flist.add(new Apple());
  8. // flist.add(new Fruit());
  9. // flist.add(new Object());
  10. flist.add(null); // Legal but uninteresting
  11. System.out.println(flist.size());
  12. // We know that it returns at least Fruit:
  13. Fruit f = flist.get(0);
  14. }
  15. } /*
  16. 1
  17. *//:~

对于上面这段代码,发现了flist根本就无法接收任何类型的对象,只能存入null。其实这样是很合理的,由于泛型不能在运行期进行错误检测,只有在编译期保证代码的正确性。对于flist引用类型,你可以将它读作“具有任何从Fruit继承的类型的列表”。也就是说这个引用类型可以指向List<Fruit>List<Apple>List<Jonathan>List<Orange>List<任何Fruit的导出类>对象。当你想要调用其add(..)方法时,对于编译器来说,它并不知道flist引用指向哪一种,为了防止给导出类型存放基类对象的情况(当实际对象是List<Jonathan>,可你要存放了Apple对象),所以不允许向其中添加对象。一句话来讲:“子子孙孙无穷尽也。”

一旦执行这种类型的向上转型,你就将丢失掉向其中传递任何对象的能力,甚至是传递Object也不行。不过,当你调用一个返回Fruit的方法时,则是安全的,无论哪种对象都可以被安全的向上转型其基类类型。

  1. import java.util.*;
  2. public class GenericReading {
  3. static <T> T readExact(List<T> list) {
  4. return list.get(0);
  5. }
  6. static List<Apple> apples = Arrays.asList(new Apple());
  7. static List<Fruit> fruit = Arrays.asList(new Fruit());
  8. // A static method adapts to each call:
  9. static void f1() {
  10. Apple a = readExact(apples);
  11. Fruit f = readExact(fruit);
  12. f = readExact(apples);
  13. }
  14. // If, however, you have a class, then its type is
  15. // established when the class is instantiated:
  16. static class Reader<T> {
  17. T readExact(List<T> list) {
  18. return list.get(0);
  19. }
  20. }
  21. static void f2() {
  22. Reader<Fruit> fruitReader = new Reader<Fruit>();
  23. Fruit f = fruitReader.readExact(fruit);
  24. // Fruit a = fruitReader.readExact(apples); // Error:
  25. // readExact(List<Fruit>) cannot be
  26. // applied to (List<Apple>).
  27. }
  28. static class CovariantReader<T> {
  29. T readCovariant(List<? extends T> list) {
  30. return list.get(0);
  31. }
  32. }
  33. static void f3() {
  34. CovariantReader<Fruit> fruitReader = new CovariantReader<Fruit>();
  35. Fruit f = fruitReader.readCovariant(fruit);
  36. Fruit a = fruitReader.readCovariant(apples);
  37. }
  38. public static void main(String[] args) {
  39. f1();
  40. f2();
  41. f3();
  42. }
  43. } ///:~

无界通配符

无界通配符<?>看起来意味这“任何事物”,因此使用无界通配符好像等价于使用原生类型。

举个例子:关于List<?>List之间的差异。List<?>看起来等价于List<Object>,而List实际上也是List<Object>List实际上表示“持有任何Object类型的原生List”,而List<?>表示“具有某种特定类型 的非原生List,只是我们不知道那种类型是什么。

无论何时,只要使用了原生类型,都会放弃编译期检查。

  1. class Cellphone { }
  2. public class Holder<T> {
  3. private T a;
  4. public Holder(T a) { this.a = a; }
  5. public void set(T a) { this.a = a; }
  6. public T get() { return a; }
  7. // Raw argument:
  8. static void rawArgs(Holder holder, Object arg) {
  9. holder.set(arg); // Warning:
  10. // The method set(Object) belongs to the raw type Holder
  11. holder.set(new Cellphone()); // Same warning
  12. // OK, but type information has been lost:
  13. Object obj = holder.get();
  14. }
  15. // Similar to rawArgs(), but errors instead of warnings:
  16. static void unboundedArg(Holder<?> holder, Object arg) {
  17. // The method set(capture of ?) in the type Holder<capture of ?>
  18. // is not applicable for the arguments (Object)
  19. // ! holder.set(arg); // Error
  20. // ! holder.set(new Cellphone()); // Same error
  21. // OK, but type information has been lost:
  22. Object obj = holder.get();
  23. }
  24. static <T> T exact(Holder<T> holder) {
  25. T t = holder.get();
  26. return t;
  27. }
  28. @SuppressWarnings({ "unused", "rawtypes" })
  29. public static void main(String[] args) {
  30. Holder raw = new Holder();
  31. Holder<?> unbounded = new Holder<Long>();
  32. Holder<Long> qualified = new Holder<Long>();
  33. Object r1 = exact(raw); // Warnings:
  34. Object r3 = exact(unbounded);
  35. Long r2 = exact(qualified);
  36. }
  37. } ///~

向接受“确切”泛型类型(没有通配符)的方法exact(..)传递一个原生引用Holder,就会得到一个警告,因为确切的参数期望得到在原生类型类型中并不存在的信息。如果传递了一个无界引用,就不会有任何可以确定返回类型的类型信息。

捕捉转换:向一个使用无界通配符<?>的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,是这个方法可以回转并调用另一种使用参数类型T的方法。

笔者认为,然而这并没什么用。

逆变

超类型通配符super:由某个特定类的任何基类来界定,表示下界,super只能与通配符?结合使用,即? super

我们可以使用<? super MyClass>,甚至可以使用<? super T>,但是却不能声明<T super MyClass>不能对泛型参数给出一个超类型边界

  1. import java.util.*;
  2. public class SuperTypeWildcards {
  3. static void writeTo(List<? super Apple> apples) {
  4. apples.add(new Apple());
  5. apples.add(new Jonathan());
  6. // ! apples.add(new Fruit()); // Error
  7. }
  8. }

对于编译器来讲,apples引用类型中的泛型类型参数为? super Apple,这就意味着apples引用可以指向List<Apple>List<Fruit>,甚至是List<Object>对象。无论是指向了哪一种对象,这些容器对象都可以存储Apple类型和Apple类型的导出类型。由于List<Apple>不能存储Fruit类型对象,所以会报错。

无界通配符的五种存在形式

  1. <?>
  2. <? super MyClass>
  3. <? extends MyClass>
  4. <? super T>
  5. <? extends T>
  6. <T extends MyClass>

使用泛型时会出现的问题

1、任何基本类型都不能作为类型参数。
2、一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。

  1. interface Payable<T> {}
  2. class Employee implements Payable<Employee> {}
  3. // ! class Hourly extends Employee implements Payable<Hourly> {} ///:~
  1. interface Payable<T> {}
  2. // 去掉泛型,可以通过编译
  3. class Employee implements Payable {}
  4. class Hourly extends Employee implements Payable {} ///:~

3、使用带有泛型类型参数的转型或instanceof不会有任何效果。
4、 由于擦除的原因,重载方法将产生相同的类型签名。

  1. public class UseList<W,T> {
  2. void f1(List<T> v) {}
  3. // ! void f1(List<W> v) {}
  4. // 必须提供明显有区别的方法名
  5. void f2(List<W> v) {}
  6. } ///:~

5、基类劫持了接口,原理是一个类不能实现同一个泛型接口的两种变体。

  1. class ComparablePet implements Comparable<ComparablePet> {
  2. public int compareTo(ComparablePet arg) {
  3. return 0;
  4. }
  5. }
  6. // 报错:The interface Comparable cannot be implemented more than once with different arguments:
  7. // Comparable<ComparablePet> and Comparable<TomCat>
  8. // 基类ComparablePet劫持了Comparable接口,只能进行ComparablePet的比较,而不能进行TomCat的比较
  9. class TomCat extends ComparablePet implements Comparable<TomCat> {
  10. // Error: Comparable cannot be inherited with
  11. // different arguments: <Cat> and <Pet>
  12. public int compareTo(TomCat arg) {
  13. return 0;
  14. }
  15. } // ERROR
  16. // 下面演示实现ComparablePet中的相同接口的可行性:这只是与覆盖基类中的方法相同
  17. class Hamster extends ComparablePet implements Comparable<ComparablePet> {
  18. public int compareTo(ComparablePet arg) {
  19. return 0;
  20. }
  21. }
  22. // Or just:
  23. class Gecko extends ComparablePet {
  24. public int compareTo(ComparablePet arg) {
  25. return 0;
  26. }
  27. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注