为什么在Java中派生类重写的方法不应该比基类更加严格?

21
为什么Java中派生类重写方法不能比基类更加限制?为什么编译器会报错?有人能解释一下原因吗?
4个回答

31

重点是只知道你的超类的调用者仍然应该能够使用它所给定的任何子类实例。考虑这种情况:

public class Super
{
    public void print()
    {
        System.out.println("Hello!");
    }
}

public class Sub extends Super
{
    @Override
    void print() // Invalid
    {
        System.out.println("Package access");
    }
}

现在假设我们有来自不同包的以下内容:

public void printSuper(Super x)
{
    x.print();
}

然后我们使用以下代码调用它:

printSuper(new Sub());

你觉得这样做会有什么效果呢?由于你正在覆盖方法,所以它应该打印出“包访问权限”,但这意味着你正在从不同的包中调用一个包访问权限方法...

基本上,这只是里氏替换原则在实践中的一个例子。你应该能够将任何子类的实例视为超类的实例,很难看出如何将其与在子类中变得更加严格相结合。


5
你可以引用Liskov替换原则(Liskov Substitution Principle)。http://en.wikipedia.org/wiki/Liskov_substitution_principle - Scorpion
如果您不希望用户能够在您自己的实现中使用该函数,您可以始终使用空版本覆盖它。这样类的接口就不会被破坏。当然,您必须根据给定函数的期望来决定是否采用这种方法。 - Devolus
2
@JonSkeet 我有点懒,不想写一个新答案来说明这一点,所以我只是添加了一个提醒 - 了解你的声誉,我不应该怀疑你会添加它 :-) - Scorpion

6

访问修饰符不能更加严格,因为这会违反继承的基本规则:子类实例应该能够替换超类实例。

例如,假设Person类有一个被许多类(包括非子类)使用的getName公共方法。但是有人只是将Employee作为Person的子类,并且Employee中的getName是受保护的,只能由子类访问,那么以前的代码将开始出现问题,而Employee将无法替换Person对象。

因此Java决定强制执行此限制。


1
考虑下面的例子。
class Mammal {
    public Mammal readAndGet() throws IOException {//read file and return Mammal`s object}
}

class Human extends Mammal {
    @Override
    public Human readAndGet() throws FileNotFoundException {//read file and return Human object}
}

如果我们执行以下代码

Mammal mammal = new Human();
Mammal obj = mammal.readAndGet();

我们知道对于编译器来说,mammal.readAndGet() 是从 Mammal 类的对象中调用的,但是在运行时,JVM 将会把 mammal.readAndGet() 方法调用解析为从 Human 类中的调用,因为 mammal 持有 new Human()

readAndGet 方法在 Mammal 中被定义为公共的,为了让它在 Human 类中更少限制,我们需要将其访问修饰符删除(使其成为默认的)或将其设置为 protectedprivate

现在假设:

  • 如果在 Human 中将 readAndGet 定义为 defaultprotected,但 Human 在另一个包中定义
  • 如果在 Human 中将 readAndGet 定义为 private
我们知道JVM会在运行时解析readAndGet方法的调用,但编译器不知道这一点,在两种情况下,代码都会成功编译,因为对于编译器来说,readAndGet是从Mammal类中调用的。
但在两种情况下,在运行时JVM将无法访问Human中的readAndGet,因为由于Human类中readAndGet的限制性访问,它将受到限制。
而且,我们不能确定谁会在未来扩展我们的类,以及他将在哪个包中进行扩展,如果那个人使覆盖方法变得不那么限制性,JVM可能无法调用该方法,并且代码将在运行时出错。
因此,为了避免这种不确定性,不允许在子类中分配限制性访问权限来覆盖方法。
还有其他规则需要遵循,而我们在重写方法时需要遵循这些规则,您可以阅读Why We Should Follow Method Overriding Rules以了解原因。

1
超类的被覆盖方法不应该比覆盖它的子类更加严格,因为子类的实例应该始终能够在期望超类实例的地方使用。但是如果一个方法更加严格,当调用来自另一个类或包时,可能无法访问。例如,想象一下:
//File Number 1
public class Human {
    public int age = 21;
    public int getAge() {
        System.out.println("getAge() method in Human");
        return age;
    }
}

//File Number 2
public class Teacher extends Human {
    public int getAge() {
        System.out.println("getAge() method in Teacher");
        return age;
    }
}

//File Number 3
public class Characteristics {  
    public static void showAgeOfObject(Human human) {
        human.getAge();
    }

    public static void main(String[] args) {
        Human human = new Human();
        Teacher teacher = new Teacher();

        showAgeOfObject(human);     
        showAgeOfObject(teacher);
    }
}

现在如果两个getAge()方法都是公共的,这将被显示。
getAge() method in Human
getAge() method in Teacher

但是,如果Teacher中的getAge()是私有的,那么这将会抛出一个错误,因为我们在不同的类中,所以无法访问该方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接