专业零基础网站建设教学在哪里,统一登录入口,静宁县门户网,公司网站如何建立本章概要
问题 任何基本类型都不能作为类型参数实现参数化接口转型和警告重载基类劫持接口 自限定的类型 古怪的循环泛型自限定参数协变
问题
本节将阐述在使用 Java 泛型时会出现的各类问题。
任何基本类型都不能作为类型参数
正如本章早先提到的#xff0c;Java 泛型的…本章概要
问题 任何基本类型都不能作为类型参数实现参数化接口转型和警告重载基类劫持接口 自限定的类型 古怪的循环泛型自限定参数协变
问题
本节将阐述在使用 Java 泛型时会出现的各类问题。
任何基本类型都不能作为类型参数
正如本章早先提到的Java 泛型的限制之一是不能将基本类型用作类型参数。因此不能创建 ArrayListint 之类的东西。
解决方法是使用基本类型的包装器类以及自动装箱机制。如果创建一个 ArrayListInteger并将基本类型 int 应用于这个集合那么你将发现自动装箱机制将自动地实现 int 到 Integer 的双向转换——因此这几乎就像是有一个 ArrayListint 一样
import java.util.*;
import java.util.stream.*;public class ListOfInt {public static void main(String[] args) {ListInteger li IntStream.range(38, 48).boxed() // Converts ints to Integers.collect(Collectors.toList());System.out.println(li);}
}通常这种解决方案工作得很好——能够成功地存储和读取 int自动装箱隐藏了转换的过程。但是如果性能成为问题的话就需要使用专门为基本类型适配的特殊版本的集合一个开源版本的实现是 org.apache.commons.collections.primitives。
下面是另外一种方式它可以创建持有 Byte 的 Set
import java.util.*;public class ByteSet {Byte[] possibles {1, 2, 3, 4, 5, 6, 7, 8, 9};SetByte mySet new HashSet(Arrays.asList(possibles));// But you cant do this:// SetByte mySet2 new HashSet(// Arrays.ByteasList(1,2,3,4,5,6,7,8,9));
}自动装箱机制解决了一些问题但并没有解决所有问题。
在下面的示例中FillArray 接口包含一些通用方法这些方法使用 Supplier 来用对象填充数组这使得类泛型在本例中无法工作因为这个方法是静态的。Supplier 实现来自 数组 一章,并且在 main() 中可以看到 FillArray.fill() 使用对象填充了数组 FillArray.java import java.util.*;
import java.util.function.*;// Fill an array using a generator:
interface FillArray {static T T[] fill(T[] a, SupplierT gen) {Arrays.setAll(a, n - gen.get());return a;}static int[] fill(int[] a, IntSupplier gen) {Arrays.setAll(a, n - gen.getAsInt());return a;}static long[] fill(long[] a, LongSupplier gen) {Arrays.setAll(a, n - gen.getAsLong());return a;}static double[] fill(double[] a, DoubleSupplier gen) {Arrays.setAll(a, n - gen.getAsDouble());return a;}
}public class PrimitiveGenericTest {public static void main(String[] args) {String[] strings FillArray.fill(new String[5], new Rand.String(9));System.out.println(Arrays.toString(strings));int[] integers FillArray.fill(new int[9], new Rand.Pint());System.out.println(Arrays.toString(integers));}
}ConvertTo.java public interface ConvertTo {static boolean[] primitive(Boolean[] in) {boolean[] result new boolean[in.length];for (int i 0; i in.length; i) {result[i] in[i]; // Autounboxing}return result;}static char[] primitive(Character[] in) {char[] result new char[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static byte[] primitive(Byte[] in) {byte[] result new byte[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static short[] primitive(Short[] in) {short[] result new short[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static int[] primitive(Integer[] in) {int[] result new int[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static long[] primitive(Long[] in) {long[] result new long[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static float[] primitive(Float[] in) {float[] result new float[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static double[] primitive(Double[] in) {double[] result new double[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}// Convert from primitive array to wrapped array:static Boolean[] boxed(boolean[] in) {Boolean[] result new Boolean[in.length];for (int i 0; i in.length; i) {result[i] in[i]; // Autoboxing}return result;}static Character[] boxed(char[] in) {Character[] result new Character[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static Byte[] boxed(byte[] in) {Byte[] result new Byte[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static Short[] boxed(short[] in) {Short[] result new Short[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static Integer[] boxed(int[] in) {Integer[] result new Integer[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static Long[] boxed(long[] in) {Long[] result new Long[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static Float[] boxed(float[] in) {Float[] result new Float[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}static Double[] boxed(double[] in) {Double[] result new Double[in.length];for (int i 0; i in.length; i) {result[i] in[i];}return result;}
}Rand.java import java.util.*;
import java.util.function.*;import static com.example.test.ConvertTo.primitive;public interface Rand {int MOD 10_000;class Boolean implements Supplierjava.lang.Boolean {SplittableRandom r new SplittableRandom(47);Overridepublic java.lang.Boolean get() {return r.nextBoolean();}public java.lang.Boolean get(int n) {return get();}public java.lang.Boolean[] array(int sz) {java.lang.Boolean[] result new java.lang.Boolean[sz];Arrays.setAll(result, n - get());return result;}}class Pboolean {public boolean[] array(int sz) {return primitive(new Boolean().array(sz));}}class Byteimplements Supplierjava.lang.Byte {SplittableRandom r new SplittableRandom(47);Overridepublic java.lang.Byte get() {return (byte) r.nextInt(MOD);}public java.lang.Byte get(int n) {return get();}public java.lang.Byte[] array(int sz) {java.lang.Byte[] result new java.lang.Byte[sz];Arrays.setAll(result, n - get());return result;}}class Pbyte {public byte[] array(int sz) {return primitive(new Byte().array(sz));}}class Characterimplements Supplierjava.lang.Character {SplittableRandom r new SplittableRandom(47);Overridepublic java.lang.Character get() {return (char) r.nextInt(a, z 1);}public java.lang.Character get(int n) {return get();}public java.lang.Character[] array(int sz) {java.lang.Character[] result new java.lang.Character[sz];Arrays.setAll(result, n - get());return result;}}class Pchar {public char[] array(int sz) {return primitive(new Character().array(sz));}}class Shortimplements Supplierjava.lang.Short {SplittableRandom r new SplittableRandom(47);Overridepublic java.lang.Short get() {return (short) r.nextInt(MOD);}public java.lang.Short get(int n) {return get();}public java.lang.Short[] array(int sz) {java.lang.Short[] result new java.lang.Short[sz];Arrays.setAll(result, n - get());return result;}}class Pshort {public short[] array(int sz) {return primitive(new Short().array(sz));}}class Integerimplements Supplierjava.lang.Integer {SplittableRandom r new SplittableRandom(47);Overridepublic java.lang.Integer get() {return r.nextInt(MOD);}public java.lang.Integer get(int n) {return get();}public java.lang.Integer[] array(int sz) {int[] primitive new Pint().array(sz);java.lang.Integer[] result new java.lang.Integer[sz];for (int i 0; i sz; i) {result[i] primitive[i];}return result;}}class Pint implements IntSupplier {SplittableRandom r new SplittableRandom(47);Overridepublic int getAsInt() {return r.nextInt(MOD);}public int get(int n) {return getAsInt();}public int[] array(int sz) {return r.ints(sz, 0, MOD).toArray();}}class Longimplements Supplierjava.lang.Long {SplittableRandom r new SplittableRandom(47);Overridepublic java.lang.Long get() {return r.nextLong(MOD);}public java.lang.Long get(int n) {return get();}public java.lang.Long[] array(int sz) {long[] primitive new Plong().array(sz);java.lang.Long[] result new java.lang.Long[sz];for (int i 0; i sz; i) {result[i] primitive[i];}return result;}}class Plong implements LongSupplier {SplittableRandom r new SplittableRandom(47);Overridepublic long getAsLong() {return r.nextLong(MOD);}public long get(int n) {return getAsLong();}public long[] array(int sz) {return r.longs(sz, 0, MOD).toArray();}}class Floatimplements Supplierjava.lang.Float {SplittableRandom r new SplittableRandom(47);Overridepublic java.lang.Float get() {return (float) trim(r.nextDouble());}public java.lang.Float get(int n) {return get();}public java.lang.Float[] array(int sz) {java.lang.Float[] result new java.lang.Float[sz];Arrays.setAll(result, n - get());return result;}}class Pfloat {public float[] array(int sz) {return primitive(new Float().array(sz));}}static double trim(double d) {return((double) Math.round(d * 1000.0)) / 100.0;}class Doubleimplements Supplierjava.lang.Double {SplittableRandom r new SplittableRandom(47);Overridepublic java.lang.Double get() {return trim(r.nextDouble());}public java.lang.Double get(int n) {return get();}public java.lang.Double[] array(int sz) {double[] primitive new Rand.Pdouble().array(sz);java.lang.Double[] result new java.lang.Double[sz];for (int i 0; i sz; i) {result[i] primitive[i];}return result;}}class Pdouble implements DoubleSupplier {SplittableRandom r new SplittableRandom(47);Overridepublic double getAsDouble() {return trim(r.nextDouble());}public double get(int n) {return getAsDouble();}public double[] array(int sz) {double[] result r.doubles(sz).toArray();Arrays.setAll(result,n - result[n] trim(result[n]));return result;}}class Stringimplements Supplierjava.lang.String {SplittableRandom r new SplittableRandom(47);private int strlen 7; // Default lengthpublic String() {}public String(int strLength) {strlen strLength;}Overridepublic java.lang.String get() {return r.ints(strlen, a, z 1).collect(StringBuilder::new,StringBuilder::appendCodePoint,StringBuilder::append).toString();}public java.lang.String get(int n) {return get();}public java.lang.String[] array(int sz) {java.lang.String[] result new java.lang.String[sz];Arrays.setAll(result, n - get());return result;}}
}自动装箱不适用于数组因此我们必须创建 FillArray.fill() 的重载版本或创建产生 Wrapped 输出的生成器。 FillArray 仅比 java.util.Arrays.setAll() 有用一点因为它返回填充的数组。
实现参数化接口
一个类不能实现同一个泛型接口的两种变体由于擦除的原因这两个变体会成为相同的接口。下面是产生这种冲突的情况
interface PayableT {
}class Employee implements PayableEmployee {
}class Hourly extends Employee implements PayableHourly {
}Hourly 不能编译因为擦除会将 PayableEmploye 和 PayableHourly 简化为相同的类 Payable这样上面的代码就意味着在重复两次地实现相同的接口。十分有趣的是如果从 Payable 的两种用法中都移除掉泛型参数就像编译器在擦除阶段所做的那样这段代码就可以编译。
在使用某些更基本的 Java 接口例如 ComparableT 时这个问题可能会变得十分令人恼火就像你在本节稍后看到的那样。
转型和警告
使用带有泛型类型参数的转型或 instanceof 不会有任何效果。下面的集合在内部将各个值存储为 Object并在获取这些值时再将它们转型回 T
import java.util.*;
import java.util.stream.*;class FixedSizeStackT {private final int size;private Object[] storage;private int index 0;FixedSizeStack(int size) {this.size size;storage new Object[size];}public void push(T item) {if (index size) {storage[index] item;}}SuppressWarnings(unchecked)public T pop() {return index 0 ? null : (T) storage[--index];}SuppressWarnings(unchecked)StreamT stream() {return (StreamT) Arrays.stream(storage);}
}public class GenericCast {static String[] letters ABCDEFGHIJKLMNOPQRS.split();public static void main(String[] args) {FixedSizeStackString strings new FixedSizeStack(letters.length);Arrays.stream(ABCDEFGHIJKLMNOPQRS.split()).forEach(strings::push);System.out.println(strings.pop());strings.stream().map(s - s ).forEach(System.out::print);}
}如果没有 **SuppressWarnings ** 注解编译器将对 pop() 产生 “unchecked cast” 警告。由于擦除的原因编译器无法知道这个转型是否是安全的并且 pop() 方法实际上并没有执行任何转型。
这是因为T 被擦除到它的第一个边界默认情况下是 Object 因此 pop() 实际上只是将 Object 转型为 Object。
有时泛型没有消除对转型的需要这就会由编译器产生警告而这个警告是不恰当的。例如 NeedCasting.java import java.io.*;public class NeedCasting {SuppressWarnings(unchecked)public void f(String[] args) throws Exception {ObjectInputStream in new ObjectInputStream(new FileInputStream(args[0]));ListWidget shapes (ListWidget) in.readObject();}
}FactoryConstraint.java import java.util.*;
import java.util.function.*;class IntegerFactory implements SupplierInteger {private int i 0;Overridepublic Integer get() {return i;}
}class Widget {private int id;Widget(int n) {id n;}Overridepublic String toString() {return Widget id;}public staticclass Factory implements SupplierWidget {private int i 0;Overridepublic Widget get() {return new Widget(i);}}
}class Fudge {private static int count 1;private int n count;Overridepublic String toString() {return Fudge n;}
}class Foo2T {private ListT x new ArrayList();Foo2(SupplierT factory) {Suppliers.fill(x, factory, 5);}Overridepublic String toString() {return x.toString();}
}public class FactoryConstraint {public static void main(String[] args) {System.out.println(new Foo2(new IntegerFactory()));System.out.println(new Foo2(new Widget.Factory()));System.out.println(new Foo2(Fudge::new));}
}Suppliers.java import java.util.*;
import java.util.function.*;
import java.util.stream.*;public class Suppliers {// Create a collection and fill it:public static T, C extends CollectionT Ccreate(SupplierC factory, SupplierT gen, int n) {return Stream.generate(gen).limit(n).collect(factory, C::add, C::addAll);}// Fill an existing collection:public static T, C extends CollectionTC fill(C coll, SupplierT gen, int n) {Stream.generate(gen).limit(n).forEach(coll::add);return coll;}// Use an unbound method reference to// produce a more general method:public static H, A H fill(H holder,BiConsumerH, A adder, SupplierA gen, int n) {Stream.generate(gen).limit(n).forEach(a - adder.accept(holder, a));return holder;}
}readObject() 无法知道它正在读取的是什么因此它返回的是必须转型的对象。但是当注释掉 **SuppressWarnings ** 注解并编译这个程序时就会得到下面的警告。
NeedCasting.java uses unchecked or unsafe operations.
Recompile with -Xlint:unchecked for details.And if you follow the instructions and recompile with -
Xlint:unchecked :(如果遵循这条指示使用-Xlint:unchecked来重新编译)NeedCasting.java:10: warning: [unchecked] unchecked castListWidget shapes (ListWidget)in.readObject();required: ListWidgetfound: Object
1 warning你会被强制要求转型但是又被告知不应该转型。为了解决这个问题必须使用 Java 5 引入的新的转型形式即通过泛型类来转型
import java.io.*;
import java.util.*;public class ClassCasting {SuppressWarnings(unchecked)public void f(String[] args) throws Exception {ObjectInputStream in new ObjectInputStream(new FileInputStream(args[0]));// Wont Compile:// ListWidget lw1 // List.class.cast(in.readObject());ListWidget lw2 List.class.cast(in.readObject());}
}但是不能转型到实际类型 ListWidget 。也就是说不能声明
ListWidget.class.cast(in.readobject())甚至当你添加一个像下面这样的另一个转型时
(ListWidget)List.class.cast(in.readobject())仍旧会得到一个警告。
重载
下面的程序是不能编译的即使它看起来是合理的
import java.util.*;public class UseListW, T {void f(ListT v) {}void f(ListW v) {}
}因为擦除所以重载方法产生了相同的类型签名。
因而当擦除后的参数不能产生唯一的参数列表时你必须提供不同的方法名
import java.util.*;public class UseList2W, T {void f1(ListT v) {}void f2(ListW v) {}
}幸运的是编译器可以检测到这类问题。
基类劫持接口
假设你有一个实现了 Comparable 接口的 Pet 类
public class ComparablePet implements ComparableComparablePet {Overridepublic int compareTo(ComparablePet o) {return 0;}
}尝试缩小 ComparablePet 子类的比较类型是有意义的。例如Cat 类可以与其他的 Cat 比较
class Cat extends ComparablePet implements ComparableCat {// error: Comparable cannot be inherited with// different arguments: Cat and ComparablePet// class Cat// ^// 1 errorpublic int compareTo(Cat arg) {return 0;}
}不幸的是这不能工作。一旦 Comparable 的类型参数设置为 ComparablePet其他的实现类只能比较 ComparablePet
public class Hamster extends ComparablePet implements ComparableComparablePet {Overridepublic int compareTo(ComparablePet arg) {return 0;}
}// Or just:
class Gecko extends ComparablePet {Overridepublic int compareTo(ComparablePet arg) {return 0;}
}Hamster 显示了重新实现 ComparablePet 中相同的接口是可能的只要接口完全相同包括参数类型。然而正如 Gecko 中所示这与直接覆写基类的方法完全相同。
自限定的类型
在 Java 泛型中有一个似乎经常性出现的惯用法它相当令人费解
class SelfBoundedT extends SelfBoundedT { // ...
}这就像两面镜子彼此照向对方所引起的目眩效果一样是一种无限反射。SelfBounded 类接受泛型参数 T而 T 由一个边界类限定这个边界就是拥有 T 作为其参数的 SelfBounded。
当你首次看到它时很难去解析它它强调的是当 extends 关键字用于边界与用来创建子类明显是不同的。
古怪的循环泛型
为了理解自限定类型的含义我们从这个惯用法的一个简单版本入手它没有自限定的边界。
不能直接继承一个泛型参数但是可以继承在其自己的定义中使用这个泛型参数的类。也就是说可以声明
class GenericTypeT {
}public class CuriouslyRecurringGeneric extends GenericTypeCuriouslyRecurringGeneric {
}这可以按照 Jim Coplien 在 C 中的_古怪的循环模版模式_的命名方式称为古怪的循环泛型CRG。“古怪的循环”是指类相当古怪地出现在它自己的基类中这一事实。
为了理解其含义努力大声说“我在创建一个新类它继承自一个泛型类型这个泛型类型接受我的类的名字作为其参数。”
当给出导出类的名字时这个泛型基类能够实现什么呢好吧Java 中的泛型关乎参数和返回类型因此它能够产生使用导出类作为其参数和返回类型的基类。它还能将导出类型用作其域类型尽管这些将被擦除为 Object 的类型。下面是表示了这种情况的一个泛型类
public class BasicHolderT {T element;void set(T arg) {element arg;}T get() {return element;}void f() {System.out.println(element.getClass().getSimpleName());}
}这是一个普通的泛型类型它的一些方法将接受和产生具有其参数类型的对象还有一个方法在其存储的域上执行操作尽管只是在这个域上执行 Object 操作。 我们可以在一个古怪的循环泛型中使用 BasicHolder
class Subtype extends BasicHolderSubtype {
}public class CRGWithBasicHolder {public static void main(String[] args) {Subtype st1 new Subtype(), st2 new Subtype();st1.set(st2);Subtype st3 st1.get();st1.f();}
}注意这里有些东西很重要新类 Subtype 接受的参数和返回的值具有 Subtype 类型而不仅仅是基类 BasicHolder 类型。这就是 CRG 的本质基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模版但是这些功能对于其所有参数和返回值将使用导出类型。
也就是说在所产生的类中将使用确切类型而不是基类型。因此在Subtype 中传递给 set() 的参数和从 get() 返回的类型都是确切的 Subtype。
自限定
BasicHolder 可以使用任何类型作为其泛型参数就像下面看到的那样
class Other {
}class BasicOther extends BasicHolderOther {
}public class Unconstrained {public static void main(String[] args) {BasicOther b new BasicOther();BasicOther b2 new BasicOther();b.set(new Other());Other other b.get();b.f();}
}限定将采取额外的步骤强制泛型当作其自身的边界参数来使用。观察所产生的类可以如何使用以及不可以如何使用
class SelfBoundedT extends SelfBoundedT {T element;SelfBoundedT set(T arg) {element arg;return this;}T get() {return element;}
}class A extends SelfBoundedA {
}class B extends SelfBoundedA {
} // Also OKclass C extends SelfBoundedC {C setAndGet(C arg) {set(arg);return get();}
}class D {
}
// Cant do this:
// class E extends SelfBoundedD {}
// Compile error:
// Type parameter D is not within its bound// Alas, you can do this, so you cannot force the idiom:
class F extends SelfBounded {
}public class SelfBounding {public static void main(String[] args) {A a new A();a.set(new A());a a.set(new A()).get();a a.get();C c new C();c c.setAndGet(new C());}
}自限定所做的就是要求在继承关系中像下面这样使用这个类
class A extends SelfBoundedA{}这会强制要求将正在定义的类当作参数传递给基类。
自限定的参数有何意义呢它可以保证类型参数必须与正在被定义的类相同。正如你在 B 类的定义中所看到的还可以从使用了另一个 SelfBounded 参数的 SelfBounded 中导出尽管在 A 类看到的用法看起来是主要的用法。对定义 E 的尝试说明不能使用不是 SelfBounded 的类型参数。
遗憾的是 F 可以编译不会有任何警告因此自限定惯用法不是可强制执行的。如果它确实很重要可以要求一个外部工具来确保不会使用原生类型来替代参数化类型。
注意可以移除自限定这个限制这样所有的类仍旧是可以编译的但是 E 也会因此而变得可编译
public class NotSelfBoundedT {T element;NotSelfBoundedT set(T arg) {element arg;return this;}T get() {return element;}
}class A2 extends NotSelfBoundedA2 {
}class B2 extends NotSelfBoundedA2 {
}class C2 extends NotSelfBoundedC2 {C2 setAndGet(C2 arg) {set(arg);return get();}
}class D2 {
}// Now this is OK:
class E2 extends NotSelfBoundedD2 {
}因此很明显自限定限制只能强制作用于继承关系。如果使用自限定就应该了解这个类所用的类型参数将与使用这个参数的类具有相同的基类型。这会强制要求使用这个类的每个人都要遵循这种形式。 还可以将自限定用于泛型方法
public class SelfBoundingMethods {static T extends SelfBoundedT T f(T arg) {return arg.set(arg).get();}public static void main(String[] args) {A a f(new A());}
}这可以防止这个方法被应用于除上述形式的自限定参数之外的任何事物上。
参数协变
自限定类型的价值在于它们可以产生_协变参数类型_——方法参数类型会随子类而变化。
尽管自限定类型还可以产生与子类类型相同的返回类型但是这并不十分重要因为_协变返回类型_是在 Java 5 引入
class Base {
}class Derived extends Base {
}interface OrdinaryGetter {Base get();
}interface DerivedGetter extends OrdinaryGetter {// Overridden method return type can vary:OverrideDerived get();
}public class CovariantReturnTypes {void test(DerivedGetter d) {Derived d2 d.get();}
}DerivedGetter 中的 get() 方法覆盖了 OrdinaryGetter 中的 get() 并返回了一个从 OrdinaryGetter.get() 的返回类型中导出的类型。尽管这是完全合乎逻辑的事情导出类方法应该能够返回比它覆盖的基类方法更具体的类型但是这在早先的 Java 版本中是不合法的。
自限定泛型事实上将产生确切的导出类型作为其返回值就像在 get() 中所看到的一样
interface GenericGetterT extends GenericGetterT {T get();
}interface Getter extends GenericGetterGetter {
}public class GenericsAndReturnTypes {void test(Getter g) {Getter result g.get();GenericGetter gg g.get(); // Also the base type}
}注意这段代码不能编译除非是使用囊括了协变返回类型的 Java 5。
然而在非泛型代码中参数类型不能随子类型发生变化
class OrdinarySetter {void set(Base base) {System.out.println(OrdinarySetter.set(Base));}
}class DerivedSetter extends OrdinarySetter {void set(Derived derived) {System.out.println(DerivedSetter.set(Derived));}
}public class OrdinaryArguments {public static void main(String[] args) {Base base new Base();Derived derived new Derived();DerivedSetter ds new DerivedSetter();ds.set(derived);// Compiles--overloaded, not overridden!:ds.set(base);}
}set(derived) 和 set(base) 都是合法的因此 DerivedSetter.set() 没有覆盖 OrdinarySetter.set() 而是重载了这个方法。从输出中可以看到在 DerivedSetter 中有两个方法因此基类版本仍旧是可用的因此可以证明它被重载过。 但是在使用自限定类型时在导出类中只有一个方法并且这个方法接受导出类型而不是基类型为参数
interface SelfBoundSetterT extends SelfBoundSetterT {void set(T arg);
}interface Setter extends SelfBoundSetterSetter {
}public class SelfBoundingAndCovariantArguments {voidtestA(Setter s1, Setter s2, SelfBoundSetter sbs) {s1.set(s2);//- s1.set(sbs);// error: method set in interface SelfBoundSetterT// cannot be applied to given types;// s1.set(sbs);// ^// required: Setter// found: SelfBoundSetter// reason: argument mismatch;// SelfBoundSetter cannot be converted to Setter// where T is a type-variable:// T extends SelfBoundSetterT declared in// interface SelfBoundSetter// 1 error}
}编译器不能识别将基类型当作参数传递给 set() 的尝试因为没有任何方法具有这样的签名。实际上这个参数已经被覆盖。
如果不使用自限定类型普通的继承机制就会介入而你将能够重载就像在非泛型的情况下一样
class GenericSetterT { // Not self-boundedvoid set(T arg) {System.out.println(GenericSetter.set(Base));}
}class DerivedGS extends GenericSetterBase {void set(Derived derived) {System.out.println(DerivedGS.set(Derived));}
}public class PlainGenericInheritance {public static void main(String[] args) {Base base new Base();Derived derived new Derived();DerivedGS dgs new DerivedGS();dgs.set(derived);dgs.set(base); // Overloaded, not overridden!}
}这段代码在模仿 OrdinaryArguments.java在那个示例中DerivedSetter 继承自包含一个 set(Base) 的OrdinarySetter 。而这里DerivedGS 继承自泛型创建的也包含有一个 set(Base)的 GenericSetterBase。
就像 OrdinaryArguments.java 一样你可以从输出中看到 DerivedGS 包含两个 set() 的重载版本。如果不使用自限定将重载参数类型。如果使用了自限定只能获得方法的一个版本它将接受确切的参数类型。