CodeGym /Java Blog /Inheritance in Java /Widening and narrowing of reference types
Author
John Selawsky
Senior Java Developer and Tutor at LearningTree

Widening and narrowing of reference types

Published in the Inheritance in Java group
Hi! In a past lesson, we discussed casting primitive types. Let's briefly recall what was discussed. Widening and narrowing of reference types - 1We imagined primitive types (in this case, numeric types) as nesting dolls that vary in size according to the amount of memory they occupy. As you'll remember, putting a smaller doll inside a larger one is simple both in real life and in Java programming.

public class Main {
   public static void main(String[] args) {
       int bigNumber = 10000000;
       short smallNumber = (short) bigNumber;
       System.out.println(smallNumber);
   }
}
This is an example of automatic conversion or widening. It happens by itself, so you don't need to write additional code. In the end, we aren't doing anything unusual: we're just putting a smaller doll into a bigger doll. It's another matter if we try to do the opposite and put a larger Russian doll into a smaller doll. You can't do that in real life, but in programming you can. But there's one nuance. If we try to put an int into a short variable, things don't go so smoothly for us. After all, the short variable only holds 16 bits of information, but an int occupies 32 bits! As a result, the passed value is distorted. The compiler will give us an error ('Dude, you're doing something suspicious!'). But if we explicitly indicate the type that we're converting our value to, it will go ahead and perform the operation.

public class Main {

   public static void main(String[] args) {

       int bigNumber = 10000000;

       bigNumber = (short) bigNumber;

       System.out.println(bigNumber);

   }

}
That's just what we did in the example above. The operation was performed, but because the short variable can accommodate only 16 of the 32 bytes, the final value is distorted and we get the number -27008. Such an operation is called an explicit conversion, or narrowing.

Examples of widening and narrowing of reference types

Now let's talk about the same operators applied not to primitive types, but to objects and reference variables! How does this work in Java? It's actually quite simple. There are objects that are unrelated. It would be logical to assume that they cannot be converted to each other, neither explicitly nor automatically:

public class Cat {
}

public class Dog {
}

public class Main {

   public static void main(String[] args) {

       Cat cat = new Dog(); // Error!

   }

}
Here, of course, we get an error. The Cat and Dog classes are not related to each other, and we have not written a 'converter' to move from one to the other. It makes sense that we can't do this: the compiler has no idea how to convert these objects from one type to the other. If the objects are related, well, that's another matter! Related how? Above all, through inheritance. Let's try using inheritance to create a small system of classes. We'll have a common class to represent animals:

public class Animal {

   public void introduce() {

       System.out.println("I'm Animal");
   }
}
Everybody knows that animals can be domesticated (pets) or wild:

public class WildAnimal extends Animal {

   public void introduce() {

       System.out.println("I'm WildAnimal");
   }
}

public class Pet extends Animal {

   public void introduce() {

       System.out.println("I'm Pet");
   }
}
For example, take canines — we have domestic dogs and coyotes:

public class Dog extends Pet {

   public void introduce() {

       System.out.println("I'm Dog");
   }
}



public class Coyote extends WildAnimal {

   public void introduce() {

       System.out.println ("I'm Coyote");
   }
}
We specifically chosen the most basic classes to make them easier to understand. We don't really need any fields, and one method is enough. Let's try executing this code:

public class Main {

   public static void main(String[] args) {

       Animal animal = new Pet();
       animal.introduce();
   }
}
What do you think will be displayed on the console? Will the introduce method of the Pet class or the Animal class be invoked? Try to justify your answer before you continue reading. And here's the result! I'm Pet Why did we get that? It's all simple. We have a parent variable and a descendant object. By writing,

Animal animal = new Pet();
we widened a Pet reference and assigned it to an Animal variable. As with primitive types, reference types are automatically widened in Java. You don't need to write additional code to make it happen. Now we have a descendant object assigned to a parent reference. As a result, we see that the method call is made on the descendant class. If you still don't fully understand why this code works, rewrite it in plain language:

Animal animal = new DomesticatedAnimal();
There's no problem with this, right? Imagine that this is real life, and the reference is simply a paper label with 'Animal' written on it. If you take that piece of paper and attach it to the collar of any pet, everything will be correct. After all, any pet is an animal! The reverse process — moving down the inheritance tree to descendants — is narrowing:

public class Main {

   public static void main(String[] args) {

       WildAnimal wildAnimal = new Coyote();

       Coyote coyote = (Coyote) wildAnimal;

       coyote.introduce();
   }
}
As you can see, here we clearly indicate the class that we want to convert our object to. We previously had a WildAnimal variable, and now we have a Coyote, which is lower on the inheritance tree. It makes sense that without an explicit indication the compiler won't allow such an operation, but if we indicate the type in parentheses, then everything works. Widening and narrowing of reference types - 2Consider another more interesting example:

public class Main {

   public static void main(String[] args) {

       Pet pet = new Animal(); // Error!
   }
}
The compiler generates an error! But why? Because you are trying to assign a parent object to a descendant reference. In other words, you're trying to do something like this:

DomesticatedAnimal domesticatedAnimal = new Animal();
Well, maybe everything will work if we explicitly specify the type that we're trying to convert to? That worked with numbers — Let's give it a try! :)

public class Main {

   public static void main(String[] args) {

       Pet pet = (Pet) new Animal();
   }
}
Exception in thread "main" java.lang.ClassCastException: Animal cannot be cast to Pet Error! The compiler didn't yell at us this time, but we ended up with an exception. We already know the reason: we're trying to assign a parent object to a descendant reference. But why exactly can't you do that? Because not all Animals are DomesticatedAnimals. You created an Animal object and are trying to assign it to a Pet variable. A coyote is also an Animal, but it is not a Pet. In other words, when you write

Pet pet = (Pet) new Animal();
new Animal() could represent any animal, not necessarily a pet! Naturally, your Pet pet variable is only suitable for storing Pets (and their descendants) and not any type of animal. That's why a special Java exception, ClassCastException, was created for cases where an error occurs while casting classes. Let's review it again to make things clearer. A parent reference can point to instances of a descendant class:

public class Main {

   public static void main(String[] args) {

       Pet pet = new Pet();
       Animal animal = pet;

       Pet pet2 = (Pet) animal;
       pet2.introduce();
   }
}
For example, here we have no problems. We have a Pet object referenced by a Pet variable. Later, an Animal reference pointed at the very same object. After that, we convert animal to a Pet. By the way, why did that work for us? Last time we got an exception! Because this time our original object is a Pet!

Pet pet = new Pet();
But in the last example, it was an Animal object:

Pet pet = (Pet) new Animal();
You cannot assign an ancestor object to a descendant variable. You can do the opposite.
Comments (23)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Anonymous #10428383 Level 12, Seattle, United States
11 January 2024
casting down/up will permite you a rounded access of the objects properties : parent<->child
PAUL Level 23, Poland
21 March 2023
in first example this is isnt widening conversion. its narrowing conversion
Mike S Level 28, Saint Louis, United States
4 January 2023
This article seems to state the opposite as indicated by a recent example shown below (and verified to be correct). In the article, the instantiation/"right side" of the reference variable declaration indicates the methods available to it. (See "Animal pet = new Pet()" example.) But in the exercise (code pasted below), it seems to show the exact opposite. In the haveFun() method, it doesn't see/have access to the Player or Dancer methods until it's cast as Player or Dancer. What the heck? Thank you in advance!

public class Solution {
    public static void main(String[] args) throws Exception {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        Person person = null;
        String key;
        while (!(key = reader.readLine()).equals("exit")) {
            if ("player".equals(key)) {
                person = new Player();
            } else if ("dancer".equals(key)) {
                person = new Dancer();
            }
            haveFun(person);
        }
    }

    public static void haveFun(Person person) {
        //write your code here
        if(person instanceof Player){
            Player playerPerson = (Player) person;
            playerPerson.play();
        }else{
            Dancer dancerPerson = (Dancer) person;
            dancerPerson.dance();
        }
    }

    interface Person {
    }

    static class Player implements Person {
        void play() {
            System.out.println("playing");
        }
    }

    static class Dancer implements Person {
        void dance() {
            System.out.println("dancing");
        }
    }
}
Aldo Luna Bueno Level 28, Peru
23 February 2022
You cannot assign an ancestor object to a descendant variable. This doesn't work:

Pet pet = (Pet) new Animal();
Andrew Evans Level 17, San Jose, Canada
20 February 2022
A string is an object, but an object is not a string.
Gabriela Knapik Level 31, Poland
20 November 2021
I am confused about this extract of code: The article claimed that this is widening operation and it happens automatically. However, in the article Widening and narrowing the author says, that exactly the same situation is an example of narrowing (I attach the scrap here) There is possibility that I misinterpreted one of these cases so I would appreciate correct explanation. Thanks!
kyrie Level 18, 重庆
12 June 2021
Pet pet = new Pet(); Animal animal = pet; Pet pet2 = (Pet) animal; pet2.introduce(); Pet pet2 = (Pet) animal; 后为何输出 还是 pet的introduce?
John Level 17, Mansfield, Philippines
2 June 2021
This makes everything clear to me ;)
Roman Grygorczuk Level 19, Cracow, Poland
9 December 2020
Can someone explain to me based on my example what it the purpose of narrowing and using pet2 if we can take pet and call the print method dirrectly without any complication?

public class Main {
    public static void main(String[] args) {
        Pet pet = new Pet();
        Animal animal = pet;
        Pet pet2 = (Pet) animal;
        pet2.print();
        pet.print();

        Animal animal1 = new Animal();
        animal1.print();
    }
}

class Animal {
    public void print(){
        System.out.println("parent");
    }
}

class Pet extends Animal {
    public void print(){
        System.out.println("child");
    }
}
Chandan Thapa Level 22, Dubai, United Arab Emirates
25 November 2020
This is so much informative! Thanks