Java中数据的双重性质
我有一个班级Java中数据的双重性质,java,Java,我有一个班级X。它有两个属性a和b。在某些情况下,X对象的相等性将基于a的相等性,在某些情况下基于b的相等性。我想知道对这些数据建模的最佳方法 我不能简单地基于某个标志拥有两个equals函数,因为我使用了很多集合和列表,所以我必须重写equals()。这就是我的想法: 一个接口X,它有两个实现Xa和Xb。问题是,我需要在Xa和Xb之间进行转换,我希望有数百个实例,所以创建新副本的成本会很高 由于基于a的相等预计大部分时间都会发生,因此实现equals()比较a。当需要基于b的等式时,只需为其编
X
。它有两个属性a
和b
。在某些情况下,X
对象的相等性将基于a
的相等性,在某些情况下基于b
的相等性。我想知道对这些数据建模的最佳方法
我不能简单地基于某个标志拥有两个equals函数,因为我使用了很多集合和列表,所以我必须重写equals()
。这就是我的想法:
X
,它有两个实现Xa
和Xb
。问题是,我需要在Xa
和Xb
之间进行转换,我希望有数百个实例,所以创建新副本的成本会很高a
的相等预计大部分时间都会发生,因此实现equals()
比较a
。当需要基于b
的等式时,只需为其编写一个单独的方法。问题是,我必须重新发明用于比较集合和列表的轮子X
首先有缺陷吗?我可以用更好的方式实现吗?解决方案0:重构
我想提出的第一件事是考虑重新设计你的对象层次结构。您描述的情况听起来不太清楚,尽管我们对您试图根据您提供的信息建模的实际问题知之甚少
解决方案1:“开关”多态性
以你所说的为坚定的要求,我可以想到以下——不是特别漂亮——的解决方案。基本思想是,X
对象的每个实例都会得到一个标志,表明其“性别”。在性别之间转换仅仅是分配一个单词的问题。但是,请注意,这也会将对象大小增加一个单词。如果您有许多小对象,那么额外的开销可能会很大。(在下面的玩具示例中,它高达三分之一,在本例中,我更倾向于仅在需要时创建类型为Xa
或Xb
的新对象。)根据其他等式比较和哈希代码计算的成本,案例选择的额外开销可能也很明显,虽然可能可以接受
下面的类是精心设计的,它符合我所知道的所有契约,可以在任何集合中使用,并且可以自由地来回转换。但是,当对象包含在任何集合中时,不得触摸对象的性别,并且集合只能包含特定性别的X
s。正如您所看到的,我们正在慢慢地脱离面向对象,必须管理我们自己的不变量。编译器无法帮助我们执行它们。这应该足以引起一个大的红旗
public final class X implements Comparable<X> {
public static enum Genders { A, B };
private Genders gender;
private final String a;
private final Integer b;
public X(final String a, final Integer b, final Genders gender) {
if (a == null) {
throw new NullPointerException("a");
}
if (b == null) {
throw new NullPointerException("b");
}
if (gender == null) {
throw new NullPointerException("gender");
}
this.a = a;
this.b = b;
this.gender = gender;
}
public Genders getGender() {
return this.gender;
}
public void setGender(final Genders gender) {
if (gender == null) {
throw new NullPointerException("gender");
}
this.gender = gender;
}
@Override
public boolean equals(final Object other) {
if (other instanceof X) {
final X otherX = (X) other;
if (this.gender == otherX.gender) {
switch (this.gender) {
case A:
return this.a.equals(otherX.a);
case B:
return this.b.equals(otherX.b);
default:
throw new AssertionError("unexpected gender");
}
}
}
return false;
}
@Override
public int hashCode() {
switch (this.gender) {
case A:
return this.a.hashCode();
case B:
return this.b.hashCode();
default:
throw new AssertionError("unexpected gender");
}
}
@Override
public int compareTo(final X other) {
// It seems acceptable to allow the case that
// this.gender != other.gender here.
switch (this.gender) {
case A:
return this.a.compareTo(other.a);
case B:
return this.b.compareTo(other.b);
default:
throw new AssertionError("unexpected gender");
}
}
@Override
public String toString() {
return String.format("{a: \"%s\", b: %d, gender: %s}",
this.a, this.b, this.gender);
}
}
解决方案2:装饰器模式
如果你可以(我很肯定你可以)生活在一个新的每个对象“转换”的开销中,一个更干净、更不容易出错的解决方案就是使用
让我们首先为您的类型定义一个接口。(你的玩具可能比这个玩具要复杂得多。)
接下来,我们提供该接口的一个基本实现,它除了在比较中采取立场之外,什么都可以做。请注意,该类(可以)是不可变的(尤其是final
)。因为我没有重写equals
和hashCode
,甚至不去实现compariable
,所以这个“基类”的实例将具有继承自对象的身份比较语义。这正是我们想要的(见下文)
有了所有的业务逻辑,我们现在可以求助于我们的装饰师了。我们将定义其中两个:Xa
和Xb
。除了提供适当的equals
和hashCode
实现和实现Comparable
之外,他们将把所有内容(在这个精心设计的示例中并不多)委托给包含的X
实例
由于两个装饰器的委托逻辑是相同的,所以我将把公共代码分解成一个中间包私有类
abstract class DecoratedX implements X {
private final X x;
protected DecoratedX(final X x) {
if (x == null) {
throw new NullPointerException("x");
}
this.x = x;
}
protected final X getX() {
return this.x;
}
@Override
public final String getA() {
return this.x.getA();
}
@Override
public final Integer getB() {
return this.x.getB();
}
@Override
public final String toString() {
return this.x.toString();
}
}
这将Xa
和Xb
中的代码简化为比较逻辑,这在每个类中都是唯一的。请注意,Xa
和Xb
可以是final
public final class Xa extends DecoratedX implements X, Comparable<Xa> {
public Xa(final X x) {
super(x);
}
@Override
public boolean equals(final Object other) {
if (other instanceof Xa) {
final Xa otherXa = (Xa) other;
return this.getA().equals(otherXa.getA());
}
return false;
}
@Override
public int hashCode() {
return this.getA().hashCode();
}
@Override
public int compareTo(final Xa other) {
return this.getA().compareTo(other.getA());
}
}
我们走了。总而言之,我们可以做比以前更酷的事情。请注意,我们现在可以使用不同的比较语义在三个不同的集合中同时拥有相同的对象(尽管在两种情况下包装(装饰)
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public final class Main {
public static void main(final String[] args) {
final List<X> theXs = new ArrayList<>();
final Set<Xa> theXas = new HashSet<>();
final Set<Xb> theXbs = new TreeSet<>();
theXs.add(new BasicX("alpha", 1));
theXs.add(new BasicX("alpha", 1));
theXs.add(new BasicX("beta", 2));
theXs.add(new BasicX("beta", 3));
theXs.add(new BasicX("gamma", 2));
theXs.add(new BasicX("delta", 3));
for (final X x : theXs) {
theXas.add(new Xa(x));
theXbs.add(new Xb(x));
}
System.out.println("These are the As:\n");
for (final X x : theXas) {
System.out.println(x);
}
System.out.println();
System.out.println("These are the Bs:\n");
for (final X x : theXbs) {
System.out.println(x);
}
}
}
还要注意,这种设计是类型安全的:编译器不允许我们在Xa
s的集合中拥有Xb
对象。在本例中,我直接从BasicX
s创建了Xa
s和Xb
s。如果您想“将Xa
转换为Xb
”,或者反之亦然,代码当然是
Xb a2b(final Xa xa) {
return new Xb(xa.getX());
}
及
反之亦然。您必须将DecoratedX.getX()
方法public
设置为实际工作。(从技术上讲,您也可以将Xa
粘贴到Xb
中:毕竟它是X
。虽然这完全可行,并且适用于装饰器模式的其他应用程序,但在这种情况下,无用的间接层很快就会变得令人讨厌,并且很容易避免。)为最后一个问题定义比较器
s并酌情应用:这是可能的。我们需要更多的信息来确定。它主要基于a
这一事实让我相信您可能想要重新设计它。@chrylis:定义比较器
s没有帮助,因为我主要需要集s和L的相等性
public final class Xa extends DecoratedX implements X, Comparable<Xa> {
public Xa(final X x) {
super(x);
}
@Override
public boolean equals(final Object other) {
if (other instanceof Xa) {
final Xa otherXa = (Xa) other;
return this.getA().equals(otherXa.getA());
}
return false;
}
@Override
public int hashCode() {
return this.getA().hashCode();
}
@Override
public int compareTo(final Xa other) {
return this.getA().compareTo(other.getA());
}
}
final class Xb extends DecoratedX implements X, Comparable<Xb> {
public Xb(final X x) {
super(x);
}
@Override
public boolean equals(final Object other) {
if (other instanceof Xb) {
final Xb otherXb = (Xb) other;
return this.getB().equals(otherXb.getB());
}
return false;
}
@Override
public int hashCode() {
return this.getB().hashCode();
}
@Override
public int compareTo(final Xb other) {
return this.getB().compareTo(other.getB());
}
}
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public final class Main {
public static void main(final String[] args) {
final List<X> theXs = new ArrayList<>();
final Set<Xa> theXas = new HashSet<>();
final Set<Xb> theXbs = new TreeSet<>();
theXs.add(new BasicX("alpha", 1));
theXs.add(new BasicX("alpha", 1));
theXs.add(new BasicX("beta", 2));
theXs.add(new BasicX("beta", 3));
theXs.add(new BasicX("gamma", 2));
theXs.add(new BasicX("delta", 3));
for (final X x : theXs) {
theXas.add(new Xa(x));
theXbs.add(new Xb(x));
}
System.out.println("These are the As:\n");
for (final X x : theXas) {
System.out.println(x);
}
System.out.println();
System.out.println("These are the Bs:\n");
for (final X x : theXbs) {
System.out.println(x);
}
}
}
Xb a2b(final Xa xa) {
return new Xb(xa.getX());
}
Xa b2a(final Xb xb) {
return new Xa(xb.getX());
}