2

Suppose I have a Point class with fields x,y,z,name. Objects of this class must be inserted in a Hash Set in two different ways depending on the user choice:

  • equals() checks equality of x,y,z fields
  • equals() checks equality of x,y,z,name fields

So I wonder what is the better way to organize such equals() redefinition on the fly during program execution?

Oswald
  • 31,254
  • 3
  • 43
  • 68
Roman
  • 199
  • 1
  • 5
  • You can't have two `equals()` in a class. But you can inherit a new class and override `equals()`. – Aman Arora Jan 27 '14 at 09:53
  • 3
    Sounds like you could either use 2 methods, `equals()` and `equalsWithName()`, or 2 classes, `Point` and `NamedPoint`... – vikingsteve Jan 27 '14 at 09:54
  • You could have a sub-class which extends the equal method of its super class by comparing the names. – A4L Jan 27 '14 at 09:56
  • IMO, equals should check all members. For any other specific checks, you'd compare the values manually. – Ben Dale Jan 27 '14 at 09:56
  • @vikingsteve HashSet will not call `equalsWithName()`.It can only call `equals()`. Right? – Aman Arora Jan 27 '14 at 10:01
  • If you can use `TreeSet`, then using more than one `comparator` strategy, you can solve the problem easily. – Aman Arora Jan 27 '14 at 10:12
  • @AmanArora Right. Thats why my suggestion for 2 classes might be better. – vikingsteve Jan 27 '14 at 10:12
  • @AmanArora that sounds like a great idea, care to add it as an answer? – vikingsteve Jan 27 '14 at 10:16
  • @vikingsteve the question is specific to `HashSet`. – Aman Arora Jan 27 '14 at 10:17
  • If you have the option to use an alternative class then [HashedMap](http://commons.apache.org/proper/commons-collections/javadocs/api-release/org/apache/commons/collections4/map/HashedMap.html) from Apache commons collections provides hooks that let you subclass it and change the default equality and hash code computation routines to use something other than the keys' own `.equals()` and `.hashCode()` methods. – Ian Roberts Jan 27 '14 at 10:35

2 Answers2

3

You can create 2 Classes: Point and NamedPoint and override the equals() and hashcode() methods.

Here is an example in pseudo code:

class Point{
   String name
   int x,y,z
   equals{
     ...
   }
   hashCode{
     ...
   }
}

class NamedPoint extends Point{
   equals{
   }
   hashCode{
   }
}

Another option is to use one class, but add a new boolean switch, which changes the behavior of equals and hashCode.

P.S. I recommend you to read What issues should be considered when overriding equals and hashCode in Java?. You must be careful overriding these methods. It is useful to use the eclipse feature "Source --> Generate hashCode() and equals().." and after that to change the behavior of the generated methods.

P.S.2: Grigory Kalabin's link describes, what are the problems that might occure doing this.

Martin
  • 157
  • 1
  • 3
  • 8
aphex
  • 3,372
  • 2
  • 28
  • 56
  • 2
    Also be aware that such hierarchy may broke the symmetric property of equals: http://www.billthelizard.com/2008/08/implementing-equals-in-java.html – Gregory Kalabin Jan 27 '14 at 10:06
  • For example, shouldn't `((Point) p).equals(anotherPoint)` be the same as `((NamedPoint) p).equals(anotherPoint)`, and `point.equals(namedPoint)` be the same as `namedPoint.equals(point)`? – vikingsteve Jan 27 '14 at 10:13
1

If you can change from using HashSet to a TreeSet and add a custom comparator:

public class Point implements Comparable<Point> {
    private double x;
    private double y;
    private double z;
    private String name;

    public Point( final double x, final double y, final double z, final String name ) {
        setX( x );
        setY( y );
        setZ( z );
        setName( name );
    }

    public boolean equals( final Point point ){
        final boolean isEqual =
                (   this.getX() == point.getX()
                &&  this.getY() == point.getY()
                &&  this.getZ() == point.getZ() )
            ||  (   this.getName().equals( point.getName() ) );
    //  System.out.println( this.toString() + " == " + point.toString() + " = " + isEqual );
        return isEqual;
    }

    /// @return the x
    public double getX() { return x; }
    /// @return the y
    public double getY() { return y; }
    /// @return the z
    public double getZ() { return z; }
    /// @return the name
    public String getName() { return name; }
    /// @param x the x to set
    public void setX(final double x) { this.x = x; }
    /// @param y the y to set
    public void setY(final double y) { this.y = y; }
    /// @param z the z to set
    public void setZ(final double z) { this.z = z; }
    /// @param name the name to set
    public void setName(final String name) { this.name = name; }

    public String toString() {
        final StringBuffer str = new StringBuffer();
        str.append( '(' );
        str.append( getX() );
        str.append( ',' );
        str.append( getY() );
        str.append( ',' );
        str.append( getZ() );
        str.append( ',' );
        str.append( getName() );
        str.append( ')' );
        return str.toString();
    }

    public double distanceFromOriginSquared(){
        return this.getX()*this.getX()
                + this.getY()*this.getY()
                + this.getZ()*this.getZ();
    }

    @Override
    public int compareTo( final Point point ) {
        if ( this.getName().equals( point.getName() ) )
            return 0;
        final double td = this.distanceFromOriginSquared();
        final double pd = point.distanceFromOriginSquared();
        if ( td < pd ) return -1;
        if ( td > pd ) return +1;
        if ( this.getX() < point.getX() ) return -1;
        if ( this.getX() > point.getX() ) return +1;
        if ( this.getY() < point.getY() ) return -1;
        if ( this.getY() > point.getY() ) return +1;
        return 0;   
    }
}

Running this:

public static void main( final String[] args ){
    Point[] pts = {
            new Point( 1, 1, 1, "1" ),
            new Point( 2, 2, 2, "2" ),
            new Point( 3, 3, 3, "3" ),
            new Point( 1, 1, 1, "4" ),
            new Point( 4, 4, 4, "2" )
    };

    TreeSet<Point> ps = new TreeSet<Point>();
    for ( Point p : pts )
        ps.add( p );
    System.out.println( ps );
}

Gives an output of

[(1.0,1.0,1.0,1), (2.0,2.0,2.0,2), (3.0,3.0,3.0,3)]

So the last two values are not entered into the TreeSet as they are rejected by the comparator.

MT0
  • 143,790
  • 11
  • 59
  • 117