edit further explanation for an enum
-approach
Say you have a Rank
and Suit
enum
:
enum Rank {
ONE,
TWO,
// ...
JACK,
QUEEN,
KING,
ACE; // opt-ing to 'rank' ACE as the highest
public int getValue() {
return ordinal() + 1;
}
}
enum Suit {
CLUB,
DIAMOND,
HEART,
SPADE;
}
Some benefits of using enum
s are:
- Values are
Comparable
to each other. - Efficient use in
Map
s andSet
s viaEnumMap
andEnumSet
classes, when required. - built-in
toString()
representation. - built-in index-based lookup for values via
ordinal()
. - built-in
String
-based lookup for values viavalueOf(String)
. - safe
==
-based comparison.
For your Card
class, you can then do something like:
// note: final modifier to indicate this class cannot be subclassed
// in order to preserve the immutable features
public final class Card {
private final Rank rank;
private final Suit suit;
private final String toString;
private final int hashCode;
public Card(Rank rank, Suit suit) {
this.rank = rank;
this.suit = suit;
this.toString = getToString(rank, suit);
// Objects.hash() is from Java 7
this.hashCode = Objects.hash(rank, suit);
}
private static String getToString(Rank rank, Suit suit) {
// code review comment: will return e.g. "Seven of Hearts"
return String.format("%s of %ss",
capitalizeFirstLetter(rank.toString()),
capitalizeFirstLetter(suit.toString()));
}
@Override
public String toString() {
return toString;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Card)) {
return false;
}
Card otherCard = (Card) o;
return otherCard.rank == rank && otherCard.suit == suit;
}
}
I hope this kind of illustrates that your Value
/Rank
, Suit
and Card
classes can have similar functionality with less boilerplate-like template code... The idea behind pre-computing the String
representation and hash code first is to avoid repeated computations, should the methods be called frequently. This works especially well for immutable classes like the Card
class as shown.