在 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 ):
- 找到 class 以搜索适用的方法。这基于您调用该方法的对象的编译时类型。在这种情况下,
a
。 a
的编译时类型是Icecream
,所以只会考虑Icecream
的方法。请注意,它没有在 Faloodeh
中找到 displayName
方法,因为 a
的编译时类型是 Icecream
.
- 根据您传递的参数确定要调用的方法重载。这里只有一个选择。与更改前后一样,
displayName
是唯一与您传递的参数兼容的重载。
- 根据您调用该方法的对象的运行时类型,确定调用该方法的哪个实现。
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...)
的方法一无所知。
当我调用 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
- 找到 class 以搜索适用的方法。这基于您调用该方法的对象的编译时类型。在这种情况下,
a
。a
的编译时类型是Icecream
,所以只会考虑Icecream
的方法。请注意,它没有在Faloodeh
中找到displayName
方法,因为a
的编译时类型是Icecream
. - 根据您传递的参数确定要调用的方法重载。这里只有一个选择。与更改前后一样,
displayName
是唯一与您传递的参数兼容的重载。 - 根据您调用该方法的对象的运行时类型,确定调用该方法的哪个实现。
a
的运行时类型是Faloodeh
。更改前,displayName
未在Faloodeh
中被覆盖,因此它调用了 superclass 实现。更改后,displayName
被覆盖,因此调用Faloodeh
中的实现。
关于您的编辑:
在这种情况下,由于b
的编译时类型是Faloodeh
,因此要搜索的class是Faloodeh
(步骤1)。但是,有 2 种方法与您提供的参数相匹配(第 2 步):
-
在
displayName(String...)
和;displayName(String)
这是继承的。
Faloodeh
中声明的 在这种情况下,编译器总是支持没有变量 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...)
的方法一无所知。