Java 泛型的泛型是如何工作的?

Java 泛型的泛型是如何工作的?,java,generics,wildcard,capture,Java,Generics,Wildcard,Capture,虽然我确实理解泛型的一些常见情况,但下面的例子中我遗漏了一些东西 我有以下课程 1 public class Test<T> { 2 public static void main(String[] args) { 3 Test<? extends Number> t = new Test<BigDecimal>(); 4 List<Test<? extends Number>> l =Collections.sin

虽然我确实理解泛型的一些常见情况,但下面的例子中我遗漏了一些东西

我有以下课程

1 public class Test<T> {
2   public static void main(String[] args) {
3     Test<? extends Number> t = new Test<BigDecimal>();
4     List<Test<? extends Number>> l =Collections.singletonList(t);
5   }
6 }
1公共类测试{
2公共静态void main(字符串[]args){

3测试原因是编译器不知道您的通配符类型是相同的类型

它也不知道您的实例是
null
。虽然
null
是所有类型的成员,但编译器在检查类型时只考虑声明的类型,而不考虑变量的值可能包含的内容

如果代码被执行,它不会导致异常,但这只是因为该值为null。仍然存在潜在的类型不匹配,这就是编译器的工作——不允许类型不匹配。

看看。问题是“编译时”这是Java强制执行这些泛型的唯一机会,因此如果它允许这样做,它将无法判断您是否尝试插入无效的内容。这实际上是一件好事,因为这意味着一旦程序编译,泛型在运行时不会招致任何性能损失

让我们用另一种方式来看看你的例子(让我们用两种类型来扩展数字,但是行为非常不同)。考虑下面的程序:

import java.math.BigDecimal;
import java.util.*;

public class q16449799<T extends Number> {
  public T val;

  public static void main(String ... args) {
    q16449799<BigDecimal> t = new q16449799<>();
    t.val = new BigDecimal(Math.PI);

    List<q16449799<BigDecimal>> l = Collections.singletonList(t);
    for(q16449799<BigDecimal> i : l) {
      System.out.println(i.val);
    }
  }
}
现在假设您提供的代码没有导致编译器错误:

import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicLong;

public class q16449799<T extends Number> {
  public T val;

  public static void main(String ... args) {
    q16449799<BigDecimal> t = new q16449799<>();
    t.val = new BigDecimal(Math.PI);

    List<q16449799<AtomicLong>> l = Collections.singletonList(t);
    for(q16449799<AtomicLong> i : l) {
      System.out.println(i.val);
    }
  }
}
import java.math.BigDecimal;
导入java.util.concurrent.AtomicLong;
公开课q16449799{
公共T值;
公共静态void main(字符串…参数){
q16449799 t=新q16449799();
t、 val=新的BigDecimal(Math.PI);
列表l=集合。单音列表(t);
对于(q16449799 i:l){
系统输出打印项次(i.val);
}
}
}
您希望输出是什么?您不能合理地将BigDecimal强制转换为AtomicLong(您可以从BigDecimal的值构造一个AtomicLong,但是强制转换和构造是不同的事情,泛型作为编译时糖实现以确保强制转换成功).至于@KennyTM的评论,当您在初始示例中查找具体类型时,请尝试编译以下内容:

import java.math.BigDecimal;
import java.util.*;

public class q16449799<T> {
  public T val;

  public static void main(String ... args) {
    q16449799<? extends Number> t = new q16449799<BigDecimal>();

    t.val = new BigDecimal(Math.PI);

    List<q16449799<? extends Number>> l = Collections.<q16449799<? extends Number>>singletonList(t);
    for(q16449799<? extends Number> i : l) {
      System.out.println(i.val);
    }
  }
}
import java.math.BigDecimal;
导入java.util.*;
公开课q16449799{
公共T值;
公共静态void main(字符串…参数){

q16449799也许这可以解释编译器的问题:

List<? extends Number> myNums = new ArrayList<Integer>();

List没有潜在的运行时错误,它只是超出了编译器静态确定该错误的能力。每当您导致类型推断时,它会自动生成一个新的
捕获,正如Kenny在其评论中所指出的,您可以通过以下方法解决此问题:

List<Test<? extends Number>> l =
    Collections.<Test<? extends Number>>singletonList(t);

这很好,因为
T
解析为
capture您可以通过编写
集合来编译。通过使用
您告诉编译器它们不一定相等,如果您希望它们被视为相等,您可以使用
T
或其他占位符类型Kennytm:现在我完全同意了nfused。这很有效,但为什么呢?看看。@Jonathan,我的猜测是,在这种情况下,使用
T
和使用
T extensed Number
可以更好地传递不同类型的扩展数(并且不会让事情爆炸)。看起来你在寻求“做正确的事情”的灵活性泛型(集合只是对象的集合)被引入以进行分类(强制某种类型安全的外观)正如我已经写过的,我确实理解,这是潜在的危险。问题是,如果行是合法的,是否存在运行时错误的示例装箱/拆箱成本如何?此外,“真实”的性能损失是什么在其他语言中使用的泛型?装箱/取消装箱只涉及从原语到对象再向后的转换(这适用于数字,并且有运行时成本)。Java中的泛型是在Java的类型系统之上实现的,并且向后兼容得惊人(通过在类路径中添加jar将它们添加到Java 1.4中)(您仍然需要使用Java1.5来编译它们)。但是我不能代表其他语言。正如我已经写过的:我对代码有一种不好的直觉,但我想不出具体的例子会出什么问题。那么,有什么问题吗?@JasonSperske是对的,对不起,用词不当。我的意思是向上/向下转换。我相信带有类型擦除的泛型通过将输入转换为对象
,然后从
对象
强制转换任何输出(正是为了保持您所说的兼容性).是这样吗?在这种情况下,与C#等语言相比,这种强制转换不存在开销吗?C#等语言实际上是JIT编译泛型类的不同具体实现,并且不在两者之间放置任何强制转换?我知道代码,它在Oracle教程中的某个地方,不幸的是,它不适用于泛型的泛型。我试过了我的例子和你的代码,在这两种情况下,我都得到了
无法转换
的错误是的,你是对的。我的IDE不再对此抱怨了,但当我制作整个项目时,我也得到了错误(使用Java 1.7)。也许他们通过一些Java更新修复了这个问题。但是问题依赖于“?”,它不能用于创建对象(这正是歌手想要做的)。
List<? extends Number> myNums = new ArrayList<Integer>();
public static void main(String[] args) {

    List<? extends Number> list1 = new ArrayList<BigDecimal>();
    List<List<? extends Number>> list2 = copyHelper(list1);


}

private static <T> List<List<T>> copyHelper(List<T> list) {
    return Collections.singletonList(list);

}
List<Test<? extends Number>> l = Collections.<Test<? extends Number>>singletonList(t);
public static void swap(List<? extends Number> l1, List<? extends Number> l2) {
    Number num = l1.get(0);
    l1.add(0, l2.get(0));
    l2.add(0, num);
}
List<Test<? extends Number>> l =
    Collections.<Test<? extends Number>>singletonList(t);
public static void main(String... args) {
    List<? extends String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
    List<? extends String> cycledTwice = cycle(cycle(list));
}

public static <T> List<T> cycle(List<T> list) {
    list.add(list.remove(0));
    return list;
}
public static List<? extends String> cycle(List<? extends String> list) {
    list.add(list.remove(0));
    return list;
}
public static <T extends Number> List<Test<? extends Number>> assign(List<Test<T>> t) {
    return t;
} 
//all this would be valid
List<Test<Double>> doubleTests = null;
List<Test<? extends Number>> numberTests = assign(doubleTests);

Test<Integer> integerTest = null;
numberTests.add(integerTest); //type error, now doubleTests contains a Test<Integer>
List<Test<? extends Number>> l =
    Collections.<Test<? extends Number>>singletonList(t);
Test<capture<? extends Number>> capturedT;
Test<? extends Number> t = capturedT;
public static <T extends Number> Test<? extends Number> assign(Test<T> t) {
    return t;
} 
List<? extends Number> l = new List<Double>();