Inheritance
Building a new class on top of an existing one, the is-a relationship, and what a subclass inherits.
Finished reading?
Mark this session so you can track where you are.
Building a new class on top of an existing one, the is-a relationship, and what a subclass inherits.
Finished reading?
Mark this session so you can track where you are.
You can now build a class, give it fields and methods, protect those fields, and construct an object in a valid state from its first breath. So picture two classes you might write next, a Dog and a Cat, and notice something uncomfortable: most of what you would type into both is identical. There is a tool for exactly this feeling, and it is one of the load-bearing ideas of the whole language.
class Dog extends Animal and explain what Dog gains from Animal.super(...) and explain why it must come first.protected one we set aside, and the this keyword and references. That is the whole toolkit for this session. We will not change behavior that a class inherits yet; we only learn how a class can stand on top of another.Suppose we are modelling animals in a small shelter program. A dog has a name and an age, and it can eat and sleep. A cat has a name and an age, and it can eat and sleep too. Written separately, with everything we already know, the two classes look like this.
public class Main { public static void main(String[] args) { Dog d = new Dog(); d.name = "Rex"; d.age = 3; d.eat(); d.bark(); Cat c = new Cat(); c.name = "Mittens"; c.eat(); c.meow(); }} class Dog { String name; int age; void eat() { System.out.println(name + " is eating."); } void sleep() { System.out.println(name + " is asleep."); } void bark() { System.out.println(name + " says woof."); }} class Cat { String name; int age; void eat() { System.out.println(name + " is eating."); } void sleep() { System.out.println(name + " is asleep."); } void meow() { System.out.println(name + " says meow."); }}Rex is eating. Rex says woof. Mittens is eating. Mittens says meow.
Run it and it works. But look at what we typed. The name field, the age field, theeat method, and the sleep method appear twice, character for character. The only real difference is that a dog barks and a cat meows. If the shelter later adds a weight field, we add it in two places and hope we never forget one. Add a Rabbit and a Hamster and we are copying the same four lines into every single class.
When several classes share the same fields and behavior, copying that shared part into each one is not just tedious. It means the same idea now lives in many places, and they can quietly drift apart. We want to write what is common exactly once.
Here is the move. We notice that a dog and a cat are both, more generally, an animal. So we write one class, Animal, that holds everything they share: the name, the age, and the eat and sleep methods. Then Dog and Cat are each built on top of Animal using the keyword extends.
The class being built on is the superclass (here Animal). The class doing the extending is the subclass (here Dog). The subclass automatically gets, or inherits, the fields and methods of its superclass, exactly as if they had been typed inside it. You do not copy them. They are simply there.
public class Main { public static void main(String[] args) { Dog d = new Dog(); d.name = "Rex"; d.age = 3; d.eat(); d.sleep(); d.bark(); }} class Animal { String name; int age; void eat() { System.out.println(name + " is eating."); } void sleep() { System.out.println(name + " is asleep."); }} class Dog extends Animal { void bark() { System.out.println(name + " says woof."); }}Rex is eating. Rex is asleep. Rex says woof.
Read the Dog class. It declares no name, no age, no eat, no sleep. It only adds bark. And yet in main we set d.name, read it through d.eat(), and call d.sleep(), all of which succeed. That is inheritance: a Dog object truly has a name field and a working eat method, because it inherited them from Animal. The output is:
Rex is eating.Rex is asleep.Rex says woof.Rex is eating. Rex is asleep. Rex says woof.
Inheritance describes an is-a relationship. A dog is an animal. A cat is an animal. That sentence has to be true in plain English for the inheritance to be sensible. When it is true, the subclass is a more specific kind of the superclass, and everything true of the general thing stays true of the specific one.
Before you write
class B extends A, say “a B is an A” out loud. If that sentence is a lie, inheritance is the wrong tool, no matter how much code it would save.
Car has an engine, but a car is not an engine, so Car extends Engineis wrong even though both have fields. “Has-a” is solved by giving the class a field of that type (a Car with an Engine engine;field), which you already know how to do. “Is-a” is what extends is for.A subclass is not limited to what it inherits. It starts with everything from the superclass and then adds its own fields and methods on top. That is the whole point: the shared part lives in Animal once, and each subclass contributes only what is genuinely particular to it.
public class Main { public static void main(String[] args) { Dog d = new Dog(); d.name = "Rex"; d.age = 3; d.breed = "Husky"; d.eat(); d.describe(); d.bark(); }} class Animal { String name; int age; void eat() { System.out.println(name + " is eating."); } void describe() { System.out.println(name + " is " + age + " years old."); }} class Dog extends Animal { String breed; void bark() { System.out.println(name + " the " + breed + " says woof."); }}Rex is eating. Rex is 3 years old. Rex the Husky says woof.
Step through it. The Dog object that d points at has four fields in total: the inherited name and age from Animal, plus its own breed. The object view shows them together because they all belong to this one object. Notice the quiet power inbark: it freely reads name (inherited) and breed (its own) in the same line, as if there were no seam between them. From inside the object, there is not. The output is:
Rex is eating.Rex is 3 years old.Rex the Husky says woof.Rex is eating. Rex is 3 years old. Rex the Husky says woof.
Dog can see and use Animal's members, but Animal knows nothing about Dog. The describe method living in Animal cannot mention breed, because plenty of animals are not dogs. The general parent must not depend on its specific children. The child reaches up; the parent never reaches down.So far we built the object the loose way: new Dog() and then a pile of assignment lines. But you learned to do better than that with constructors, which set an object up in one valid step. The question is how constructors behave once one class extends another. The answer is a new keyword, super.
Give Animal a constructor that takes a name, and give Dog a constructor that takes a name and a breed. The Dog constructor does not set name by hand. The name field belongs to the Animal part of the object, so Dog hands the name up to Animal's constructor by calling super(name), and then sets its own breed.
public class Main { public static void main(String[] args) { Dog d = new Dog("Rex", "Husky"); d.describe(); d.bark(); }} class Animal { String name; Animal(String name) { System.out.println("Animal constructor: setting name."); this.name = name; } void describe() { System.out.println("This animal is called " + name + "."); }} class Dog extends Animal { String breed; Dog(String name, String breed) { super(name); System.out.println("Dog constructor: setting breed."); this.breed = breed; } void bark() { System.out.println(name + " the " + breed + " says woof."); }}Animal constructor: setting name. Dog constructor: setting breed. This animal is called Rex. Rex the Husky says woof.
Watch the order in the trace. When new Dog("Rex", "Husky") runs, the very first thing theDog constructor does is super(name), which runs Animal's constructor and sets name on the object. Only then does Dog continue and set breed. The object is built from the inside out: the Animal part first, the Dog part second. The output is:
Animal constructor: setting name.Dog constructor: setting breed.This animal is called Rex.Rex the Husky says woof.Animal constructor: setting name. Dog constructor: setting breed. This animal is called Rex. Rex the Husky says woof.
That constructor chaining is the rule, not a special case. Building a Dog always means building its Animal part first, because the Dog part is meaningless without it. There is no half-built dog with a breed but no animal underneath.
super(...), Java quietly inserts a call to the parent's no-argument constructor, super(), for you. That is fine when the parent has a no-argument constructor. But once Animal only has Animal(String name), there is no Animal() to call, and a Dog constructor that omits super(name) will fail to compile. The fix is simply to call super(name) yourself, as the first statement, and pass the parent what it needs.Back in encapsulation you met three access words and we put one aside. privatemeans “only inside this class”. public means “anyone”. The third, protected, only makes sense now that subclasses exist: a protected member is visible inside its own class and inside any subclass, but still hidden from unrelated outside code.
That is exactly the level a parent often wants for fields its children should be free to use. IfAnimal.name were private, a Dog method could not read it directly, because private means strictly this class and nobody else, subclasses included. Marking it protected opens it to the family without opening it to the whole program.
public class Main { public static void main(String[] args) { Dog d = new Dog("Rex", "Husky"); d.bark(); }} class Animal { protected String name; Animal(String name) { this.name = name; }} class Dog extends Animal { private String breed; Dog(String name, String breed) { super(name); this.breed = breed; } void bark() { System.out.println(name + " the " + breed + " barks."); }}Rex the Husky barks.
Here name is protected, so Dog's bark method reads it without a problem. The program prints Rex the Husky barks. Meanwhile breed is private to Dog, which is fine because no other class needs it. Reach for protected when a member is genuinely meant for subclasses to use, and keep everything else private by default.
Dog cannot touch a private field of Animal.Dog can touch a protected field of Animal.protected is a real loosening of encapsulation: every present and future subclass can now depend on that field directly. Use it when subclasses truly need the access. When in doubt, keep the field private and offer a protected or public getter instead, so the parent keeps control of how the value is read.Dog stuck with Animal's generic describe cannot say anything dog-specific in it. Replacing an inherited method with your own version is called overriding, and it is the entire next session: method overriding and super. For now, notice the ceiling: we inherit and extend, we do not rewrite.Try each one yourself first, then open the answer.
extends is the right fit by reading it as “is-a”: Cat and Animal; Wheel and Car; Student and Person.Dog extends Animal and Dog only declares bark. Why does d.eat() work even though Dog never wrote an eat method?super example, the output starts with Animal constructor: setting name. before Dog constructor: setting breed. Explain that order in one sentence.name in Animal is marked private. A Dog extends Animal tries to read name directly in its own method. Does it work? What one-word change fixes it? The program below already applies the fix, so it runs.Take these away. They continue exactly what we just did.
Vehicle with Car and Motorcycle). List which fields and methods belong in the parent because they are shared, and which belong in each child because they are specific. Justify each placement with the is-a test.Animal, an int age, set through the Animal constructor. Update both constructors so a Dog is created with a name, an age, and a breed, with the age handled by super. Confirm the print order of the constructor messages does not change.super(...) to be the first statement of a constructor. Use the idea of the object being built from the inside out, and what could go wrong if the subclass ran first.Animal only the constructor Animal(String name), then write a Dog constructor that does not call super(name). Predict whether it compiles and explain why, using what you learned about the silent super() Java tries to insert. Then fix it.