为什么 java 需要将有界类型参数实例化为其上限 class?
Why does java require a cast for the instantiation of a bounded type parameter to its upper bound class?
Java 需要将有界类型参数实例化为其上限 class 才能进行强制转换,例如:
<T extends Integer> void passVal (T t) {
Integer number = 5;
t = (T) number; // Without cast a compile error is issued
}
T
在声明时已经限制为 Integer
或其子 class 之一(我知道,没有),在我看来任何有界类型参数只能实例化为其上限 class 或其子 class 之一,那么为什么需要强制转换?
此外,我知道如果我通过在此方法之外的调用来实例化参数,情况就不是这样了,所以不要提供它作为答案;该问题特定于在其声明 class/methods.
中实例化的有界类型参数
T 并不意味着 Integer,它必须对 Integer 或从它延伸的任何 class 有效。假设 StrangeInteger 从 Integer 扩展并用 StrangeInteger 替换 T:
void passVal (StrangeInteger t) {
Integer number = 5;
t = (StrangeInteger) number;
}
它试图将一个 Integer 变量分配给一个 StrangeInteger 变量,你不能这样做,除非 number 首先是一个 StrangeInteger 或派生的 class。事实上,你的代码应该(概念上)在运行时抛出异常,除非 t 是一个整数,但在这种情况下,由于类型擦除,它实际上不会这样做(见编辑 2)。
情况类似于:
Object obj = "Hello"
String t = (String)obj; // this will not fail, but requires a cast
Object obj2 = getDBConnection();
String t2 = (String)obj2; // this will fail at runtime
编辑:整数确实是最终的,所以 T 只能是整数,但是编译器可能没有检查上限是否是最终的,毕竟上限是最终的没有什么意义,所以允许这种特殊情况会增加复杂性,而实际收益却很少。
编辑 2:
TL; DR:你混淆了上限和下限,但类型擦除有一些警告。一旦你使用泛型而不是仅仅使用基类型做任何值得做的事情,这就会中断。
英语不是我的第一语言,所以我可能不是很清楚。
我认为您正在努力区分使用具有上限的泛型类型和仅使用上限作为类型之间的区别。泛型(来自 C++ 和其他语言)的思想是,如果将 T 替换为边界允许的任何类型 T,则代码必须有效,因此您不能调用任何未在上限中定义的方法。
A 作为 T 的上限也意味着您始终可以将 T 对象分配给 A 变量。您不能将 A 对象安全地分配给 T 变量(除非 A == T),只有当 A 是 T 的下限而不是上限时才能这样做。另见 Understanding upper and lower bounds on ? in Java Generics.
Java 使用 type erasure to implement generics, ,但这会导致一些并不总是很明显的限制。由于类型擦除,在这种情况下,转换本身不会失败,在类型检查 T 被类型擦除步骤中的上限替换后,即 (T)number 被 (Integer)number 替换。如果你做任何导致转换到 subclass 的事情,异常仍然会发生,例如,如果你 return 改变的 t 并将结果分配给 subclass 的变量,因为编译器添加了一个隐式转换。
如果你调用一个依赖于 T 的子class 的方法,这也是失败的,这是一种常见的模式,例如:
List<Person> persons = ...
Comparator<Person> nameComparator = (p1,p2) -> p1.getName().compareTo(p2.getName())
java.util.Collections.sort(persons,nameComparator);
以下代码示例显示了几种情况下的行为。我对所有内容都使用了 System.err 以避免输出中的顺序问题。
import java.util.function.Consumer;
import java.util.function.Function;
class A {
@Override public String toString(){ return "A";}
public String foo(){ return "foo";}
}
class B extends A {
@Override public String toString(){ return "B";}
public String bar(){ return "bar";}
}
class C extends B { }
public class Main {
public static void main(String[] args) {
Function<A,String> funA = a -> a.foo();
Function<B,String> funB = b -> b.bar();
Function<C,String> funC = c -> c.bar();
Consumer<B> ignoreArgument = b -> {
System.err.println(" Consumer called");
};
B b = new B();
System.err.println("* voidTest *");
voidTest(b);
System.err.println("------------");
System.err.println("* returnTest *");
returnTest(b);
System.err.println("returnTest without using result did not throw");
System.err.println("------------");
try {
System.err.println("Returned " + returnTest(b).toString());
System.err.println("returnTest: invoking method on result did not throw");
}
catch(Exception ex) {
System.err.println("returnTest: invoking method on result threw");
ex.printStackTrace();
}
System.err.println("------------");
B b2 = null;
try {
b2 = returnTest(b);
System.err.println("returnTest: assigning result to a B variable did not throw");
}
catch(Exception ex) {
System.err.println("returnTest: assigning result to a B variable threw");
ex.printStackTrace();
}
System.err.println("------------");
System.err.println("* functionTest funA *");
functionTest(b, funA);
System.err.println("------------");
System.err.println("* functionTest funB * ");
functionTest(b, funB);
System.err.println("------------");
System.err.println("* consumerTest *");
consumerTest(b, ignoreArgument);
// The following won't work because C is not B or a superclass of B
// Compiler error functionTest(T, Function<? super T,String>) is not applicable for the arguments (B, Function<C,String>)
// functionTest(b, funC);
}
private static <T extends A> void voidTest(T t){
System.err.println(" Before: " + t.toString());
t = (T)new A(); // warning Type safety: Unchecked cast from A to T
System.err.println(" After: " + t.toString());
}
private static <T extends A> T returnTest(T t){
System.err.println(" Before: " + t.toString());
t = (T)new A();
System.err.println(" After: " + t.toString());
return t;
}
private static <T extends A> void functionTest(T t, Function<? super T,String> fun) {
System.err.println(" fun Before: " + fun.apply(t));
t = (T)new A();
try {
System.err.println(" fun After: " + fun.apply(t));
}
catch(Exception ex) {
System.err.println(" fun After: threw");
ex.printStackTrace();
}
}
private static <T extends A> void consumerTest(T t, Consumer<? super T> c) {
System.err.print(" Before: ");
c.accept(t);
t = (T)new A();
try {
System.err.println(" After: ");
c.accept(t);
System.err.println(" c.accept(t) After: worked");
}
catch(Exception ex) {
System.err.println(" c.accept(t) After: threw");
ex.printStackTrace();
}
}
}
OpenJDK 11下的输出为:
* voidTest *
Before: B
After: A
------------
* returnTest *
Before: B
After: A
returnTest without using result did not throw
------------
Before: B
After: A
returnTest: invoking method on result threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.main(Main.java:35)
------------
Before: B
After: A
returnTest: assigning result to a B variable threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.main(Main.java:45)
------------
* functionTest funA *
fun Before: foo
fun After: foo
------------
* functionTest funB *
fun Before: bar
fun After: threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.functionTest(Main.java:83)
at Main.main(Main.java:57)
------------
* consumerTest *
Before: Consumer called
After:
c.accept(t) After: threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.consumerTest(Main.java:97)
at Main.main(Main.java:60)
我不完全确定 resultTest 为什么在结果被完全忽略的情况下没有引发异常,可能在那种情况下语言不需要强制转换,或者编译器将其删除。调用在结果上限中定义的方法仍然导致异常。最后来自 consumerTest 的观察是它不需要调用 bar() 来引发 ClassCastException,它只需要将 t 传递给需要 B 参数的消费者。
语言规范目前不包括任何意义上的最终类型的显式检查。
我检查过:
而且,即使它们不适用于您的情况,我还是冒昧地检查了一下:
- §8.1.2 - Generic Classes and Type Parameters
- §8.8.4 - Generic Constructors
- §9.1.2 - Generic Interfaces and Type Parameters
尽管 Integer
是最终的,但如果不是,您的代码可能会崩溃。
假设编译器不检查最终类型,您所拥有的类似于:
class Super
class Sub extends Super
<T extends Super> void passVal (T t) {
Super super = new Super();
return (T) super;
}
如果我们调用会中断:
passVal(new Sub());
Java 需要将有界类型参数实例化为其上限 class 才能进行强制转换,例如:
<T extends Integer> void passVal (T t) {
Integer number = 5;
t = (T) number; // Without cast a compile error is issued
}
T
在声明时已经限制为 Integer
或其子 class 之一(我知道,没有),在我看来任何有界类型参数只能实例化为其上限 class 或其子 class 之一,那么为什么需要强制转换?
此外,我知道如果我通过在此方法之外的调用来实例化参数,情况就不是这样了,所以不要提供它作为答案;该问题特定于在其声明 class/methods.
中实例化的有界类型参数T 并不意味着 Integer,它必须对 Integer 或从它延伸的任何 class 有效。假设 StrangeInteger 从 Integer 扩展并用 StrangeInteger 替换 T:
void passVal (StrangeInteger t) {
Integer number = 5;
t = (StrangeInteger) number;
}
它试图将一个 Integer 变量分配给一个 StrangeInteger 变量,你不能这样做,除非 number 首先是一个 StrangeInteger 或派生的 class。事实上,你的代码应该(概念上)在运行时抛出异常,除非 t 是一个整数,但在这种情况下,由于类型擦除,它实际上不会这样做(见编辑 2)。
情况类似于:
Object obj = "Hello"
String t = (String)obj; // this will not fail, but requires a cast
Object obj2 = getDBConnection();
String t2 = (String)obj2; // this will fail at runtime
编辑:整数确实是最终的,所以 T 只能是整数,但是编译器可能没有检查上限是否是最终的,毕竟上限是最终的没有什么意义,所以允许这种特殊情况会增加复杂性,而实际收益却很少。
编辑 2: TL; DR:你混淆了上限和下限,但类型擦除有一些警告。一旦你使用泛型而不是仅仅使用基类型做任何值得做的事情,这就会中断。
英语不是我的第一语言,所以我可能不是很清楚。
我认为您正在努力区分使用具有上限的泛型类型和仅使用上限作为类型之间的区别。泛型(来自 C++ 和其他语言)的思想是,如果将 T 替换为边界允许的任何类型 T,则代码必须有效,因此您不能调用任何未在上限中定义的方法。
A 作为 T 的上限也意味着您始终可以将 T 对象分配给 A 变量。您不能将 A 对象安全地分配给 T 变量(除非 A == T),只有当 A 是 T 的下限而不是上限时才能这样做。另见 Understanding upper and lower bounds on ? in Java Generics.
Java 使用 type erasure to implement generics,
如果你调用一个依赖于 T 的子class 的方法,这也是失败的,这是一种常见的模式,例如:
List<Person> persons = ...
Comparator<Person> nameComparator = (p1,p2) -> p1.getName().compareTo(p2.getName())
java.util.Collections.sort(persons,nameComparator);
以下代码示例显示了几种情况下的行为。我对所有内容都使用了 System.err 以避免输出中的顺序问题。
import java.util.function.Consumer;
import java.util.function.Function;
class A {
@Override public String toString(){ return "A";}
public String foo(){ return "foo";}
}
class B extends A {
@Override public String toString(){ return "B";}
public String bar(){ return "bar";}
}
class C extends B { }
public class Main {
public static void main(String[] args) {
Function<A,String> funA = a -> a.foo();
Function<B,String> funB = b -> b.bar();
Function<C,String> funC = c -> c.bar();
Consumer<B> ignoreArgument = b -> {
System.err.println(" Consumer called");
};
B b = new B();
System.err.println("* voidTest *");
voidTest(b);
System.err.println("------------");
System.err.println("* returnTest *");
returnTest(b);
System.err.println("returnTest without using result did not throw");
System.err.println("------------");
try {
System.err.println("Returned " + returnTest(b).toString());
System.err.println("returnTest: invoking method on result did not throw");
}
catch(Exception ex) {
System.err.println("returnTest: invoking method on result threw");
ex.printStackTrace();
}
System.err.println("------------");
B b2 = null;
try {
b2 = returnTest(b);
System.err.println("returnTest: assigning result to a B variable did not throw");
}
catch(Exception ex) {
System.err.println("returnTest: assigning result to a B variable threw");
ex.printStackTrace();
}
System.err.println("------------");
System.err.println("* functionTest funA *");
functionTest(b, funA);
System.err.println("------------");
System.err.println("* functionTest funB * ");
functionTest(b, funB);
System.err.println("------------");
System.err.println("* consumerTest *");
consumerTest(b, ignoreArgument);
// The following won't work because C is not B or a superclass of B
// Compiler error functionTest(T, Function<? super T,String>) is not applicable for the arguments (B, Function<C,String>)
// functionTest(b, funC);
}
private static <T extends A> void voidTest(T t){
System.err.println(" Before: " + t.toString());
t = (T)new A(); // warning Type safety: Unchecked cast from A to T
System.err.println(" After: " + t.toString());
}
private static <T extends A> T returnTest(T t){
System.err.println(" Before: " + t.toString());
t = (T)new A();
System.err.println(" After: " + t.toString());
return t;
}
private static <T extends A> void functionTest(T t, Function<? super T,String> fun) {
System.err.println(" fun Before: " + fun.apply(t));
t = (T)new A();
try {
System.err.println(" fun After: " + fun.apply(t));
}
catch(Exception ex) {
System.err.println(" fun After: threw");
ex.printStackTrace();
}
}
private static <T extends A> void consumerTest(T t, Consumer<? super T> c) {
System.err.print(" Before: ");
c.accept(t);
t = (T)new A();
try {
System.err.println(" After: ");
c.accept(t);
System.err.println(" c.accept(t) After: worked");
}
catch(Exception ex) {
System.err.println(" c.accept(t) After: threw");
ex.printStackTrace();
}
}
}
OpenJDK 11下的输出为:
* voidTest *
Before: B
After: A
------------
* returnTest *
Before: B
After: A
returnTest without using result did not throw
------------
Before: B
After: A
returnTest: invoking method on result threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.main(Main.java:35)
------------
Before: B
After: A
returnTest: assigning result to a B variable threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.main(Main.java:45)
------------
* functionTest funA *
fun Before: foo
fun After: foo
------------
* functionTest funB *
fun Before: bar
fun After: threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.functionTest(Main.java:83)
at Main.main(Main.java:57)
------------
* consumerTest *
Before: Consumer called
After:
c.accept(t) After: threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.consumerTest(Main.java:97)
at Main.main(Main.java:60)
我不完全确定 resultTest 为什么在结果被完全忽略的情况下没有引发异常,可能在那种情况下语言不需要强制转换,或者编译器将其删除。调用在结果上限中定义的方法仍然导致异常。最后来自 consumerTest 的观察是它不需要调用 bar() 来引发 ClassCastException,它只需要将 t 传递给需要 B 参数的消费者。
语言规范目前不包括任何意义上的最终类型的显式检查。
我检查过:
而且,即使它们不适用于您的情况,我还是冒昧地检查了一下:
- §8.1.2 - Generic Classes and Type Parameters
- §8.8.4 - Generic Constructors
- §9.1.2 - Generic Interfaces and Type Parameters
尽管 Integer
是最终的,但如果不是,您的代码可能会崩溃。
假设编译器不检查最终类型,您所拥有的类似于:
class Super
class Sub extends Super
<T extends Super> void passVal (T t) {
Super super = new Super();
return (T) super;
}
如果我们调用会中断:
passVal(new Sub());