【问题标题】:Generics Method Argument Behavior泛型方法参数行为
【发布时间】:2019-01-06 14:11:34
【问题描述】:

考虑下面的通用方法程序:

class GenericTest {
    int i;
}
public class GenericMethod {

    public <T> List<T> addList(T t, T t1) {
        List<T> list=new ArrayList<T>();
        list.add(t);
        list.add(t1);
        return list;
    }
    public <T> List<T> addList(T t, T t1, List<T> l) {
        List<T> list=new ArrayList<T>();
        list.add(t);
        list.add(t1);
        list.addAll(l);
        return list;
    }
    public static void main(String[] args) {
        GenericMethod gm=new GenericMethod();
        List l=gm.addList(new Integer(42), "java");           //Line 21
        System.out.println(l);
        List<Object> list = new ArrayList<Object>();
        list.add(false);
        List l2 = gm.addList("java", new Integer(42), list);     //Line 25
        System.out.println(l2);
        GenericTest gt = new GenericTest();
        List l3 = gm.addList("java", gt);          //Line 28
        System.out.println(l3);
        List l4 = gm.addList(gm, gt);              //Line 30
        System.out.println(l4);
        List lst = new ArrayList();
        lst.add(new Object());
        List l5 = gm.addList("java", new Integer(42), lst);    //Line 34
        System.out.println(l5);
        System.out.println(l5.get(0));
    }
}

通过参考链接Java Generics - Confusing behavior 我推断,

在第 21 行中,由于该方法同时使用 String 和 Integer 调用,因此它使用 Object 调用 T 的方法。第 25 行也是如此,因为 List 也具有 Object 类型,它使用 Object 调用 T 的方法。调用 List 使用其他类型会产生错误。我的推断正确吗?

我想不到的是关于

1) 传递String和class时的第28行,

2) 传递两个不同类时的第 30 行,

3) 第 34 行,当 List 声明为无类型时作为参数传递。

任何人都可以分享他们的知识以澄清我的理解。任何帮助,将不胜感激!谢谢!

【问题讨论】:

  • 如果你停止使用原始类型并弄清楚你的列表是什么数据类型,你可以清楚地理解所有这些......另一方面,你所有的方法调用都很容易解释使用根据哪个参数映射到哪个addList 方法调用的参数数量。

标签: java generics methods collections


【解决方案1】:

使用其他类型调用 List 会产生错误。我的推断正确吗?

是的;例如,您不能在那里传递List&lt;String&gt;。通常,您的第二个 addList 方法的意图是错误的。正确的签名是这样的:

public <T> List<T> addList(T t1, T t2, List<? extends T> list)

注意? extends 语法。如果您所做的只是从列表中读取,则没有理由不添加它。 T 的某些特定子类型的列表保证包含 T。完全不同的原因是:如果您想添加到这个列表怎么办?假设 T 是Number,你传入了Integer 的列表。双倍是一个数字。使用List&lt;T&gt; list,您可以调用list.add(5.0),它会编译并运行,将双精度数放入您的整数列表中。对于List&lt;? extends T&gt;,添加调用始终是编译器错误(除非您尝试添加null,这会起作用)。对于阅读,你会得到一个T,无论你是在List&lt;? extends T&gt;List&lt;T&gt; 上调用get,都没有关系。

1) 传递String和class时的第28行,

不,您正在传递一个 String 类型的实例和一个 GenericTest 类型的实例。 String 和 GenericTest 一样是一个类。字符串并不特殊。这与您在第 21 行中的调用完全相同:您传递了 2 个表达式;一个对象类型 X 和另一个对象类型 Y。因此,T 被推断为两个传递的实例之间最具体的共享类型(可能是所谓的 lub 类型:一组类型)。在第 21 行的情况下,IntegerString 最具体的共享类型只是 Object(从技术上讲,它实际上是 lub 类型 Object &amp; Serializable,但这在这里大多无关紧要)。在第 28 行,它是 StringGenericTest 之间最具体的共享类型,仍然是 Object,没有区别。

2) 传递两个不同类时的第 30 行,

见上文;完全相同的情况。 GenericTestGenericMethod 之间最具体的共享类型是 Object

3) 第 34 行,当 List 声明为无类型时作为参数传递。

第 34 行中的表达式 lst 是“原始”类型 ArrayList。当您使用原始类型时,会发生两件事:[1] 编译器会警告您正在使用原始类型,并且 [2] 几乎所有泛型测试和检查对于任何涉及任何原始类型的调用都被禁用,因此,编译器只会让这种情况发生。

请记住,泛型是编译器想象的虚构。他们的目的是让编译器告诉您您的代码已损坏。就是这样。

【讨论】:

    【解决方案2】:

    一般来说,尽量避免在没有类型参数的情况下声明List。即使你必须做List&lt;Object&gt;,也更清楚。 (对你和编译器)

    第 25 行 不会使用 StringIntegerList&lt;String&gt; 进行编译,因为传递 List&lt;String&gt; 会特别导致编译器假定 &lt;T&gt; 是 @987654329 @。

    List l2 = gm.addList("java", new Integer(42), new ArrayList&lt;Object&gt;()); // compiles

    List l2 = gm.addList("java", new Integer(42), new ArrayList&lt;String&gt;()); // does not compile

    可以addList 的声明更改为:

    public &lt;T&gt; List&lt;T&gt; addList(T t, T t1, List&lt;? extends T&gt; l) {

    然后两个例子都会编译。 List 参数上的类型参数不再严格固定为T,而是固定为TT 的超类型。编译器认为类型参数是Object,因为Object 是代码编译的String(和其他参数)的超类型。

    就编译器而言,

    第 28 行、第 30 行和第 34 行正在返回 List&lt;Object&gt;。唯一可以在参数之间共享的公共类型TObject,因此编译器推断TObject

    就运行时的 JVM 而言,由于type erasure 而没有类型参数,它们都只是List。类型参数在运行时不存在。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多