montag
montag

Reputation: 11

Polymorphism and abstract classes in Java

Let's say I have the following classes:

public abstract class Animal {
    public abstract void talk();
}
class Dog extends Animal {
    @Override
    public void talk() {
        System.out.println("Woof");
    }
}

class Cat extends Animal {
    @Override
    public void talk() {
        System.out.println("Meow");
    }
} 

Now I create a new class for testing and I want a method that will call the corresponding talk() method for the type of animal passed as its parameter. Then I call the method on objects dog and cat.

public class Test {
    
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        
        Test t = new Test();
        t.makeAnimalTalk(dog);
        t.makeAnimalTalk(cat);
        
    }
    
    public void makeAnimalTalk(Animal animal) {
        animal.talk();
    }
}

And this works. But what's bothering me is this:

public void makeAnimalTalk(Animal animal) {         
    animal.talk();  
}    

The parameter being passed here is of class type Animal, which is an abstract class. I know very well that we cannot make objects of an abstract class, so what is animal? How does this work? The program works even if I do something like this:

public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();

I'm coming from C++ and as far as I know this wouldn't work written in the language.

Upvotes: 1

Views: 115

Answers (4)

Sergey A Kryukov
Sergey A Kryukov

Reputation: 916

You need to understand such concepts as runtime type vs compile-time type.

public void makeAnimalTalk(Animal animal) {         
    animal.talk();  
}

//...

 Animal dog = new Dog(); // is valid
 makeAnimalTalk(dog); // is valid

The call above is valid, because this animal is a formal parameter, and it's type Animal is a compile-time type. During runtime, it is never Animal (but Dog and Cat are animals). During runtime, the types of actual objects are always non-abstract runtime types, in out examples, derived from Animal.

How it works? There are two aspects: 1) extension, 2) call indirection => late binding.

  1. When some method works with the abstract type Animal, it never has direct access to the extended part of Animal. Extended parts of the runtime types are different, but the code knows nothing about these parts. Therefore, it is safe to assign a cat to an Animal variable. On the other hand, you cannot assign a cat to a Dog variable. Why? In this case, the code can get access to the Dog extension of Animal, but it does not exist in the object, because the object's runtime type is Cat. Such access can lead to an attempt to handling non-existing parts of memory.

  2. At the same time, the code can get indirect access to some extension of the type Animal. How? Only via a virtual method. Virtual methods are called indirectly (dynamically dispatched) via a virtual method table. The efficiency of this call is almost as good as the direct call of a non-virtual function (they exist in most OOP languages). Why? Because the slots of all functions in the virtual method table are always fixed, and because there is only one level of indirection. The code working with the compile-time type Animal statically call makeAnimalTalk on an instance of one of the derived runtime types. The reference to this instance internally points to a particular virtual method table where the slot of makeAnimalTalk is replaced by the reference pointing to Cat.makeAnimalTalk or Dog.makeAnimalTalk.

This is how it all works. And it is the very heart of OOP every developers should understand perfectly well.

Upvotes: 0

Hifzan Ahmed
Hifzan Ahmed

Reputation: 1

In your case, you are creating the different objects based on extension of abstract. Methods will be recognized and execute properly as providing the actual reference to dog and cat.

public static void main() {
        Animal dog = new Dog();
        Animal cat = new Cat();}

In other case, if we are passing the different objects using polymorphism which are created based on extension of abstract and referring to abstract class instead of original in calling method then abstract may or may not have the calling method. Here it will fail to recognize the calling methods and failed with error 'java: cannot find symbol'

Upvotes: 0

Marce Puente
Marce Puente

Reputation: 478

Before giving any explanation, let's go with a verification, adding implicit constructors to the classes:

public abstract class Animal {
   int age;

   Animal() {
      System.out.println( "An animal has been created" );
   }

   int getAge() { 
      return age;
   }

   abstract void talk();
}

class Dog extends Animal {
   Dog() {
      System.out.println( "An dog has been created" );
   }

   @Override
   public void talk() {
       System.out.println("Woof");
   }

   // we do not declare the attribute “age” nor its corresponding “getter” since 
   // they are inherited from “Animal”.
}

and from somewhere in our code, we proceed to create a dog:

Dog blacky = new Dog();

and... surprise! our console shows:

"An animal has been created" 
"An dog has been created" 

This is because a dog is a dog and it is also an animal.

Now let's see a little about the classes that are extended, these, apart from being a base to create new classes, also work as an “umbrella” that allows us to group objects of different classes, although (as in the case of those that are “abstract”) objects of the same ones are not created, for example:

Animal animals[] = { new Dog(), new Cat() }; 

and we can invoke the methods of the class Animal:

for( Animal animal : animals ) animal.getAge();

if instead we invoke the talk() method:

for( Animal animal : animals ) animal.talk();

the “magic” of polymorphism appears, each object will invoke its own version of talk().

now if our dog, had a method giveThePaw(), try:

animal.giveThePaw(); 

would throw an error, because the Animal class does not have that method.

Upvotes: 1

Mureinik
Mureinik

Reputation: 312076

In C++'s terms, all the (instance) methods in Java are virtual. A method that takes an Animal parameter can be called with any concrete type of Animal, and can use any method declared by that class (or one of its super classes or interfaces). When the method is called, as you've seen, the concrete method of the concrete type will be invoked.

Upvotes: 2

Related Questions