Talk About“Upcasting”
The most important aspect of inheritance is not that it provides methods for the new class. It’s the relationship expressed between the new class and the base class. This relationship can be summarized by saying, “The new class
is a type of the existing class.”
This description is not just a fanciful way of explaining inheritance—it’s supported directly by the language. As an example, consider a base class called
Human that represents musical Humans, and a derived class called
Boy. Because inheritance means that all of the methods in the base class are also available in the derived class, any message you can send to the base class can also be sent to the derived class. If the
Human class has a
sleep( ) method, so will
Boy Humans. This means we can accurately say that a
Boy object is also a type of
Human. The following example shows how the compiler supports this notion:
//file:Human.java
Inheritance & upcasting.
import java.util.*;
class Human {
public void sleep() {}
static void breathe(Human h) {
// ...
h.sleep();
}
}
// Boy objects are Humans
// because they have the same interface:
public class man extends Human {
public static void main(String[] args) {
man boy = new man();
Human.breathe(boy); // Upcasting
}
} ///:~
What’s interesting in this example is the
breathe( ) method, which accepts an
Human reference. However, in
Boy.
main( ) the
breathe( ) method is called by giving it a
Boy reference. Given that Java is particular about type checking, it seems strange that a method that accepts one type will readily accept another type, until you realize that a
Boy object is also an
Human object, and there’s no method that
breathe( ) could call for an
Human that isn’t also in
Boy. Inside
breathe( ), the code works for
Human and anything derived from
Human, and the act of converting a
Boy reference
Why “upcasting”?
The reason for the term is historical, and based on the way class inheritance diagrams have traditionally been drawn: with the root at the top of the page, growing downward. (Of course, you can draw your diagrams any way you find helpful.) The inheritance diagram for
Boy.java is then:
human
|
man
|
Casting from a derived type to a base type moves
up on the inheritance diagram, so it’s commonly referred to as
upcasting. Upcasting is always safe because you’re going from a more specific type to a more general type. That is, the derived class is a superset of the base class. It might contain more methods than the base class, but it must contain
at least the methods in the base class. The only thing that can occur to the class interface during the upcast is that it can lose methods, not gain them. This is why the compiler allows upcasting without any explicit casts or other special notation.
In last Contens you saw how an object can be used as its own type or as an object of its base type. Taking an object reference and treating it as a reference to its base type is called
upcasting because of the way inheritance trees are drawn with the base class at the top. You also saw a problem arise, which is embodied in the following example about class Human. Since several examples has there skinColor, we should create the
color class separately, in a package:
//: Human:color.java
// EveryOne has there skinColor
package myApp.human;
public class color {
private String colorName;
private color(String colorName) {
this.colorName = colorName;
}
public String toString() { return noteName; }
public static final color
BLACK = new color("black man"),
WHITE = new color("white man"),
YELLO = new Note("yello man");
// Etc.
} ///:~
This is an “enumeration” class, which has a fixed number of constant objects to choose from. You can’t make additional objects because the constructor is private.
In the following example,
man is a type of Human, therefore man is inherited from Human:
//: organism.java
// Inheritance & upcasting.
Package myApp.Human;
public class organism {
public static void breathe(Human h) {
// ...
h.skin(color.BLACK);
}
public static void main(String[] args) {
man m = new man();
breathe(m); // Upcasting
});
}
} ///:~
//: man.java
package myApp.human;
public class man extends Human {
// Redefine interface method:
public void skin(color c) {
System.out.println("man.skin() " + c);
}
} ///:~
//: organism.java
// Inheritance & upcasting.
package myApp.human;
public class organism {
public static void breathe(human h) {
// ...
h.skin(color.WHITE);
}
public static void main(String[] args) {
man m = new man();
breathe(m); // Upcasting
}
} ///:~
The method organism
.breathe( ) accepts an
Human reference, but also anything derived from
Human. In
main( ), you can see this happening as a
Man reference is passed to
breath( ), with no cast necessary. This is acceptable—the interface in
Human must exist in
Man, because
Man is inherited from
Human. Upcasting from
Man to
Human may “narrow” that interface, but it cannot make it anything less than the full interface to
Human.
when shall we use INHERITANCE?
In object-oriented programming, the most likely way that you’ll create and use code is by simply packaging data and methods together into a class, and using objects of that class. You’ll also use existing classes to build new classes with composition. Less frequently, you’ll use inheritance. So although inheritance gets a lot of emphasis while learning OOP, it doesn’t mean that you should use it everywhere you possibly can. On the contrary, you should use it sparingly, only when it’s clear that inheritance is useful. One of the clearest ways to determine whether you should use composition or inheritance is to ask whether you’ll ever need to upcast from your new class to the base class. If you must upcast, then inheritance is necessary, but if you don’t need to upcast, then you should look closely at whether you need inheritance, if you remember to ask “Do I need to upcast?” you’ll have a good tool for deciding between composition and inheritance.