繁体   English   中英

java 是如何处理泛型可变参数的?

[英]How does java deal with generic varargs exactly?

我有一个名为Foo<T>的通用类。

public class Foo<T> {
    public void doSomething() {
        Class<T> klass = T.class;  // This won't compile
    }
}

经过一番谷歌搜索,我发现由于java通过执行类型擦除来实现泛型,因此它不知道T的运行时类型并且拒绝编译。 但是,如果我尝试这个......

public class Foo<T> {
    public void doSomething(T... ts) {
        Class<T> klass = (Class<T>) ts.getClass().getComponentType();
        System.out.println(klass);
    }
}

...并运行以下代码...

Foo<String> foo1 = new Foo<>();
foo1.doSomething("foo", "bar");  // class java.lang.String
foo1.doSomething();  // class java.lang.String

Foo foo2 = foo1;
foo2.doSomething("foo", "bar");  // class java.lang.Object
foo2.doSomething();  // class java.lang.Object

Foo<? super String> foo3 = foo1;
foo3.doSomething("foo", "bar");  // class java.lang.Object
foo3.doSomething();  // class java.lang.Object

第一个打印class java.lang.String而其他两个打印class java.lang.Object ,这对我来说没有多大意义 - 对象的行为如何取决于它分配给的变量类型? 另外,如果java做了类型擦除,它又怎么知道第一种情况下类型参数实际上是java.lang.String呢? 而且,由于我无法将Object分配给T ,java 如何将Object[]分配给T[] 似乎它破坏了类型安全!

我想 java 在这里隐式地传递了运行时类,因为它不能从参数中推断出来,因为在第二个方法调用中根本没有参数。 我想知道 java 在幕后做了什么,如果有人能告诉我更多关于通用可变参数的信息,我会很高兴。

我是 StackOverflow 的新手。 如果我违反了一些规则,请告诉我:)

首先,可变参数只是数组参数的语法糖。 所以:

clazz.varArgsMethod("A", "B");

与(并且可以作为)相同:

clazz.varArgsMethod(new SomeElementType[]{"A", "B"});

其中SomeElementType由编译器确定。


在呼叫站点:

Foo<String> foo1 = new Foo<>();
foo1.doSomething("foo", "bar");
foo1.doSomething();

编译器知道doSomething是一个可变参数方法,并且需要一个T[]作为可变参数参数。 因为它知道TString为一个Foo<String> ,它知道T必须是String 所以它将这段代码编译为:

Foo<String> foo1 = new Foo<>();
foo1.doSomething(new String[]{"foo", "bar"});
foo1.doSomething(new String[]{});

String[]数组类型具有String组件类型。 该方法传递了String[]实例,可以反射性地查询它们的组件类型。


其他两种情况基本相同: TFoo的类型确定:

  • 对于原始FooT被擦除。 T的擦除是Object
  • 对于Foo<? super String> Foo<? super String> , T的上界为Object

因此,它创建并传递一个Object[]

...而且,由于我无法将 Object 分配给 T,java 如何将 Object[] 分配给 T[]? 似乎它破坏了类型安全!

是的,你是对的。 泛型参数类型的可变参数实际上确实违反了 Java 自己的类型安全规则。

为什么?

因为数组是协变的(和具体化的)而泛型是不变的(被擦除的,或非具体化的)。 用 Joshua Bloch 的话来说,“数组和泛型不能很好地结合在一起。” Java 语言的开发人员做出了一个非常深思熟虑的决定来实现 varargs 语言功能,尽管它违反了类型安全。 他们得出结论,拥有可变参数(被反射和注释工具广泛使用)的价值证明了它们的风险和局限性。

在 Java 中,您不能编译此代码:

T[] genericArray = new T[SIZE];

这是非法的,因为根据 Java 自己的类型安全规则,实例化非具体类型的数组不是类型安全的。 然而,当客户端调用具有泛型类型的 varargs 参数的方法时,运行时系统将实例化该泛型类型的数组! 它不是类型安全的!

如果不小心使用可变参数系统,程序中可能会出现ClassCastException或其他错误。 本质上,开发人员和运行时系统之间隐含了一个不可执行的合同:

由通用 varargs 参数产生的数组...

  • 只会被读取
  • 永远不会被写入
  • 结果永远不会返回
  • 不会传递给另一个客户端(除非该客户端也承诺只从数组中读取)

换句话说,开发人员承诺将生成的通用数组用作完成计算的可变数量的参数。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM