Details the type conversion and dynamic binding of Java polymorphic objects

  • 2020-04-01 04:13:47
  • OfStack

Type conversion for Java polymorphic objects
By object type conversion, I mean an object of inheritance, not an object of any type. When there is no inheritance object for casting, the Java runtime throws Java lang. ClassCastException.

In the inheritance chain, we call the transition from subclass to parent class "up transition" and the transition from parent to child class "down transition".

A lot of times, we will define a variable as the type of the parent class, but we will refer to the object of the subclass. The program runtime calls subclass methods through dynamic binding, known as polymorphism.

However, sometimes in order to accomplish something that the parent class doesn't have, we need to turn the upturned subclass object into a subclass, and then call the methods of the subclass, which is the downward transformation.

Note: you cannot cast an object of a parent class to a subclass type directly, only an upcast subclass object to a subclass type again. That is, subclass objects must be transformed up before they can be transformed down again. Look at the following code:


public class Demo {
  public static void main(String args[]) {
    SuperClass superObj = new SuperClass();
    SonClass sonObj = new SonClass();
    //The following code throws an exception when run and cannot directly cast a superclass object to a subclass type
    // SonClass sonObj2 = (SonClass)superObj;
    //First up, then down
    superObj = sonObj;
    SonClass sonObj1 = (SonClass)superObj;
  }
}
class SuperClass{ }
class SonClass extends SuperClass{ } 

Comment out line 7, the runtime throws an exception, but the compilation passes.

Since downward transitions are risky, when you receive a reference to a parent class, be sure to use the instanceof operator to determine whether the object is the subclass you want. See the following code:


public class Demo {
  public static void main(String args[]) {
    SuperClass superObj = new SuperClass();
    SonClass sonObj = new SonClass();
    //SuperObj is not an instance of the SonClass class
    if(superObj instanceof SonClass){
      SonClass sonObj1 = (SonClass)superObj;
    }else{
      System.out.println(" Cannot be converted ");
    }
    superObj = sonObj;
    //SuperObj is an instance of the SonClass class
    if(superObj instanceof SonClass){
      SonClass sonObj2 = (SonClass)superObj;
    }else{
      System.out.println(" Cannot be converted ");
    }
  }
}
class SuperClass{ }
class SonClass extends SuperClass{ }

Operation results:


 Cannot be converted 

Summary: object casts are checked while the program is running, the upcast is automatically done, and the downcast object must be a subclass of the current reference type.

Java polymorphism and dynamic binding
In Java, a variable of a parent class can refer to either an instance of the parent class or an instance of a subclass.

Let's start with a piece of code:


public class Demo {
  public static void main(String[] args){
    Animal obj = new Animal();
    obj.cry();
    obj = new Cat();
    obj.cry();
    obj = new Dog();
    obj.cry();
  }
}
class Animal{
  //Animal calls
  public void cry(){
    System.out.println(" Don't know what to call it ");
  }
  
}
class Cat extends Animal{
  //The cat's cry
  public void cry(){
    System.out.println(" Meow meow ~");
  }
}
class Dog extends Animal{
  //The dog's barking
  public void cry(){
    System.out.println(" Wang wang ~");
  }
}

Operation results:


 Don't know what to call it 
 Meow meow ~
 Wang wang ~

The code above defines three classes: Animal, Cat, and Dog. The Cat and Dog classes both inherit from the Animal class. The obj variable is of type Animal, and it can point to either an instance of the Animal class or an instance of the Cat and Dog classes, which is correct. That is, a variable of a parent class can refer to either an instance of the parent class or an instance of a subclass. Note that the reverse is wrong, because all cats are animals, but not all animals are cats.

As you can see, obj can be either a human, a cat or a dog, and it has different forms of expression, which is called polymorphism. Polymorphism is when something has different forms or forms.

Another example is "human", there are many different expressions or realizations, he or she can be a driver, teacher, doctor, etc., when you hate yourself will say "new life", so you can become a driver, teacher, doctor in your next life, we say "human" has polymorphism.

There are three necessary conditions for polymorphic existence: inheritance, overrides, and parent class variables referencing subclass objects.

When calling a method in polymorphic mode:
First check to see if the method is in the parent class. If it is not, a compilation error occurs. If so, check whether the subclass overrides the method.
If the subclass overrides the method, the subclass method is called, otherwise the superclass method is called.

As you can see from the above example, one of the benefits of polymorphism is that there is no need to define more than one variable when there are many subclasses, and you can define only one variable of the parent class type to refer to instances of different subclasses. Here's another example:


public class Demo {
  public static void main(String[] args){
    //With polymorphism, the owner can feed many animals
    Master ma = new Master();
    ma.feed(new Animal(), new Food());
    ma.feed(new Cat(), new Fish());
    ma.feed(new Dog(), new Bone());
  }
}
//Animal and its subclasses
class Animal{
  public void eat(Food f){
    System.out.println(" I am a small animal and am eating " + f.getFood());
  }
}
class Cat extends Animal{
  public void eat(Food f){
    System.out.println(" I'm a little cat and I'm eating " + f.getFood());
  }
}
class Dog extends Animal{
  public void eat(Food f){
    System.out.println(" I'm a dog. I'm eating " + f.getFood());
  }
}
//Food and its subclasses
class Food{
  public String getFood(){
    return " things ";
  }
}
class Fish extends Food{
  public String getFood(){
    return " fish ";
  }
}
class Bone extends Food{
  public String getFood(){
    return " The bone ";
  }
}
//Master class
class Master{
  public void feed(Animal an, Food f){
    an.eat(f);
  }
}

Operation results:


 I'm a small animal and I'm eating food 
 I'm a little cat and I'm eating fish 
 I'm a dog, eating a bone 

The feed method of the Master class has two parameters, Animal type and Food type, and since it is a parent class, you can pass an instance of a subclass to it so that the Master class does not need multiple methods to feed different animals.
Dynamic binding

To understand the nature of polymorphism, let's look at the detailed flow of Java method invocation.

1) the compiler looks at the declared type and method name of the object.

Suppose you call obj.func(param), which is an object of the Cat class. It is important to note that there may be multiple methods with different names for func but different parameter signatures. For example, there might be methods func(int) and func(String). The compiler will enumerate all the methods named func in the Cat class and func in the parent Animal class that access the property public.

In this way, the compiler gets a list of all the candidate methods that might be called.

2) next, the encoder checks the parameter signatures provided when the method is invoked.

If there is one method among all methods named func that exactly matches the parameter signature provided, select this method. This process is called overloading resolution. For example, if func("hello") is called, the compiler selects func(String) instead of func(int). Due to the existence of automatic type casting, for example, int can be converted to double. If the same method as the calling method parameter signature is not found, the casting can be continued after the search. If no matching type is found or more than one method is matched, the compilation error occurs.

In this way, the compiler gets the method name and parameter signature that it needs to call.

3) if the method modifiers are private, static, final (static and final will be explained later), or constructor methods, then the compiler will know exactly which method to call, we call this way as static binding.

Instead, the method is called depending on the actual type of object and implements dynamic binding at run time. For example, if you call func("hello"), the compiler will dynamically bind to generate an instruction that calls func(String).

4) when a program runs and a method is invoked using dynamic binding, the JVM must invoke the method of the class that is most appropriate for the actual type of object obj references. We have assumed that the actual type of obj is Cat, which is a subclass of Animal. If Cat defines func(String), call it, otherwise it will be found in the Animal class and its superclass.

It is expensive to search every time a method is called, so the JVM creates a method lable for each class in advance, listing the names of all the methods, their parameter signatures, and the classes to which they belong. This way, when the method is actually called, the virtual machine simply looks up this table. In the above example, the JVM searches the method table of the Cat class to find methods that match the call to func("hello"). This method could be cat.func (String) or animal.func (String). Note that if super.func("hello") is called, the compiler searches the parent class's method table for rows.

Assuming that the Animal class contains cry(), getName(), and getAge(), the list of its methods is as follows:
Cry () - > Animal. Cry ()
GetName () - > Animal. The getName ()
GetAge () - > Animal. GetAge ()

In fact, Animal also has a default parent class Object (which will be explained later) that inherits the methods of Object, so the methods listed above are not complete.

Suppose the Cat class overrides the cry() method in the Animal class and adds a new method, climbTree(), then its argument list is:
Cry () - > The cry ()
GetName () - > Animal. The getName ()
GetAge () - > Animal. GetAge ()
ClimbTree () - > The climbTree ()

At run time, the obj.cry() method is called as follows:
The JVM first accesses the method table of the actual type of obj, either the method table of the Animal class or the method table of the Cat class and its subclasses.
The JVM searches the method table for a method that matches cry (), and when it finds it, it knows which class it belongs to.
The JVM calls this method.


Related articles: