为什么我们不能在子类中分配较弱的权限

我有一个类,它有一个方法,默认情况下访问说明符是public。 现在,我想在子类中扩展此类,并且我想覆盖此方法以使访问说明符为“private”。 编译此代码时,我收到编译错误:

“试图分配较弱的访问权限”。

有人可以向我解释在子类中分配较弱权限有什么问题吗?

以下是导致编译错误的代码:

class Superclass { void foo() { System.out.println("Superclass.foo"); } } class Subclass extends Superclass { private void foo() { System.out.println("Subclass.foo"); } } 

简短的回答是不允许这样做,因为它会破坏类型的可替代性; 另见Liskov替代原则(LSP) 。

关键是Java(和其他编程语言)中的多态性依赖于您能够将子类的实例视为超类的实例。 但是如果该方法在子类中受到限制,您会发现编译器无法确定访问规则是否允许调用方法…

例如,假设您的示例代码是合法的:

 // Assume this code is in some other class ... SuperClass s1 = new SuperClass(); s1.foo(); // OK! SuperClass s2 = new Subclass(); s2.foo(); // What happens now? SuperClass s3 = OtherClass.someMethod(); s3.foo(); // What happens now? 

如果你决定是否允许在声明的s2类型上使用s2.foo() ,那么你允许从Subclass的抽象边界之外调用private方法。

如果您根据s2引用的对象的实际类型做出决策,则无法静态执行访问检查。 s3案例使这一点更加清晰。 编译器绝对无法知道someMethod返回的对象的实际类型是什么。

可能导致运行时exception的访问检查将成为Java应用程序中的主要错误来源。 这里讨论的语言限制避免了这个令人讨厌的问题。

您无法限制访问权限,因为您已在超类中允许更多访问权限。 例如

 SuperClass sc = new SubClass(); sc.foo(); // is package local, not private. 

sc的访问权限由引用sc的类型决定,而不是它引用的内容,因为编译器无法在所有情况下知道对象在运行时的类型。 为了这是一个安全的假设,子类必须遵守父类给出的合同,否则它不能成为有效的子类。 这与父母说实现方法但子类说它不是(或不可访问)没有什么不同

您可以通过说您只能通过父级访问子类方法来解决这个问题,而不是直接访问。 这个问题是你不知道父母何时可以添加方法,当你将方法设为私有时,你会这样做,因为你希望它是私有的,而不是另一种方式。

BTW您仍然可以通过reflection访问私有方法,这会产生副作用,导致JVM出现各种问题。 例如,它必须保持私有方法,即使它可能确定无法正常调用它。

简而言之,您希望代码表示它所说的内容,而不是具有分裂的个性。 它既可以是本地包,也可以是私有的,而不是介于两者之间但不是真的。 这不是另一种方式的问题。 即如果子类是公共的。 它只是意味着子类可以在比父级更多的地方使用,就像它可以实现更多的方法一样。

如果允许这样做,就会有一个后门,通过它可以调用不应该访问的方法。

让我们说这是允许的

 class Super { public void method() { System.out.println("Super"); } } class Sub extends Super { // This is not allowed, but suppose it was allowed protected void method() { System.out.println("Sub"); } } // In another class, in another package: Super obj = new Sub(); obj.method(); 

obj.method是可能的,因为method()在类Super中是public 。 但它不应该被允许,因为obj实际上是指Sub的一个实例,并且在该类中,该方法受到保护!

要限制对不能从外部访问的类Sub中的方法的调用,将放置此限制。

限制超类方法的访问修饰符是一个无效的覆盖,因为它打破了超类合约并使替换原则无效,即子类对象IS-A超类对象也是如此。

 public void doSomething(SuperClass sc) { sc.publicMethodInSuperClass(); } doSomething(new SubClass()); // would break 

如果允许这样做,上面的客户端代码将会中断,因为您的SubClass没有公开该方法。

参考:
利斯科夫替代原则

除了使用这种结构的明显问题(正如Peter Lawrey在他的回答中所指出的那样),请阅读它背后的理论: LSP ,这意味着你必须能够用它的子类替换主类型。

我认为简短的回答是编译器编写者已经将规则设置为以这种方式工作。 LSP与手头的问题无关。

我可以想到有这个限制的唯一原因是当子类派生自接口时,作为客户端程序员,您希望能够从对派生类的引用调用接口的所有可访问方法。

假设您可以编写OP已显示的代码。 如果您有对派生类的引用,则应该能够调用派生类的任何公共成员(尽管在这种情况下没有)。 但是,将引用作为参数传递给方法,该方法引用基类,并且该方法将期望调用任何公共或包方法,即foo 。 这是其他贡献者正在寻找的LSP!

C ++示例:

 class Superclass{ public: virtual void foo(){ cout << "Superclass.foo" << endl; } }; class Subclass: public Superclass{ virtual void foo(){ cout << "Subclass.foo" << endl; } }; int main(){ Superclass s1; s1.foo() // Prints Superclass.foo Subclass s2; // s2.foo(); // Error, would not compile Superclass& s1a=s2; // Reference to Superclass 'pointing' to Subclass s1a.foo(); // Compiles OK, Prints Subclass.foo() } 

在动态方法调度中,对重写方法的调用在运行时而不是编译时解析。 它基于通话时提到的对象……

现在假设允许较弱的访问权限,我们在您的代码中编写以下语句:

 Superclass ref=new Subclass(); ref.foo() 

现在在运行时,当java遇到语句ref.foo() ,它必须调用Subclass foo() …但是Subclass foo()方法在代码中被声明为私有,并且私有不能在其外部调用自己的类..所以现在有一个冲突,它会导致运行时exception……