14

我有一个类,它有一个默认访问说明符是公共的方法。现在,我想在一个子类中扩展这个类,并且我想重写这个方法来获得访问说明符“private”。编译此代码时,出现编译错误:

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

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

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

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

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

8 回答 8

32

简短的回答是不允许这样做,因为它会破坏类型可替代性;另见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.foo()允许的声明类型来决定,那么您允许从 的抽象边界之外s2调用方法。 privateSubclass

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

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

于 2013-07-07T07:39:19.433 回答
8

您不能限制访问,因为您已经在超类中允许了更多访问。例如

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

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

您可以通过说您只能通过父类而不是直接访问子类方法来解决此问题。这样做的问题是您不知道父母何时可以添加方法以及何时创建方法private,因为您希望它是私有的,并且无法以其他方式访问。

顺便说一句,您仍然可以通过反射访问私有方法,它的副作用是它会给 JVM 带来各种问题。例如,它必须保留私有方法,即使它可能确定无法正常调用它。

简而言之,你想要的代码就是它所说的意思,而不是人格分裂。它要么是本地包,要么是私有的,不是介于两者之间的东西,但也不是。反过来,这不是这样的问题。即如果子类是公共的。这只是意味着子类可以在比父类更多的地方使用,就像它可以实现更多方法一样。

于 2013-07-07T07:39:54.520 回答
7

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

可以说这是允许的

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()publicSuper 类中。但这不应该被允许,因为 obj 实际上是指 Sub 的一个实例,并且在那个类中,该方法是受保护的!

为了限制对 Sub 类中不应从外部访问的方法的调用,设置了此限制。

于 2013-07-07T07:47:45.767 回答
1

限制超类方法的访问修饰符是无效的覆盖,因为它破坏了超类契约并使替换原则无效,即子类对象也是超类对象

public void doSomething(SuperClass sc) {
  sc.publicMethodInSuperClass();
}

doSomething(new SubClass()); // would break

如果允许,上述客户端代码将中断,因为您的SubClass没有该方法public

参考:
里氏替换原理

于 2013-07-07T07:41:14.083 回答
0

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

于 2013-07-07T07:40:16.717 回答
0

我认为简短的回答是编译器编写者已经设置了以这种方式工作的规则。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()
}
于 2013-07-07T13:51:16.517 回答
0

在动态方法分派中,对被覆盖方法的调用在运行时解决,而不是在编译时解决。它基于调用时引用的对象...

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

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

现在在运行时,当java遇到语句时ref.foo(),它必须调用...但是子类foo()的方法在您的代码中被声明为私有,并且私有不能在它自己的类之外调用..所以现在存在冲突并且它会导致运行时异常...Subclassfoo()

于 2013-08-29T13:47:31.903 回答
-1

因为你改变了访问权限的顺序,所以你得到了错误

正确的顺序是:==> 私有到默认保护到公共

*在您的情况下,只要您以错误的顺序进入,您就会默认 ==> 私有,结果是错误

访问权限的正确顺序是——

`class SuperClass{
          void foo(){
    System.out.print("SuperClass");
     }
   class SubClass extends{

  //--default to default--//
         void foo(){
  System.out.print("SubClass");
   }
 //--default to protected--//

  protected void foo(){
  System.out.print("SubClass");

 //--default to public //

 public void foo(){
 System.out.print("SubClass");
 }` 

    **you make sure to preserve correct order while overriding **
于 2019-09-20T05:48:51.223 回答