21

在抽象类中,我必须将哪个访问修饰符用于方法,以便子类可以决定它是否应该是公共的?是否可以在 Java 中“覆盖”修饰符?

public abstract class A {

    ??? void method();
}

public class B extends A {
    @Override
    public void method(){
        // TODO
    }
}

public class C extends B {
    @Override
    private void method(){
        // TODO
    }
}

我知道静态绑定会有问题,如果有人调用:

// Will work
A foo = new B()
foo.method();

// Compiler ?
A foo = new C();
foo.method();

但也许还有另一种方式。我怎样才能做到这一点?

4

6 回答 6

12

可以放宽限制,但不能使其更具限制性:

public abstract class A {
    protected void method();
}

public class B extends A {
    @Override
    public void method(){    // OK
    }
}

public class C extends A {
    @Override
    private void method(){    // not allowed
    }
}

制作原始方法private也不起作用,因为这种方法在子类中不可见,因此不能被覆盖。

我建议使用interfaces 来选择性地公开或隐藏该方法:

public interface WithMethod {
    // other methods
    void method();
}

public interface WithoutMethod {
    // other methods
    // no 'method()'
}

public abstract class A {
    protected void method();
}

public class B extends A implements WithMethod {
    @Override
    public void method(){
      //TODO
    }
}

public class C extends B implements WithoutMethod {
    // no 'method()'
}

...然后只能通过接口处理实例。

于 2016-03-12T10:08:01.373 回答
8

覆盖方法时,只能将修饰符更改为更宽的修饰符,反之则不行。例如,此代码将是有效的:

public abstract class A {

    protected void method();
}

public class B extends A {
    @Override
    public void method() { }
}

然而,如果你试图缩小可见性,你会得到一个编译时错误:

public abstract class A {
    protected void method();
}

public class B extends A {
    @Override
    private void method() {}
}

对于你的情况,我建议C不要实现A,因为A抽象意味着有一个非私有的method()

public class C {
    private void method(){
      //TODO
    }
}

另一种选择是method()C抛出 RuntimeException 时实现:

public class C extends A {

    @Override
    public void method(){
        throw new UnsupportedOperationException("C doesn't support callbacks to method()");
    }
}
于 2016-03-12T10:06:01.783 回答
7

你所要求的不可能有很好的理由。

Liskov 替换原则基本上说:一个类 S 是另一个类 T 的子类,只有当你可以用某个“S 对象”替换任何出现的某个“T 对象”时 - 而不会注意到。

如果您允许 S 将公共方法简化为私有方法,那么您就不能再这样做了。因为突然之间,可以在使用某些 T ... 时调用的方法不再可以在 S 上调用。

长话短说:继承不是天上掉下来的东西。它是作为程序员负责的类的属性。换句话说:继承不仅仅意味着在你的源代码中写下“class S extends T”!

于 2016-03-12T10:20:43.763 回答
4

由于多态性,这是不可能的。考虑以下。您在类A中有一些访问修饰符不是private. 为什么不是私人的?因为如果它是私有的,那么任何其他类都无法知道它的存在。所以它必须是别的东西,而且必须可以从某个地方访问到别的东西。

现在让我们假设您将一个类的实例传递C某处。但是您A事先将其向上转换,因此您最终会在某处获得此代码:

void somewhereMethod(A instance) {
    instance.method(); // Ouch! Calling a private method on class C.
}

一个很好的例子是 Qt 中的QSaveFile。与 Java 不同,C++ 实际上允许降低访问权限。所以他们就这样做了,禁止使用这种close()方法。他们最终得到的是一个QIODevice不再是真正的子类QIODevice。如果你传递一个指向QSaveFile某个方法的指针QIODevice*,他们仍然可以调用close(),因为它在QIODevice. QSaveFile::close()他们通过发出(私人的)调用来“修复”这个问题abort(),所以如果你这样做,你的程序会立即崩溃。不是一个很好的“解决方案”,但没有更好的“解决方案”。这只是糟糕的OO设计的一个例子。这就是Java不允许它的原因。

编辑

并不是我错过了你的课程是抽象的,而是我也错过了一个事实B extends C,不是A。这样你想要做的事情是完全不可能的。如果该方法public在 B 中,它也将在所有子类中公开。您唯一能做的就是记录不应调用它可能将其覆盖为 throw UnsupportedOperationException。但这会导致与QSaveFile. 请记住,您班级的用户可能甚至不知道这是一个实例,C因此他们甚至没有机会阅读其文档。

总的来说,这只是一个非常糟糕的面向 OO 的想法。也许你应该问另一个关于你试图用这个层次结构解决的确切问题的问题,也许你会得到一些关于如何正确解决问题的体面的建议。

于 2016-03-12T11:03:55.423 回答
3

这是@Override合同的一部分。

答案是:没有任何可能实现你所拥有的。

访问级别不能比被覆盖方法的访问级别更严格。例如:如果超类方法被声明为public,那么子类中的覆盖方法既不能是私有的也不能是受保护的。

这不仅仅是关于abstract类的问题,而是关于所有类和方法的问题。

于 2016-03-12T10:05:35.610 回答
3

理论:

您有确定的修饰符顺序:

public <- protected <- default-access X<- private

当您覆盖该方法时,您可以增加但不能减少修饰符级别。例如,

public -> []
protected -> [public]
default-access -> [public, default-access]
private -> []

实践:

在你的情况下,你不能???变成一些修饰符,因为最低修饰符和类成员没有被继承privateprivate

于 2016-03-12T10:12:32.113 回答