public class PrivateOverride {
private void f() {
System.out.println("private f()");
}
}
public class Derived extends PrivateOverride {
public void f() { //this method is never run.
System.out.println("public f()");
}
}
public static void main(String[] args) {
// instantiate Derived and assign it to
// object po of type PrivateOverride.
PrivateOverride po = new Derived();
// invoke method f of object po. It
// chooses to run the private method of PrivateOveride
// instead of Derived
po.f();
}
}
因此,此代码的输出为private f()
。现在,问题出现在我的脑海中:作为 Derived 类的对象的 po 如何调用 PrivateOverride 的私有方法,这是它的基础类?
答案 0 :(得分:45)
因为您在PrivateOverride
类中定义了main方法。如果将main方法放在Derived类中,则无法编译,因为.f()
在那里不可见。
po.f()调用不是多态,因为PrivateOverride
类中的f()
为PrivateOverride
,因此private
位于f()
{1}}类未被覆盖。
答案 1 :(得分:4)
我真的没有看到问题。该方法在类中被称为“”,这是非常期待的。 这个方法根本不会被覆盖,而是被另一个方法遮蔽。
答案 2 :(得分:3)
根据接收方的静态类型调度Java中的方法,在本例中为PrivateOverride
。不要因为po
变量通过检查代码只能在该行保存Derived
实例这一事实而感到困惑:只有在搜索可用方法时,声明才会起作用。
顺便说一句,对f()
的调用甚至没有转换为最终字节码中的虚拟调用,因为当编译器在类PrivateOverride
中查找可能适用的方法时,它只找到Object
方法和f()
定义,这只是可见的,因为main()方法是在PrivateOverride
本身中定义的(参见JLS 15.12)
答案 3 :(得分:2)
当调用方法时,JVM必须确定要执行的代码段:有时这是在运行时完成的(例如,当覆盖方法时);有时这是在编译时完成的(例如,在重载方法时)。一旦JVM解析了它正在执行的代码,你所引用的实际实例实际上并不比任何其他参数更重要。
给出的示例代码设置了一个可能看起来像方法覆盖但不是的方案,因此该方法最终会在编译时受到限制。未违反private
可见性修饰符,因为调用未触及任何Derived
代码。
查看字节码(通过javac
编译Java代码)是有益的 -
假设我们稍微将原始代码修改为:
public class PrivateOverride {
private void f() {
System.out.println("private f()");
}
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f();
Derived d = new Derived();
d.f();
}
}
class Derived extends PrivateOverride {
public void f() {
System.out.println("public f()");
}
}
主要方法编译为(为简洁起见编辑):
public static main([Ljava/lang/String;)V
NEW Derived
DUP
INVOKESPECIAL Derived.<init>()V
ASTORE 1
ALOAD 1
INVOKESPECIAL PrivateOverride.f()V
NEW Derived
DUP
INVOKESPECIAL Derived.<init>()V
ASTORE 2
ALOAD 2
INVOKEVIRTUAL Derived.f()V
RETURN
请注意,在每种情况下,都会在编译时类型上调用该方法。另请注意,f()的第二次调用使用INVOKEVIRTUAL指令。这就是告诉JVM检查运行时类型并根据它决定调用的内容。
答案 4 :(得分:2)
我刚刚浏览了上述类的编译版本的字节代码,并获得了invokespecial Opcode。这个操作码足以说明实际输出显而易见的原因。 Invokespecial用于三种情况,其中必须根据引用的类型调用实例方法,而不是基于对象的类。这三种情况是:
1)调用实例的initialization()方法
2)调用私有方法
3)使用super关键字
调用方法上面的示例位于我们调用私有方法的第二个场景中。因此,根据引用的类型调用该方法,即PrivateOverride而不是类的类型,即Derived
所以现在问题出现了为什么要特别注意?我们有其他Opcode,比如invokevirtual,它基于classtype而不是引用类型被调用。因此,让我们讨论为什么invokespecial Opcode用于私有方法。但我们应该知道invokevirtual和invokespecial之间的区别。 Invokespecial与invokevirtual的不同之处主要在于invokespecial根据引用的类型而不是对象的类选择方法。换句话说,它执行静态绑定而不是动态绑定。在使用invokespecial的三种情况中的每种情况下,动态绑定都不会产生所需的结果。
答案 5 :(得分:1)
它的行为方式是因为这就是JVM在这些情况下的行为定义。
困难的部分是了解发生了什么以及为什么。
您从私有方法中调用私有方法。所以特洛伊木马在城堡内,他可以摆弄私人变量。将特洛伊木马带出城堡,私人方法不再可见。
这个例子可能会搞清楚,请考虑这个程序:
public class Bicycle {
private void getCost() {
System.out.println("200");
}
public static void main(String[] args) {
Bicycle ACME_bike = new ACME_bike();
ACME_bike.getCost();
Bicycle mybike = new Bicycle();
mybike.getCost();
ACME_bike acme_bike = new ACME_bike();
acme_bike.getCost();
//ACME_bike foobar = new Bicycle(); //Syntax error: Type mismatch:
//cannot convert from
//Bicycle to ACME_bike
}
}
class ACME_bike extends Bicycle {
public void getCost(){
System.out.println("700");
}
}
此程序打印:
200
200
700
如果您将Bicycle内的getCost访问修饰符更改为public
,protected
或包私有(无修饰符),则打印出来:
700
200
700
答案 6 :(得分:0)
Java中有四个可见性级别,包级别(不使用任何可见性暗示),public(每个人都可以调用它),private(只有I,以及将我的函数看作全局的内部类,可以调用它)和protected(我和任何子类都可以调用它)。
你可以让你的第二个类成为一个内部类,然后你不要覆盖,但只是调用该函数就好像它是全局的(就内部类而言),但是你无法在任何地方创建实例你想要的是因为它是一个可以由所有者独占使用的类(所以如果你想在别处使用它,那么所有者需要成为一个工厂并将其作为基类返回)或者你可以使方法受到保护,然后扩展类可以调用该方法。