The labs on this site need JavaScript to run. The same content is written out below.
Learn › Java › Classes and objects
Polymorphism and method selection
Who decides which method runs, and when?
The declared type of a variable and the type of the object that it actually holds can be different. An overridden method is chosen at run time by looking at the actual object, but overloading, static methods, and fields are decided at compile time from the declared type alone.
Checked against real execution · Every combination was compiled and run on JDK 21 and checked against the result
Concepts in this lab
- Polymorphism
- This is the property that calling the same method through a reference of the same type runs different behavior depending on the kind of the actual object. A variable of a supertype (an interface or a superclass) can hold objects of several subtypes, and which implementation runs is decided at run time (dynamic method lookup). It lets you add a new implementation without changing the calling code.
- Overriding
- This means that a subclass redefines a method of its superclass (or of an interface) with the same signature. At run time, the method that matches the type of the actual object is called, and this is how polymorphism is implemented. The access level cannot be made narrower, and static, private, and final methods cannot be overridden.
- Overloading
- This means defining several methods with the same name but with a different number or different types of parameters. Which method is called is decided at compile time from the types of the arguments. Methods that differ only in return type are not overloaded; that is a compile-time error.
- static
- This keyword marks a member as belonging to the class, not to an instance. A static field exists only once per class and is shared by all instances, and a static method is called without creating an object. A static field whose value changes is effectively a global variable, so it easily causes problems when several threads access it at the same time.
- Inheritance
- This means defining a new class (the subclass) that receives the fields and methods of an existing class (the superclass). It reuses common code, and the subclass adds or overrides only the parts it needs. A Java class supports single inheritance only, and a change to the superclass affects every subclass, so it is best used only when there is a clear "is-a" relationship.
Try these
- Default settings — A Dog object in an Animal variable. For the overridden method, the one in Dog runs.
- Passing the same variable to an overloaded method — print(Animal) is selected even though the actual object is a Dog.
- A static method — It is not overridden. The one in the declared type is called.
- A field — Polymorphism does not apply to fields either.
- Changing the declared type to Dog — With overloading, print(Dog) is selected.
- An Animal object in a Dog variable — This does not compile.
Running with the default settings
- Compile time: Animal a = new Dog(). The declared type of the variable a is Animal, and when the code runs, an object of class Dog is created on the heap. The two are different.
- Compile time: The compiler checks only whether the declared type Animal has sound(). It does not decide which class's sound() will run, and it leaves only the instruction "call sound()".
- Run time: The JVM checks the class of the actual object that a refers to (Dog), and it finds and runs the sound() of that class (dynamic method lookup).
Result of this run
- An overridden instance method is selected by the type of the actual object, not by the declared type of the variable. This is polymorphism, and it is the principle that lets Spring run an implementation that was injected as an interface type.
Code
class Animal {
String name = "animal";
void sound() { System.out.println("Animal.sound"); }
static void info() { System.out.println("Animal.info"); }
}
class Dog extends Animal {
String name = "dog"; // a field with the same name
@Override
void sound() { System.out.println("Dog.sound"); }
static void info() { System.out.println("Dog.info"); }
}
class Printer {
void print(Animal x) { System.out.println("print(Animal)"); }
void print(Dog x) { System.out.println("print(Dog)"); }
}
Animal a = new Dog();
a.sound();
Assumptions of this model
The output of every combination was checked against the result of actually compiling and running the same code on JDK 21 on this PC. Calling a static method through a variable, as in a.info(), is allowed by the syntax but is not a recommended practice, and it is included here to show that the call is decided by the declared type.
Previous lab: Inheritance and overriding · Next lab: Interfaces and abstract classes