【问题标题】:Why is the base type not returned when no generic type specified? [duplicate]为什么没有指定泛型类型时不返回基类型? [复制]
【发布时间】:2026-02-04 17:30:01
【问题描述】:

我有这样的代码:

public class A<T extends String> {

    T field;
    List<T> fields;

    public T getField() {
        return field;
    }

    public List<T> getFields() {
        return fields;
    }

    public static void test(){
        A a = new A();
        String s = a.getField();
        List<String> ss = a.getFields();
    }
}

为什么创建没有泛型类型的A getFiled 返回StringgetFileds 返回List&lt;Object&gt;

我必须将A 定义为A&lt;String&gt; a = new A&lt;&gt;() 才能正常工作。

谢谢,

【问题讨论】:

  • 通过不指定任何泛型类型,您声明您的类型使用站点不使用任何泛型信息。这意味着在 test() 方法中,getFields() 的有效签名是 List getFields()(没有泛型),而不是您想象的 List&lt;String&gt; getFields()
  • @M.Prokhorov 那么为什么field 会返回为String
  • 因为T getField()的签名中没有泛型。一旦编译了该类型,就会有一个String getField() 和一个由编译器生成的桥接方法,以支持将getField() 与任意类型一起使用。这些都没有使用泛型。
  • getField 会返回一个Object,为什么你一直说它返回一个String?我现在看到了我的错误。

标签: java generics java-8


【解决方案1】:

声明T field中类型参数T的擦除是String,因为T已经绑定:

<T extends String>

并且String 中的任何subtype 也是String1

但是,这不适用于通用 List&lt;T&gt;。这源于泛型不是协变的:

List<String> str = new ArrayList<String>();
List<Object> obj = str; // Error: incompatible types (even though String extends Object)

在声明List&lt;T&gt; fields 中删除List&lt;T&gt;List,特别是因为List&lt;T&gt; 可以包含String 或其subtypes1。鉴于A 是原始类型,编译器无法猜测getFields() 方法返回的列表中元素的类型。

不管怎样,这行代码:

List<T> fields;

被编译 (Erasure of Generic Types) 成:

List fields;

结果,当你写:

A a = new A();  instead of  A<String> a = new A<>();

您失去了类型安全性并收到“未经检查的赋值”警告 - 在这种情况下,编译器无法保证 fields list 正是 List&lt;String&gt;

List<String> list = a.getFields(); // Unchecked assignment: 'List' to 'List<String>' ...

P.S.不要使用原始类型!


1 - 如您所知,String 类是最终的,不能扩展。如果您愿意,请将其替换为任何可以扩展的类型。

【讨论】:

  • +1 ... 换句话说,给定约束,getField 返回的任何内容都可以分配给String,而getFields 返回的任何内容不一定都可以分配给@ 987654350@(如果我们忽略 Stringfinal)。
  • @Konrad 感谢您的评论!这实际上是我想在我的回答中展示的。
  • @KonradRudolph 是的,但值得注意的是,List&lt;? extends X&gt; 仍然保证所有包含的对象都可以分配给X,但不允许插入X 类型的新对象,如它们可能与列表的实际类型不兼容。如果您可以排除插入,则可以进行分配,即如果您有A&lt;?&gt; a;,则可以使用List&lt;String&gt; list = Collections.unmodifiableList( a.getFields());,因为包装器会阻止您插入不兼容的元素。
【解决方案2】:

当您使用原始类型时,您仍然有自己的界限。所以T extends String 你知道返回的任何东西都会扩展字符串。当您使用原始类型时,它 like 具有? extends String

A<?> a = new A<>();
String s = a.getField(); 
List<? extends String> ss = a.getFields();
String s2 = ss.get(0);

NPE当然会有错误,但思路是有的。

原始类型的一个区别是,get Fields 中的所有类型信息都会丢失,因为返回的是原始类型列表。

List ss = a.getFields();

这意味着,连String的边界都丢失了。

另外,String 是一个 final 类,所以这里的约束并没有真正增加任何值,因为我们知道 T 始终是一个 String。您也可以使用:

class A {
    String field;
    List<String> fields;

    public String getField() {
        return field;
    }

    public List<String> getFields() {
        return fields;
    }

    public static void test(){
        A a = new A();
        String s = a.getField();
        List<String> ss = a.getFields();
    }
}

【讨论】:

    【解决方案3】:

    这不是答案,但只有在这里我可以放代码。所以我尝试了这个示例(但我将其命名为TestGenerics 并添加main 而不是test())然后我对其进行了反编译:

    javap -c TestGenerics
    Compiled from "TestGenerics.java"
    public class TestGenerics<T extends java.lang.String> {
      T field;
    
      java.util.List<T> fields;
    
      public TestGenerics();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public T getField();
        Code:
           0: aload_0
           1: getfield      #2                  // Field field:Ljava/lang/String;
           4: areturn
    
      public java.util.List<T> getFields();
        Code:
           0: aload_0
           1: getfield      #3                  // Field fields:Ljava/util/List;
           4: areturn
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #4                  // class TestGenerics
           3: dup
           4: invokespecial #5                  // Method "<init>":()V
           7: astore_1
           8: aload_1
           9: invokevirtual #6                  // Method getField:()Ljava/lang/String;
          12: astore_2
          13: aload_1
          14: invokevirtual #7                  // Method getFields:()Ljava/util/List;
          17: astore_3
          18: return
    }
    

    正如我们在代码中看到的 fieldgetField 仍然具有通用的 T 类型,而不是 compiled into String。那么为什么getField() 可以正常工作呢?

    【讨论】:

    • 你在界限之内,例如。如果我有class B extends Runnable,那么我可以使用Runnable b = new B();。在这种情况下,您有T extends String,因此您可以将T 分配给字符串。 getFields 返回一个原始类型,所以只有 List 和 T 扩展 String 的信息丢失了。