7

因此,我试图编写一种方法来回答我之前的一个问题:如何确定任意 java.lang.Method 是否覆盖另一个?为此,我正在阅读 JLS,在一个案例中似乎缺少一些部分。

假设您有以下课程:

public class A<T> {
    public void foo(T param){};
}

public class B extends A<String> {
    public void foo(String param){};
}

在这种情况下,很明显B.foooverrides A.foo,但是我不明白这种情况如何符合规范。

关于方法覆盖,JLS §8.4.8.1指出:

在类 C 中声明的实例方法 m1 覆盖在类 A 中声明的另一个实例方法 m2,前提是满足以下所有条件:

  1. C 是 A 的子类。

  2. m1 的签名是 m2 签名的子签名(第 8.4.2 节)。

  3. 任何一个:

    • m2 是公共的、受保护的或在与 C 相同的包中声明为具有默认访问权限,或
    • m1 覆盖方法 m3(m3 不同于 m1,m3 不同于 m2),这样 m3 会覆盖 m2。

显然第 1 点和第 3 点在我们的案例中得到满足。让我们在 JLS 中更深入地了解子签名的含义。JLS §8.4.2说:

如果两个方法具有相同的名称和参数类型,则它们具有相同的签名。

如果满足以下所有条件,则两个方法或构造函数声明 M 和 N 具有相同的参数类型:

  1. 它们具有相同数量的形式参数(可能为零)

  2. 它们具有相同数量的类型参数(可能为零)

  3. 令 A1, ..., An 为 M 的类型参数,令 B1, ..., Bn 为 N 的类型参数。将 N 的类型中出现的 Bi 重命名为 Ai 后,相应类型变量的边界为相同,并且M和N的形参类型相同。

在我们的例子中,第 1 点显然是正确的(两者都有 1 个参数)。

第 2 点有点混乱(这就是我不确定规范的确切含义的地方):这两种方法都没有声明自己的类型参数,而是A.foo使用Twhich 是对类进行参数化的类型变量。

所以我的第一个问题是:在这种情况下,在类中声明的类型变量是否计数?

好的,现在让我们假设这T不算数,因此第 2 点是错误的(在这种情况下,我什至不知道如何应用第 3 点)。我们的两个方法没有相同的签名,但这并不妨碍B.foo成为A.foo.

JLS §8.4.2中更进一步,它说:

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

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

  2. m1 的签名与 m2 签名的擦除(第 4.6 节)相同。

我们已经确定第 1 点是错误的。

根据JLS §4.6的方法的擦除签名是a signature consisting of the same name as s and the erasures of all the formal parameter types given in s. 所以 A.foo 的擦除是foo(Object)并且 B.foo 的擦除是foo(String)。这两个是不同的签名,因此第 2 点也是错误的,并且 B.foo 不是 A.foo 的子签名,因此 B.foo 不会覆盖 A.foo。

除了它...

我错过了什么?是否有一些我没有看到的难题,或者在这种情况下规范真的不完整?

4

3 回答 3

5

类中声明的类型变量是否计数?

有问题的元素是方法,而不是包含类型声明。所以,不,他们不算数。

好的,现在让我们假设 T 不算数,因此第 2 点是错误的

为什么?它们都有 0 类型参数,所以这是真的。


一步步:

  • 它们具有相同数量的形式参数(可能为零)

两种方法都有 1 个形式参数。查看。

  • 它们具有相同数量的类型参数(可能为零)

两种方法都没有声明类型参数。查看。

令 A1, ..., An 为 M 的类型参数,令 B1, ..., Bn 为 N 的类型参数。将 N 的类型中出现的 Bi 重命名为 Ai 后,相应类型变量的边界为相同,并且M和N的形参类型相同。

由于这两种方法都没有声明类型参数,因此无需重命名,并且形式参数类型相同 -- T[T=String] = String。查看。

B.foo(String)与 具有相同的签名A<String>.foo(String)。⇒B.foo(String)A<String>.foo(String)

由于B它是一个子类A并且A<String>.foo(String)是公共的,我们可以得出 B.foo(String)overrides的结论A<String>.foo(String)

于 2012-08-28T07:49:15.847 回答
1

你必须这样看:

该类B扩展了A<String>具有方法的类型foo(String param)

A<String>是对泛型类型的调用A<T>。它本身就是一种类型。JLS 4.5暗示了这一点,它定义了参数化类型是什么。A<String>是参数化类型,与任何其他引用类型处于同等地位。在讨论直接超类的概念时,参数化这一事实并不重要。

于 2012-08-28T07:46:31.153 回答
0

摘自Oracle 的泛型教程:

类型擦除后,方法签名不匹配。Node 方法变为 setData(Object),MyNode 方法变为 setData(Integer)。因此,MyNode setData 方法不会覆盖 Node setData 方法。为了解决这个问题并在类型擦除后保留泛型类型的多态性,Java 编译器会生成一个桥接方法来确保子类型化按预期工作。

所以基本上,编译器的魔力可以让你的编译代码按预期工作,即使是 JLS 也是如此。我不确定在哪里指定了这种行为。

于 2012-08-28T07:49:21.477 回答