如何覆盖java枚举中的(final)equals方法?

neX*_*Xus 14 java enums overriding final equals

我在覆盖Enum中的equals方法时遇到问题,使其与其他类兼容.Enum实现了一个接口,其思路是可以测试此接口的所有实现是否相等,无论其类型如何.例如:

public interface Group {
    public Point[] getCoordinates();
}

public enum BasicGroups implements Group {
    a,b,c; // simplified, they actually have constructors
    // + fields and methods
}

public class OtherGroup implements Group {
    // fields and methods
}
Run Code Online (Sandbox Code Playgroud)

如果a BasicGroup和a OtherGroup具有相同的坐标(按任意顺序),则equals方法应返回true.

执行时没问题,myOtherGroup.equals(BasicGroup.a)但由于Enums中的equals方法是最终的,我无法覆盖它们.

有办法解决这个问题吗?就像在另一个BasicGroup上测试时一样,使用默认的equals方法(引用相等),并且在测试其他类时使用我自己的实现.我该如何确保java不使用错误的BasicGroup.a.equals(myOtherGroup)

pol*_*nts 17

不能用 @Override一种final方法(§8.4.3.3); 这很清楚.enum类型(§8.9)在Java中处理得非常特别,这就是为什么equalsfinal(也是clone,hashCode等),这是根本不可能@Overrideequals的方法enum,也不是你真的想在一个更典型的使用场景.

但是,从整体上看,看起来您正在尝试遵循Effective Java 2nd Edition中推荐的模式,第34项:使用接口模拟可扩展枚举(有关更多信息,请参阅语言指南enum):

您已经定义了这个interface(现在明确记录了预期的equals行为):

public interface Group implements Group {
    public Point[] getCoordinates();

    /*
     * Compares the specified object with this Group for equality. Returns true
     * if and only if the specified object is also a Group with exactly the same
     * coordinates
     */
    @Override public boolean equals(Object o);
}
Run Code Online (Sandbox Code Playgroud)

当然interface,定义equals实现者的方法应该如何表现是完全可以接受的.例如,情况就是如此List.equals.空LinkedListequals空的ArrayList,反之亦然,因为这是interface任务.

在你的情况,你选择实施一些Group作为enum.不幸的是,你现在无法equals按照规范实现,因为它是final,你不能@Override.但是,由于目标是遵守Group 类型,您可以使用装饰器模式具有ForwardingGroup如下:

public class ForwardingGroup implements Group {
   final Group delegate;
   public ForwardingGroup(Group delegate) { this.delegate = delegate; }

   @Override public Point[] getCoordinates() {
       return delegate.getCoordinates();
   }
   @Override public boolean equals(Object o) {
       return ....; // insert your equals logic here!
   }
}
Run Code Online (Sandbox Code Playgroud)

现在,而不是用enum直接常量Group,你包裹他们的一个实例ForwardingGroup.现在,此Group对象将具有所需的equals行为,如interface.

也就是说,而不是:

// before: using enum directly, equals doesn't behave as expected
Group g = BasicGroup.A;
Run Code Online (Sandbox Code Playgroud)

你现在有类似的东西:

// after: using decorated enum constants for proper equals behavior
Group g = new ForwardingGroup(BasicGroup.A);
Run Code Online (Sandbox Code Playgroud)

补充说明

事实上enum BasicGroups implements Group,即使它本身不符合规范Group.equals,也应该非常清楚地记录下来.必须警告用户必须将常量包装在一个ForwardingGroup正确的equals行为中.

另请注意,您可以ForwardingGroup为每个enum常量缓存一个实例.这有助于减少创建的对象数量.根据Effective Java 2nd Edition,第1项:考虑静态工厂方法而不是构造函数,您可以考虑ForwardingGroup定义static getInstance(Group g)方法而不是构造函数,允许它返回缓存的实例.

我假设这Group是一个不可变的类型(Effective Java 2nd Edition,Item 15:Minimize mutability),否则你可能不应该enum首先实现它.鉴于此,请考虑Effective Java 2nd Edition,Item 25:Prefer list to arrays.您可以选择getCoordinates()返回a List<Point>而不是Point[].你可以使用Collections.unmodifiableList(另一个装饰器!),这将使返回的List不可变.相比之下,由于数组是可变的,因此在返回时你将被迫执行防御性复制Point[].

也可以看看

  • 也就是说,而不是`Group g = BasicGroup.A;`(你现在不能做,因为`BasicGroup`不再`实现Group`,你做`Group g = BasicCoordinates.A.asGroup()`,其中方法返回对`final Group wrapper`字段的引用.这样你不必担心管理装饰器的缓存实例,因为每个常量都有自己的包装器.如果需要,我可以更详细地详细说明这个概念. (2认同)

aio*_*obe 8

在Java中不可能这样做.(当涉及到方法时,final关键字的唯一目的是防止覆盖!)

equalsEnums上的一些其他方法是最终的,所以你不能改变它们的行为.(你不应该 :)这是我对相关问题的回答:


处理枚举常量的客户端的直觉是,equal当且仅当它们是相同的常量时,两个常量才是.因此,除了return this == other违反直觉和容易出错之外的任何其他实现.

同样的道理也适用于hashCode(),clone(),compareTo(Object),name(),ordinal(),和getDeclaringClass().

JLS并没有激励选择让它成为最终版本,但在这里的枚举语中提到了相同的内容.片段:

Enum中的equals方法是一个最终方法,它只在其参数上调用super.equals并返回结果,从而执行身份比较.