We previously used classes to create new types, and used inheritance to facilitate our process of creating classes. In this lecture I'll dive into types and introduce the concept of polymorphism.
type checking
Any variables and references in Java can only be used after passing a type declaration. We have seen before that object data, class data, method parameters, method return values, and automatic variables inside methods all need to declare their types. Java is a strongly typed language, which checks types. If we use the wrong type, it will cause errors.
Type does not match, cuteness is invalid
For example, in the Test class below, we assign a Cup class object to aPerson class reference:
public class Test{ public static void main(String[] args) { Human aPerson; aPerson = new Cup(); }}class Human{ /** * constructor */ public Human(int h) { this.height = h; } /** * accessor */ public int getHeight() { return this.height; } /** * mutator */ public void growHeight(int h) { this.height = this.height + h; } private int height;}class Cup { public void addWater(int w) { this.water = this.water + w; } public void drinkWater(int w) { this.water = this.water - w; } private int water = 0 ;}
javac will return:
found : Cuprequired: Human aPerson = new Cup(); ^1 error
Basic type conversion
Java can perform type conversion on variables of basic types. Different basic types have different lengths and storage ranges. If we convert from a high-precision type to a low-precision type, such as converting from float to int, then we may lose information. Such a conversion is called a narrowing conversion. In this case, we need to explicitly declare the type conversion, such as:
public class Test{ public static void main(String[] args) { int a; a = (int) 1.23; // narrowing conversion System.out.println(a); }}
If we convert from a low-precision type to a high-precision type, there is no concern about information loss. Such a transformation is called a widening conversion. We don't need to explicitly require type conversion, Java can do it automatically:
public class Test{ public static void main(String[] args) { int a = 3; double b; b = a; // widening conversion System.out.println(a); }}
Basic type conversion
upcast and polymorphism
In Java, references can also be type-cast, but there are restrictions.
We can convert a derived class reference to its base class reference, which is called upcast or relaxed conversion. The following BrokenCup class inherits from the Cup class and overrides the original addWater() and drinkWater() methods in the Cup class:
public class Test{ public static void main(String[] args) { Cup aCup; BrokenCup aBrokenCup = new BrokenCup(); aCup = aBrokenCup; // upcast aCup.addWater(10); // method binding }}class Cup { public void addWater(int w) { this.water = this.water + w; } public void drinkWater(int w) { this.water = this.water - w; } private int water = 0;} class BrokenCup extends Cup{ public void addWater(int w) { System.out.println("shit, broken cup"); } public void drinkWater(int w) { System.out. println("om...num..., no water inside"); }}
Program running results:
shit, broken cup
As you can see above, without any explicit instructions, we assign the derived class reference aBrokenCup to its base class reference aCup. Type conversion will be done automatically by Java.
We then called the addWater() method of aCup (which we declared to be of type Cup). Although aCup is a reference of type Cup, it actually calls the addWater() method of BrokenCup! In other words, even if we loosen the reference type to its base class through upcast, Java can still correctly identify the type of the object itself and call the correct method. Java can identify the true type of an object based on the current situation. This is called polymorphism. Polymorphism is an important aspect of object-oriented.
Polymorphism is a mechanism supported by Java and is also an important object-oriented concept. This raises a taxonomic question as to whether subclass objects actually "are" parent class objects. For example, a bird is also an animal; a car must also be a means of transportation. Java tells us that a derived class object can be used as a base class object, and Java will handle this situation correctly.
For example, the following inheritance relationship:
We can say that we drink water from a cup. In fact, the specific meaning of the action of drinking water will change greatly in the derived class. For example, drinking water through a straw and drinking water from a broken cup are very different, although we all talk about "drinking water" in the abstract. Of course, we can program separately for each derived class and call different drinkWater methods. However, as programmers, we can program a cup and call Cup's drinkWater() method, regardless of what kind of derived cup the cup is. Java will call the corresponding correct method, as we can see in the above program.
Looking at a more meaningful example, we add a drink() method to the Human class. This method receives a cup object and an integer as parameters. Integers represent the amount of water to drink:
public class Test{ public static void main(String[] args) { Human guest = new Human(); BrokenCup hisCup = new BrokenCup(); guest.drink(hisCup, 10); }}class Human { void drink(Cup aCup , int w) { aCup.drinkWater(w); }}
Program running results:
shit, no water inside
In the definition of drink() of the Human class, we require that the first parameter be a reference of type Cup. But in actual application (Test class), the BrokenCup derived class object of Cup is used. This actually upcasts hisCup to the Cup class and passes it to the drink() method. In the method, we called the drinkWater() method. Java discovered that this object was actually a BrokenCup object, so it actually called the corresponding method of BrokenCup.
downcast
We can downcast a base class reference to a derived class reference, but the object pointed to by the base class reference is already the derived class object to be downcast. For example, the hisCup above can be transformed upward into a Cup class reference, and then downwardly transformed into a BrokenCup class reference.
Object type
In Java, all classes actually have a common inheritance ancestor, which is the Object class. The Object class provides some methods, such as toString(). We can override these methods in our own class definition.
Object: ancestor
We can write a program that operates Object objects, and pass any object to the program through upcast.
I will delve into the Object class later.
(The implementation of polymorphism relies on RTTI support. I will go into more detail later.)
Summarize
Basic type conversion
polymorphism
downcast
Object