← Back to Core Java Course | Chapter 8: Inheritance | Lesson 5 of 7

Java Method Overriding

What is Method Overriding?

Method overriding lets a subclass supply its own implementation of a method the parent class already defines, which is the mechanism underneath runtime polymorphism — code calling the method through a parent-typed reference still gets the subclass's specific behavior.

Example: What is Method Overriding?

java
class Animal {
	void sound() { System.out.println("Some sound"); }
}
class Dog extends Animal {
	@Override
	void sound() { System.out.println("Bark"); }
}
public class Main {
	public static void main(String[] args) {
		Animal a = new Dog();
		a.sound(); // Bark, decided by actual object type
	}
}

Overriding Rules

For an override to be valid, the subclass method must match the parent's method exactly in name and parameter list, and its return type must be the same or a covariant (more specific) subtype — mismatching any of these creates a new overloaded method instead of an actual override.

Example: Overriding Rules

java
class Animal {
	void sound() { System.out.println("Some sound"); }
}
class Dog extends Animal {
	@Override
	void sound() { // same name, same parameters, same return type
		System.out.println("Bark");
	}
}
public class Main {
	public static void main(String[] args) {
		new Dog().sound();
	}
}

The @Override Annotation

The @Override annotation isn't strictly required, but it tells the compiler you intend to override a parent method, so if you accidentally mistype the signature, the compiler flags it as an error instead of silently letting you create an unrelated new method.

Example: The @Override Annotation

java
class Animal {
	void sound() { System.out.println("Some sound"); }
}
class Dog extends Animal {
	@Override // compiler verifies this actually overrides a parent method
	void sound() { System.out.println("Bark"); }
}
public class Main {
	public static void main(String[] args) {
		new Dog().sound();
	}
}

Dynamic Method Dispatch

Dynamic method dispatch is what decides, at runtime, which version of an overridden method actually executes — the JVM looks at the object's real, concrete type, not the type of the variable it's referenced through, which is why polymorphism works correctly through parent-typed references.

Example: Dynamic Method Dispatch

java
class Animal {
	void sound() { System.out.println("Some sound"); }
}
class Dog extends Animal {
	@Override
	void sound() { System.out.println("Bark"); }
}
public class Main {
	public static void main(String[] args) {
		Animal a = new Dog();
		a.sound(); // JVM picks Dog's version at runtime, not Animal's
	}
}

Preventing Overriding with final

Marking a parent method final blocks any subclass from overriding it at all, which is the right tool when a method's behavior must be guaranteed identical everywhere it's inherited, such as core validation logic you never want a subclass to bypass.

Example: Preventing Overriding with final

java
class Animal {
	final void identify() {
		System.out.println("I am an animal");
	}
}
class Dog extends Animal {
	// void identify() {} // would not compile: identify() is final
}
public class Main {
	public static void main(String[] args) {
		new Dog().identify();
	}
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.