Why List<String> can't simultaneously be argument of a Base-class generic method and Derived-class non-generic method?

huangapple go评论73阅读模式
英文:

Why List<String> can't simultaneously be argument of a Base-class generic method and Derived-class non-generic method?

问题

为什么 List&lt;String&gt; 不能同时作为基类泛型方法和派生类非泛型方法的参数?

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

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

    // 上述代码会产生编译错误:Derived 类中的方法 f(List<String>) 具有相同的擦除类型
    // 与 Base 类中的 f(List<String>) 方法,但并没有对其进行重写
}

我不理解编译器的这个提示消息和编译错误的原因。

返回类型没有问题:

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

class Derived extends Base {
    List<String> f() { return null; }  // 返回类型是完全有效的
}
英文:

Why List&lt;String&gt; can't simultaneously be argument of a Base-class generic method and Derived-class non-generic method?

 class Base {
	 &lt;T&gt; void f(List&lt;String&gt; arg) {}
 }
 
 class Derived extends Base {
	 void f(List&lt;String&gt; arg) {}

	 // above is compile ERROR: method f(List&lt;String&gt;) of type Derived has the same erasure 
	 //as f(List&lt;String&gt;) of type Base but does not override it
 }

I don't understand that compiler message and the reason for compilation error.

There is no problem with return type:

class Base {
	 &lt;T&gt; List&lt;String&gt; f() { return null; }
}

class Derived extends Base {	 
	List&lt;String&gt; f() { return null; }  // perfectly valid as return-type
}

答案1

得分: 4

"Wow, that was a fun search.

This depends on the exact specification of when a method is said to override another method.

The relevant part is in §8.4.8.1 Overriding (by Instance Methods) of the Java Language Specification (I'm using the one for Java 14).

> An instance method mC declared in or inherited by class C, overrides from C another method mA declared in class A, iff all of the following are true:

> [...]

> * The signature of mC is a subsignature (§8.4.2) of the signature of mA.

So let's look at §8.4.2. Method Signature:

> Two methods or constructors, M and N, have the same signature if they have the same name, the same type parameters (if any) (§8.4.4), and, after adapting the formal parameter types of N to the the type parameters of M, the same formal parameter types.

> The signature of a method m1 is a subsignature of the signature of a method m2 if either:

> m2 has the same signature as m1, or

> the signature of m1 is the same as the erasure (§4.6) of the signature of m2.

> Two method signatures m1 and m2 are override-equivalent iff either m1 is a subsignature of m2 or m2 is a subsignature of m1.

For our case there are a couple thing to note:

  1. The return value is not part of the signature, so it basically gets ignored when deciding if one method overrides another. There are additional constraints based on the return type, but these constraints do not affect if two method override each other, but if the overriding does compile. See §8.4.8.3 Requirements in Overriding and Hiding.

  2. The two signatures in question are not the same because they don't have the same number of type parameters.

  3. They are not subsignatures, because this would require one to be the erasure of the other, but both signatures contain generic types (List<String>). Note that this changes if there are no generics in the method signatures, i.e. if you use List as the parameter or if List<String> only appears in the return value.

=> the method in Derived does not override the one in Base.

But their erasure is the same. Erasure basically removes all type parameters. See §4.6 Type Erasure for the obviously way more complex details. But what matters here is that the erasure of the signatures is: void f(List arg)

This violates a section in §8.4.8.3 Requirements in Overriding and Hiding:

> It is a compile-time error if a type declaration T has a member method m1 and there exists a method m2 declared in T or a supertype of T such that all of the following are true:

> * m1 and m2 have the same name.

  • m2 is accessible (§6.6) from T.
  • The signature of m1 is not a subsignature (§8.4.2) of the signature of m2.
  • The signature of m1 or some method m1 overrides (directly or indirectly) has the same erasure as the signature of m2 or some method m2 overrides (directly or indirectly).

Of course this brings us to the question: why is the weird definition of subsignature used?

This is actually explained in §8.4.2. Method Signature:

> The notion of subsignature is designed to express a relationship between two methods whose signatures are not identical, but in which one may override the other. Specifically, it allows a method whose signature does not use generic types to override any generified version of that method. This is important so that library designers may freely generify methods independently of clients that define subclasses or subinterfaces of the library."

英文:

Wow, that was a fun search.

This depends on the exact specification of when a method is said to override another method.

The relevant part is in §8.4.8.1 Overriding (by Instance Methods) of the Java Language Specification (I'm using the one for Java 14).

> An instance method mC declared in or inherited by class C, overrides from C another method mA declared in class A, iff all of the following are true:

> [...]

> * The signature of mC is a subsignature (§8.4.2) of the signature of mA.

So let's look at §8.4.2. Method Signature:

> Two methods or constructors, M and N, have the same signature if they have the same name, the same type parameters (if any) (§8.4.4), and, after adapting the formal parameter types of N to the the type parameters of M, the same formal parameter types.

> The signature of a method m1 is a subsignature of the signature of a method m2 if either:

> m2 has the same signature as m1, or

> the signature of m1 is the same as the erasure (§4.6) of the signature of m2.

> Two method signatures m1 and m2 are override-equivalent iff either m1 is a subsignature of m2 or m2 is a subsignature of m1.

For our case there are a couple thing to note:

  1. The return value is not part of the signature, so it basically gets ignored when deciding if one method overrides another. There are additional constraints based on the return type, but these constraints do not affect if two method override each other, but if the overriding does compile. See §8.4.8.3 Requirements in Overriding and Hiding.

  2. The two signatures in question are not the same because the don't have the same number of type parameters.

  3. They are not subsignatures, because this would require one to be the erasure of the other, but both signatures contain generic types (List&lt;String&gt;). Note that this changes if there are no generics in the method signatures, i.e. if you use List as the parameter or if List&lt;String&gt; only appears in the return value.

=> the method in Derived does not override the one in Base.

But their erasure is the same. Erasure basically removes all type parameters. See §4.6 Type Erasure for the obviously way more complex details. But what matters here is that the erasure of the signatures is: void f(List arg)

This violates a section in §8.4.8.3 Requirements in Overriding and Hiding:

> It is a compile-time error if a type declaration T has a member method m1 and there exists a method m2 declared in T or a supertype of T such that all of the following are true:

> * m1 and m2 have the same name.

  • m2 is accessible (§6.6) from T.
  • The signature of m1 is not a subsignature (§8.4.2) of the signature of m2.
  • The signature of m1 or some method m1 overrides (directly or indirectly)has the same erasure as the signature of m2or some methodm2` overrides (directly or indirectly).

Of course this brings us to the question: why is the weird definition of subsignature used?

This is actually explained in §8.4.2. Method Signature:

> The notion of subsignature is designed to express a relationship between two methods whose signatures are not identical, but in which one may override the other. Specifically, it allows a method whose signature does not use generic types to override any generified version of that method. This is important so that library designers may freely generify methods independently of clients that define subclasses or subinterfaces of the library.

答案2

得分: 0

问题出在您的第一个声明上有一些不完整之处:

&lt;T&gt; void f(List&lt;String&gt; arg) {}de here

通常情况下,如果在返回类型之前定义了泛型,应该在方法的参数中使用该泛型。由于它未被使用;由于不完整,编译器会首先将超类和子类中的方法视为相似,因为它们具有相同的参数,但是当它看到超类中涉及一个子类中没有涉及的泛型时,就无法确定它的确切含义。

对于这个问题,返回类型并不起任何作用,例如,尝试以下代码仍然会显示错误:

class Base {
     &lt;T&gt; List&lt;String&gt; f(List&lt;String&gt; arg) { return null; }
}

class Derived extends Base {     
    List&lt;String&gt; f(List&lt;String&gt; arg) { return null; }
}

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

英文:

Well the problem is with your first declaration being incomplete:

&lt;T&gt; void f(List&lt;String&gt; arg) {}de here

Any generic defined before return type is normally supposed to be used in the method's argument. As it is not used; For the incompleteness, reason compiler sees the method in superclass vs child class as similar first due to the same arguments but as it sees there is a generic involved in superclass which is not involved in child class it fails to determine what it exactly means.

The return types don't play any role for this one for example, try following will still show error:

class Base {
     &lt;T&gt; List&lt;String&gt; f(List&lt;String&gt; arg) { return null; }
}

class Derived extends Base {     
    List&lt;String&gt; f(List&lt;String&gt; arg) { return null; }
}

I would suggest to follow the generics syntax completely in order not to have such odd behaviors.

huangapple
  • 本文由 发表于 2020年4月5日 23:06:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/61044677.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定