Upcasting and Downcasting
At a hospital reception, everyone is simply a "visitor": the receptionist only needs your name and time. Inside, a doctor is still a doctor and can do things a visitor can't. In Java, a SavingsAccount object can be held in an Account variable, and then you can only use Account features through it. That's upcasting. Getting the savings features back means downcasting, which needs care, because not every account is a savings account.
The Reference Type Decides What You Can Call
| Upcasting | Downcasting | |
|---|---|---|
| Direction | Child type → parent type | Parent type → child type |
| Example | Account a = new SavingsAccount(...); | SavingsAccount s = (SavingsAccount) a; |
| Cast needed? | No, automatic | Yes, explicit (SavingsAccount) |
| Always safe? | Yes: a savings account is an account | No: fails if the object isn't really that type |
| What you can call | Only the parent's members | The child's members too |
Up and Down in Code
public class Cast {
static class Account {
String type() { return "Account"; }
}
static class Savings extends Account {
@Override String type() { return "Savings"; }
double rate() { return 3.5; }
}
static class Current extends Account {
@Override String type() { return "Current"; }
}
public static void main(String[] args) {
Account a = new Savings();
System.out.println(a.type());
Savings s = (Savings) a;
System.out.println("Rate: " + s.rate());
Account[] accounts = {new Savings(), new Current()};
for (Account acc : accounts) {
if (acc instanceof Savings sv) {
System.out.println(acc.type() + " earns " + sv.rate() + "%");
} else {
System.out.println(acc.type() + " earns no interest");
}
}
}
}Savings
Rate: 3.5
Savings earns 3.5%
Current earns no interestHow it works
- Line 16: upcasting happens automatically: a
Savingsobject stored in anAccountvariable. - Line 17:
a.type()still prints"Savings": the object's real class decides which overridden method runs (dynamic dispatch, see "Runtime Polymorphism and Dynamic Method Dispatch"). - Through
a, you can't callrate();Accountdoesn't have it. Line 18 downcasts to get it back. - Lines 23 to 27 use
instanceofwith a pattern (Java 16+): ifaccis aSavings, it's checked and cast into the new variablesvin one step. Otherwise, no cast is attempted.
Try it yourself: write Savings x = (Savings) accounts[1];. What happens when you run it?
When a Downcast Fails
Casting an object to a type it isn't compiles, but fails at runtime:
Exception in thread "main" java.lang.ClassCastException: class Cast$Current cannot be cast to class Cast$SavingsFor unrelated types, the compiler catches it straight away: (Integer) "a" gives incompatible types: String cannot be converted to Integer.
Why Upcast at All?
Upcasting lets one piece of code work with every kind of account: an Account[] holding savings and current accounts, a method void audit(Account a) that accepts any of them, a List<Account>. Each object still behaves as its real type when overridden methods are called. That combination, one parent-type view and many child behaviors, is polymorphism, the subject of the Polymorphism chapter.
Common Mistakes
Casting without checking. It throws ClassCastException for the wrong subtype.
Thinking upcasting changes the object. The object keeps all its fields; only the variable's view is narrower.
Expecting fields to behave like methods. Methods are chosen by the object's real type; fields by the variable's type. Another reason not to hide fields.
Interview Corner
What is upcasting and downcasting?
Upcasting assigns a subclass object to a superclass reference; it's implicit and always safe, but only the superclass's members are accessible through that reference. Downcasting converts a superclass reference back to a subclass type; it needs an explicit cast, and throws ClassCastException at runtime if the object isn't actually of that subclass. instanceof checks first.
What does instanceof with a pattern do?
if (obj instanceof Savings s) checks the object's type and, if it matches, declares s already cast to Savings for use in that branch. It replaces the older two-step check-then-cast and was added in Java 16.
Practice
- Easy: Store a
Managerin anEmployeevariable and call a method that onlyEmployeehas. - Harder: Write
static double totalInterest(Account[] accounts)that adds uprate()only for savings accounts, using aninstanceofpattern.
Key Takeaways
Was this tutorial helpful?