在Java中重写私有方法

如此处简洁地描述的那样,在Java中重写私有方法是无效的,因为父类的私有方法是“自动最终的,并且不能从派生类中隐藏”。 我的问题主要是学术问题。

如果不允许父母的私有方法被“重写”(即在一个子类中独立实施,具有相同的签名),那么它是不是违反了封装? 根据封装原则,父类的私有方法不能被子类访问或inheritance。 它是隐藏的。

那么,为什么要限制儿童class级使用相同的名称/签名来执行自己的方法呢? 这是否有一个很好的理论基础,还是仅仅是某种实用的解决scheme呢? 其他语言(C ++或C#)对此有不同的规则吗?

你不能重写一个私有方法,但是你可以在派生类中引入一个没有问题的方法。 这编译罚款:

class Base { private void foo() { } } class Child extends Base { private void foo() { } } 

请注意,如果您尝试将Child.foo()注释应用于Child.foo() ,则会出现编译时错误。 只要你有你的编译器/ IDE设置给你警告或错误,如果你错过了一个@Override注解,一切都应该是好的。 无可否认,我更喜欢将C#作为关键字override方法,但在Java中这样做显然为时已晚。

至于C#处理“覆盖”一个私有方法 – 私有方法首先不能是虚拟的,但是你肯定可以在基类中引入一个与私有方法同名的新的私有方法。

那么,允许私有方法被覆盖将导致封装泄漏或安全风险。 如果我们认为这是可能的 ,那么我们会得到以下情况:

  1. 假设有一个私有方法boolean hasCredentials()那么扩展类可以简单地覆盖它:

     boolean hasCredentials() { return true; } 

    从而打破安全检查。

  2. 原始类别阻止这一点的唯一方法是将其方法声明为final 。 但现在,这是通过封装来泄漏实现信息的,因为派生类现在不能再创build一个方法hasCredentials – 它将与基类中定义的方法冲突。

    这很糟糕:让我们说这个方法在Base是不存在的。 现在,一个实现者可以合法地派生一个Derived类,并给它一个方法hasCredentials ,如预期的那样工作。

    但现在, 版本的原始Base类已经发布。 它的公共接口不会改变(也不会改变它的不variables),所以我们必须期望它不会破坏现有的代码。 只有它,因为现在有一个名字与派生类中的方法冲突。

我认为这个问题源于一个误解:

它是如何/不是违反了封装,不允许父母的私有方法被“重写”(即,在一个子类中独立实施,具有相同的签名)

括号内的文字与之前的文字相反 。 Java 确实允许你“在一个子类中独立地实现一个私有方法,并且具有相同的签名”。 不允许这将违反封装,正如我上面解释的。

但是“不允许父母的私人方法被”重写“是不同的,并且是确保封装的必要条件。

“其他语言(C ++或C#)对此有不同的规定吗?”

那么C ++有不同的规则:静态或dynamic成员函数绑定进程和访问权限执行是正交的。

授予成员函数的private访问权限修改器意味着这个函数只能被其声明类调用,而不能被其他人(甚至不是派生类)调用。 当你将一个private成员函数声明为virtual ,甚至是纯虚拟的( virtual void foo() = 0; )时,你仍然允许基类从专精中受益,同时仍然强制执行访问权限。

当涉及virtual成员函数时,访问权限告诉你你应该做什么:

  • private virtual意味着你被允许专门化行为,但是成员函数的调用是由基类来实现的,当然是以受控的方式
  • protected virtual意味着您在重写时应该/必须调用成员函数的上层types版本

所以,在C ++中,访问权限和虚拟性是相互独立的。 确定函数是静态还是dynamic绑定是parsing函数调用的最后一步。

最后,模板方法devise模式应该优于public virtual成员函数。

参考: 对话:几乎是你的

本文给出了一个private virtual成员函数的实际使用。


ISO / IEC 14882-2003§3.4.1

名称查找可能会将多个声明与一个名称相关联,如果它发现该名称是一个函数名称; 声明被认为是一组重载函数(13.1)。 重载parsing(13.3)在名称查找成功后发生。 访问规则(第11章)只有在名称查找和函数重载parsing(如果适用)成功后才会被考虑。 只有在名称查找之后,函数重载parsing(如果适用)和访问检查成功,才是由expression式处理(第5节)中进一步使用的名称声明引入的属性。

ISO / IEC 14882-2003§5.2.2

成员函数调用中调用的函数通常根据对象expression式的静态types(第10章)进行select,但是如果函数是virtualand并且没有使用aqualified-id指定,那么实际调用的函数将是最终的覆盖(10.3)在对象expression式的dynamictypes中select的函数[注意:dynamictypes是对象expression式的当前值指向或引用的对象的types。

父类的私有方法不能被子类访问或inheritance,符合封装原则。 它是隐藏的。

那么,为什么要限制儿童class级以相同的名称/签名来执行自己的方法呢?

没有这样的限制。 你可以做到这一点没有任何问题,它只是不叫“重写”。

被覆盖的方法受到dynamic分配的影响,也就是说,实际调用的方法是在运行时根据调用的对象的实际types来select的。 用私人方法,这不会发生(不应该,根据你的第一个陈述)。 这就是“私人方法不能被重写”的说法。

我认为你误解了这个post的内容。 这并不是说儿童class“被限制使用相同的名字/签名来执行自己的方法”。

以下是稍作修改的代码:

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

和报价:

你可能会合理地预期输出是“public f()”,

引用的原因是variablespo实际上包含Derived的一个实例。 但是,由于该方法被定义为私有,所以编译器实际上查看variables的types,而不是对象的types。 它将方法调用转换为invokespecial (我认为这是正确的操作码,没有检查JVM规范),而不是invokeinstance

这似乎是一个select和定义的问题。 你不能在java中这样做的原因是因为这个规范是这么说的,但是这个问题更多的是规范说明的原因。

C ++允许这个事实(即使我们使用虚拟关键字来强制dynamic调度),这表明不允许这样做的内在原因。

但是, 取代该方法似乎是完全合法的:

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

似乎编译好(在我的编译器),但D.foo是不相关的B.foo(即它不覆盖它) – 酒吧()总是返回42(通过调用B.foo)和Frob()总是返回43(通过调用D.foo),无论是调用B或D实例。

Java不允许重写该方法的一个原因是,他们不希望像Konrad Rudolph的例子那样改变方法。 请注意,C ++在这里有所不同,因为您需要使用“虚拟”关键字来获取dynamic分派 – 默认情况下,它不能修改依赖于hasCredentials方法的基类中的代码。 上面的例子也可以防止这种情况,因为D.foo不会从B中取代对foo的调用。

当方法是私人的时候,它的孩子是不可见的。 所以没有压倒它的意思。

我很抱歉使用错误重写错误,并与我的描述不一致。 我的描述描述了这个场景。 下面的代码扩展了Jon Skeet的例子来描述我的场景:

 class Base { public void callFoo() { foo(); } private void foo() { } } class Child extends Base { private void foo() { } } 

用法如下所示:

 Child c = new Child(); c.callFoo(); 

我遇到的问题是父函数foo()被调用,尽pipe如代码所示,我正在调用子实例variables的callFoo()。 我以为我在Child()中定义了一个新的私有方法foo(),inheritance的callFoo()方法会调用它,但我认为kdgregory所说的一些可能适用于我的场景 – 可能是由于派生类构造函数正在调用super(),或者不是。

Eclipse中没有编译器警告,代码也编译了。 结果是意外的。

除了之前所说的任何东西之外,还有一个非语义的原因,不允许私有方法被重写…他们是私有的!

如果我写一个class级,而且我指出一种方法是“私人的”,那么外部世界应该完全不可见。 没有人应该能够访问它,覆盖它,或其他任何东西。 我只是应该能够知道,这是我的方法,没有人会去干扰它,或依靠它。 如果有人可以把它弄糟的话,这不能算是私人的。 我相信这真的很简单。

一个类是通过它提供的方法以及它们的行为来定义的。 而不是如何在内部实现(例如通过调用私有方法)。

由于封装与行为而不是实现细节有关,私有方法与封装思想无关。 从某种意义上说,你的问题是没有意义的。 这就像问:“如何将咖啡放入咖啡不违反封装?

假定私有方法被公开的东西所使用。 你可以覆盖。 这样做,你已经改变了行为。