Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java equals when equality can't be determined

Tags:

java

equals

I'm wondering what's the proper Java programming paradigm for overriding the equals (and hashCode) methods of an object of class C in cases where either (a) there's not enough information to determine if two instances of C are equal or (b) the calling method should not be able to determine if two instance of C are equal.

In my project, for example, I have a PlayingCard class. It seems to me that if a PlayingCard is face up, then calling methods should have access to its properties, but if it's face down, then those properties should remain unknown:

class PlayingCard {
    private Rank rank;
    private Suit suit;
    private boolean isFaceDown;

    public PlayingCard(Rank rank, Suit suit, boolean isFaceDown) {
        this.rank = rank;
        this.suit = suit;
        this.isFaceDown = isFaceDown;
    }

    public Rank getRank() { return isFaceDown ? null : rank; }

    public Suit getSuit() { return isFaceDown ? null : suit; }

It also seems like, for the sake of the Java Collections Framework, two playing cards should be equal if they have the same rank and suit:

    public boolean equals(Object obj) {       // attempt #1
        if(this == obj) return true;
        if(obj == null) return false;
        if(!(obj instanceof PlayingCard)) return false;
        PlayingCard other = (PlayingCard) obj;
        if(rank != other.rank) return false;
        if(suit != other.suit) return false;
        return true;
    }
 }

But that reveals too much information:

class Malicious {

    public Rank determineRankOfFaceDownCard(PlayingCard faceDownCard) {
        Set<PlayingCard> allCards = /* a set of all 52 PlayingCards face up */;
        for(PlayingCard c : allCards) {
            if(c.equals(faceDownCard)) {
                return c.getRank();
            }
        }
        return null;
    }
}

Using the getRank and getSuit` methods doesn't seem to work either:

    public boolean equals(Object obj) {       // attempt #1
        if(this == obj) return true;
        if(obj == null) return false;
        if(!(obj instanceof PlayingCard)) return false;
        PlayingCard other = (PlayingCard) obj;
        if(getRank() != other.getRank()) return false;
        if(getSuit() != other.getSuit()) return false;
        return true;
    }
}

/* outside the PlayingCard class */

Set<PlayingCard> s = new HashSet<PlayingCard>();
s.add(new PlayingCard(Rank.ACE, Suit.SPADES, true));
s.contains(new PlayingCard(Rank.TWO, Rank.HEARTS, true)); // returns true

How have other developers dealt with this situation? Is this a situation where throwing some sort of RuntimeException would be appropriate? Thanks for any input and ideas.

like image 506
jay Avatar asked Jun 19 '12 15:06

jay


2 Answers

You could add this condition in the equals method:

if(this.isFaceDown || other.isFaceDown) return false;

I think it's the only way to completely hide the card if it's faced down. The problem is that when adding it to a set you can have duplicates if the cards you're adding are faced down.

like image 161
tibtof Avatar answered Sep 17 '22 06:09

tibtof


I am not sure if I am missing something, but maybe there should be some logic outside of the class holding your comparator that will not compare cards when not visible?

like image 40
John Kane Avatar answered Sep 17 '22 06:09

John Kane