重写Java中的私有方法

发布于 2024-08-17 05:32:38 字数 384 浏览 5 评论 0原文

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

不允许父类的私有方法被“覆盖”(即,在子类中使用相同的签名独立实现),这怎么可能违反封装性呢?父类的私有方法不能被子类访问或继承,符合封装原则。它是隐藏的。

那么,为什么要限制子类实现具有相同名称/签名的自己的方法呢?这是否有良好的理论基础,或者这只是某种实用的解决方案?其他语言(C++ 或 C#)对此有不同的规则吗?

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.

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)? 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

电影里的梦 2024-08-24 05:32:38

您不能重写私有方法,但可以在派生类中引入私有方法,而不会出现问题。编译正常:

class Base
{
   private void foo()
   {
   }
}

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

请注意,如果您尝试将 @Override 注释应用于 Child.foo(),您将收到编译时错误。只要您将编译器/IDE 设置为在缺少 @Override 注释时向您发出警告或错误,一切都应该没问题。诚然,我更喜欢使用 override 作为关键字的 C# 方法,但在 Java 中这样做显然为时已晚。

至于C#对“重写”私有方法的处理——私有方法首先不能是虚拟的,但你当然可以引入一个与基类中的私有方法同名的新私有方法。

You can't override a 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 missing an @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.

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.

琉璃繁缕 2024-08-24 05:32:38

那么,允许重写私有方法要么会导致封装泄漏,要么会带来安全风险。如果我们假设这是可能,那么我们会得到以下情况:

  1. 假设有一个私有方法boolean hasCredentials(),那么扩展类可以只需像这样覆盖它:

    boolean hasCredentials() { return true; } }
    

    从而破坏安全检查。

  2. 原始类防止这种情况的唯一方法是声明其方法final。但现在,这通过封装泄漏了实现信息,因为派生类现在无法创建方法hasCredentials - 它会与基类中定义的方法发生冲突.

    这很糟糕:假设这个方法最初在 Base 中不存在。现在,实现者可以合法地派生类 Derived 并为其提供一个按预期工作的方法 hasCredentials

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

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

不允许父类的私有方法被“覆盖”(即,在子类中使用相同的签名独立实现),这/不/违反封装性吗?

括号内的文本是相反的 /em> 之前的文本。 Java确实允许您“在子类中使用相同的签名独立实现[私有方法]”。正如我上面所解释的,不允许这样做会违反封装性。

但是“不允许父级的私有方法被“重写””是不同的,并且是确保封装所必需的。

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:

  1. 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.

  2. 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 cannot create a method hasCredentials 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 class Derived and give it a method hasCredentials which works as expected.

    But now, a new version 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.

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 opposite of the text before it. Java does allow 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.

But “to not allow a parent's private method to be "overridden"” is something different, and necessary to ensure encapsulation.

許願樹丅啲祈禱 2024-08-24 05:32:38

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

嗯,C++ 有不同的规则:静态或动态成员函数绑定过程和访问权限强制执行是正交的。

为成员函数赋予private访问权限修饰符意味着该函数只能由其声明类调用,而不能由其他类(甚至派生类)调用。当您将 private 成员函数声明为 virtual 时,甚至是纯虚函数 (virtual void foo() = 0;),您允许基类从专业化中受益,同时仍然强制执行访问权限。

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

  • private virtual意味着您可以专门化该行为,但不能对成员进行调用函数是由基类创建的,当然以受控的方式
  • protected virtual 意味着您在重写它时应该/必须调用成员函数的上层类版本

因此,在 C++ 中,访问权限和虚拟性是彼此独立。确定函数是静态绑定还是动态绑定是解决函数调用的最后一步。

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

参考:对话:虚拟您的

本文给出了私有虚拟的实际使用成员函数。


ISO/IEC 14882-2003 §3.4.1

如果名称查找发现名称是函数名称,则名称查找可能会将多个声明与名称相关联;据说这些声明形成了一组重载函数(13.1)。重载解析 (13.3) 在名称查找成功后发生。仅当名称查找和函数重载解析(如果适用)成功时,才会考虑访问规则(第 11 条)。只有在名称查找、函数重载解析(如果适用)和访问检查成功之后,名称声明引入的属性才会在表达式处理中进一步使用(第 5 条)。

ISO/IEC 14882-2003 §5.2.2

在成员函数调用中调用的函数通常是根据对象表达式的静态类型(第 10 条)来选择的,但是如果该函数是虚拟的并且没有使用 aqualified-id 指定,那么实际调用的函数将是最终的重写器( 10.3) 对象表达式的动态类型中的所选函数[注:动态类型是对象表达式的当前值所指向或引用的对象的类型。

"Do other languages (C++ or C#) have different rules on this?"

Well, C++ has different rules: the static or dynamic member function binding process and the access privileges enforcements are orthogonal.

Giving a member function the private access privilege modifier 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 benefit from specialization while still enforcing the access privileges.

When it comes to virtual member functions, access privileges tells you what you are supposed to do:

  • 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 fashion
  • protected virtual means that you should / must invoke the upper class version of the member function when overriding it

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.

Finally, the Template Method design pattern should be preferred over public virtual member functions.

Reference: Conversations: Virtually Yours

The article gives a practical use of a private virtual member function.


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).

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.

南…巷孤猫 2024-08-24 05:32:38

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

那么,为什么子类应该是
限制实施自己的
具有相同名称/签名的方法?

没有这样的限制。你可以毫无问题地做到这一点,只是不称为“覆盖”。

重写的方法受动态调度的影响,即实际调用的方法是在运行时根据调用的对象的实际类型来选择的。使用私有方法,这种情况不会发生(根据您的第一个声明,也不应该发生)。这就是“私有方法不能被重写”这句话的含义。

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".

人海汹涌 2024-08-24 05:32:38

我认为您误解了该帖子的内容。这并不是说子类“被限制使用相同的名称/签名实现自己的方法”。

这是稍微编辑过的代码:

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( )”,

引用该引号的原因是变量 po 实际上保存了 Derived 的实例。但是,由于该方法被定义为私有,因此编译器实际上会查看变量的类型,而不是对象的类型。它将方法调用转换为invokespecial(我认为这是正确的操作码,尚未检查JVM规范)而不是invokeinstance

I think you're misinterpreting what that post says. It's not saying 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( )”,

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.

最偏执的依靠 2024-08-24 05:32:38

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

事实上,C++ 允许这样做(即使我们使用 virtual 关键字强制动态分派),这表明没有固有的原因不能允许这样做。

然而,替换该方法似乎完全合法:

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 无关(即它不会覆盖它) - bar() 始终返回 42(通过调用 B.foo),而 frob() 始终返回 43(通过调用 D.foo),无论是在 B 还是 D 实例上调用。

Java 不允许重写该方法的原因之一是他们不喜欢允许更改该方法,如 Konrad Rudolph 的示例中所示。请注意,C++ 在这里有所不同,因为您需要使用“virtual”关键字才能获得动态分派 - 默认情况下它没有,因此您无法修改依赖于 hasCredentials 方法的基类中的代码。上面的示例也可以防止这种情况,因为 D.foo 不会替换 B 中对 foo 的调用。

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.

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.

However it seem to be perfectly legal to replace the 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.

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.

女皇必胜 2024-08-24 05:32:38

当该方法是私有的时,它对其子级不可见。所以没有重写它的意义。

When the method is private, it's not visible to its child. So there is no meaning of overriding it.

不交电费瞎发啥光 2024-08-24 05:32:38

对于错误地使用术语“覆盖”并且与我的描述不一致,我深表歉意。我的描述描述了这个场景。以下代码扩展了 Jon Skeet 的示例来描述我的场景:

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

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

用法如下:

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

我遇到的问题是,即使如代码所示,我在子级上调用 callFoo() 方法,也会调用父级 foo() 方法实例变量。我以为我在 Child() 中定义了一个新的私有方法 foo() ,继承的 callFoo() 方法将调用该方法,但我认为 kdgregory 所说的一些内容可能适用于我的场景 - 可能是由于派生类构造函数的方式正在调用 super(),或者可能没有。

Eclipse 中没有编译器警告,并且代码确实可以编译。结果出乎意料。

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:

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.

There was no compiler warning in Eclipse and the code did compile. The result was unexpected.

猫卆 2024-08-24 05:32:38

除了之前所说的之外,不允许重写私有方法还有一个非常语义的原因......它们是私有的!

如果我编写一个类,并指出一个方法是“私有”的,那么外界应该完全看不到它。任何人都不应该能够访问它、覆盖它或进行其他任何操作。我只是应该能够知道这是我独有的方法,没有其他人会破坏它或依赖它。如果有人可以破坏它,它就不能被认为是私人的。我相信事情就是这么简单。

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.

恋竹姑娘 2024-08-24 05:32:38

类是通过它提供的方法及其行为方式来定义的。不是那些内部实现方式(例如通过调用私有方法)。

因为封装与行为有关,而不是实现细节,所以私有方法与思想封装无关。从某种意义上来说,你的问题没有任何意义。这就像问“在咖啡中加入奶油如何不违反封装?”

据推测,私有方法是由公共方法使用的。你可以覆盖它。通过这样做,你改变了行为。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文