在派生类中禁用继承的方法

36

在Java的一个派生类中,有没有办法“禁用”从基类继承来的方法和/或字段?

例如,假设您有一个 Shape 基类,它具有 rotate() 方法。您还有各种类型派生自 Shape 类: Square Circle UpwardArrow 等。

Shape 具有 rotate() 方法。但是我不希望 Circle 的用户能够使用 rotate() ,因为这是无意义的,或者不希望 UpwardArrow 能够旋转。

11个回答

45
我认为这是不可能的。 但是,您可以通过从其规范中删除rotate()方法并定义另一个名为RotatableShape子类来进一步细化Shape类,并让 Circle Shape派生,而所有其他 Rotatable classes 都从 RotatableShape 派生。 例如:
public class Shape{
 //all the generic methods except rotate()
}

public class RotatableShape extends Shape{

 public void rotate(){
    //Some Code here...
 }
}

public class Circle extends Shape{
 //Your implementation specific to Circle
}

public class Rectangle extends RotatableShape{
 //Your implementation specific to Rectangle
}

4
比抛出“UnsupportedOperationException”更好的解决方案。 - helpermethod
1
干净的解决方案,简单而优雅 +1 - user657496
实现一个Rotatable接口或类似的东西,比如public class Circle extends Shape implements Rotatable {怎么样?这种方式可行吗?不行吗?哪种更好?(我不是专家) - Joshua Pinter
查看Liskov替换原则,这是你困惑的原因和这个答案的原因。基本上,它表明任何子类都最好能像其超类一样运行。 - Nick Crews

23

您可以在想要禁用此操作的类中重写特定方法"rotate()",例如:

public void rotate() {
    throw new UnsupportedOperationException();
}

2
这回答了一个更普遍的问题,即开发人员可能不会创建超类。 - Brian Risk
没错。目前这里发布的所有答案都没有考虑到一种情况,即基类可能是一个您无法重新实现的标准类。也许这不是一个非常好的实际例子,但让我们想象一下,您想要实现一个从HashMap派生出来的FastHashMap,但您只想允许putIfAbsent()方法,并禁止put()方法(自Java 8以来已存在putIfAbsent())。 - Alexander Samoylov

8
如果需要在子类中禁用方法,则说明您的类层次结构存在问题。任何子类都应能够顺畅地使用其超类的方法。这被称为“里氏替换原则”(https://en.wikipedia.org/wiki/Liskov_substitution_principle)。您可以在此主题中阅读更多信息:https://softwareengineering.stackexchange.com/questions/219543/should-a-class-know-about-its-subclasses
按照Chandu的建议进行操作。不要将rotate()放在Shape中。相反,创建一个名为RotatableShape的Shape子类,并在其中放置rotate()。然后Circle可以从Shape继承,而Rectangle可以从RotatableShape继承。

1
LSP几乎不会重复。 - clwhisk
Liskov替换原则的提及是这个页面的有价值的补充,这是被接受的答案(或任何其他答案)没有提到的。 - Paul Rooney

5

编号。

  • 你可以将该方法仅在Circle类(或仅由该类实现的接口)中调用。
  • 你可以在不支持该方法的类中提供空实现或抛出UnsupportedOperationException异常的实现。

@Jean-François Corbett 这是 Eclipse 的术语。这意味着您将它从超类移动到子类。 - Bozho

3
在“子”类中声明相同的函数将覆盖基类中声明的默认函数。因此,在您的子类中,创建一个名为rotate()的函数,该函数不执行任何操作,这将覆盖默认行为。

3

你能在圆形中使用一个什么也不做的方法吗? 对于箭头,我会重新考虑对象层次结构。


2
一种处理方法是定义第二个方法(比如说)boolean isRotatable(),并使用它来确定是否向用户提供旋转控件。
另一种选择是引入一个Rotatable接口,并使用shape instanceof Rotatable来进行判断。(然而,我认为isRotatable()方法更加灵活。)
在任何情况下,您都可以将rotate()方法实现在不应该被旋转的类中:
public void rotate() {
    throw new UnsupportedOperationException("rotate");
}

Java语言没有提供一种在子类中“删除”或“禁用”方法的方式。这将违反可替换性原则并破坏多态性。子类不能从父类的API中删除可见成员。


1

我认为你不能按照你的建议禁用一个方法。

在你的例子中,假设你有一个接受形状的方法。

public void handleShape(Shape s){
  s.rotate();
}

然后你将一个圆形传递给这个方法

handleShape(new Circle());

应该发生什么?基本上你是在要求对Java的类型系统进行根本性改变。

如果圆形是一个形状,不应该旋转,那可能意味着形状设计得不好,不应该有旋转方法。可以将旋转添加到层次结构中的另一个类中,比如RotatableShape,或者可能使用Rotatable接口。


1
所以这里有两种可能性。一种是你有一个可以旋转但在过程中没有变化的对象。另一种是你有一个由方向定义的对象。每个都应该分别处理并以自己的方式处理。
对于圆形,我们有一个旋转函数的存根,因为系统中没有变化。不喜欢它,那就太糟了。我推迟了我的点群测验来写这篇文章。
对于向上箭头,我们抛出异常,因为我们不想在单行道上开错车,而且这应该是恒定的。
在我的案例中,我通过继承定义四元数的类的操作来定义三维空间中的一个点。三维点仅使用四元数的三个虚坐标,如果四元数的实分量具有非零值,则会破坏从四元数通过三维坐标继承的渲染算法的重要操作。
请原谅我过于哲学,然而,这个问题提出了CS中如何处理这个问题的一个有效观点。正如Stephen指出的那样,能够隐藏、禁用或删除子类中继承的函数"...将违反可替代性原则并破坏多态性。"
我的情况属于向上箭头,只不过我指向想象力。我试图截断一个派生类,这意味着任何“孙子”类(可能继承自旋转算符)不再必须是四元数。通过在截断派生类的类型转换处修剪基类,可以避免使用抛出异常的方法,这将更加优雅。另一种选择是将三维坐标定义为带四元数成员变量的单独类;缺点是实现中有太多的粘合剂。
然后,我可以将新的旋转操作放入三个平面(ij、jk、ik)的三维坐标的派生类中,然后通过循环和每个顶点的相同成员函数调用来旋转具有三维顶点的对象。在开始旋转定义球面表面的点之前,您可能需要自己跟踪对称性,这就是为什么需要使用存根函数并可能实现自己的“常量”标志的原因。

0

有一种可能性,而且非常简单。不要使用继承,而是使用组合:

public class Shape{
   public method1();
   public method2();
   public rotate();
   public method3();
}

public class Circle{
  private Shape shape;

  public Circle(){
    this.shape = new Shape();
  }

  public method1(){
    shape.method1()
  }

  public method2(){
    shape.method2()
  }

  public method3(){
    shape.method3()
  }
}

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