【问题标题】:What does Java's type parameter wildcard really mean? What's the real difference between Foo and Foo<?>?Java 的类型参数通配符到底是什么意思? Foo 和 Foo<?> 之间的真正区别是什么?
【发布时间】:2012-03-07 01:56:38
【问题描述】:

对于通用接口:

public interface Foo<T> {
    void f(T t); 
} 

两个字段的区别:

public class Bar {
    Foo foo1; 
    Foo<?> foo2; 
}

foo2 是泛型类型,foo 不是。由于? 是通配符(我认为它表示任何类型)并且每种类型都是Object 的子类型,因此我希望Foo&lt;?&gt;Foo&lt;Object&gt; 在语义和语法上是等效的。

但是,请检查以下内容:

public class Puzzler {
    void f() {
        Integer i = null; 
        Foo<?> foo1 = null;
        foo1.foo(i); // ERROR 
        Foo foo2 = null; 
        foo2.foo(i); // OKAY
        Foo<Integer> foo3 = null; 
        foo3.foo(i); // OKAY 
        Foo<Object> foo4 = null; 
        foo4.foo(i); // OKAY
    }

    private interface Foo<T> {
        void foo(T t);
    } 
}

所以Foo&lt;?&gt;Foo&lt;Object&gt; 在语法上是相同的。

这里发生了什么?我很难理解这一点。

【问题讨论】:

  • 假设这些是编译错误?
  • @andrewcooke 该代码是有效的;但是,由于该对象将存储在Foo&lt;?&gt; 变量中,因此它不会知道String 参数。因此,如果Foo 有任何采用T 类型参数的方法,则唯一允许的值将是null,如果它有任何返回T 的方法,则将其视为Object。跨度>
  • 对不起,在我看到你的回复之前删除了我的评论。我现在很困惑。捕获什么时候起作用?
  • 哦,好的,捕获只与自身完全匹配。很明显,现在我在一些测试代码中看到了编译器错误...

标签: java generics jakarta-ee types


【解决方案1】:

Foo&lt;?&gt; 在语义上与Foo&lt;? extends Object&gt; 相同:它是带有特定类型参数的Foo,但关于“某事”的唯一已知是它是Object 的某个子类(即不要说太多,因为所有类都是Object 的子类)。另一方面,Foo&lt;Object&gt; 是一个Foo,其类型参数具体为Object。虽然所有内容都与Object 赋值兼容,但并非所有内容都与? 赋值兼容,其中? 扩展Object

以下是Foo&lt;?&gt; 应生成错误的示例:

public class StringFoo implements Foo<String> {
    void foo(String t) { . . . }
}

现在将您的示例更改为:

Foo<?> foo1 = new StringFoo();

由于iInteger,编译器不可能允许foo1.foo(i) 进行编译。

注意

Foo<Object> foo4 = new StringFoo();

也不会根据matching parameterized types 的规则进行编译,因为ObjectString 可证明是不同的类型。

Foo(根本没有类型参数——原始类型)通常应被视为编程错误。但是,根据Java Language Specification (§4.8),编译器接受此类代码是为了不破坏非通用的遗留代码。

由于type erasure,这对生成字节码没有任何影响。也就是说,它们之间的唯一区别在于编译时。

【讨论】:

  • 编译器不能将原始类型视为错误,请参阅the JLS
  • @Daniel - 在 Eclipse 中,您可以设置编译器将使用原始类型视为错误。您还可以将大多数编译器设置为将所有警告视为错误,并且编译器当然可以警告使用原始类型。
  • 详细说明:如果您使用Foo 作为原始类型(即,没有类型参数),这将(a)生成编译时警告(您可以忽略),(b)导致编译器对所有泛型类型使用Object,以及(c)阻止编译器在其他泛型的上下文中检查类型参数的正确性。例如,Foo&lt;String&gt; f = new Foo&lt;Object&gt;(); 是编译时错误,但Foo&lt;String&gt; f = new Foo(); 将编译(带有警告),然后如果类型错误,则在运行时引发ClassCastException。这是不可取的,所以不要使用原始类型。
【解决方案2】:

考虑这些类型:

  • List&lt;Object&gt;
  • List&lt;CharSequence&gt;
  • List&lt;String&gt;

尽管StringCharSequence 的子类型,Object 的子类型,但这些列表类型没有任何子类型-超类型关系。 (奇怪的是,String[] CharSequence[] 的子类型,Object[] 的子类型,但这是出于历史原因。)

假设我们要编写一个打印列表的方法。如果我们这样做

void print(List<Object> list) {...}

这将无法打印List&lt;String&gt;(没有破解),因为List&lt;String&gt; 不是List&lt;Object&gt;。但是有了通配符,我们可以写

void print(List<?> list) {...}

并将任何列表传递给它。

通配符可以有上限和下限以增加灵活性。假设我们要打印一个仅包含 CharSequences 的列表。如果我们这样做

void print(List<CharSequence> list) {...}

然后我们遇到同样的问题——我们只能传递一个List&lt;CharSequence&gt;,而我们的List&lt;String&gt;不是List&lt;CharSequence&gt;。但如果我们改为这样做

void print(List<? extends CharSequence> list) {...}

然后我们可以传递一个List&lt;String&gt; 和一个List&lt;StringBuilder&gt;,等等。

【讨论】:

    【解决方案3】:

    好吧,由于类型擦除,任何与泛型相关的东西都只是编译时的;我想这就是你所说的语法。
    我认为你的初步评估是正确的,真正的区别在于它使它成为一个泛型类型的变量,而普通的 Foo 不是。

    【讨论】:

      【解决方案4】:

      Object 是 java 中所有类型的超类型。然而,Foo 并不是所有 Foo 的超类型。所有 Foo 的超类型是 Foo。有关详细信息,请参阅http://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html

      【讨论】:

        【解决方案5】:

        Foo&lt;?&gt; 中的通配符表示在当前范围内,您不知道也不关心您拥有什么类型的 'Foo'。

        Foo&lt;?&gt;Foo&lt;? extends Object&gt; 相同(第一个是另一个的简写)。 Foo&lt;Object&gt; 不同。

        一个具体的例子

        您可以将任何类型的List 分配给List&lt;?&gt; 例如

        List<?> list1 = new ArrayList<String>();
        List<?> list2 = new ArrayList<Object>();
        List<?> list3 = new ArrayList<CharSequence>();
        

        如果您有List&lt;?&gt;,您可以致电size(),因为您不需要知道它是什么类型的列表来找出它的大小。您可以调用get(i),因为我们知道列表包含某种Object,因此编译器会将其视为get 返回和Object
        但是你不能调用add(o),因为你不知道(而且编译器也不知道)你正在处理什么样的列表。
        在上面的示例中,您不想允许 list1.add(new Object());,因为它应该是 Strings 的列表

        使用通配符的原因是你可以这样做:

        public static boolean containsNull(List<?> list)
        {
            for(Object o : list )
            {
               if( o == null ) return true;
            }
            return false;
        }
        

        该代码可以用于您想要的任何类型的列表,List&lt;String&gt;List&lt;Object&gt;List&lt;Integer&gt; 等。

        如果签名是public static boolean containsNull(List&lt;Object&gt; list),那么您只能将List&lt;Object&gt; 传递给它,List&lt;String&gt; 将不起作用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-06
          • 1970-01-01
          • 2010-09-06
          • 1970-01-01
          • 2012-09-17
          • 2014-05-06
          相关资源
          最近更新 更多