问题

Java需要将有界类型参数实例化到其上限类,以便有一个转换,例如:

 <T extends Integer> void passVal (T t) {
    Integer number = 5;
    t = (T) number; // Without cast a compile error is issued
}
 

T在声明时已经限制为Integer或其中一个子类(我知道,没有任何),在我看来任何有界类型参数只能实例化为其上限类或其中一个子类,所以为什么需要转换?

此外,我知道,如果我在此方法之外通过调用实例化参数,情况并非如此,所以不要将其作为答案提供;问题特定于在声明类/方法中实例化有界类型参数.

  最佳答案

T并不意味着Integer,它必须对Integer或从中扩展的任何类有效.假设StrangeInteger从Integer扩展并用StrangeInteger替换T:

 void passVal (StrangeInteger t) {
    Integer number = 5;
    t = (StrangeInteger) number;
}
 

它尝试将Integer变量分配给StrangeInteger变量,除非数字首先是StrangeInteger或派生类,否则您无法做到这一点.事实上,您的代码应该(概念上)在运行时抛出异常,除非t是Integer,但由于类型删除,它实际上不会这样做(见Edit 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只能是Integer,但编译器可能不检查上限是否是最终的,毕竟它对于上限是最终的几乎没有意义,因此允许该特殊情况为非常小的实际收益增加复杂性.

编辑2: TL;DR:您的上界和下界令人困惑,但有类型删除的警告.一旦您做任何值得使用泛型而不仅仅使用基本类型的事情,这将会破坏.

英语不是我的第一语言,所以我可能不完全清楚。

我认为您正在努力使用具有上限的通用类型和只使用上限作为类型之间的区别.泛型(来自C和其他语言)的想法是,如果您用边界允许的任何类型T替换T,则代码必须有效,因此您不能调用上限中未定义的任何方法.

T上的上限也意味着您总是可以将T对象分配给A变量.您无法将A对象安全地分配给T变量(除非A = = T),您只能在A在T上的下限而不是上限时执行此操作.另见理解上限和下限?在Java Generics中.

Java使用类型删除来实现泛型,有一些优点,但这会导致一些不总是明显的限制.由于在这种情况下的类型删除,转换本身不会失败,在类型删除步骤中的上限替换类型检查T后,即(T)号被(Integer)号替换.如果您做任何导致子类转换的事情,则仍会发生异常,例如,如果您返回更改t并将结果分配给子类的变量,因为添加了隐式转换.

如果您调用依赖于T子类的方法,这也会失败,例如:

 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为什么在完全忽略结果时没有引起异常,也许在这种情况下语言不需要cast,或者编译器删除它.调用结果上限中定义的方法仍然引起异常.最后消费测试的观察是它不需要调用bar()来导致ClassCastException,它只需要将t传递给期望B参数的消费者.

  相同标签的其他问题

javagenericsbounded-wildcard