外贸哪些免费网站开发客户,做一套品牌设计多少钱,移动互联网开发学什么,网页qq登录入口官网官方本章概要
边界通配符 编译器有多聪明逆变无界通配符捕获转换
边界
边界#xff08;bounds#xff09;在本章的前面进行了简要介绍。边界允许我们对泛型使用的参数类型施加约束。尽管这可以强制执行有关应用了泛型类型的规则#xff0c;但潜在的更重要的效果是我们可以在…本章概要
边界通配符 编译器有多聪明逆变无界通配符捕获转换
边界
边界bounds在本章的前面进行了简要介绍。边界允许我们对泛型使用的参数类型施加约束。尽管这可以强制执行有关应用了泛型类型的规则但潜在的更重要的效果是我们可以在绑定的类型中调用方法。
由于擦除会删除类型信息因此唯一可用于无限制泛型参数的方法是那些 Object 可用的方法。但是如果将该参数限制为某类型的子集则可以调用该子集中的方法。为了应用约束Java 泛型使用了 extends 关键字。
重要的是要理解当用于限定泛型类型时extends 的含义与通常的意义截然不同。此示例展示边界的基础应用
interface HasColor {java.awt.Color getColor();
}class WithColorT extends HasColor {T item;WithColor(T item) {this.item item;}T getItem() {return item;}// The bound allows you to call a method:java.awt.Color color() {return item.getColor();}
}class Coord {public int x, y, z;
}// This fails. Class must be first, then interfaces:
// class WithColorCoordT extends HasColor Coord {// Multiple bounds:
class WithColorCoordT extends Coord HasColor {T item;WithColorCoord(T item) {this.item item;}T getItem() {return item;}java.awt.Color color() {return item.getColor();}int getX() {return item.x;}int getY() {return item.y;}int getZ() {return item.z;}
}interface Weight {int weight();
}// As with inheritance, you can have only one
// concrete class but multiple interfaces:
class SolidT extends Coord HasColor Weight {T item;Solid(T item) {this.item item;}T getItem() {return item;}java.awt.Color color() {return item.getColor();}int getX() {return item.x;}int getY() {return item.y;}int getZ() {return item.z;}int weight() {return item.weight();}
}class Boundedextends Coord implements HasColor, Weight {Overridepublic java.awt.Color getColor() {return null;}Overridepublic int weight() {return 0;}
}public class BasicBounds {public static void main(String[] args) {SolidBounded solid new Solid(new Bounded());solid.color();solid.getY();solid.weight();}
}你可能会观察到 BasicBounds.java 中似乎包含一些冗余它们可以通过继承来消除。在这里每个继承级别还添加了边界约束
class HoldItemT {T item;HoldItem(T item) {this.item item;}T getItem() {return item;}
}class WithColor2T extends HasColorextends HoldItemT {WithColor2(T item) {super(item);}java.awt.Color color() {return item.getColor();}
}class WithColorCoord2T extends Coord HasColorextends WithColor2T {WithColorCoord2(T item) {super(item);}int getX() {return item.x;}int getY() {return item.y;}int getZ() {return item.z;}
}class Solid2T extends Coord HasColor Weightextends WithColorCoord2T {Solid2(T item) {super(item);}int weight() {return item.weight();}
}public class InheritBounds {public static void main(String[] args) {Solid2Bounded solid2 new Solid2(new Bounded());solid2.color();solid2.getY();solid2.weight();}
}HoldItem 拥有一个对象因此此行为将继承到 WithColor2 中这也需要其参数符合 HasColor。 WithColorCoord2 和 Solid2 进一步扩展了层次结构并在每个级别添加了边界。现在这些方法已被继承并且在每个类中不再重复。
这是一个具有更多层次的示例
import java.util.List;interface SuperPower {
}interface XRayVision extends SuperPower {void seeThroughWalls();
}interface SuperHearing extends SuperPower {void hearSubtleNoises();
}interface SuperSmell extends SuperPower {void trackBySmell();
}class SuperHeroPOWER extends SuperPower {POWER power;SuperHero(POWER power) {this.power power;}POWER getPower() {return power;}
}class SuperSleuthPOWER extends XRayVisionextends SuperHeroPOWER {SuperSleuth(POWER power) {super(power);}void see() {power.seeThroughWalls();}
}class
CanineHeroPOWER extends SuperHearing SuperSmellextends SuperHeroPOWER {CanineHero(POWER power) {super(power);}void hear() {power.hearSubtleNoises();}void smell() {power.trackBySmell();}
}class SuperHearSmellimplements SuperHearing, SuperSmell {Overridepublic void hearSubtleNoises() {}Overridepublic void trackBySmell() {}
}class DogPerson extends CanineHeroSuperHearSmell {DogPerson() {super(new SuperHearSmell());}
}public class EpicBattle {// Bounds in generic methods:static POWER extends SuperHearingvoid useSuperHearing(SuperHeroPOWER hero) {hero.getPower().hearSubtleNoises();}static POWER extends SuperHearing SuperSmellvoid superFind(SuperHeroPOWER hero) {hero.getPower().hearSubtleNoises();hero.getPower().trackBySmell();}public static void main(String[] args) {DogPerson dogPerson new DogPerson();useSuperHearing(dogPerson);superFind(dogPerson);// You can do this:List? extends SuperHearing audioPeople;// But you cant do this:// List? extends SuperHearing SuperSmell dogPs;}
}接下来将要研究的通配符将会把范围限制在单个类型。
通配符
你已经在 集合章节中看到了一些简单示例使用了通配符——在泛型参数表达式中的问号在 类型信息 一章中这种示例更多。本节将更深入地探讨这个特性。
我们的起始示例要展示数组的一种特殊行为你可以将派生类的数组赋值给基类的引用
class Fruit {
}class Apple extends Fruit {
}class Jonathan extends Apple {
}class Orange extends Fruit {
}public class CovariantArrays {public static void main(String[] args) {Fruit[] fruit new Apple[10];fruit[0] new Apple(); // OKfruit[1] new Jonathan(); // OK// Runtime type is Apple[], not Fruit[] or Orange[]:try {// Compiler allows you to add Fruit:fruit[0] new Fruit(); // ArrayStoreException} catch (Exception e) {System.out.println(e);}try {// Compiler allows you to add Oranges:fruit[0] new Orange(); // ArrayStoreException} catch (Exception e) {System.out.println(e);}}
}main() 中的第一行创建了 Apple 数组并赋值给一个 Fruit 数组引用。这是有意义的因为 Apple 也是一种 Fruit因此 Apple 数组应该也是一个 Fruit 数组。
但是如果实际的数组类型是 Apple[]你可以在其中放置 Apple 或 Apple 的子类型这在编译期和运行时都可以工作。但是你也可以在数组中放置 Fruit 对象。这对编译器来说是有意义的因为它有一个 Fruit[] 引用——它有什么理由不允许将 Fruit 对象或任何从 Fruit 继承出来的对象比如 Orange放置到这个数组中呢因此在编译期这是允许的。然而运行时的数组机制知道它处理的是 Apple[]因此会在向数组中放置异构类型时抛出异常。
向上转型用在这里不合适。你真正在做的是将一个数组赋值给另一个数组。数组的行为是持有其他对象这里只是因为我们能够向上转型而已所以很明显数组对象可以保留有关它们包含的对象类型的规则。看起来就像数组对它们持有的对象是有意识的因此在编译期检查和运行时检查之间你不能滥用它们。
数组的这种赋值并不是那么可怕因为在运行时你可以发现插入了错误的类型。但是泛型的主要目标之一是将这种错误检测移到编译期。所以当我们试图使用泛型集合代替数组时会发生什么呢
import java.util.*;public class NonCovariantGenerics {// Compile Error: incompatible types:ListFruit flist new ArrayListApple();
}尽管你在首次阅读这段代码时会认为“不能将一个 Apple 集合赋值给一个 Fruit 集合”。记住泛型不仅仅是关于集合它真正要表达的是“不能把一个涉及 Apple 的泛型赋值给一个涉及 Fruit 的泛型”。如果像在数组中的情况一样编译器对代码的了解足够多可以确定所涉及到的集合那么它可能会留下一些余地。
但是它不知道任何有关这方面的信息因此它拒绝向上转型。然而实际上这也不是向上转型—— Apple 的 List 不是 Fruit 的 List。Apple 的 List 将持有 Apple 和 Apple 的子类型Fruit 的 List 将持有任何类型的 Fruit。是的这包括 Apple但是它不是一个 Apple 的 List它仍然是 Fruit 的 List。Apple 的 List 在类型上不等价于 Fruit 的 List即使 Apple 是一种 Fruit 类型。
真正的问题是我们在讨论的集合类型而不是集合持有对象的类型。与数组不同泛型没有内建的协变类型。这是因为数组是完全在语言中定义的因此可以具有编译期和运行时的内建检查但是在使用泛型时编译器和运行时系统不知道你想用类型做什么以及应该采用什么规则。
但是有时你想在两个类型间建立某种向上转型关系。通配符可以产生这种关系。
import java.util.*;public class GenericsAndCovariance {public static void main(String[] args) {// Wildcards allow covariance:List? extends Fruit flist new ArrayList();// Compile Error: cant add any type of object:// flist.add(new Apple());// flist.add(new Fruit());// flist.add(new Object());flist.add(null); // Legal but uninteresting// We know it returns at least Fruit:Fruit f flist.get(0);}}flist 的类型现在是 List? extends Fruit你可以读作“一个具有任何从 Fruit 继承的类型的列表”。然而这实际上并不意味着这个 List 将持有任何类型的 Fruit。通配符引用的是明确的类型因此它意味着“某种 flist 引用没有指定的具体类型”。因此这个被赋值的 List 必须持有诸如 Fruit 或 Apple 这样的指定类型但是为了向上转型为 Fruit这个类型是什么没人在意。
List 必须持有一种具体的 Fruit 或 Fruit 的子类型但是如果你不关心具体的类型是什么那么你能对这样的 List 做什么呢如果不知道 List 中持有的对象是什么类型你怎能保证安全地向其中添加对象呢就像在 CovariantArrays.java 中向上转型一样你不能除非编译器而不是运行时系统可以阻止这种操作的发生。你很快就会发现这个问题。
你可能认为事情开始变得有点走极端了因为现在你甚至不能向刚刚声明过将持有 Apple 对象的 List 中放入一个 Apple 对象。是的但编译器并不知道这一点。List? extends Fruit 可能合法地指向一个 ListOrange。一旦执行这种类型的向上转型你就丢失了向其中传递任何对象的能力甚至传递 Object 也不行。
另一方面如果你调用了一个返回 Fruit 的方法则是安全的因为你知道这个 List 中的任何对象至少具有 Fruit 类型因此编译器允许这么做。
编译器有多聪明
现在你可能会猜想自己不能去调用任何接受参数的方法但是考虑下面的代码
import java.util.*;public class CompilerIntelligence {public static void main(String[] args) {List? extends Fruit flist Arrays.asList(new Apple());Apple a (Apple) flist.get(0); // No warningflist.contains(new Apple()); // Argument is Objectflist.indexOf(new Apple()); // Argument is Object}}这里对 contains() 和 indexOf() 的调用接受 Apple 对象作为参数执行没问题。这是否意味着编译器实际上会检查代码以查看是否有某个特定的方法修改了它的对象
通过查看 ArrayList 的文档我们发现编译器没有那么聪明。尽管 add() 接受一个泛型参数类型的参数但 contains() 和 indexOf() 接受的参数类型是 Object。因此当你指定一个 ArrayList? extends Fruit 时add() 的参数就变成了? extends Fruit。从这个描述中编译器无法得知这里需要 Fruit 的哪个具体子类型因此它不会接受任何类型的 Fruit。如果你先把 Apple 向上转型为 Fruit也没有关系——编译器仅仅会拒绝调用像 add() 这样参数列表中涉及通配符的方法。
contains() 和 indexOf() 的参数类型是 Object不涉及通配符所以编译器允许调用它们。这意味着将由泛型类的设计者来决定哪些调用是“安全的”并使用 Object 类作为它们的参数类型。为了禁止对类型中使用了通配符的方法调用需要在参数列表中使用类型参数。
下面展示一个简单的 Holder 类
import java.util.Objects;public class HolderT {private T value;public Holder() {}public Holder(T val) {value val;}public void set(T val) {value val;}public T get() {return value;}Overridepublic boolean equals(Object o) {return o instanceof Holder Objects.equals(value, ((Holder) o).value);}Overridepublic int hashCode() {return Objects.hashCode(value);}public static void main(String[] args) {HolderApple apple new Holder(new Apple());Apple d apple.get();apple.set(d);// HolderFruit fruit apple; // Cannot upcastHolder? extends Fruit fruit apple; // OKFruit p fruit.get();d (Apple) fruit.get();try {Orange c (Orange) fruit.get(); // No warning} catch (Exception e) {System.out.println(e);}// fruit.set(new Apple()); // Cannot call set()// fruit.set(new Fruit()); // Cannot call set()System.out.println(fruit.equals(d)); // OK}
}Holder 有一个接受 T 类型对象的 set() 方法一个返回 T 对象的 get() 方法和一个接受 Object 对象的 equals() 方法。正如你所见如果创建了一个 HolderApple就不能将其向上转型为 HolderFruit但是可以向上转型为 Holder? extends Fruit。如果调用 get()只能返回一个 Fruit——这就是在给定“任何扩展自 Fruit 的对象”这一边界后它所能知道的一切了。
如果你知道更多的信息就可以将其转型到某种具体的 Fruit 而不会导致任何警告但是存在得到 ClassCastException 的风险。set() 方法不能工作在 Apple 和 Fruit 上因为 set() 的参数也是? extends Fruit意味着它可以是任何事物编译器无法验证“任何事物”的类型安全性。
但是equals() 方法可以正常工作因为它接受的参数是 Object 而不是 T 类型。因此编译器只关注传递进来和要返回的对象类型。它不会分析代码以查看是否执行了任何实际的写入和读取操作。
Java 7 引入了 java.util.Objects 库使创建 equals() 和 hashCode() 方法变得更加容易当然还有很多其他功能。
逆变
还可以走另外一条路即使用超类型通配符。这里可以声明通配符是由某个特定类的任何基类来界定的方法是指定 super MyClass 或者甚至使用类型参数 super T尽管你不能对泛型参数给出一个超类型边界即不能声明 T super MyClass 。这使得你可以安全地传递一个类型对象到泛型类型中。因此有了超类型通配符就可以向 Collection 写入了
import java.util.*;public class SuperTypeWildcards {static void writeTo(List? super Apple apples) {apples.add(new Apple());apples.add(new Jonathan());// apples.add(new Fruit()); // Error}
}参数 apples 是 Apple 的某种基类型的 List这样你就知道向其中添加 Apple 或 Apple 的子类型是安全的。但是因为 Apple 是下界所以你知道向这样的 List 中添加 Fruit 是不安全的因为这将使这个 List 敞开口子从而可以向其中添加非 Apple 类型的对象而这是违反静态类型安全的。 下面的示例复习了一下逆变和通配符的的使用
import java.util.*;public class GenericReading {static ListApple apples Arrays.asList(new Apple());static ListFruit fruit Arrays.asList(new Fruit());static T T readExact(ListT list) {return list.get(0);}// A static method adapts to each call:static void f1() {Apple a readExact(apples);Fruit f readExact(fruit);f readExact(apples);}// A class type is established// when the class is instantiated:static class ReaderT {T readExact(ListT list) {return list.get(0);}}static void f2() {ReaderFruit fruitReader new Reader();Fruit f fruitReader.readExact(fruit);//- Fruit a fruitReader.readExact(apples);// error: incompatible types: ListApple// cannot be converted to ListFruit}static class CovariantReaderT {T readCovariant(List? extends T list) {return list.get(0);}}static void f3() {CovariantReaderFruit fruitReader new CovariantReader();Fruit f fruitReader.readCovariant(fruit);Fruit a fruitReader.readCovariant(apples);}public static void main(String[] args) {f1();f2();f3();}
}readExact() 方法使用了精确的类型。如果使用这个没有任何通配符的精确类型就可以向 List 中写入和读取这个精确类型。另外对于返回值静态的泛型方法 readExact() 可以有效地“适应”每个方法调用并能够从 ListApple 中返回一个 Apple 从 ListFruit 中返回一个 Fruit 就像在 f1() 中看到的那样。因此如果可以摆脱静态泛型方法那么在读取时就不需要协变类型了。
然而对于泛型类来说当你创建这个类的实例时就要为这个类确定参数。就像在 f2() 中看到的fruitReader 实例可以从 ListFruit 中读取一个 Fruit 因为这就是它的确切类型。但是 ListApple 也应该产生 Fruit 对象而 fruitReader 不允许这么做。
为了修正这个问题CovariantReader.readCovariant() 方法将接受 Listextends T 因此从这个列表中读取一个 T 是安全的你知道在这个列表中的所有对象至少是一个 T 并且可能是从 T 导出的某种对象。在 f3() 中你可以看到现在可以从 ListApple 中读取 Fruit 了。
无界通配符
无界通配符 ? 看起来意味着“任何事物”因此使用无界通配符好像等价于使用原生类型。事实上编译器初看起来是支持这种判断的
import java.util.*;public class UnboundedWildcards1 {static List list1;static List? list2;static List? extends Object list3;static void assign1(List list) {list1 list;list2 list;//- list3 list;// warning: [unchecked] unchecked conversion// list3 list;// ^// required: List? extends Object// found: List}static void assign2(List? list) {list1 list;list2 list;list3 list;}static void assign3(List? extends Object list) {list1 list;list2 list;list3 list;}public static void main(String[] args) {assign1(new ArrayList());assign2(new ArrayList());//- assign3(new ArrayList());// warning: [unchecked] unchecked method invocation:// method assign3 in class UnboundedWildcards1// is applied to given types// assign3(new ArrayList());// ^// required: List? extends Object// found: ArrayList// warning: [unchecked] unchecked conversion// assign3(new ArrayList());// ^// required: List? extends Object// found: ArrayList// 2 warningsassign1(new ArrayList());assign2(new ArrayList());assign3(new ArrayList());// Both forms are acceptable as List?:List? wildList new ArrayList();wildList new ArrayList();assign1(wildList);assign2(wildList);assign3(wildList);}
}有很多情况都和你在这里看到的情况类似即编译器很少关心使用的是原生类型还是 ? 。在这些情况中? 可以被认为是一种装饰但是它仍旧是很有价值的因为实际上它是在声明“我是想用 Java 的泛型来编写这段代码我在这里并不是要用原生类型但是在当前这种情况下泛型参数可以持有任何类型。” 第二个示例展示了无界通配符的一个重要应用。当你在处理多个泛型参数时有时允许一个参数可以是任何类型同时为其他参数确定某种特定类型的这种能力会显得很重要
import java.util.*;public class UnboundedWildcards2 {static Map map1;static Map?, ? map2;static MapString, ? map3;static void assign1(Map map) {map1 map;}static void assign2(Map?, ? map) {map2 map;}static void assign3(MapString, ? map) {map3 map;}public static void main(String[] args) {assign1(new HashMap());assign2(new HashMap());//- assign3(new HashMap());// warning: [unchecked] unchecked method invocation:// method assign3 in class UnboundedWildcards2// is applied to given types// assign3(new HashMap());// ^// required: MapString,?// found: HashMap// warning: [unchecked] unchecked conversion// assign3(new HashMap());// ^// required: MapString,?// found: HashMap// 2 warningsassign1(new HashMap());assign2(new HashMap());assign3(new HashMap());}
}但是当你拥有的全都是无界通配符时就像在 Map?,? 中看到的那样编译器看起来就无法将其与原生 Map 区分开了。另外 UnboundedWildcards1.java 展示了编译器处理 List? 和 List? extends Object 是不同的。
令人困惑的是编译器并非总是关注像 List 和 List? 之间的这种差异因此它们看起来就像是相同的事物。事实上因为泛型参数擦除到它的第一个边界因此 List? 看起来等价于 ListObject 而 List 实际上也是 ListObject ——除非这些语句都不为真。List 实际上表示“持有任何 Object 类型的原生 List ”而 List? 表示“具有某种特定类型的非原生 List 只是我们不知道类型是什么。”
编译器何时才会关注原生类型和涉及无界通配符的类型之间的差异呢下面的示例使用了前面定义的 HolderT 类它包含接受 Holder 作为参数的各种方法但是它们具有不同的形式作为原生类型具有具体的类型参数以及具有无界通配符参数
public class Wildcards {// Raw argument:static void rawArgs(Holder holder, Object arg) {//- holder.set(arg);// warning: [unchecked] unchecked call to set(T)// as a member of the raw type Holder// holder.set(arg);// ^// where T is a type-variable:// T extends Object declared in class Holder// 1 warning// Cant do this; dont have any T:// T t holder.get();// OK, but type information is lost:Object obj holder.get();}// Like rawArgs(), but errors instead of warnings:static void unboundedArg(Holder? holder, Object arg) {//- holder.set(arg);// error: method set in class HolderT// cannot be applied to given types;// holder.set(arg);// ^// required: CAP#1// found: Object// reason: argument mismatch;// Object cannot be converted to CAP#1// where T is a type-variable:// T extends Object declared in class Holder// where CAP#1 is a fresh type-variable:// CAP#1 extends Object from capture of ?// 1 error// Cant do this; dont have any T:// T t holder.get();// OK, but type information is lost:Object obj holder.get();}static T T exact1(HolderT holder) {return holder.get();}static T T exact2(HolderT holder, T arg) {holder.set(arg);return holder.get();}static T T wildSubtype(Holder? extends T holder, T arg) {//- holder.set(arg);// error: method set in class HolderT#2// cannot be applied to given types;// holder.set(arg);// ^// required: CAP#1// found: T#1// reason: argument mismatch;// T#1 cannot be converted to CAP#1// where T#1,T#2 are type-variables:// T#1 extends Object declared in method// T#1wildSubtype(Holder? extends T#1,T#1)// T#2 extends Object declared in class Holder// where CAP#1 is a fresh type-variable:// CAP#1 extends T#1 from// capture of ? extends T#1// 1 errorreturn holder.get();}static T void wildSupertype(Holder? super T holder, T arg) {holder.set(arg);//- T t holder.get();// error: incompatible types:// CAP#1 cannot be converted to T// T t holder.get();// ^// where T is a type-variable:// T extends Object declared in method// TwildSupertype(Holder? super T,T)// where CAP#1 is a fresh type-variable:// CAP#1 extends Object super:// T from capture of ? super T// 1 error// OK, but type information is lost:Object obj holder.get();}public static void main(String[] args) {Holder raw new Holder();// Or:raw new Holder();HolderLong qualified new Holder();Holder? unbounded new Holder();Holder? extends Long bounded new Holder();Long lng 1L;rawArgs(raw, lng);rawArgs(qualified, lng);rawArgs(unbounded, lng);rawArgs(bounded, lng);unboundedArg(raw, lng);unboundedArg(qualified, lng);unboundedArg(unbounded, lng);unboundedArg(bounded, lng);//- Object r1 exact1(raw);// warning: [unchecked] unchecked method invocation:// method exact1 in class Wildcards is applied// to given types// Object r1 exact1(raw);// ^// required: HolderT// found: Holder// where T is a type-variable:// T extends Object declared in// method Texact1(HolderT)// warning: [unchecked] unchecked conversion// Object r1 exact1(raw);// ^// required: HolderT// found: Holder// where T is a type-variable:// T extends Object declared in// method Texact1(HolderT)// 2 warningsLong r2 exact1(qualified);Object r3 exact1(unbounded); // Must return ObjectLong r4 exact1(bounded);//- Long r5 exact2(raw, lng);// warning: [unchecked] unchecked method invocation:// method exact2 in class Wildcards is// applied to given types// Long r5 exact2(raw, lng);// ^// required: HolderT,T// found: Holder,Long// where T is a type-variable:// T extends Object declared in// method Texact2(HolderT,T)// warning: [unchecked] unchecked conversion// Long r5 exact2(raw, lng);// ^// required: HolderT// found: Holder// where T is a type-variable:// T extends Object declared in// method Texact2(HolderT,T)// 2 warningsLong r6 exact2(qualified, lng);//- Long r7 exact2(unbounded, lng);// error: method exact2 in class Wildcards// cannot be applied to given types;// Long r7 exact2(unbounded, lng);// ^// required: HolderT,T// found: HolderCAP#1,Long// reason: inference variable T has// incompatible bounds// equality constraints: CAP#1// lower bounds: Long// where T is a type-variable:// T extends Object declared in// method Texact2(HolderT,T)// where CAP#1 is a fresh type-variable:// CAP#1 extends Object from capture of ?// 1 error//- Long r8 exact2(bounded, lng);// error: method exact2 in class Wildcards// cannot be applied to given types;// Long r8 exact2(bounded, lng);// ^// required: HolderT,T// found: HolderCAP#1,Long// reason: inference variable T// has incompatible bounds// equality constraints: CAP#1// lower bounds: Long// where T is a type-variable:// T extends Object declared in// method Texact2(HolderT,T)// where CAP#1 is a fresh type-variable:// CAP#1 extends Long from// capture of ? extends Long// 1 error//- Long r9 wildSubtype(raw, lng);// warning: [unchecked] unchecked method invocation:// method wildSubtype in class Wildcards// is applied to given types// Long r9 wildSubtype(raw, lng);// ^// required: Holder? extends T,T// found: Holder,Long// where T is a type-variable:// T extends Object declared in// method TwildSubtype(Holder? extends T,T)// warning: [unchecked] unchecked conversion// Long r9 wildSubtype(raw, lng);// ^// required: Holder? extends T// found: Holder// where T is a type-variable:// T extends Object declared in// method TwildSubtype(Holder? extends T,T)// 2 warningsLong r10 wildSubtype(qualified, lng);// OK, but can only return Object:Object r11 wildSubtype(unbounded, lng);Long r12 wildSubtype(bounded, lng);//- wildSupertype(raw, lng);// warning: [unchecked] unchecked method invocation:// method wildSupertype in class Wildcards// is applied to given types// wildSupertype(raw, lng);// ^// required: Holder? super T,T// found: Holder,Long// where T is a type-variable:// T extends Object declared in// method TwildSupertype(Holder? super T,T)// warning: [unchecked] unchecked conversion// wildSupertype(raw, lng);// ^// required: Holder? super T// found: Holder// where T is a type-variable:// T extends Object declared in// method TwildSupertype(Holder? super T,T)// 2 warningswildSupertype(qualified, lng);//- wildSupertype(unbounded, lng);// error: method wildSupertype in class Wildcards// cannot be applied to given types;// wildSupertype(unbounded, lng);// ^// required: Holder? super T,T// found: HolderCAP#1,Long// reason: cannot infer type-variable(s) T// (argument mismatch; HolderCAP#1// cannot be converted to Holder? super T)// where T is a type-variable:// T extends Object declared in// method TwildSupertype(Holder? super T,T)// where CAP#1 is a fresh type-variable:// CAP#1 extends Object from capture of ?// 1 error//- wildSupertype(bounded, lng);// error: method wildSupertype in class Wildcards// cannot be applied to given types;// wildSupertype(bounded, lng);// ^// required: Holder? super T,T// found: HolderCAP#1,Long// reason: cannot infer type-variable(s) T// (argument mismatch; HolderCAP#1// cannot be converted to Holder? super T)// where T is a type-variable:// T extends Object declared in// method TwildSupertype(Holder? super T,T)// where CAP#1 is a fresh type-variable:// CAP#1 extends Long from capture of// ? extends Long// 1 error}
}在 rawArgs() 中编译器知道 Holder 是一个泛型类型因此即使它在这里被表示成一个原生类型编译器仍旧知道向 set() 传递一个 Object 是不安全的。由于它是原生类型你可以将任何类型的对象传递给 set() 而这个对象将被向上转型为 Object 。因此无论何时只要使用了原生类型都会放弃编译期检查。对 get() 的调用说明了相同的问题没有任何 T 类型的对象因此结果只能是一个 Object。
人们很自然地会开始考虑原生 Holder 与 Holder? 是大致相同的事物。但是 unboundedArg() 强调它们是不同的——它揭示了相同的问题但是它将这些问题作为错误而不是警告报告因为原生 Holder 将持有任何类型的组合而 Holder? 将持有具有某种具体类型的同构集合因此不能只是向其中传递 Object 。
在 exact1() 和 exact2() 中你可以看到使用了确切的泛型参数——没有任何通配符。你将看到exact2()与 exact1() 具有不同的限制因为它有额外的参数。
在 wildSubtype() 中在 Holder 类型上的限制被放松为包括持有任何扩展自 T 的对象的 Holder 。这还是意味着如果 T 是 Fruit 那么 holder 可以是 HolderApple 这是合法的。为了防止将 Orange 放置到 HolderApple 中对 set() 的调用或者对任何接受这个类型参数为参数的方法的调用都是不允许的。但是你仍旧知道任何来自 Holderextends Fruit 的对象至少是 Fruit 因此 get() 或者任何将产生具有这个类型参数的返回值的方法都是允许的。
wildSupertype() 展示了超类型通配符这个方法展示了与 wildSubtype() 相反的行为holder 可以是持有任何 T 的基类型的容器。因此 set() 可以接受 T 因为任何可以工作于基类的对象都可以多态地作用于导出类这里就是 T 。但是尝试着调用 get() 是没有用的因为由 holder 持有的类型可以是任何超类型因此唯一安全的类型就是 Object 。 这个示例还展示了对于在 unbounded() 中使用无界通配符能够做什么不能做什么所做出的限制因为你没有 T所以你不能将 set() 或 get() 作用于 T 上。
在 main() 方法中你看到了某些方法在接受某些类型的参数时没有报错和警告。为了迁移兼容性rawArgs() 将接受所有 Holder 的不同变体而不会产生警告。unboundedArg() 方法也可以接受相同的所有类型尽管如前所述它在方法体内部处理这些类型的方式并不相同。
如果向接受“确切”泛型类型没有通配符的方法传递一个原生 Holder 引用就会得到一个警告因为确切的参数期望得到在原生类型中并不存在的信息。如果向 exact1() 传递一个无界引用就不会有任何可以确定返回类型的类型信息。
可以看到exact2() 具有最多的限制因为它希望精确地得到一个 HolderT 以及一个具有类型 T 的参数正由于此它将产生错误或警告除非提供确切的参数。有时这样做很好但是如果它过于受限那么就可以使用通配符这取决于是否想要从泛型参数中返回类型确定的返回值就像在 wildSubtype() 中看到的那样或者是否想要向泛型参数传递类型确定的参数就像在 wildSupertype() 中看到的那样。
因此使用确切类型来替代通配符类型的好处是可以用泛型参数来做更多的事但是使用通配符使得你必须接受范围更宽的参数化类型作为参数。因此必须逐个情况地权衡利弊找到更适合你的需求的方法。
捕获转换
有一种特殊情况需要使用 ? 而不是原生类型。如果向一个使用 ? 的方法传递原生类型那么对编译器来说可能会推断出实际的类型参数使得这个方法可以回转并调用另一个使用这个确切类型的方法。下面的示例演示了这种技术它被称为捕获转换因为未指定的通配符类型被捕获并被转换为确切类型。这里有关警告的注释只有在 SuppressWarnings 注解被移除之后才能起作用
public class CaptureConversion {static T void f1(HolderT holder) {T t holder.get();System.out.println(t.getClass().getSimpleName());}static void f2(Holder? holder) {f1(holder); // Call with captured type}SuppressWarnings(unchecked)public static void main(String[] args) {Holder raw new Holder(1);f1(raw);// warning: [unchecked] unchecked method invocation:// method f1 in class CaptureConversion// is applied to given types// f1(raw);// ^// required: HolderT// found: Holder// where T is a type-variable:// T extends Object declared in// method Tf1(HolderT)// warning: [unchecked] unchecked conversion// f1(raw);// ^// required: HolderT// found: Holder// where T is a type-variable:// T extends Object declared in// method Tf1(HolderT)// 2 warningsf2(raw); // No warningsHolder rawBasic new Holder();rawBasic.set(new Object());// warning: [unchecked] unchecked call to set(T)// as a member of the raw type Holder// rawBasic.set(new Object());// ^// where T is a type-variable:// T extends Object declared in class Holder// 1 warningf2(rawBasic); // No warnings// Upcast to Holder?, still figures it out:Holder? wildcarded new Holder(1.0);f2(wildcarded);}
}f1() 中的类型参数都是确切的没有通配符或边界。在 f2() 中Holder 参数是一个无界通配符因此它看起来是未知的。但是在 f2() 中调用了 f1()而 f1() 需要一个已知参数。这里所发生的是在调用 f2() 的过程中捕获了参数类型并在调用 f1() 时使用了这种类型。
你可能想知道这项技术是否可以用于写入但是这要求在传递 Holder? 时同时传递一个具体类型。捕获转换只有在这样的情况下可以工作即在方法内部你需要使用确切的类型。注意不能从 f2() 中返回 T因为 T 对于 f2() 来说是未知的。捕获转换十分有趣但是非常受限。