Java 为什么要列出<;字符串>;can';是否同时是基类泛型方法和派生类非泛型方法的参数?

Java 为什么要列出<;字符串>;can';是否同时是基类泛型方法和派生类非泛型方法的参数?,java,generics,overriding,Java,Generics,Overriding,为什么List不能同时作为基类泛型方法和派生类非泛型方法的参数 class Base { <T> void f(List<String> arg) {} } class Derived extends Base { void f(List<String> arg) {} // above is compile ERROR: method f(List<String>) of type Derived has

为什么
List
不能同时作为基类泛型方法和派生类非泛型方法的参数

 class Base {
     <T> void f(List<String> arg) {}
 }

 class Derived extends Base {
     void f(List<String> arg) {}

     // above is compile ERROR: method f(List<String>) of type Derived has the same erasure 
     //as f(List<String>) of type Base but does not override it
 }
类基{
void f(列表arg){}
}
类派生的扩展基{
void f(列表arg){}
//上面是编译错误:派生类型的方法f(List)具有相同的擦除
//作为Base类型的f(列表),但不覆盖它
}
我不明白编译器的消息和编译错误的原因

返回类型没有问题:

class Base {
     <T> List<String> f() { return null; }
}

class Derived extends Base {     
    List<String> f() { return null; }  // perfectly valid as return-type
}
类基{
列表f(){return null;}
}
类派生扩展基{
List f(){return null;}//作为返回类型完全有效
}

问题在于您的第一次声明不完整:

<T> void f(List<String> arg) {}de here
void f(List arg){}de在此
在返回类型之前定义的任何泛型通常都应该在方法的参数中使用。因为它没有被使用;对于不完整性,reason编译器首先将超类与子类中的方法视为类似,因为参数相同,但当它看到超类中涉及一个泛型,而子类中不涉及该泛型时,它无法确定它的确切含义

返回类型对此类型不起任何作用。例如,尝试以下操作仍将显示错误:

class Base {
     <T> List<String> f(List<String> arg) { return null; }
}

class Derived extends Base {     
    List<String> f(List<String> arg) { return null; }
}
类基{
列表f(列表参数){returnnull;}
}
类派生扩展基{
列表f(列表参数){returnnull;}
}

我建议完全遵循泛型语法,以免出现这种奇怪的行为。

哇,这是一个有趣的搜索

这取决于一个方法何时重写另一个方法的确切说明

相关部分在中(我使用的是Java14)

在类
C
中声明或由类
C
继承的一个实例方法
mC
,重写了类
A
中声明的另一个方法
mA
,如果以下所有条件均为真:

[……]

  • mC的签名是mA签名的子签名(§8.4.2)
让我们看看:

两种方法或构造函数,
M
N
,如果它们具有相同的名称、相同的类型参数(如有)(§8.4.4),则具有相同的签名,并且在将
N
的形式参数类型调整为
M
的类型参数后,具有相同的形式参数类型

方法
m1
的签名是方法
m2
签名的子签名,如果:

m2
具有与
m1
相同的签名,或

m1
的签名与
m2
签名的擦除(§4.6)相同

m1
m2
的子符号或
m2
m1
的子符号时,两个方法签名
m1
m2
是覆盖等价的

对于我们的案例,有两件事需要注意:

  • 返回值不是签名的一部分,因此在决定一个方法是否重写另一个方法时,它基本上被忽略。还有基于返回类型的其他约束,但如果两个方法相互重写,但重写确实编译,则这些约束不会影响。看

  • 所讨论的两个签名不相同,因为没有相同数量的类型参数

  • 它们不是子签名,因为这要求一个签名是另一个签名的擦除,但两个签名都包含泛型类型(
    List
    )。请注意,如果方法签名中没有泛型,即如果使用
    List
    作为参数,或者如果
    List
    仅出现在返回值中,则此更改

  • =>派生的中的方法不会覆盖基本的中的方法

    但它们的擦除是一样的。擦除基本上删除所有类型参数。有关更复杂的细节,请参见。但这里重要的是签名的删除是:
    void f(List arg)

    这违反了以下章节的规定:

    如果类型声明T有一个成员方法m1,并且存在一个在T或T的超类型中声明的方法m2,则这是一个编译时错误,并且以下所有情况均为真:

    • m1和m2具有相同的名称
    • m2可从T进入(§6.6)
    • m1的签名不是m2签名的子签名(§8.4.2)
    • m1
      或某些方法
      m1覆盖(直接或间接)的签名与
      m2
      或某些方法
      m2`覆盖(直接或间接)的签名具有相同的擦除率
    当然,这给我们带来了一个问题:为什么要使用
    子签名
    的奇怪定义

    这实际上在以下章节中进行了解释:

    子签名的概念旨在表示两个方法之间的关系,这两个方法的签名不完全相同,但其中一个方法可以覆盖另一个方法。具体来说,它允许签名不使用泛型类型的方法重写该方法的任何泛型版本。这一点很重要,这样库设计人员就可以独立于定义库的子类或子接口的客户端自由地生成方法


    你明白是什么吗?是的。在运行时编译(字节码)后,到处都只有列表(在两个方法/类中)。尖括号内的类型边界仅用于编译期间的安全检查。但是我没有看到这里的问题——为什么相同的返回类型可以编译而作为参数——没有。我不知道这是为什么,但我可以想象它与
    这个
    有关。请注意,在这两种情况下,您都定义了一个类型参数
    t
    (通过
    ),然后简单地将它放进去