为啥 Java 允许增加子类中受保护方法的可见性?

Posted

技术标签:

【中文标题】为啥 Java 允许增加子类中受保护方法的可见性?【英文标题】:Why Java allows increasing the visibility of protected methods in child class?为什么 Java 允许增加子类中受保护方法的可见性? 【发布时间】:2017-09-14 19:47:21 【问题描述】:
abstract class Base
      protected abstract void a();


class Child extends Base
      @Override
      public void a()
          //why is this valid
      

为什么我们不能降低可见度却可以提高可见度?

我还需要实现 模板模式,其中可见的公共方法只能是基类。

例子:

abstract class Base
      public void callA()
      //do some important stuff
      a();
      

      protected abstract void a();


class Child extends Base
      @Override
      public void a()
          //why is this valid
      

现在如果 java 允许增加可见性,那么有两种公开可见的方法吗??

我知道界面是一种解决方案,但还有其他出路吗???

【问题讨论】:

【参考方案1】:

为什么不允许降低可见性已经在其他回复中解释过(这会破坏父类的约定)。

但是为什么允许增加方法的可见性呢?首先,它不会违反任何合同,因此没有理由不允许它。有时它会很方便,因为在子类中不保护方法是有意义的。

其次,不允许它可能会产生有时无法同时扩展类和实现接口的副作用:

interface Interface1 
   public void method();


public class Parent 
   protected abstract void method();


public class Child extends Parent implements Interface1 
   @Override
   public void method() 
   
   //This would be impossible if the visibility of method() in class Parent could not be increased.

关于你的第二个问题,你无能为力。您必须相信实现子类的人不会做任何破坏您的实现的事情。即使 java 不允许增加可见性,这仍然不能解决您的问题,因为可以创建一个具有不同名称的公共方法来调用抽象方法:

class Child extends Base
      @Override
      protected void a()

      

      public void a2() 
           a(); //This would have the same problems that allowing to increase the visibility.
      

【讨论】:

谢谢巴勃罗。这就是我一直在寻找的,一个允许增加可见度的有效论据。 @NP_JavaGeek 如果这是您正在寻找的答案,您应该接受它(如 here 解释的那样) 想想克隆方法。它受保护,但您应该将其设为受保护或公开 还有第三个很好的理由,子类总是可以使另一个公共方法可用,从而间接公开受保护的功能。因此,任何禁止它的尝试都是毫无意义的,因此 Java 允许您提高可见性。【参考方案2】:

如果基类做出关于可见性的承诺,那么子类就不能违反该承诺并仍然满足 Liskov 替换原则。如果承诺被破坏,则在承诺方法暴露的任何情况下,您都不能使用子类。

子类 IS-A 基类。如果基类公开了一个方法,那么子类也必须公开一个方法。

Java 或 C++ 没有出路。我猜 C# 也是如此。

【讨论】:

我没有完全得到答案??所以你是说我不能限制子类以任何方式增加可见性?? 每个问题一个问号就可以了。我认为“不”应该很容易理解。您不能使方法在子级中的可见性低于在父级中的可见性。让编译器告诉你答案。总比来这里问好。 请原谅问号。但是你能帮我写第二个例子吗? 有什么问题?孩子可以选择使方法比其父方法可见更多,但不能使其不可见可见。它仍然满足 Liskov;它仍然是父母;您可以在调用父方法 a() 的任何上下文中调用更可见的子方法 a()。 是的,我现在明白这个原则了。但是我能做些什么来强制执行基类方法,这是我的实际问题!看我的第二个例子。【参考方案3】:

为什么能见度不能降低却能提高?

假设可以降低可见度。然后看下面的代码:

class Super 
    public void method() 
        // ...
    


class Sub extends Super 
    @Override
    protected void method() 
        // ...
    

假设您在另一个包中有另一个类,您在其中使用这些类:

Super a = new Sub();

// Should this be allowed or not?
a.method();

为了检查一个方法调用是否被允许,编译器会查看你调用它的变量的类型。变量a 的类型是Super。但是a所指的实际对象是Sub,而那里的方法是protected,所以你会说不应该允许从包外不相关的类调用方法。为了解决这种奇怪的情况,禁止让被覆盖的方法不那么显眼。

请注意,反过来(使方法更可见)不会导致同样的问题。

【讨论】:

我现在明白了,谢谢。但是你能帮我解决模板方法的问题吗?我需要在基类中做一些重要的事情,如果有两种方法是公开可用的,那么我无法在基类中保持控制? 语义上,允许子类隐藏父成员是没有问题的。接收迭代器的代码可能会在其上调用remove,因为调用者可能只传递支持该方法的迭代器;然而,通过允许接收对不可变集合的迭代器实现的强类型引用的代码在其上调用remove,没有任何收获,因为该操作不可能工作。允许 private 覆盖的危险在于子类可能打算创建私有函数而不是覆盖公共函数。【参考方案4】:

由于Java允许超类引用指向子类对象。所以,限制不应该从compile-time增加到runtime..

让我们通过一个例子来看看:-

public class B 
    public void meth() 

    


class A extends B 
    private void meth()   // Decrease visibility.

    

现在,您创建一个 A 类的对象,并为其分配 B.. 类的引用。 让我们看看如何:-

B obj = new A();  // Perfectly valid.

obj.meth();  // Compiler only checks the reference class..
             // Since meth() method is public in class B, Compiler allows this..
             // But at runtime JVM - Crashes..

现在,由于compiler 只检查引用变量的类型,并检查该类(B类)中方法的可见性,它不检查reference obj 指的是什么类型的对象。所以,它并不担心。它在运行时留给JVM 来解析适当的方法。。

但是在运行时,JVM实际上会尝试调用A类的meth方法,因为对象属于A类。但是,现在会发生什么... BooooOOMM ---> JVM 崩溃.. 因为meth 方法在class A 中是私有的...

这就是为什么不允许降低可见度的原因..

【讨论】:

以上是关于为啥 Java 允许增加子类中受保护方法的可见性?的主要内容,如果未能解决你的问题,请参考以下文章

在 kotlin 中,如何从子类访问父类中受保护的静态成员

为啥我们不能在 kotlin 中将类的可见性标记为“受保护”?

更改子类中父方法的可见性范围

为啥受保护的实例成员在不同包的子类中不可见,但受保护的类成员是? [复制]

Java:受保护的,可见的[重复]

Java--继承