

Animal
class:
public class Animal {
String name;
int age;
}
We can declare 2 child classes: Cat
and Dog
. This is done using the keyword extends.
public class Cat extends Animal {
}
public class Dog extends Animal {
}
We may find this helpful in the future. For example, if there is a task to catch mice, we'll create a Cat
object in our program. If the task is to chase after a stick, then we'll use a Dog
object. And if we create a program that simulates a veterinary clinic, it will work with the Animal
class (and thus be able to treat both cats and dogs).
It is very important to remember that when an object is created, the constructor of its base class is first called. Only after that constructor is finished does the program execute the constructor of the class corresponding to the object we are creating.
In other words, when creating a Cat
object, the Animal
constructor is run first, and only afterward is the Cat
constructor executed.
To see this, add some console output to the Cat
and Animal
constructors.
public class Animal {
public Animal() {
System.out.println("Animal constructor executed");
}
}
public class Cat extends Animal {
public Cat() {
System.out.println("Cat constructor executed!");
}
public static void main(String[] args) {
Cat cat = new Cat();
}
}
Console output:
Animal constructor executed
Cat constructor executed!
Indeed, it does work that way!
Why? One reason is to avoid duplicating fields shared between the two classes. For example, every animal has a heart and brain, but not every animal has a tail.
We could declare brain and heart fields, which are common to all animals, in the Animal
parent class, and a tail field in the Cat
subclass. .
Now we'll declare a Cat
class constructor that takes arguments for all 3 fields.
public class Cat extends Animal {
String tail;
public Cat(String brain, String heart, String tail) {
this.brain = brain;
this.heart = heart;
this.tail = tail;
}
public static void main(String[] args) {
Cat cat = new Cat("Brain", "Heart", "Tail");
}
}
Note: The constructor works correctly even though the Cat
class has no brain and heart fields.
These fields are "inherited" from the Animal
base class. The inheriting class has access to the fields of the base class, so they are visible in our Cat
class. As a result, we don't need to duplicate these fields in the Cat
class. We can take them from the Animal
class.
What's more, we can explicitly call the base class constructor in the child class constructor. A base class is also called a "superclass". That's why Java uses the keyword super to indicate the base class.
In the previous example
public Cat(String brain, String heart, String tail) {
this.brain = brain;
this.heart = heart;
this.tail = tail;
}
We separately assigned each field in our parent class. We don't actually have to do this.
It's enough to call the parent class constructor and pass the necessary arguments:
public class Animal {
String brain;
String heart;
public Animal(String brain, String heart) {
this.brain = brain;
this.heart = heart;
}
public class Cat extends Animal {
String tail;
public Cat(String brain, String heart, String tail) {
super(brain, heart);
this.tail = tail;
}
public static void main(String[] args) {
Cat cat = new Cat("Brain", "Heart", "Tail");
}
}
In the Cat
constructor, we called the Animal
constructor and passed two fields. We had only one field to explicitly initialize: tail, which is not in Animal
.
Remember we mentioned that the parent class constructor is called first when an object is created? That's why super() should always be first in a constructor!
Otherwise, the constructor logic will be violated and the program will generate an error.
public class Cat extends Animal {
String tail;
public Cat(String brain, String heart, String tail) {
this.tail = tail;
super(brain, heart);// Error!
}
public static void main(String[] args) {
Cat cat = new Cat("Brain", "Heart", "Tail");
}
}
The compiler knows that when an object of a child class is created, the base class constructor is called first. And if you try to manually change this behavior, the compiler won't allow it.
How an object is created
We previously looked at an example with a base and parent class:Animal
and Cat
.
Using these two classes as examples, we'll now look at the process of creating an object and initializing variables.
We know that there are static and instance (non-static) variables.
We also know that the Animal
base class has variables, and the Cat
child class has its own.
For clarity, we'll add one static variable each to the Animal
and Cat
classes.
The animalCount variable in the Animal
class will represent the total number of animal species on Earth, and the catCount variable will signify the number of cat species.
Additionally, we'll assign starting values to all non-static variables in both classes (which will then be changed in the constructor).
public class Animal {
String brain = "Initial value of brain in the Animal class";
String heart = "Initial value of heart in the Animal class";
public static int animalCount = 7700000;
public Animal(String brain, String heart) {
System.out.println("Animal base class constructor is running");
System.out.println("Have the variables of the Animal class already been initialized?");
System.out.println("Current value of static variable animalCount = " + animalCount);
System.out.println("Current value of brain in the Animal class = " + this.brain);
System.out.println("Current value of heart in the Animal class = " + this.heart);
System.out.println("Have the variables of the Cat class already been initialized?");
System.out.println("Current value of static variable catCount = " + Cat.catCount);
this.brain = brain;
this.heart = heart;
System.out.println("Animal base class constructor is done!");
System.out.println("Current value of brain = " + this.brain);
System.out.println("Current value of heart = " + this.heart);
}
}
public class Cat extends Animal {
String tail = "Initial value of tail in the Cat class";
static int catCount = 37;
public Cat(String brain, String heart, String tail) {
super(brain, heart);
System.out.println("The cat class constructor has started (The Animal constructor already finished)");
System.out.println("Current value of static variable catCount = " + catCount);
System.out.println("Current value of tail = " + this.tail);
this.tail = tail;
System.out.println("Current value of tail = " + this.tail);
}
public static void main(String[] args) {
Cat cat = new Cat("Brain", "Heart", "Tail");
}
}
So we're creating a new instance of the Cat
class, which inherits Animal
. We've added some detailed console output to see what's happening and in what order.
This is what will be displayed when a Cat
object is created:
Animal base class constructor is running
Have the variables of the Animal class already been initialized?
Current value of static variable animalCount = 7700000
Current value of brain in the Animal class = Initial value of brain in the Animal class
Current value of heart in the Animal class = Initial value of heart in the Animal class
Have the variables of the Cat class already been initialized?
Current value of static variable catCount = 37
Animal base class constructor is done!
Current value of brain = Brain
Current value heart = Heart
The cat class constructor has started (The Animal constructor already finished)
Current value of static variable catCount = 37
Current value of tail = Initial value of tail in the Cat class
Current value of tail = Tail
So, now we can clearly see the order of variable initialization and constructor calls when a new object is created:
- Static variables of the base class (
Animal
) are initialized. In our case, theAnimal
class's variable animalCount is set to 7700000. Static variables of the child class (
Cat
) are initialized.Note: we're still inside the
Animal
constructor and we've already displayed:Animal base class constructor is running
Have the variables of the Animal class already been initialized?
Current value of static variable animalCount = 7700000
Current value of brain in the Animal class = Initial value of brain in the Animal class
Current value of heart in the Animal class = Initial value of heart in the Animal class
Have the variables of the Cat class already been initialized?
Current value of static variable catCount = 37Then the non-static variables of the base class are initialized. We specifically assigned them initial values, which are then replaced in the constructor. The Animal constructor has not finished yet, but the initial values of brain and heart have already been assigned:
Animal base class constructor is running
Have the variables of the Animal class already been initialized?
Current value of static variable animalCount = 7700000
Current value of brain in the Animal class = Initial value of brain in the Animal class
Current value of heart in the Animal class = Initial value of heart in the Animal classThe base class constructor starts.
We've already convinced ourselves that this step is fourth: in the first three steps at the beginning of theAnimal
constructor, many variables have already been assigned values.Non-static fields of the child class (
Cat
) are initialized.
This happens before theCat
constructor starts running.
When it starts running, the tail variable already has a value:The cat class constructor has started (The Animal constructor already finished) Current value of static variable catCount = 37 Current value of tail = Initial value of tail in the Cat class
The constructor of the
Cat
child class is calledAnd that's what creating an object looks like in Java!
I must say that we are not big fans of rote-learning, but it's best to memorize the order of variable initialization and constructor calls.
This will greatly increase your understanding of the flow of the program, and the state of your objects at any particular moment.
Moreover, many classes don't use inheritance. In this case, the steps related to the base class don't apply.
More reading: |
---|
GO TO FULL VERSION