Overview
The Card class is a simple, immutable representation of a standard playing card. It combines a rank (card value) with a suit to form a complete card in the Blackjack game. As a Java record, it provides automatic implementations of equals, hashCode, and toString methods.
Class Structure
public record Card(Rank rank, Suit suit) {
public Card {
Objects.requireNonNull(rank, "rank");
Objects.requireNonNull(suit, "suit");
}
}
Fields
The rank (value) of the card. Determines the card’s numeric value in Blackjack scoring. Cannot be null.Possible values: TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE
The suit of the card. In Blackjack, suits do not affect scoring but are tracked for completeness. Cannot be null.Possible values: CLUBS, DIAMONDS, HEARTS, SPADES
Validation
The compact constructor enforces non-null constraints:
public Card {
Objects.requireNonNull(rank, "rank");
Objects.requireNonNull(suit, "suit");
}
Attempting to create a card with null rank or suit throws NullPointerException.
Rank Enum
The Rank enum defines card values and their Blackjack scoring:
public enum Rank {
TWO(2), THREE(3), FOUR(4), FIVE(5), SIX(6), SEVEN(7),
EIGHT(8), NINE(9), TEN(10),
JACK(10), QUEEN(10), KING(10),
ACE(11);
private final int defaultValue;
Rank(int defaultValue) { this.defaultValue = defaultValue; }
public int getDefaultValue() { return defaultValue; }
public boolean isAce() { return this == ACE; }
}
Rank Values
Numeric cards (2-9) have face value: 2, 3, 4, 5, 6, 7, 8, 9
All face cards and tens are worth 10 points
Default value is 11 points. The Hand class handles automatic conversion to 1 point when necessary to avoid busting.
Rank Methods
getDefaultValue() - Returns the base Blackjack value
Rank.FIVE.getDefaultValue(); // 5
Rank.KING.getDefaultValue(); // 10
Rank.ACE.getDefaultValue(); // 11
isAce() - Checks if the rank is an Ace
Rank.ACE.isAce(); // true
Rank.KING.isAce(); // false
This method is used by the Hand class to implement flexible Ace scoring (11 or 1).
Suit Enum
The Suit enum defines the four standard playing card suits:
public enum Suit {
CLUBS, DIAMONDS, HEARTS, SPADES
}
Suit Values
Note: In Blackjack, suits have no impact on scoring or gameplay. They are included for completeness and potential future features (e.g., displaying card images).
Usage Examples
Creating Cards
// Create specific cards
Card aceOfSpades = new Card(Rank.ACE, Suit.SPADES);
Card kingOfHearts = new Card(Rank.KING, Suit.HEARTS);
Card fiveOfClubs = new Card(Rank.FIVE, Suit.CLUBS);
// Access card properties
System.out.println(aceOfSpades.rank()); // ACE
System.out.println(aceOfSpades.suit()); // SPADES
Getting Card Values
Card card = new Card(Rank.QUEEN, Suit.DIAMONDS);
int value = card.rank().getDefaultValue();
System.out.println(value); // 10
Card ace = new Card(Rank.ACE, Suit.CLUBS);
if (ace.rank().isAce()) {
System.out.println("This is an Ace!");
}
Card Equality
As a record, Card automatically implements value-based equality:
Card card1 = new Card(Rank.SEVEN, Suit.HEARTS);
Card card2 = new Card(Rank.SEVEN, Suit.HEARTS);
Card card3 = new Card(Rank.SEVEN, Suit.SPADES);
card1.equals(card2); // true (same rank and suit)
card1.equals(card3); // false (different suit)
Standard Deck Generation
The Deck class uses Card to generate a standard 52-card deck:
public static Deck standardShuffled(){
List<Card> all = new ArrayList<>();
for (Suit s : Suit.values()) {
for (Rank r : Rank.values()) {
all.add(new Card(r, s));
}
}
Collections.shuffle(all);
return new Deck(all);
}
This creates all 52 combinations:
- 4 suits × 13 ranks = 52 cards
Immutability
The Card record is fully immutable:
- Records are implicitly final (cannot be subclassed)
- All fields are final
- No setter methods exist
Rank and Suit are enums (inherently immutable)
Benefits
- Thread Safety - Cards can be safely shared across threads
- Hashable - Can be used as Map keys or in Sets reliably
- Cacheable - Same cards are always equal (value semantics)
- No Defensive Copying - Can be freely passed around without cloning
String Representation
Records automatically generate a readable toString() method:
Card card = new Card(Rank.ACE, Suit.SPADES);
System.out.println(card); // "Card[rank=ACE, suit=SPADES]"
Usage in Hand Scoring
Cards are primarily consumed by the Hand class for scoring:
public int score() {
int total = 0;
int aces = 0;
for (Card c: cards) {
total += c.rank().getDefaultValue();
if (c.rank().isAce()) aces++;
}
// Adjust Aces from 11 to 1 if needed
while (total > 21 && aces > 0) {
total -= 10;
aces--;
}
return total;
}
Complete Example
// Create a hand of cards
Card card1 = new Card(Rank.ACE, Suit.HEARTS);
Card card2 = new Card(Rank.KING, Suit.SPADES);
// Check values
System.out.println(card1.rank().getDefaultValue()); // 11
System.out.println(card2.rank().getDefaultValue()); // 10
System.out.println(card1.rank().isAce()); // true
// This would be a Blackjack! (21 with 2 cards)
int handValue = card1.rank().getDefaultValue() + card2.rank().getDefaultValue();
System.out.println(handValue); // 21
// Cards are immutable and comparable
Card duplicate = new Card(Rank.ACE, Suit.HEARTS);
System.out.println(card1.equals(duplicate)); // true
System.out.println(card1 == duplicate); // false (different objects)
Testing Support
The simple structure makes cards easy to construct for testing:
// Create specific test scenarios
Card bustCard = new Card(Rank.KING, Suit.CLUBS); // High value card
Card lowCard = new Card(Rank.TWO, Suit.DIAMONDS); // Low value card
Card ace = new Card(Rank.ACE, Suit.SPADES); // Flexible value card
// Build custom decks for unit tests
List<Card> testDeck = List.of(
new Card(Rank.ACE, Suit.HEARTS),
new Card(Rank.KING, Suit.SPADES),
// ... more cards
);
Deck customDeck = Deck.fromCards(testDeck);
- Hand - Collection of cards with scoring logic
- Deck - Manages a collection of cards for drawing
- Game - Uses cards through Deck and Hand
- Rank - Enum defining card ranks and values (documented above)
- Suit - Enum defining card suits (documented above)