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

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

downcasting in java + calling method with variable arguments

问题

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

输出-

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

但是当我将方法更改为 displayName(String s)(我在代码中已将该部分注释掉),它会调用 Faloodeh 类的方法。

新的输出-

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

我想知道为什么会发生这种情况。

编辑:

谢谢您的答案。请帮我解答另一个疑问。

我将代码更改为:

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 是 Faloodeh 类的对象。displayName(String...s) 方法并没有在 Faloodeh 类中被重写。

但是在输出中,它显示了 test Icecream,为什么会这样呢?

英文:

When I call a.displayName("Test") ,it calls the method of class Icecream. displayName(String...s) method takes in variable arguments.
Output-

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

But when I change the method to displayName(String s)(I have commented out that section in the code), then it calls the method of class Faloodeh.
New output-

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

I wanted to know why does that happen.

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");
    }
}

**Edit- **
Thanks for the answers. Please help me with another doubt.
I changed the code to -

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");
    }
}

Now this gives the following output-

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

As you all explained, here b is an object of class Faloodeh. And displayName(String...s) of class Faloodeh doesn't gets override.
Still in the output, it displays test Icecream
Why so?

答案1

得分: 5

关键点在于将 displayName(String... s) 更改为 displayName(String s) 会导致 Faloodeh 中的 displayName(String s) 方法覆盖其超类中的方法。

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

在Java中,方法调用大致分为三个步骤(更多信息请参见:JLS §15.12,我在这里也有更详细的解释链接):

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

关于您的编辑:

在这种情况下,由于 b 的编译时类型是 Faloodeh,所以要搜索的类是 Faloodeh(步骤1)。然而,有两个方法与您给出的参数匹配(步骤2):

  • 声明在 Faloodeh 中的 displayName(String...)
  • 继承的 displayName(String)

在这种情况下,编译器始终偏向于不使用可变参数的重载方法 - displayName(String)。这在 JLS §15.12.2 中明确规定。特别地,步骤2又进一步分为三个子步骤。第一个子步骤尝试找到一个不允许可变参数方法的方法,如果任何子步骤找到了方法,剩下的子步骤就会被跳过。

英文:

The key point here is that changing displayName(String... s) to displayName(String s) causes the displayName(String s) method in Faloodeh to override the method in its superclass.

Icecream.displayName(String... s) and Faloodeh.displayName(String s) have different signatures, so they do not override each other. But changing the former to accept one String only causes them to have the same signature, which causes overriding to occur.

In Java, method calls are resolved in roughly three steps (for more info: JLS §15.12, I also explained in more detail here):

  1. Find the class to search for applicable methods. This is based on the compile time type of the object on which you are calling the method. In this case, a. a's compile time type is Icecream, so only Icecream's methods will be considered. Notice that it doesn't find the displayName method in Faloodeh because the compile time type of a is Icecream.
  2. Determine which overload of the method to call based on the arguments you passed. There is only one choice here. As both before and after the change, displayName is the only overload that is compatible with the arguments you passed.
  3. Determine which implementation of the method to call based on the runtime type of the object on which you called the method. a's runtime type is Faloodeh. Before the change, displayName is not overridden in Faloodeh, so it calls the superclass implementation. After the change, displayName becomes overridden, so the implementation in Faloodeh is called.

Regarding your edit:

In this case, since the compile time type of b is Faloodeh, the class to search is Faloodeh (Step 1). However, there are 2 methods that matches the arguments you gave (Step 2):

  • displayName(String...) which is declared in Faloodeh, and;
  • displayName(String) which is inherited.

In such a situation, the compiler always favours the overload without variable arity - displayName(String). This is specified clearly in JLS §15.12.2. In particular, Step 2 is further split into three more sub-steps. The first sub-step tries to find a method without allowing variable arity methods, and if any sub-step finds any method, the remaining sub-steps are skipped.

答案2

得分: 1

你的测试似乎表明你在使用多态性方面很有趣。所以,正如你可能知道的,在Java中,我们可以说你的对象具有两种类型:

  • 静态类型:基于变量的声明:Icecream a = ...。对象 a 具有编译(静态)类型 Icecream
  • 动态类:基于将该变量赋值为:... a = new Faloodeh()

编写代码时,假设你只使用静态类型。这意味着编译器知道你可以使用哪些类/字段/方法,并允许你使用它们。这就是为什么你可以编写以下代码的原因:

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

而不能编写:

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

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

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

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

但是方法 displayName 很棘手,因为你以相同的方式调用它,但实际上它们并不相同,这是因为它们所接受的参数不同。让我们尝试成为一个编译器。我编译了你的两个类,这是我创建的内容:

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

现在,在运行代码时,让我们看看你的主函数中的代码将调用哪些方法:

Icecream a = new Faloodeh();
Faloodeh b = (Faloodeh)a;
a.displayName("test"); => Icecream => 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

编辑
这基本上是相同的原因。你的两个类都静态地有一个方法 displayName(String),另一个方法有 displayName(String...)

当使用 b.displayName("test") 时,它首先会匹配 displayName(String),只有 Icecream 对象中实现了该方法。因此,这种行为。

这是不可能的情况,例如:

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

因为 Icecream 不知道任何名为 displayName(String...) 的方法。

英文:

Your tests seem to indicate you're having fun with Polymorphism. So, as you might know, in Java, we can say your object possesses two 2 types :

  • A static type: based on the declaration of your variable: Icecream a = .... The Object a has a compile (i.e static) type of Icecream.
  • A dynamic class: based on the affectation of this variable: ... a = new Faloodeh().

When writing code, let's say that you only use the static type. Which means that the compiler is aware of classes/fields/methods you can use, and let you use them. This is the reason why you can write code like :

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

And can't write:

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

The compiler knows that your class Icecream has a method named displayName that takes a var-arg. It also knows that there is a Faloodeh class. The compiler knows that this class can have its own methods and fields, and have also access to the methods and fields of its parent.

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

So basically, when you declare and implement a method in a class, its children can override the behaviour by re-implementing the method. Which is what you did with the method void describe(String s).

But the method displayName is tricky because you call it the same way, but in reality, they are not the same, because of the args they both take. Let's try to be a compiler. I compiled both of your classes, and here what I have created:

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

Now, when running the code, let's see what methods you will be really called with the lines in your 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

Why is the first line calling the method displayName(String...) and not the displayName(String) ? Because, statically, the compiler saw that you were using the Icecream type to call the displayName method, and dynamically, there is only one method displayName with a varargs, this method has never been override by Faloodeh. Hence, you call directly the Icecream displayName method.

If you uncomment the method displayName(String s) in Icecream, then you have Faloodeh taking over, because, dynamically, the Faloodeh have its own implementation on the displayName(String), which is why it gets called.

Hope it helps. More information on polymorphism: https://www.tutorialspoint.com/java/java_polymorphism.htm

** Edit **
That's pretty much the same reason. Both of your classes have statically a method displayName(String), and another one has the displayName(String...).

When using b.displayName("test"), it will first match the displayName(String), only implemented in your Icecream object. Hence, the behavior.

This will not be possible for instance:

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

As Icecream doesn't know anything about a method named displayName(String...).

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

发表评论

匿名网友

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

确定