覆盖 Java 中的私有方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2000137/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Overriding private methods in Java
提问by Bill
As succinctly described here, overriding private methods in Java is invalid because a parent class's private methods are "automatically final, and hidden from the derived class". My question is largely academic.
正如这里简洁描述的,在 Java 中覆盖私有方法是无效的,因为父类的私有方法是“自动最终的,并且对派生类隐藏”。我的问题主要是学术性的。
How is it nota violation of encapsulation to not allow a parent's private method to be "overridden" (ie, implemented independently, with the same signature, in a child class)? A parent's private method cannot be accessed or inherited by a child class, in line with principles of encapsulation. It is hidden.
不允许父类的私有方法被“覆盖”(即,在子类中独立实现,具有相同的签名)如何不是违反封装?父类的私有方法不能被子类访问或继承,符合封装原则。它是隐藏的。
So, why should the child class be restricted from implementing its own method with the same name/signature? Is there a good theoretical foundation for this, or is this just a pragmatic solution of some sort? Do other languages (C++ or C#) have different rules on this?
那么,为什么要限制子类实现自己的具有相同名称/签名的方法呢?对此是否有良好的理论基础,或者这只是某种务实的解决方案?其他语言(C++ 或 C#)对此有不同的规则吗?
回答by Jon Skeet
You can't overridea private method, but you can introduce one in a derived class without a problem. This compiles fine:
您不能覆盖私有方法,但可以在派生类中引入一个,而不会出现问题。这编译得很好:
class Base
{
private void foo()
{
}
}
class Child extends Base
{
private void foo()
{
}
}
Note that if you try to apply the @Override
annotation to Child.foo()
you'll get a compile-time error. So long as you have your compiler/IDE set to give you warnings or errors if you're missingan @Override
annotation, all should be well. Admittedly I prefer the C# approach of override
being a keyword, but it was obviously too late to do that in Java.
请注意,如果您尝试将@Override
注释应用于Child.foo()
您将收到编译时错误。只要你有你的编译器/ IDE设置,给你警告或错误,如果你缺少一个@Override
注释,都应该很好。诚然,我更喜欢 C# 方法override
作为关键字,但在 Java 中这样做显然为时已晚。
As for C#'s handling of "overriding" a private method - a private method can't be virtual in the first place, but you can certainly introduce a new private method with the same name as a private method in the base class.
至于 C# 对“覆盖”私有方法的处理 - 私有方法首先不能是虚拟的,但是您当然可以在基类中引入一个与私有方法同名的新私有方法。
回答by Konrad Rudolph
Well, allowing private methods to be overwritten will either cause a leak of encapsulation or a security risk. If we assume that it were possible, then we'd get the following situation:
好吧,允许私有方法被覆盖将导致封装泄漏或安全风险。如果我们假设这是可能的,那么我们会得到以下情况:
Let's say that there's a private method
boolean hasCredentials()
then an extended class could simply override it like this:boolean hasCredentials() { return true; }
thus breaking the security check.
The only way for the original class to prevent this would be to declare its method
final
. But now, this is leaks implementation information through the encapsulation, because a derived class now cannotcreate a methodhasCredentials
any more – it would clash with the one defined in the base class.That's bad: lets say this method doesn't exist at first in
Base
. Now, an implementor can legitimately derive a classDerived
and give it a methodhasCredentials
which works as expected.But now, a newversion of the original
Base
class is released. Its public interface doesn't change (and neither do its invariants) so we must expect that it doesn't break existing code. Only it does, because now there's a name clash with a method in a derived class.
假设有一个私有方法,
boolean hasCredentials()
那么扩展类可以像这样简单地覆盖它:boolean hasCredentials() { return true; }
从而破坏了安全检查。
原始类防止这种情况的唯一方法是声明它的方法
final
。但是现在,这是通过封装泄漏实现信息,因为派生类现在不能再创建方法hasCredentials
——它会与基类中定义的方法发生冲突。这很糟糕:假设此方法最初在
Base
. 现在,实现者可以合法地派生一个类Derived
并为其提供一个hasCredentials
按预期工作的方法。但是现在,发布了原始类的新版本
Base
。它的公共接口不会改变(它的不变量也不会改变),所以我们必须期望它不会破坏现有代码。只有这样,因为现在与派生类中的方法存在名称冲突。
I think the question stems from a misunderstanding:
我认为这个问题源于一个误解:
How is it /not/ a violation of encapsulation to not allow a parent's private method to be "overridden" (ie, implemented independently, with the same signature, in a child class)
不允许“覆盖”父类的私有方法(即,在子类中独立实现,具有相同签名)如何/不/违反封装
The text inside the parentheses is the oppositeof the text before it. Java doesallow you to “independently implement [a private method], with the same signature, in a child class”. Not allowing this would violate encapsulation, as I've explained above.
括号内的文本与其前面的文本相反。Java确实允许您“在子类中独立实现 [一个私有方法],具有相同的签名”。不允许这样做会违反封装,正如我在上面解释的那样。
But “to not allow a parent's private method to be "overridden"” is something different, and necessary to ensureencapsulation.
但是“不允许父级的私有方法被“覆盖”是不同的,并且是确保封装所必需的。
回答by z5h
A class is defined by what methods it makes available and how they behave. Not how those are implemented internally (e.g. via calls to private methods).
一个类由它提供的方法及其行为方式定义。不是这些是如何在内部实现的(例如,通过调用私有方法)。
Because encapsulation has to do with behavior and not implementation details, private methods have nothing to do with the idea encapsulation. In a sense, your question makes no sense. It's like asking "How is putting cream in coffee not a violation of encapsulation?"
因为封装与行为有关而不是实现细节,所以私有方法与思想封装无关。从某种意义上说,你的问题没有意义。这就像问“如何在咖啡中加入奶油而不违反封装?”
Presumably the private method is used by something that is public. You can override that. In doing so, you've changed behavior.
据推测,私有方法被公共的东西使用。你可以覆盖它。通过这样做,你改变了行为。
回答by Michael Borgwardt
A parent's private method cannot be accessed or inherited by a child class, inline with principles of encapsulation. It is hidden.
So, why should the child class be restricted from implementing its own method with the same name/signature?
父类的私有方法不能被子类访问或继承,符合封装原则。它是隐藏的。
那么,为什么要限制子类实现自己的具有相同名称/签名的方法呢?
There is no such restriction. You can do that without any problems, it's just not called "overriding".
没有这样的限制。您可以毫无问题地做到这一点,只是不称为“覆盖”。
Overridden methods are subject to dynamic dispatch, i.e. the method that is actually called is selected at runtime depending on the actual type of the object it's called on. With private method, that does not happen (and should not, as per your first statement). And that's what is meant by the statement "private methods can't be overridden".
重写的方法受制于动态调度,即实际调用的方法是在运行时根据调用它的对象的实际类型选择的。使用私有方法,这不会发生(根据您的第一条语句,也不应该发生)。这就是“私有方法不能被覆盖”这句话的意思。
回答by kdgregory
I think you're misinterpreting what that post says. It's notsaying that the child class is "restricted from implementing its own method with the same name/signature."
我认为你误解了那个帖子的意思。这并不是说子类“被限制实现自己的具有相同名称/签名的方法”。
Here's the code, slightly edited:
这是代码,稍作修改:
public class PrivateOverride {
private static Test monitor = new Test();
private void f() {
System.out.println("private f()");
}
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f();
});
}
}
class Derived extends PrivateOverride {
public void f() {
System.out.println("public f()");
}
}
And the quote:
和报价:
You might reasonably expect the output to be “public f( )”,
你可能合理地期望输出是“public f()”,
The reason for that quote is that the variable po
actually holds an instance of Derived. However, since the method is defined as private, the compiler actually looks at the type of the variable, rather than the type of the object. And it translates the method call into invokespecial(I think that's the right opcode, haven't checked JVM spec) rather than invokeinstance.
引用的原因是该变量po
实际上包含 Derived 的一个实例。但是,由于方法被定义为私有的,编译器实际上查看的是变量的类型,而不是对象的类型。它将方法调用转换为invokespecial(我认为这是正确的操作码,尚未检查 JVM 规范)而不是invokeinstance。
回答by GuruKulki
When the method is private, it's not visible to its child. So there is no meaning of overriding it.
当方法是私有的时,它的子级是不可见的。所以没有覆盖它的意义。
回答by Gregory Pakosz
"Do other languages (C++ or C#) have different rules on this?"
“其他语言(C++ 或 C#)对此有不同的规则吗?”
Well, C++ has different rules: the static or dynamic member function binding process and the access privileges enforcements are orthogonal.
好吧,C++ 有不同的规则:静态或动态成员函数绑定过程和访问权限实施是正交的。
Giving a member function the private
access privilege modi?er means that this function can only be called by its declaring class, not by others (not even the derived classes). When you declare a private
member function as virtual
, even pure virtual (virtual void foo() = 0;
), you allow the base class to bene?t from specialization while still enforcing the access privileges.
给一个成员函数private
访问权限修饰符意味着这个函数只能被它的声明类调用,不能被其他类调用(甚至不能被派生类调用)。当您将private
成员函数声明为virtual
,即使是纯虚 ( virtual void foo() = 0;
) 时,您也允许基类受益于专业化,同时仍然强制执行访问权限。
When it comes to virtual
member functions, access privileges tells you what you are supposed to do:
当涉及到virtual
成员函数时,访问权限会告诉您应该做什么:
private virtual
means that you are allowed to specialize the behavior but the invocation of the member function is made by the base class, surely in a controlled fashionprotected virtual
means that you should / must invoke the upper class version of the member function when overriding it
private virtual
意味着您可以专门化行为,但成员函数的调用是由基类进行的,肯定是以受控方式进行的protected virtual
意味着您应该/必须在覆盖成员函数时调用它的上层版本
So, in C++, access privilege and virtualness are independent of each other. Determining whether the function is to be statically or dynamically bound is the last step in resolving a function call.
因此,在 C++ 中,访问权限和虚拟性是相互独立的。确定函数是静态绑定还是动态绑定是解析函数调用的最后一步。
Finally, the Template Method design pattern should be preferred over public virtual
member functions.
最后,模板方法设计模式应该优于public virtual
成员函数。
Reference: Conversations: Virtually Yours
参考:对话:几乎是你的
The article gives a practical use of a private virtual
member function.
文章给出了一个private virtual
成员函数的实际使用。
ISO/IEC 14882-2003 §3.4.1
ISO/IEC 14882-2003 §3.4.1
Name lookup may associate more than one declaration with a name if it finds the name to be a function name; the declarations are said to form a set of overloaded functions (13.1). Overload resolution (13.3) takes place after name lookup has succeeded. The access rules (clause 11) are considered only once name lookup and function overload resolution (if applicable) have succeeded. Only after name lookup, function overload resolution (if applicable) and access checking have succeeded are the attributes introduced by the name's declaration used further in expression processing (clause 5).
如果名称查找发现名称是函数名称,则名称查找可能会将多个声明与名称相关联;声明被称为形成一组重载函数(13.1)。重载解析 (13.3) 在名称查找成功后发生。仅在名称查找和函数重载解析(如果适用)成功后才考虑访问规则(第 11 条)。只有在名称查找、函数重载解析(如果适用)和访问检查成功之后,名称声明引入的属性才会在表达式处理中进一步使用(第 5 条)。
ISO/IEC 14882-2003 §5.2.2
ISO/IEC 14882-2003 §5.2.2
The function called in a member function call is normally selected according to the static type of the object expression (clause 10), but if that function isvirtualand is not specified using aqualified-idthen the function actually called will be the final overrider (10.3) of the selected function in the dynamic type of the object expression [Note: the dynamic type is the type of the object pointed or referred to by the current value of the object expression.
在成员函数调用中调用的函数通常是根据对象表达式的静态类型选择的(第 10 条),但是如果该函数是虚拟的并且没有使用 aqualified-id 指定,那么实际调用的函数将是最终的覆盖(10.3)对象表达式的动态类型中选择的函数【注:动态类型是对象表达式的当前值所指向或引用的对象的类型。
回答by Bill
I apologize for using the term override incorrectly and inconsistent with my description. My description describes the scenario. The following code extends Jon Skeet's example to portray my scenario:
对于错误地使用术语覆盖并与我的描述不一致,我深表歉意。我的描述描述了场景。以下代码扩展了 Jon Skeet 的示例以描绘我的场景:
class Base {
public void callFoo() {
foo();
}
private void foo() {
}
}
class Child extends Base {
private void foo() {
}
}
Usage is like the following:
用法如下:
Child c = new Child();
c.callFoo();
The issue I experienced is that the parent foo() method was being called even though, as the code shows, I was calling callFoo() on the child instance variable. I thought I was defining a new private method foo() in Child() which the inherited callFoo() method would call, but I think some of what kdgregory has said may apply to my scenario - possibly due to the way the derived class constructor is calling super(), or perhaps not.
我遇到的问题是父 foo() 方法被调用,尽管如代码所示,我在子实例变量上调用 callFoo() 。我以为我在 Child() 中定义了一个新的私有方法 foo(),继承的 callFoo() 方法将调用该方法,但我认为 kdgregory 所说的某些内容可能适用于我的场景 - 可能是由于派生类构造函数的方式正在调用 super(),或者不调用。
There was no compiler warning in Eclipse and the code did compile. The result was unexpected.
Eclipse 中没有编译器警告,代码确实编译了。结果出乎意料。
回答by Steve
Beyond anything said before, there's a very semantic reason for not allowing private methods to be overridden...THEY'RE PRIVATE!!!
除了之前所说的之外,不允许覆盖私有方法还有一个非常语义化的原因......它们是私有的!!!
If I write a class, and I indicate that a method is 'private', it should be completely unseeable by the outside world. Nobody should be able access it, override it, or anything else. I simply ought to be able to know that it is MY method exclusively and that nobody else is going to muck with it or depend on it. It could not be considered private if someone could muck with it. I believe that it's that simple really.
如果我写了一个类,并且我指出一个方法是“私有的”,那么它应该是完全无法被外界看到的。没有人应该能够访问它、覆盖它或其他任何东西。我只是应该能够知道这是我唯一的方法,没有其他人会使用它或依赖它。如果有人可以把它弄脏,它就不能被认为是私人的。我相信真的就是这么简单。
回答by skyking
It seems to be a matter of choice and definition. The reason you can't do this in java is because the specification says so, but the question were more why the specification says so.
这似乎是一个选择和定义的问题。你不能在 java 中这样做的原因是因为规范是这样说的,但问题更多的是规范为什么这么说。
The fact that C++ allows this (even if we use virtual keyword to force dynamic dispatch) shows that there is no inherent reason why you couldn't allow this.
C++ 允许这样做(即使我们使用 virtual 关键字强制动态调度)的事实表明,没有内在的原因不允许这样做。
However it seem to be perfectly legal to replacethe method:
但是,替换该方法似乎是完全合法的:
class B {
private int foo()
{
return 42;
}
public int bar()
{
return foo();
}
}
class D extends B {
private int foo()
{
return 43;
}
public int frob()
{
return foo();
}
}
Seems to compile OK (on my compiler), but the D.foo is not related to B.foo (ie it doesn't override it) - bar() always return 42 (by calling B.foo) and frob() always returns 43 (by calling D.foo) no matter whether called on a B or D instance.
似乎编译正常(在我的编译器上),但 D.foo 与 B.foo 无关(即它不会覆盖它) - bar() 总是返回 42(通过调用 B.foo)并且 frob() 总是无论是在 B 还是 D 实例上调用,都返回 43(通过调用 D.foo)。
One reason that Java does not allow override the method would be that they didn't like to allow the method to be changed as in Konrad Rudolph's example. Note that C++ differs here as you need to use the "virtual" keyword in order to get dynamic dispatch - by default it hasn't so you can't modify code in base class that relies on the hasCredentials method. The above example also protects against this as the D.foo does not replace calls to foo from B.
Java 不允许覆盖该方法的原因之一是他们不喜欢像 Konrad Rudolph 的示例那样允许更改该方法。请注意,C++ 在这里有所不同,因为您需要使用“virtual”关键字来获得动态调度 - 默认情况下它没有,因此您无法修改依赖于 hasCredentials 方法的基类中的代码。上面的例子也防止了这种情况,因为 D.foo 不会替换从 B 对 foo 的调用。