The double nature of data in Java

I have a class X. It has two attributes aand b. In some scenarios, equality of objects Xwill be based on equality a, and in some, on b. I would like to know how to best model this data.

I can't just have two equal functions based on some flag, because I use a lot of sets and lists, so I have to override equals(). Here is what I mean:

  • An interface Xthat has two implementations Xaand Xb. The problem is that I will need to convert between Xaand Xb, and I expect you will have hundreds of instances, so making new copies will be expensive.

  • Since aequality is expected to be based on , do most of the time, implement the equals()comparison a. When equality based on is required b, simply write a separate method for it. The problem is that I have to reinvent the wheel to compare sets and lists.

What are the pros and cons of the above? Is there any other alternative?

Is the class Xerroneous in the first place? Can I implement this better?

+4
source share
1 answer

Solution 0: Refactoring

, , - . , , , , .

1: ""

, , - . , X , "". - . , . , . ( , Xa Xb , .) , -, , , , .

, , , . , , , X . , . . , .

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);
    }

}

, .

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

public final class Main {

    public static void main(final String[] args) {
        final Set<X> theAs = new HashSet<>();
        final Set<X> theBs = new TreeSet<>();
        theAs.add(new X("alpha", 1, X.Genders.A));
        theAs.add(new X("beta",  1, X.Genders.A));
        theAs.add(new X("gamma", 2, X.Genders.A));
        theAs.add(new X("delta", 2, X.Genders.A));
        System.out.println("These are the As:\n");
        for (final X x : theAs) {
            System.out.println(x);
        }
        System.out.println();
        {
            final Iterator<X> iter = theAs.iterator();
            while (iter.hasNext()) {
                final X x = iter.next();
                iter.remove();  // remove before changing gender
                x.setGender(X.Genders.B);
                theBs.add(x);
            }
        }
        theBs.add(new X("alpha", 3, X.Genders.B));
        theBs.add(new X("alpha", 4, X.Genders.B));
        System.out.println("These are the Bs:\n");
        for (final X x : theBs) {
            System.out.println(x);
        }
    }
}

:

These are the As:

{a: "alpha", b: 1, gender: A}
{a: "delta", b: 2, gender: A}
{a: "beta", b: 1, gender: A}
{a: "gamma", b: 2, gender: A}

These are the Bs:

{a: "alpha", b: 1, gender: B}
{a: "delta", b: 2, gender: B}
{a: "alpha", b: 3, gender: B}
{a: "alpha", b: 4, gender: B}

2:

( , ) new "", .

. (, , .)

public interface X {

    public String getA();

    public Integer getB();
}

, , , . , ( ) (, , final). equals hashCode Comparable, "" , Object. , (. ).

public final class BasicX implements X {

    private final String a;

    private final Integer b;

    public BasicX(final String a, final Integer b) {
        if (a == null) {
            throw new NullPointerException("a");
        }
        if (b == null) {
            throw new NullPointerException("b");
        }
        this.a = a;
        this.b = b;
    }

    @Override
    public String getA() {
        return this.a;
    }

    @Override
    public Integer getB() {
        return this.b;
    }

    @Override
    public String toString() {
        return String.format("{a: \"%s\", b: %d}", this.a, this.b);
    }

    // Note: No implementation of equals() and hasCode().
}

- . : Xa Xb. ( ) X, , equals hashCode Comparable.

, - .

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());
    }
}

, , ( , ) Xb, , .

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);
        }
    }
}

:

These are the As:

{a: "alpha", b: 1}
{a: "delta", b: 3}
{a: "beta", b: 2}
{a: "gamma", b: 2}

These are the Bs:

{a: "alpha", b: 1}
{a: "beta", b: 2}
{a: "beta", b: 3}

, : Xb Xa s. Xa Xb BasicX s. " Xa Xb" , , ,

Xb a2b(final Xa xa) {
    return new Xb(xa.getX());
}

Xa b2a(final Xb xb) {
    return new Xa(xb.getX());
}

. DecoratedX.getX() public, . ( Xa Xb: X. , .)

+2

All Articles