在 java 中向下转型 + 调用带有可变参数的方法

downcasting in java + calling method with variable arguments

当我调用 a.displayName("Test") 时,它调用了 class Icecream 的方法。 displayName(String...s) 方法接受变量参数。 输出-

test Icecream
test Faloodeh 
test Faloodeh:  Faloodeh
test Faloodeh:  Faloodeh

但是当我把方法改成displayName(String s)(我在代码里把那段注释掉了),然后它调用了class Faloodeh的方法。 新输出-

test Faloodeh 
test Faloodeh 
test Faloodeh:  Faloodeh
test Faloodeh:  Faloodeh

我想知道为什么会这样。

class Icecream{
    public void displayName(String...s){
        System.out.println(s[0]+" "+"Icecream");
    }
    /*public void displayName(String s){
        System.out.println(s+" "+"Icecream");
    }
    */
    public void describe(String s) {
        System.out.println(s+" "+"Icecream: Ice cream");
    }
}
class Faloodeh extends Icecream {
    public void displayName (String s){
        System.out.println(s+" "+"Faloodeh ");
    }

    public void describe (String s) {
        System.out.println(s+" "+"Faloodeh:  Faloodeh");
    }
}
 class Test {
    public static void main(String arg[]) {
       Icecream a=new Faloodeh ();
       Faloodeh b=( Faloodeh)a;
        a.displayName("test");
        b.displayName("test");
        a.describe("test");
        b.describe("test");
    }
}

**编辑- ** 感谢您的回答。请帮我解决另一个疑问。 我将代码更改为 -

class Icecream{
    public void displayName(String s){
        System.out.println(s+" "+"Icecream");
    }
    /*public void displayName(String s){
        System.out.println(s+" "+"Icecream");
    }
    */
    public void describe(String s) {
        System.out.println(s+" "+"Icecream: Ice cream");
    }
}
class Faloodeh extends Icecream {
    public void displayName (String...s){
        System.out.println(s+" "+"Faloodeh ");
    }

    public void describe (String s) {
        System.out.println(s+" "+"Faloodeh:  Faloodeh");
    }
}
 class Test {
    public static void main(String arg[]) {
       Icecream a=new Faloodeh ();
       Faloodeh b=( Faloodeh)a;
        a.displayName("test");
        b.displayName("test");
        a.describe("test");
        b.describe("test");
    }
}

现在这给出了以下输出-

test Icecream
test Icecream
test Faloodeh:  Faloodeh
test Faloodeh:  Faloodeh

正如大家所解释的,这里的 b 是 class Faloodeh 的一个对象。 class Faloodeh 的 displayName(String...s) 不会被覆盖。 仍在输出中,它显示 test Icecream 为什么会这样?

这里的重点是把displayName(String... s)改成displayName(String s)会导致Faloodeh中的displayName(String s)方法被覆盖其 superclass.

中的方法

Icecream.displayName(String... s)Faloodeh.displayName(String s) 具有不同的签名,因此它们不会相互覆盖。但是将前者更改为接受一个 String 只会导致它们具有相同的签名,从而导致发生覆盖。

在Java中,方法调用大致分为三个步骤(更多信息:JLS §15.12, I also explained in more detail ):

  1. 找到 class 以搜索适用的方法。这基于您调用该方法的对象的编译时类型。在这种情况下,aa的编译时类型是Icecream,所以只会考虑Icecream的方法。请注意,它没有在 Faloodeh 中找到 displayName 方法,因为 a 的编译时类型是 Icecream.
  2. 根据您传递的参数确定要调用的方法重载。这里只有一个选择。与更改前后一样,displayName 是唯一与您传递的参数兼容的重载。
  3. 根据您调用该方法的对象的运行时类型,确定调用该方法的哪个实现。 a 的运行时类型是 Faloodeh。更改前,displayName 未在 Faloodeh 中被覆盖,因此它调用了 superclass 实现。更改后,displayName 被覆盖,因此调用 Faloodeh 中的实现。

关于您的编辑:

在这种情况下,由于b的编译时类型是Faloodeh,因此要搜索的class是Faloodeh(步骤1)。但是,有 2 种方法与您提供的参数相匹配(第 2 步):

    Faloodeh 中声明的
  • displayName(String...) 和;
  • displayName(String)这是继承的。

在这种情况下,编译器总是支持没有变量 arity - displayName(String) 的重载。这在 JLS §15.12.2 中有明确规定。特别地,步骤2被进一步拆分成三个更多的子步骤。第一个子步骤尝试在不允许可变元数方法的情况下找到方法,如果任何子步骤找到任何方法,则跳过其余子步骤。

您的测试似乎表明您对多态性很感兴趣。因此,您可能知道,在 Java 中,我们可以说您的对象具有两种 2 类型:

  • 静态类型:基于您的变量声明:Icecream a = ...。对象 a 的编译(即静态)类型为 Icecream.
  • 动态class:基于此变量的影响:... a = new Faloodeh()

编写代码时,假设您只使用静态类型。这意味着编译器知道 classes/fields/methods 你可以使用,并让你使用它们。这就是为什么您可以编写如下代码的原因:

Icecream a = new Icecream();
a.displayName("test");

而且不会写:

Icecream a = new Icecream();
a.unknownMethod("test");

编译器知道您的 class Icecream 有一个名为 displayName 的方法,它接受一个可变参数。它还知道有一个 Faloodeh class。编译器知道这个 class 可以有自己的方法和字段,也可以访问其父级的方法和字段。

Faloodeh b = new Faloodeh();
b.displayName("test");

所以基本上,当您在 class 中声明和实现一个方法时,它的子级可以通过重新实现该方法来覆盖行为。这就是您使用方法 void describe(String s).

所做的

但是方法 displayName 很棘手,因为您以相同的方式调用它,但实际上,它们并不相同,因为它们都采用了 args。让我们尝试成为一个编译器。我编译了你的两个 classes,这里是我创建的:

Icecream => displayName | varargs
Icecream => describe | String
Fadooleh => displayName | String
Faloodeh => describe | String

现在,当 运行 代码时,让我们看看您将使用 main 中的行真正调用哪些方法:

Icecream a = new Faloodeh();
Faloodeh b = (Faloodeh)a;
a.displayName("test"); => Icecram => displayName | varargs
b.displayName("test"); => Fadooleh => displayName | String
a.describe("test"); => Faloodeh => describe | String
b.describe("test"); => Faloodeh => describe | String

为什么第一行调用方法 displayName(String...) 而不是 displayName(String) ?因为,静态地,编译器看到您正在使用 Icecream 类型来调用 displayName 方法,而动态地,只有一个带有可变参数的方法 displayName,这个方法从未被 Faloodeh 覆盖。因此,您直接调用 Icecream displayName 方法。

如果您在 Icecream 中取消对方法 displayName(String s) 的注释,那么 Faloodeh 将接管,因为动态地,Faloodeh 在 displayName(String) 上有自己的实现,这就是它被调用的原因。

希望对您有所帮助。有关多态性的更多信息:https://www.tutorialspoint.com/java/java_polymorphism.htm

** 编辑 ** 这几乎是同一个原因。你的两个 classes 都有静态方法 displayName(String),另一个有 displayName(String...).

当使用 b.displayName("test") 时,它将首先匹配 displayName(String),仅在您的 Icecream 对象中实现。因此,行为。

这是不可能的,例如:

Icecream a = new Faloodeh()
a.displayName("test", "test");

因为 Icecream 对名为 displayName(String...) 的方法一无所知。