Learning outcome: Explain compile-time and runtime polymorphism, use upcasting safely, and design encapsulated classes.
Polymorphism means "many forms." The same method name or parent reference can represent different behaviour. Encapsulation keeps an object's state protected and exposes controlled operations.
Two Forms of Polymorphism
| Form | Achieved by | Decision time |
|---|---|---|
| Compile-time polymorphism | Method overloading | During compilation |
| Runtime polymorphism | Method overriding | While the program runs |
Method overloading and overriding are covered in detail in the next topic; the focus here is runtime dispatch.
Runtime Polymorphism
When a parent reference points to a child object, an overridden instance method is selected according to the actual object.
In simple terms: The reference type decides what may be asked; the actual object decides how it answers. Point a Vehicle reference at a Car and call start(), and Java runs the Car version.
class Animal {
void speak() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
@Override
void speak() {
System.out.println("Woof");
}
}
class Demo {
public static void main(String[] args) {
Animal pet = new Dog();
pet.speak(); // Woof
}
}
The reference type is Animal, but the object type is Dog, so Java invokes Dog.speak().
Upcasting
Upcasting stores a child object in a parent or interface reference. It is implicit and safe.
In simple terms: Upcasting is filing a savings account under the broader heading “account”. Nothing about the object changes — only the label used to refer to it becomes more general, which is why it is always safe.
Dog dog = new Dog();
Animal animal = dog; // upcasting
The reference can use members declared by its reference type. Overridden instance methods still use the child's implementation.
Animal animal = new Dog();
animal.speak();
// animal.fetch(); // does not compile if fetch() is only in Dog
Downcasting in the other direction must be explicit and is safe only when the object really has the requested type.
if (animal instanceof Dog) {
Dog sameDog = (Dog) animal;
}
A Bank Example
class Bank {
double interestRate() {
return 0.0;
}
}
class SavingsBank extends Bank {
@Override
double interestRate() {
return 6.5;
}
}
class RuralBank extends Bank {
@Override
double interestRate() {
return 7.0;
}
}
class BankDemo {
public static void main(String[] args) {
Bank[] banks = {new SavingsBank(), new RuralBank()};
for (Bank bank : banks) {
System.out.println(bank.interestRate());
}
}
}
One loop works with different bank objects because each object supplies its own implementation.
Methods vs Data Members
Runtime polymorphism applies to overridden instance methods, not fields. Fields are selected using the reference type.
In simple terms: Behaviour follows the object, but fields follow the reference. A parent reference aimed at a child object runs the child’s overridden method yet reads the parent’s field, which is exactly why hiding a field is a trap rather than a feature.
class Parent {
String label = "parent";
}
class Child extends Parent {
String label = "child";
}
class FieldHidingDemo {
public static void main(String[] args) {
Parent item = new Child();
System.out.println(item.label); // parent
}
}
Prefer private fields and polymorphic methods; hiding fields with the same name is confusing.
Encapsulation
Encapsulation bundles state and behaviour inside a class while preventing uncontrolled direct access to the state.
In simple terms: Encapsulation is an ATM rather than an open cash drawer. The balance sits inside, and the only ways to touch it are the operations the machine permits, so the account can refuse a withdrawal that would take it below zero.
public class Account {
private double balance;
public double getBalance() {
return balance;
}
public boolean deposit(double amount) {
if (amount <= 0) {
return false;
}
balance += amount;
return true;
}
}
Benefits include validation, easier maintenance, reduced coupling, and the ability to change the internal representation without changing client code.
Read-Only and Write-Only Properties
Java does not have a special property keyword. A class controls access through methods.
In simple terms: A getter with no setter is a display board: readable, not editable. A setter with no getter is a suggestion box: you can put something in, but you cannot look inside.
class Registration {
private final String registrationId;
Registration(String registrationId) {
this.registrationId = registrationId;
}
public String getRegistrationId() { // read-only to callers
return registrationId;
}
}
A setter without a getter can create write-only access, but such designs should be used carefully because callers cannot inspect the resulting state.
Common Mistakes
| Mistake | Correction |
|---|---|
| Thinking a parent reference changes the object's real type | The object remains the child object. |
| Calling a child-only method through a parent reference | Check and downcast only when genuinely necessary. |
| Expecting fields to use runtime dispatch | Runtime dispatch applies to overridden instance methods. |
| Writing setters that accept every value | Validate changes and preserve class rules. |
Practice
- Create a
Shapeparent witharea()and two subclasses with different implementations. - Store several child objects in a
Shape[]and callarea()in a loop. - Encapsulate a
temperaturefield and reject values below absolute zero. - Explain why public fields weaken encapsulation.
Quick Check
1. What determines which overridden instance method runs?
The actual runtime object determines which overridden implementation runs.
2. Is upcasting explicit?
No. A child object can be assigned to a compatible parent or interface reference implicitly.
3. What is the normal encapsulation pattern?
Keep fields private and expose a small set of public methods that read or change state under controlled rules.
Summary
Runtime polymorphism lets one parent reference represent different child objects and dispatch overridden behaviour dynamically. Encapsulation protects an object's state and preserves its rules. Together, these ideas make Java programs easier to extend and maintain.