CardCollection and Inheritance

Deck and Pile were two versions of the same code, so this lesson merges them into a CardCollection and then uses `extends` to put back the differences: a Deck is a CardCollection with a different constructor, and a Hand is a CardCollection with one extra method. Plus the substitution rule that makes it all worth it. Follows Think Java 2e, Chapter 14 (Extending Classes), Sections 14.1-14.3, pp. 233-240, cross-referenced against The Java Tutorials — Inheritance.

Subject: Java · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. CardCollection and Inheritance

Title

Think Java 2e · Chapter 14 · Extending Classes

Sections 14.1-14.3 · pp. 233-240

2. What you will be able to do

Objectives

This lesson follows Think Java 2e, Chapter 14 (Extending Classes), Sections 14.1-14.3, pp. 233-240. Everything on these slides can be checked against those pages.

1. Recognise duplication between two classes as the signal to extract a common superclass.

2. Write a class that extends another, and use super to invoke the superclass constructor.

3. Explain what is inherited and what is not.

4. State the substitution rule: a subclass object can be used wherever the superclass is expected.

5. Explain why the reverse is not true, using the cat–mammal–animal analogy.

6. Write a method that moves cards between two collections of possibly different subclasses.

3. Retrieve before you read

Warm-up

The end of Lesson 13b noticed something. This chapter acts on it.

Discussion prompt

From Lesson 13b: what did Deck and Pile have in common, and what was the one thing that forced them to use different storage? And what is a wrapper method?

Hint: Both hold cards. One changes size.

Answer:

Both are collections of cards with print, add and remove operations. Deck used a Card array because its size never changes; Pile used an ArrayList because a pile grows and shrinks. A wrapper method does little more than call another method, giving it a name that suits the class.

Furthermore, Deck and Pile are essentially two versions of the same code: one based on arrays, and the other based on ArrayList. It would be helpful to combine their features into one class that meets the needs of both. That is this lesson.

4. One class, then the differences

Concept

To implement Crazy Eights, we need to represent a deck of cards, a discard pile, a draw pile, and a hand for each player. Four things, all collections of cards, all slightly different — which is exactly the situation inheritance exists for.

Figure (svg): A UML diagram showing Deck and Hand both extending CardCollection with a hollow inheritance arrow

Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 14 (Extending Classes), Sections 14.1-14.3, pp. 233-240 — Chapter 14 opens on printed page 233.

5. Building CardCollection

Section

Section 14.1

6. One class that meets the needs of both

Concept

The Deck and Pile classes from the previous chapter meet some of these requirements. But unless we make some changes, neither of them represents a hand of cards very well. So we start over with one class.

public class CardCollection {
    private String label;
    private ArrayList<Card> cards;

    public CardCollection(String label) {
        this.label = label;
        this.cards = new ArrayList<Card>();
    }
}
attributepurpose
labeltells one collection from another
cardsan ArrayList, so the size can change

Since this class will represent different piles and hands of cards, we'll add a label attribute to tell them apart. The storage is ArrayList rather than an array, because a hand and a pile both change size — the requirement that split Deck from Pile in the first place.

7. Dropping the `this`

Notation

Section 14.1 makes a stylistic change and says exactly why it waited until now.

Annotate

  • this was a teaching aid, not a requirement — cards and this.cards mean the same thing inside an instance method.
  • There is one place it is still required: when a parameter shadows an instance variable, as in the constructor's this.label = label.
  • Look at the constructor above — it keeps this for exactly that reason, while addCard drops it.
  • Dropping it is a judgement call. With this everywhere, an attribute is obvious; without it, you have to know what is declared where.
  • The book drops it now because you no longer need the training wheels — by Chapter 14 you can tell an attribute from a local at a glance.

The rule that never changes: this is required wherever a name would otherwise be shadowed. Everywhere else it is style, and Lesson 11a's shadowing example is still the case that matters.

8. An overloaded popCard

Worked example

Two versions of the same name, and the second one exists for a reason worth reading.

public Card popCard(int i) {
    return cards.remove(i);
}

public Card popCard() {
    int i = cards.size() - 1;   // from the end of the list
    return popCard(i);
}
callremovescost
popCard(0)the first cardshifts every later card left
popCard(3)the fourth cardshifts the rest left
popCard()the last cardnothing to shift

The indexed version removes any card.

Why: It takes an index, removes the card at that location, and shifts the following cards left to fill the gap.

The no-argument version removes the last.

Why: If we are dealing cards from a shuffled deck, we don't care which card gets removed. It is most efficient to choose the last one, so we don't have to shift any cards left.

The second calls the first.

Why: return popCard(i); — one implementation, two entry points.

That is overloading, from Lesson 11a.

Why: Same name, different parameter lists, and Java picks by the call.

Verify: Pop from a 52-card collection both ways and reason about how many elements move each time.

Why: When the order does not matter, removing from the end is free and removing from the front costs a shift of the whole list. That is a real performance argument, and it is the reason the convenient version is the one that takes no argument — the cheap thing should also be the easy thing to type.

9. Why does popCard() take from the end?

Prediction

It could just as easily take from the front.

public Card popCard() {
    int i = cards.size() - 1;
    return popCard(i);
}
remove fromelements that must shift
index 0all the rest
the last indexnone

Predict first

What is the reason?

  • Removing from the end shifts nothing, and when the deck is shuffled it does not matter which card you get
  • The last card is always the highest
  • ArrayList cannot remove from the front
  • It keeps the collection sorted

Correct: Removing from the end shifts nothing, and when the deck is shuffled it does not matter which card you get

Why: It is most efficient to choose the last one, so we don't have to shift any cards left. The shuffling is what makes the choice free: any card is as random as any other, so you may as well take the cheap one.

10. The wrapper methods

Concept

To access the elements of an ArrayList, you can't use the array [] operator. Instead, you have to use the methods get and set. So CardCollection provides wrappers.

public boolean isEmpty() {
    return cards.isEmpty();
}

public int size() {
    return cards.size();
}

public Card getCard(int i) {
    return cards.get(i);
}

public Card lastCard() {
    int i = cards.size() - 1;
    return cards.get(i);
}
methodwrapswhy the name is better
isEmpty()cards.isEmpty()asks about the collection, not the list
size()cards.size()same
getCard(i)cards.get(i)says what kind of thing comes back
lastCard()cards.get(size - 1)hides the size − 1

lastCard gets the last card but doesn't remove it — which is what takeTurn will need to read the top of the discard pile. Note that it also hides size() - 1, so that off-by-one lives in one place.

11. Providing a wrapper for set

Trap

The trap

A set wrapper would let anyone overwrite any card.

// NOT provided, deliberately:
public void setCard(int i, Card card) {
    cards.set(i, card);
}

// which would allow:
hand.setCard(0, new Card(8, 2));   // conjure an eight from nowhere
with setCardconsequence
overwrite any positiona card can be replaced by any other
no card is removedcards can be duplicated or destroyed
the deck's contentsno longer a permutation of 52 cards

The invariant that matters in a card game is that the 52 cards are conserved — moved around, never created or destroyed. A public set breaks it silently.

The fix

Provide only the modifiers the class can vouch for.

public void swapCards(int i, int j) {
    Card temp = cards.get(i);
    cards.set(i, cards.get(j));
    cards.set(j, temp);
}

public void shuffle() {
    Random random = new Random();
    for (int i = cards.size() - 1; i > 0; i--) {
        int j = random.nextInt(i + 1);
        swapCards(i, j);
    }
}
modifier providedwhat it preserves
popCard(i) and popCard()removes one card — nothing is created
addCard(card)adds one card — nothing is destroyed
swapCards(i, j)exchanges two — the multiset is unchanged
setCardnot provided

In order to control the ways card collections are modified, we don't provide a wrapper for set. The only modifiers we provide are the two versions of popCard and the following version of swapCards. Every one of them preserves the cards; set would not.

12. Which methods change the collection?

Definition probe

Only some of CardCollection's methods are modifiers.

Sort into buckets

Sort each method.

modifies
popCard(); swapCards(i, j)
reads only
getCard(i); lastCard()
mod
It changes what the collection holds or where — and each provided modifier preserves the cards themselves rather than creating or destroying any.
read
It returns information without altering the collection, so it can be called freely without affecting the game state.

13. Wrap the ArrayList

Fill the middle

getCard hides the list's own method.

Fill in the blanks

public Card getCard(int i) get}(i);
}

public Card lastCard() -} 1;
return cards.get(i);
}

Why: An ArrayList has no [] operator, so element access goes through get. lastCard hides the size() - 1 inside the class, which means that particular off-by-one appears exactly once in the program rather than at every call site.

14. Why no wrapper for set?

Explain it to yourself

get has one; set does not.

Discussion prompt

The class wraps get but deliberately not set. What can a program guarantee about a CardCollection because of that omission?

Hint: Count the cards before and after any operation.

Answer:

That cards are conserved. Every modifier the class provides either moves one card out, moves one in, or exchanges two — so the collection's contents are always a rearrangement of cards that already existed.

set would break that: it overwrites, so one card vanishes and another appears. Nothing in a card game should be able to do that.

A class's guarantees come from what it refuses to offer, not only from what it does. Lesson 12a's missing setters made Card immutable by exactly the same move.

15. Inheritance

Section

Section 14.2

16. A subclass extends an existing class

Concept

At this point we have a class that represents a collection of cards. However, each kind of collection will be slightly different. Rather than add every possible feature to CardCollection, we can use inheritance to define subclasses.

public class Deck extends CardCollection {

    public Deck(String label) {
        super(label);
        for (int suit = 0; suit <= 3; suit++) {
            for (int rank = 1; rank <= 13; rank++) {
                addCard(new Card(rank, suit));
            }
        }
    }
}
partmeaning
extends CardCollectionDeck has everything CardCollection has
super(label)invoke the superclass constructor
the nested loopthe one thing a Deck adds
everything elseinherited, not rewritten

inheritance — The ability to define a new class that has the same instance variables and methods of an existing class.

subclass — A class that inherits from, or extends, an existing class.

superclass — An existing class that is extended by another class.

A subclass is a class that extends an existing class; that is, it has the attributes and methods of the existing class, plus more. Note how short Deck is: the only additional method in Deck, at least for now, is a constructor.

17. Four facts about extends

Notation

Section 14.2 states the rules compactly. All four matter later.

Annotate

  • One superclass only. Java has no multiple inheritance — a class has exactly one parent, and every chain ends at Object.
  • Every class you have written extends Object without saying so. Deck extends CardCollection, which in turn extends Object.
  • That explains Lesson 11b's mystery: the reason an object printed as Card@1a2b3c4d before you wrote a toString is that it inherited Object's.
  • Constructors are not inherited — which is why Deck has to write its own, and why it needs super to run the one it did not inherit.
  • Everything else public is inherited, so Deck has addCard, shuffle, size and the rest without a line of code.

The second and third notes retroactively explain something you met in Chapter 11. toString was not a special name the compiler knows about — it was a method you were overriding all along.

18. What super does

Worked example

The first line of the constructor uses super, which is a keyword that refers to the superclass of the current class.

public Deck(String label) {
    super(label);         // run CardCollection's constructor first
    for (int suit = 0; suit <= 3; suit++) {
        for (int rank = 1; rank <= 13; rank++) {
            addCard(new Card(rank, suit));   // an inherited method
        }
    }
}
stepwhat exists afterwards
super(label) calledlabel set, cards is an empty ArrayList
the loop runs52 Cards added via the inherited addCard
constructor returnsa Deck that is also a CardCollection

super used as a method invokes a constructor.

Why: When super is used as a method, as in this example, it invokes the constructor of the superclass.

That constructor sets up the inherited state.

Why: super invokes the CardCollection constructor, which initializes the attributes label and cards.

Then the subclass's own work happens.

Why: When it returns, the Deck constructor continues with the loop.

addCard needs no qualification.

Why: It is inherited, so it is called as if it were Deck's own — which, in every sense that matters, it is.

Verify: Remove the super(label); line and see the constructor fail — cards would never be created.

Why: The order is not optional. The superclass's constructor must run first, because the subclass's own code — here addCard — depends on the state it sets up. A Deck that added cards before cards existed would throw a NullPointerException.

19. What does Deck inherit?

Prediction

Deck's body contains only a constructor.

public class Deck extends CardCollection {
    public Deck(String label) { ... }
}
Deck deck = new Deck("Deck");
deck.shuffle();
methoddefined in
shuffleCardCollection
Deck's own methodsjust the constructor

Predict first

Does deck.shuffle() work?

  • Yes — Deck inherits every public method of CardCollection
  • No — Deck must define shuffle itself
  • No — shuffle is private
  • Only if Deck declares extends twice

Correct: Yes — Deck inherits every public method of CardCollection

Why: A Deck object has the same instance variables and methods as a CardCollection, so shuffle, addCard, size and the rest are available without a line of code in Deck. Constructors are the one exception — those are not inherited, which is why Deck has to write one.

20. Hand: the other subclass

Concept

We could define two classes, one for hands and one for piles, but there is not much difference between them. So we'll use one class, called Hand, for both hands and piles.

public class Hand extends CardCollection {

    public Hand(String label) {
        super(label);
    }

    public void display() {
        System.out.println(getLabel() + ": ");
        for (int i = 0; i < size(); i++) {
            System.out.println(getCard(i));
        }
        System.out.println();
    }
}
method used in displaycomes from
getLabel()inherited from CardCollection
size()inherited
getCard(i)inherited
display()Hand's own — the only thing it adds

In summary, a Deck is just like a CardCollection, but it provides a different constructor. And a Hand is just like a CardCollection, but it provides an additional method, display. Two subclasses, twelve lines between them, and everything else shared.

21. Forgetting that constructors are not inherited

Trap

The trap

A subclass with no constructor cannot be given a label.

public class Hand extends CardCollection {
    public void display() { ... }
    // no constructor
}

Hand hand = new Hand("Hand");     // compile error
inherited?member
yespublic methods — addCard, size, shuffle
yesthe inherited attributes exist in every object
noconstructors

Constructors are not inherited, so Hand(String) does not exist unless Hand writes it. And because CardCollection has no no-argument constructor, Java cannot supply a default either.

The fix

Write a constructor that calls super.

public class Hand extends CardCollection {

    public Hand(String label) {
        super(label);
    }

    public void display() { ... }
}
linedoes
public Hand(String label)declares the constructor clients call
super(label)runs CardCollection's constructor
nothing elseHand adds no state of its own

A constructor that does nothing but call super is not pointless — it is what makes the superclass's constructor reachable under the subclass's name. Hand's is three lines and all three are necessary.

22. Inherited or not?

Definition probe

One category is the exception.

Sort into buckets

Sort each member of CardCollection.

inherited by Deck
public void addCard(Card); public int size(); public void shuffle()
not inherited
public CardCollection(String label)
yes
All public methods and attributes are inherited, so the subclass has them without rewriting anything.
no
Constructors are not inherited — a subclass must write its own and use super to run the superclass's.

23. Write the subclass header

Fill the middle

Hand is a CardCollection with one extra method.

Fill in the blanks

public class Hand extends CardCollection super}(label);
}
}

Why: extends declares the inheritance; super(label) invokes the superclass constructor, which is what creates the ArrayList the inherited methods depend on. It must come first — the subclass's own code runs on state the superclass set up.

24. Why not just add display to CardCollection?

Socratic

Then Hand would not be needed at all.

Discussion prompt

display is eight lines. Putting it in CardCollection would delete the Hand class entirely. Why does the book keep them separate?

Hint: What would a Deck then be able to do?

Answer:

Because a Deck would then have a display method too — and printing 52 cards is not something a deck should offer. The method would exist everywhere and be meaningful in one place.

Rather than add every possible feature to CardCollection, the superclass holds only what all collections genuinely share. Each subclass adds what only it needs.

A superclass that accumulates every subclass's features stops being an abstraction and becomes a grab bag. Keeping it minimal is what makes extends worth using rather than just copying code.

25. Dealing cards

Section

Section 14.3

26. One method moves cards anywhere

Concept

To begin the game, we need to deal cards to each of the players. And during the game, we need to move cards between hands and piles. One method in CardCollection meets both requirements.

public void deal(CardCollection that, int n) {
    for (int i = 0; i < n; i++) {
        Card card = popCard();
        that.addCard(card);
    }
}

public void dealAll(CardCollection that) {
    int n = cards.size();
    deal(that, n);
}
parameterrole
thisthe collection cards come from
thatthe collection they go to
nhow many to move

The deal method removes cards from the collection it is invoked on, this, and adds them to the collection it gets as a parameter, that. Naming the parameter that is Lesson 11b's convention — the other one — and it makes the direction of the transfer readable at a glance.

27. Deal moves references between collections

Picture it

Nothing is created or destroyed. A card leaves one ArrayList and joins another.

Figure (svg): A pipeline showing a card leaving one collection with popCard and entering another with addCard

dealAll is deal with n set to everything, so the two methods are one implementation. Note that dealAll reads cards.size() directly — it is inside CardCollection, so it may.

28. Dealing a hand and a draw pile

Worked example

At this point, we can create a Deck and start dealing cards.

Deck deck = new Deck("Deck");
deck.shuffle();

Hand hand = new Hand("Hand");
deck.deal(hand, 5);
hand.display();

Hand drawPile = new Hand("Draw Pile");
deck.dealAll(drawPile);
System.out.printf("Draw Pile has %d cards.\n", drawPile.size());
afterdeckhanddrawPile
new Deck("Deck")52——
deck.deal(hand, 5)475—
deck.dealAll(drawPile)0547

Create and shuffle the deck.

Why: Because the deck is shuffled randomly, you should get a different hand each time you run this example.

Deal five to a hand.

Why: deck.deal(hand, 5) — five popCards and five addCards.

Display it.

Why: hand.display() — the method Hand adds.

Deal the rest to a draw pile.

Why: Draw Pile has 47 cards.

Verify: Run it twice and confirm the hand differs each time while the counts do not.

Why: Five plus forty-seven is fifty-two. The counts are the invariant, and the whole point of providing only conserving modifiers in Section 14.1 was to make that arithmetic always work out.

29. Is deck.deal(hand, 5) legal?

Prediction

deal declares a CardCollection parameter.

public void deal(CardCollection that, int n) { ... }
Hand hand = new Hand("Hand");
deck.deal(hand, 5);
Hand extendsso a Hand is
CardCollectionalso a CardCollection

Predict first

Does it compile?

  • Yes — a Hand is also a CardCollection, so it can be used wherever one is expected
  • No — the types do not match exactly
  • Only with a cast to CardCollection
  • Only if Hand overrides deal

Correct: Yes — a Hand is also a CardCollection, so it can be used wherever one is expected

Why: This is the substitution rule that inheritance buys. A Hand object is also considered to be a CardCollection object, so it satisfies the parameter without a cast — which is what makes one deal method serve dealing, drawing and discarding alike.

30. The strange thing about that example

Concept

If you are a careful reader, you might notice something strange. deal declares a CardCollection parameter, and the call passes a Hand.

public void deal(CardCollection that, int n) { ... }

Hand hand = new Hand("Hand");
deck.deal(hand, 5);          // a Hand, not a CardCollection
a method expectingaccepts
CardCollectiona CardCollection, a Deck, or a Hand
Handa Hand only
Decka Deck only

It's because Hand is a subclass of CardCollection, so a Hand object is also considered to be a CardCollection object. If a method expects a CardCollection, you can give it a Hand, a Deck, or a CardCollection. One object, more than one type.

31. Assuming substitution works both ways

Trap

The trap

A CardCollection is not a Hand.

public void showIt(Hand h) {
    h.display();
}

CardCollection cc = new CardCollection("misc");
showIt(cc);                  // compile error
objectis a CardCollection?is a Hand?
new Hand(...)yesyes
new Deck(...)yesno
new CardCollection(...)yesno

But it doesn't work the other way around: not every CardCollection is a Hand, so if a method expects a Hand, you have to give it a Hand, not a CardCollection or a Deck. A plain CardCollection has no display method for the parameter to promise.

The fix

Declare the parameter as loosely as the method allows.

// needs only addCard - so ask for the superclass
public void deal(CardCollection that, int n) { ... }

// needs display - so it must ask for a Hand
public void showIt(Hand h) { h.display(); }
the method usesso the parameter should be
addCard, sizeCardCollection
displayHand
nothing card-specificthe loosest type that works

Ask for the least you need. deal uses only addCard, so declaring CardCollection lets it move cards between a deck, a hand and a pile alike — which is exactly why one method covers dealing, drawing and discarding.

32. Which calls are legal?

Definition probe

Substitution runs one way only.

Sort into buckets

Sort each call, given void f(CardCollection c) and void g(Hand h).

legal
f(new Hand("x")); f(new Deck("d"))
compile error
g(new CardCollection("c")); g(new Deck("d"))
ok
The argument's class extends the parameter's class, so the object really does have every method the parameter promises.
no
The argument is a superclass or a sibling of what the parameter requires, so it may lack methods the method body calls — Java rejects it.

33. Match the object to its types

Matching

One object can belong to more than one type.

Match the pairs

  • a. new Deck("d")
  • b. new Hand("h")
  • c. new CardCollection("c")
  • d. every one of them
  • r1. a Deck and a CardCollection
  • r2. a Hand and a CardCollection
  • r3. a CardCollection only
  • r4. also an Object

Why: Every object belongs to its own class and to every class above it in the chain — and since classes with no extends inherit from java.lang.Object, everything is an Object. That is why Object's toString was what printed Card@1a2b3c4d back in Lesson 11b.

34. Every cat is a mammal

Real world

If it seems strange that an object can belong to more than one type, remember that this happens in real life too.

Discussion prompt

The book's analogy is cats, mammals and animals. Work out which direction the substitution runs, and name a sentence that is true one way and false the other.

Hint: Think about a sign saying no animals against one saying no cats.

Answer:

Every cat is also a mammal, and every mammal is also an animal. But not every animal is a mammal, and not every mammal is a cat.

A rule that applies to animals applies to your cat; a rule about cats says nothing about a dog. The specific satisfies the general, never the reverse.

That is exactly the parameter rule. A method that asks for a CardCollection will accept a Hand; a method that asks for a Hand will not accept a CardCollection — because it might call display, which only a Hand has.

35. What inheritance removed

Section

Sections 14.1-14.2 together

36. Count the lines that stopped existing

Concept

Chapter 13 had Deck and Pile, each with its own storage, its own add, its own remove, its own isEmpty. Chapter 14 has one of each and two small subclasses.

methodChapter 13Chapter 14
add a cardin Deck and in Pileonce, in CardCollection
remove a cardin Deck and in Pileonce
isEmptyin Deck and in Pileonce
shufflein Deck onlyonce, available to all
build 52 cardsin Deckin Deck — genuinely specific
displayin Pilein Hand — genuinely specific

The last two rows are what a subclass is for. Everything above them was duplication that had no reason to exist — and Hand now gets shuffle for free, which Pile never had.

37. The shape of the hierarchy

Picture it

Three classes, two arrows, and a fourth class above them all that you never wrote.

Figure (svg): A UML inheritance chain from Object down to CardCollection and then to Deck and Hand

Deck extends CardCollection, which in turn extends Object — and you never wrote that last arrow. Every class has it, which is why every object has a toString before you write one.

38. Where to put a new method

Worked example

Adding a feature now means choosing a class, and the question is always the same.

// where does each of these belong?
//   sortByRank()      - useful for a hand, a deck, any collection
//   dealFive(that)    - a Crazy Eights rule
//   buildFullDeck()   - only a deck
//   display()         - only a hand or pile
methodbelongs inbecause
sortByRankCardCollectionevery collection could want it
displayHanda deck should not offer it
building 52 cardsDeck's constructoronly a deck is a full deck
a game ruleneitherChapter 14b puts it in Player

Ask who could use it.

Why: If every subclass could, it belongs in the superclass.

Ask who should not have it.

Why: If giving it to Deck would be wrong, it does not belong in CardCollection.

Ask whether it is a game rule.

Why: The Deck and Hand classes we have defined so far could be used for any card game.

Keep the superclass minimal.

Why: Rather than add every possible feature to CardCollection, only the shared parts go there.

Verify: Put display in CardCollection and ask yourself what deck.display() should print.

Why: And that's probably a good thing, since it makes it easy to reuse these classes if we want to make another game in the future. Keeping game rules out of the card classes is what makes them reusable, which is the argument Lesson 5b called generalisation.

39. extends or an instance variable?

Definition probe

Say the relationship out loud first.

Sort into buckets

Sort each pair.

IS-A — extends
Hand and CardCollection; Deck and CardCollection
HAS-A — instance variable
Player and Hand; Eights and Scanner
isa
The sentence X is a Y is true, so X can be used anywhere a Y is expected and inherits Y's methods.
hasa
The sentence X has a Y is true — X contains one as an attribute and does not become one.

40. Inheritance against composition

Concept

Chapter 13's Pile had an ArrayList. Chapter 14's Hand is a CardCollection. Those are the two ways one class can use another.

composition — HAS-Ainheritance — IS-A
examplePile has an ArrayListHand is a CardCollection
written asan instance variableextends
gets the methods?no — you write wrappersyes — automatically
substitutable?noyes

CardCollection uses both: it has an ArrayList, and Hand is a CardCollection. Lesson 14b names these relationships properly — but the distinction is already doing work here, since it is exactly why Pile needed nine wrapper methods and Hand needs none.

41. Extending a class just to reuse its code

Trap

The trap

Inheritance for convenience, without the IS-A relationship.

// "Player needs a hand, and Hand has all the methods I want"
public class Player extends Hand {
    private String name;
}

// which now means:
someMethod(aCardCollection);   // a Player can be passed here
claimtrue?
a Player has a handyes
a Player is a handno
a Player can be dealt intononsense
does it compile?yes — that is the problem

The code works, and the design is now a lie. Every method expecting a CardCollection would accept a Player, which no one intended and nothing prevents.

The fix

Composition when it is HAS-A.

public class Player {
    private String name;
    private Hand hand;              // a Player HAS a hand

    public Player(String name) {
        this.name = name;
        this.hand = new Hand(name);
    }
}
relationshipthe right tool
a Hand is a CardCollectionextends
a Player has a Handan instance variable
an Eights game has two Playersinstance variables

Read the relationship out loud. A Hand is a CardCollection is true; a Player is a Hand is not. That sentence test is the whole rule, and Lesson 14b's Player class is written exactly this way.

42. Where does display belong?

Prediction

It prints every card in the collection.

public void display() {
    System.out.println(getLabel() + ": ");
    for (int i = 0; i < size(); i++) {
        System.out.println(getCard(i));
    }
}
if it were inthen
CardCollectiondeck.display() prints 52 cards
Handonly hands and piles have it

Predict first

Why is it in Hand rather than CardCollection?

  • Because a Deck should not offer it — the superclass holds only what all collections share
  • Because CardCollection cannot use getCard
  • Because Deck already has a display
  • Because display must be static

Correct: Because a Deck should not offer it — the superclass holds only what all collections share

Why: Rather than add every possible feature to CardCollection, only genuinely common behaviour goes in the superclass. Printing a deck's fifty-two cards is not something a deck should invite, and a superclass that accumulates every subclass's features stops being an abstraction.

43. Deal cards between collections

Fill the middle

deal takes from this and gives to that.

Fill in the blanks

public void deal(CardCollection that, int n) popCard}();
that.addCard(card);
}
}

Why: popCard() with no argument takes from the end, which shifts nothing; that.addCard(card) appends it to the destination. Because the parameter is declared as CardCollection, the same method deals to a Hand, a Deck or a plain CardCollection.

44. Is less code always better?

Counterexample

Inheritance removed a lot of duplication.

Discussion prompt

CardCollection could absorb display, scoring, matching rules and everything else, leaving Deck and Hand as empty shells. What would be lost?

Hint: What can a Deck then do?

Answer:

Every collection would gain every method, so a Deck could be displayed as a hand and a hand could be built as a deck. The type would stop telling you anything.

And the game rules would be baked into the card classes, so a different card game could not reuse them — losing exactly the reusability the chapter is careful to preserve.

Removing duplication is not the goal; it is a consequence of putting each thing in one right place. Code that is shared because it belongs together is good; code merged because it happens to look alike is not.

45. Reading an inheritance hierarchy

Section

Sections 14.1-14.3 in practice

46. Where does a method actually come from?

Concept

With inheritance, a call like hand.shuffle() names a method that is nowhere in the Hand class. Knowing where to look is a skill worth practising.

Hand hand = new Hand("Hand");

hand.display();       // defined in Hand
hand.shuffle();       // inherited from CardCollection
hand.toString();      // inherited from Object
hand.getCard(0);      // inherited from CardCollection
callfound inhow far up
displayHandthe class itself
shuffleCardCollectionone level
toStringObjecttwo levels
popCardCardCollectionone level

Java looks in the class first, then its superclass, then upward until it reaches Object. The first match wins — which is what makes overriding possible, as Lesson 11b's toString already showed without naming it.

47. Reading Hand's display method

Notation

Eight lines, and three of the calls in them are inherited. That is what a subclass looks like.

Annotate

  • getLabel() is inherited — it reads the private label that CardCollection's constructor set.
  • size() is inherited, and it is why display never mentions an ArrayList.
  • getCard(i) is inherited — the wrapper around cards.get(i).
  • None of them is qualified. There is no super.size() — inherited methods are called exactly like the class's own, because in every sense that matters they are.
  • Notice display cannot touch cards directly. It is private to CardCollection, so even a subclass must go through the public methods — which is why those wrappers exist.

The last note is the subtle one. private keeps a subclass out too, which is why CardCollection's wrapper methods are not merely convenience — they are how a subclass reaches its own inherited data.

48. Tracing a deal

Worked example

One line of game code, and four classes involved. Follow it.

Deck deck = new Deck("Deck");
Hand hand = new Hand("Hand");
deck.deal(hand, 5);
stepmethoddefined inoperates on
1deal(that, n)CardCollectiondeck (as this)
2popCard()CardCollectiondeck
3popCard(i)CardCollectiondeck
4that.addCard(card)CardCollectionhand
5repeat 5 times——

deck.deal(...) finds no deal in Deck.

Why: Java looks up to CardCollection and finds it there.

Inside deal, this is the Deck.

Why: popCard() is called on it — the deck loses a card.

that is the Hand.

Why: Declared as CardCollection, holding a Hand — legal by substitution.

Five iterations, five cards moved.

Why: Deck 52 to 47; hand 0 to 5.

Verify: Print deck.size() and hand.size() before and after, and confirm 52/0 becomes 47/5.

Why: Every method in that trace lives in CardCollection, and neither subclass wrote a line of it. That is the return on the merge — and it is what makes the same call work for dealing, drawing and discarding in Lesson 14b.

49. Where is shuffle defined?

Prediction

Hand's body contains a constructor and display.

Hand drawPile = new Hand("Draw Pile");
drawPile.shuffle();
classhas shuffle?
Handno
CardCollectionyes

Predict first

What happens?

  • It works — shuffle is inherited from CardCollection
  • A compile error: Hand has no shuffle
  • It works but shuffles nothing
  • Only if Hand declares super.shuffle()

Correct: It works — shuffle is inherited from CardCollection

Why: Java looks in Hand, does not find shuffle, and looks in the superclass. Inherited methods are called exactly like a class's own. Note that Chapter 13's Pile had no shuffle at all — Hand gets it free, which is what reshuffle needs in Lesson 14b.

50. What Chapter 14b builds on this

Concept

The card classes are now finished and game-agnostic. Everything specific to Crazy Eights goes in two new classes.

classknows aboutreusable for another game?
Cardranks and suitsyes
CardCollectionholding and moving cardsyes
Deck, Handbeing a deck, being a handyes
Playerthe Crazy Eights strategyno
Eightsthe rules and the game loopno

The Deck and Hand classes we have defined so far could be used for any card game; we have not yet implemented any of the rules specific to Crazy Eights. And that's probably a good thing, since it makes it easy to reuse these classes if we want to make another game in the future.

51. Expecting a subclass to reach private members

Trap

The trap

private excludes subclasses too.

public class Hand extends CardCollection {
    public void display() {
        for (Card c : cards) {       // compile error
            System.out.println(c);
        }
    }
}
memberdeclaredvisible in Hand?
cardsprivate in CardCollectionno
labelprivate in CardCollectionno
size()publicyes
getCard(i)publicyes

The Hand object contains a cards list — every CardCollection does — but Hand's code cannot name it. Inheriting a member and being allowed to touch it are different things.

The fix

Go through the inherited public methods.

public void display() {
    System.out.println(getLabel() + ": ");
    for (int i = 0; i < size(); i++) {
        System.out.println(getCard(i));
    }
}
wantedused instead
cards.size()size()
cards.get(i)getCard(i)
labelgetLabel()

This is why CardCollection's wrappers matter more than they look. They are not only for outside clients — they are the interface a subclass uses to reach data it technically has and cannot name.

52. Which class defines it?

Definition probe

Look in the class, then upward.

Sort into buckets

Sort each call on a Hand object.

Hand
display()
CardCollection
size(); deal(that, n)
Object
toString()
hand
The one method the subclass adds — everything else it has, it inherited.
cc
Defined once in the superclass and available to every subclass without a line of code.
obj
Every class extends Object implicitly, which is where the default toString and equals come from.

53. Reach the inherited data

Fill the middle

cards is private to CardCollection.

Fill in the blanks

public void display() size}(); i++) getCard}(i));
}
}

Why: A subclass inherits the private cards list as part of every object but cannot name it — private excludes subclasses too. The public wrappers are how Hand reaches data it technically has, which is a large part of why those wrappers were worth writing.

54. Why is it good that Deck knows no game rules?

Explain it to yourself

Crazy Eights is the only game in the book.

Discussion prompt

Think Java says keeping the rules out of Deck and Hand is probably a good thing. What does that buy, concretely?

Hint: What would you have to do to write a second game?

Answer:

A second game reuses them unchanged. Poker, War, Go Fish — all need a deck, hands and piles, and none of them needs to know what an eight means.

If cardMatches lived in Card, every game would inherit Crazy Eights' rule, and a poker program would carry a method it must ignore.

The general principle: a class should know about its own domain and no further. Card knows about cards, CardCollection knows about holding them, and only Eights knows about eights — which is why Exercise 14.4 suggests an EightsCard subclass rather than changing Card.

55. Chapter 13's classes against Chapter 14's

Comparison

Fill the blanks.

Comparison matrix

Chapter 13Chapter 14
how many collection classestwo — Deck and Pilethree — CardCollection, Deck, Hand
shared codeduplicated in bothwritten once in the superclass
storagean array in Deck, an ArrayList in Pilean ArrayList, once
can a Deck be shuffledyesyes
can a hand be shuffledno — Pile had no shuffleyes — inherited

The last row is the unplanned benefit. Hand gains shuffle for free, and Lesson 14b's reshuffle needs exactly that — a feature the old Pile did not have.

56. The pattern to carry away

Pattern

Extract a superclass when two classes are nearly the same, and let each subclass keep only its difference.

// 1. everything they share
public class CardCollection {
    private String label;
    private ArrayList<Card> cards;

    public CardCollection(String label) { ... }
    public void addCard(Card card) { ... }
    public void deal(CardCollection that, int n) { ... }
}

// 2. each subclass adds only its difference
public class Deck extends CardCollection {
    public Deck(String label) {
        super(label);           // superclass constructor FIRST
        ...build 52 cards...
    }
}

public class Hand extends CardCollection {
    public Hand(String label) { super(label); }
    public void display() { ... }
}
ruleconsequence
one superclass onlyevery chain ends at Object
constructors are not inheritedevery subclass writes one, calling super
public members are inheritedcall them unqualified
private excludes subclasses tooreach data through the public wrappers
a subclass is substitutablea Hand goes where a CardCollection is expected

57. Check: super

Check

Work it out before you click.

public class Deck extends CardCollection {
    public Deck(String label) {
        super(label);
        // ... build 52 cards with addCard ...
    }
}
super(label) doesthen
runs CardCollection's constructorcards is an empty ArrayList

Check your understanding

What happens if you delete the super(label) line?

  • A. The superclass's fields are never initialised, so addCard has no ArrayList to add to (correct)
  • B. Nothing — the fields are initialised automatically
  • C. Deck stops being a subclass
  • D. The 52 cards are created twice

Answer: A

Why: super invokes the CardCollection constructor, which initializes the attributes label and cards. Without it, cards is never created, so the first addCard has nothing to add to. The superclass's constructor must run first precisely because the subclass's own code depends on the state it sets up.

Why B tempts people
Java would try to call a no-argument superclass constructor, and CardCollection has none — so this does not even compile.
Why C tempts people
The extends clause is what makes it a subclass; super is about construction.
Why D tempts people
The loop runs once either way; super does not build cards.

58. Check: substitution

Check

Work it out before you click.

public void deal(CardCollection that, int n) { ... }
public void showIt(Hand h) { h.display(); }

Deck deck = new Deck("d");
Hand hand = new Hand("h");
call?
deal(hand, 5)?
showIt(deck)?

Check your understanding

Which calls compile?

  • A. deal(hand, 5) compiles; showIt(deck) does not (correct)
  • B. Both compile
  • C. Neither compiles
  • D. showIt(deck) compiles; deal(hand, 5) does not

Answer: A

Why: A Hand is a CardCollection, so it can be passed wherever one is expected. But it doesn't work the other way around: not every CardCollection is a Hand, and a Deck is not a Hand at all — it has no display method for showIt to call. Every cat is a mammal; not every mammal is a cat.

Why B tempts people
Substitution runs from specific to general only — a Deck cannot stand in for a Hand.
Why C tempts people
The first is exactly the legal direction: a subclass satisfies a superclass parameter.
Why D tempts people
This reverses the rule; it is the direction that fails.

59. Check: private and subclasses

Check

Work it out before you click.

public class CardCollection {
    private ArrayList<Card> cards;
}

public class Hand extends CardCollection {
    public void display() {
        for (Card c : cards) { ... }
    }
}
cards isin Hand
private to CardCollection?

Check your understanding

Does Hand's display compile?

  • A. No — private excludes subclasses too; it must use size() and getCard(i) (correct)
  • B. Yes — a subclass can access everything in its superclass
  • C. Yes, but only inside a constructor
  • D. No — Hand does not inherit cards at all

Answer: A

Why: Every Hand object contains a cards list, because every CardCollection does — but Hand's code cannot name it, since private means within this class. Inheriting a member and being allowed to touch it are different things, which is a large part of why CardCollection's public wrappers were worth writing.

Why B tempts people
That would be true of a protected member; private is stricter.
Why C tempts people
Visibility does not change inside a constructor.
Why D tempts people
It is inherited — the object has one. It just cannot be named from Hand's code.

60. IS-A is a claim, not a convenience

Real world

The extends keyword makes a promise about what your class is, and every method expecting the superclass will believe it.

Discussion prompt

Where else does the is a test decide a design, inside or outside programming — and what goes wrong when the relationship is asserted for convenience?

Hint: A square and a rectangle.

Answer:

The classic case is Square extends Rectangle. Geometrically a square is a rectangle, so it looks right — until a method calls setWidth and setHeight independently and the square stops being one.

The test is not whether the sentence sounds true, but whether every method of the superclass still makes sense on the subclass. If any does not, the relationship is not IS-A.

Outside code the same trap appears whenever a category is inherited for convenience — a form that treats one thing as another because the paperwork already exists. Composition is the honest answer whenever the sentence is really has a, which is why Player has a Hand rather than being one.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

Which of these is NOT inherited by a subclass?

  • Constructors
  • Public methods
  • Public attributes
  • The ability to be used where the superclass is expected

Correct: Constructors

Why: Think Java states it directly: constructors are not inherited, but all other public attributes and methods are. That is why Deck and Hand each write their own constructor, and why each begins with super(label) — the superclass's constructor still has to run, it just has to be invoked explicitly. The fourth option is inherited in the sense that matters: a subclass object satisfies any parameter declared as the superclass, which is what makes one deal method serve every collection in the game.

62. Explain it to someone else

Explain it

Two minutes, out loud.

Discussion prompt

A classmate asks why the book bothered writing CardCollection when Deck and Pile already worked. Give them the argument, and name one thing the new design can do that the old one could not.

Hint: Count the copies of addCard. And ask whether a hand could be shuffled.

Answer:

Deck and Pile were essentially two versions of the same code — add, remove, isEmpty, all written twice, so every bug had two homes and every change had to be made in both.

CardCollection holds what they share, and each subclass keeps only its difference: Deck adds a constructor that builds 52 cards, Hand adds a display method. That is about twelve lines between them.

And the new design can shuffle a hand — Pile never had a shuffle, and Hand inherits one. A shared superclass spreads improvements automatically, which is the benefit that keeps paying after the duplication is gone.

63. Exit ticket

Exit ticket

One question before you close the deck.

Predict first

Why can a Hand be passed to a method that expects a CardCollection?

  • Because Hand extends CardCollection, so a Hand object is also a CardCollection object
  • Because Java converts it automatically
  • Because both classes have the same methods
  • Because CardCollection is abstract

Correct: Because Hand extends CardCollection, so a Hand object is also a CardCollection object

Why: Inheritance means an object belongs to more than one type at once: a Hand is a Hand, a CardCollection, and an Object. Nothing is converted — the object genuinely has every method the parameter promises. The reverse fails for the same reason: not every CardCollection is a Hand, so a method expecting a Hand may call display, which a plain CardCollection does not have. Every cat is a mammal; not every mammal is a cat.

64. Draw the whole lesson

Connect it up

One page, from memory.

Draw it

Draw the four-class hierarchy — Object, CardCollection, Deck, Hand — with hollow inheritance arrows, and write in each box only what that class actually declares. Beside it, list the four rules of extends: one superclass, Object at the top, constructors not inherited, everything else public inherited. Then write deck.deal(hand, 5) and trace which class each method in it comes from. Finish with the cat–mammal–animal sentence and the two parameter rules it settles.

65. Recap

Recap

Three sections that turn duplicated code into a hierarchy, and then show what the hierarchy buys.

if you remember one thingit is this
about when to extendsay is a out loud; if it is has a, use a field
about superthe superclass constructor runs first, always
about substitutionspecific satisfies general, never the reverse

Sources

  1. Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 14 (Extending Classes), Sections 14.1-14.3, pp. 233-240
  2. The Java Tutorials — Inheritance
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108