重写抽象方法还是在枚举中使用单个方法?

2022-09-04 00:36:21

考虑下面,哪个更好?它们都可以以完全相同的方式使用,但是它们彼此相比有什么优势呢enums

1. 覆盖抽象方法:

public enum Direction {
    UP {
        @Override
        public Direction getOppposite() {
            return DOWN;
        }
        @Override
        public Direction getRotateClockwise() {
            return RIGHT;
        }
        @Override
        public Direction getRotateAnticlockwise() {
            return LEFT;
        }
    },
    /* DOWN, LEFT and RIGHT skipped */
    ;
    public abstract Direction getOppposite();
    public abstract Direction getRotateClockwise();
    public abstract Direction getRotateAnticlockwise();
}

2. 使用单一方法:

public enum Orientation {
    UP, DOWN, LEFT, RIGHT;
    public Orientation getOppposite() {
        switch (this) {
        case UP:
            return DOWN;
        case DOWN:
            return UP;
        case LEFT:
            return RIGHT;
        case RIGHT:
            return LEFT;
        default:
            return null;
        }
    }
    /* getRotateClockwise and getRotateAnticlockwise skipped */
}

编辑:我真的希望看到一些合理/精心阐述的答案,以及特定主张的证据/来源。由于缺乏证明,大多数关于性能的现有答案并不真正令人信服。

你可以提出替代方案,但它必须清楚它如何比所述更好和/或所述如何更糟,并在需要时提供证据。


答案 1

忘记此比较中的性能;需要一个真正庞大的枚举才能在两种方法之间产生有意义的性能差异。

让我们把重点放在可维护性上。假设您完成了枚举的编码,并最终转到一个更负盛名的项目。同时,另一个开发人员被授予了您的旧代码的所有权,包括 - 让我们称他为Jimmy。DirectionDirection

在某些时候,要求要求吉米添加两个新方向:和。Jimmy很累,工作过度,懒得完全研究这将如何影响现有功能 - 他只是这样做。让我们看看现在会发生什么:FORWARDBACKWARD

1. 覆盖抽象方法:

Jimmy 立即收到编译器错误(实际上他可能会发现枚举常量声明正下方的方法重写)。在任何情况下,问题都会在编译时发现并修复。

2. 使用单一方法:

Jimmy 不会从他的 IDE 中收到编译器错误,甚至不会收到不完整的开关警告,因为您已经有一个案例。稍后,在运行时,调用一段代码,返回 。这会导致意外行为,充其量只能快速导致抛出 。switchdefaultFORWARD.getOpposite()nullNullPointerException

让我们备份并假装您添加了一些面向未来的内容:

default:
    throw new UnsupportedOperationException("Unexpected Direction!");

即使这样,问题也不会在运行时被发现。希望该项目经过适当的测试!

现在,您的示例非常简单,因此此方案可能看起来很夸张。但是,在实践中,枚举可以像其他类一样容易地发展成维护问题。在具有多个开发人员对重构的弹性的较大,较旧的代码库中,这是一个合理的问题。许多人谈论优化代码,但他们可能会忘记开发时间也需要优化 - 这包括编码以防止错误。Direction

编辑:JLS示例§8.9.2-4下的注释似乎同意:

特定于常量的类主体将行为附加到常量。[此] 模式比在基类型中使用 switch 语句安全得多...因为该模式排除了忘记为新常量添加行为的可能性(因为枚举声明会导致编译时错误)。


答案 2

我实际上做了一些不同的事情。您的解决方案遇到了挫折:抽象的重写方法引入了相当多的开销,并且 switch 语句很难维护。

我建议以下模式(适用于您的问题):

public enum Direction {
    UP, RIGHT, DOWN, LEFT;

    static {
      Direction.UP.setValues(DOWN, RIGHT, LEFT);
      Direction.RIGHT.setValues(LEFT, DOWN, UP);
      Direction.DOWN.setValues(UP, LEFT, RIGHT);
      Direction.LEFT.setValues(RIGHT, UP, DOWN);
    }

    private void setValues(Direction opposite, Direction clockwise, Direction anticlockwise){
        this.opposite = opposite;
        this. clockwise= clockwise;
        this. anticlockwise= anticlockwise;
    }

    Direction opposite;
    Direction clockwise;
    Direction anticlockwise;

    public final Direction getOppposite() { return opposite; }
    public final Direction getRotateClockwise() { return clockwise; }
    public final Direction getRotateAnticlockwise() { return anticlockwise; }
}

通过这样的设计,您可以:

  • 永远不要忘记设置方向,因为它是由构造函数强制执行的(如果可以的话)case

  • 方法调用开销很小,因为方法是最终的,而不是虚拟的

  • 干净和简短的代码

  • 但是,您可以忘记设置一个方向的值