【问题标题】:Are type arguments passed along with an argument to a method?类型参数是否与参数一起传递给方法?
【发布时间】:2021-06-02 17:08:47
【问题描述】:

根据我自己的实验,我得出的结论是,对象或对象引用的任何类型参数在作为参数传递给方法时都会被剥离。如果方法的参数使用类型参数进行参数化,就会弹出类型参数的问题:

<T> void method (ArrayList<T> list) {
    list.add( (T) new Integer(4));
    sysout (list.get(0));
}

那么,如果我们将两个参数化的 ArrayList 中的每一个都传递给该方法的调用:

method(new ArrayList<Integer>());
method(new ArrayList<String>());

我们将看到两者都没有产生错误并且都打印 4。我假设编译器将 T 的擦除保留为 Object。这个实验不是证明类型参数没有传递给方法吗?

【问题讨论】:

  • T 是无限的,所以你真正拥有的是ArrayList&lt;Object&gt;。这就是你的代码编译的原因。
  • Java 编译器只是在编译时检查泛型。然后他们被删除。尽管在 JVM 中运行的字节码不知道您使用的是什么泛型类型。我猜这可能是因为兼容性,因为在Java 5之前只有List(没有泛型,因为当时没有泛型),而当Java 5发布时它改为List&lt;T&gt;,但是你今天仍然可以使用List(没有泛型)事件并且只收到一些编译器警告。

标签: java generics parameter-passing pass-by-value


【解决方案1】:

ArrayList&lt;T&gt; 的情况实际上与普通的T 的情况相同。在这两种情况下,T 在编译时用于静态类型检查。但是,在运行时,T 会被删除。

看你贴的代码,编译后的方法是这样的:

void method (ArrayList<Object> list) {
    list.add( (Object) new Integer(4));
    sysout (list.get(0));
}

并且调用是有效的:

method(new ArrayList<Object>());
method(new ArrayList<Object>());

所以从运行时的角度来看,您将Object 存储在ArrayList&lt;Object&gt; 中,这很好。

现在,如果没记错的话,您应该会收到有关表达式 (T) new Integer(4) 的未经检查的强制转换的警告。这是一个需要注意的好警告!如果目标类型与实际类型不兼容,强制转换通常会引发异常。但是,在这种情况下,T 在运行时不存在,因此无法动态检查 - 强制转换是无操作的。但是,如果我们修改示例以返回强制转换的值,您可以在运行时开始看到问题:

<T> T method (ArrayList<T> list) {
    T value = (T) new Integer(4);
    list.add(value);
    return value;
}

然后

String result = method(new ArrayList<String>());

即使转换成功(它只是一个无操作),当返回值分配给 result 时也会抛出异常,因为该值是 Integer 而不是 String

【讨论】:

  • 那么,在我的示例中,我是否正确地说我传递给方法的参数化对象的类型参数被剥离并替换为Object?您说“调用实际上是:new ArrayList&lt;Object&gt;
  • 基本上,是的。更准确地说,对象本身没有参数化 - 它们在运行时创建之前不存在,而 T 仅在编译时存在。您可以说T 在生成字节码之前已从表达式 中删除。
  • “对象本身没有参数化——它们在运行时创建之前不存在”你是什么意思?你是说感觉参数化类型是非具体化对象也不能被参数化吗?考虑这些 sn-ps:ArrayList&lt;String&gt; list = new ArrayList();list = new ArrayList&lt;String&gt;();list = new ArrayList&lt;Integer&gt;(); 最后一个 sn-p 导致错误。这不表明一个对象可以被参数化(在编译时)?
  • 由于对象在编译时不存在(仅在运行时),不,它不会显示。不要将 objects(运行时)与 expressions(编译时)混淆。 new expression 被编译为 instructions,在运行时创建 object表达式(和其他句法元素)可以参数化,但对象不能。
【解决方案2】:

这只是一个简单的情况,编译器未启用以强制您在特定范围内使用T。正如您所注意到的,这有点像您的 method() 方法不关心 T 作为一种类型。

代码正在编译,但出现警告。您对T 的未经检查的强制转换并非总是没有后果:

ArrayList<String> strings = new ArrayList<>();
method(strings);
System.out.println(strings.get(0));

还有:

Exception in thread "main" java.lang.ClassCastException: 
    java.lang.Integer cannot be cast to java.lang.String

此异常由System.out.println(strings.get(3)); 引发,因为对println 的此调用与采用String 的重载相关联,并且强制转换未通过。

这意味着尽管method() 不检查转换为T(在编译时或运行时),但调用者会检查。在我的main() 方法中,T 被推断为String,并且运行时能够执行相关的类型检查。

我假设编译器将 T 的擦除保留为 Object

粗略地说,是的。但是在T 是有界的其他情况下,编译器将能够强制执行更多类型安全。以下甚至不会编译:

static <T extends String> void method(ArrayList<T> list) {
    list.add((T) new Integer(4));
    System.out.println(list.get(0));
}

那么,“这个实验是否不能证明类型参数没有传递给方法?”不,这只是众多场景中的一种。

【讨论】:

  • 如果答案是否定的,您能否提供一个示例,将参数化对象传递给我的示例方法来显示这一点?在您的第一个示例中,您在将其传递给方法后使用 sysout,这是产生错误的原因,因为在 sysout 中您使用的参数化类型具有参数strings 具有类型参数(也是因为打印流就像你说的)。在第二个示例中,您使用有界类型参数。如果答案是否定的,那么您一定是在将对象传递给方法时保留类型参数。请显示示例以便我理解
  • 我还可以想到一些例子,我可以用有界类型参数做同样的事情。 NumberIntegerDouble 的父级,因此我可以将与 Integer 不兼容的值分配给 Double
  • 我理解“传递给方法”的方式是“具体类型参数在方法中'看到'并强制执行”。如果我误解了,请澄清。该类型未“通过”。编译器只是在进行类型推断后强制执行它。
  • 是的,我就是这个意思。但是,至少在我的示例中,编译器似乎正在强制执行 T 的类型擦除,而不是我传递给该方法的两个参数化对象的类型参数。是否可以让编译器强制传递给方法的对象或对象引用的类型参数,或者是否可以将类型参数剥离并替换为编译器推断的内容?
  • 另一个答案说,在我的示例中,由于T的擦除,我传递给方法的两个对象的类型参数变为Object
猜你喜欢
  • 2010-11-15
  • 2011-01-16
  • 2015-12-26
  • 1970-01-01
  • 2011-06-03
  • 1970-01-01
  • 2018-12-16
  • 1970-01-01
  • 2016-08-15
相关资源
最近更新 更多