CodeGym /Courses /Java Core /Type casting. Widening and narrowing conversions

Type casting. Widening and narrowing conversions

Java Core
Level 4 , Lesson 3
Available

"Hi, Amigo! The topic of today's lesson is widening and narrowing type conversions. You learned about widening and narrowing primitive types a long time ago. On Level 10. Today we're going to talk about how it works for reference types, i.e. instances of classes."

In fact, it's all quite simple. Imagine a class's inheritance chain: the class, its parent, the parent of the parent, etc., all the way back to the Object class. Because a class contains all member methods of the class it inherits, an instance of the class can be saved in a variable whose type is that of any of its parents.

Here's an example:

Code Description
class Animal
{
public void doAnimalActions();
}class Cat extends Animal
{
public void doCatActions();
}class Tiger extends Cat
{
public void doTigerActions();
}
Here we have three class declarations: Animal, Cat, and Tiger. Cat inherits Animal. And Tiger inherits Cat.
public static void main(String[] args)
{
Tiger tiger = new Tiger();
Cat cat = new Tiger(); Animal animal = new Tiger(); Object obj = new Tiger();
}
A Tiger object can always be assigned to a variable whose type is that of one of its ancestors. For the Tiger class, these are Cat, Animal, and Object.

Now let's take a look at widening and narrowing conversions.

If an assignment operation causes us to move up the inheritance chain (toward the Object class), then we're dealing with a widening conversion (also known as upcasting). If we move down the chain toward the object's type, then it's a narrowing conversion (also known as downcasting).

Moving up the inheritance chain is called widening, because it leads to a more general type. However, in doing so we lose the ability to invoke the methods added to the class through inheritance.

Code Description
public static void main(String[] args)
{
Object obj = new Tiger();
Animal animal = (Animal) obj; Cat cat = (Cat) obj; Tiger tiger = (Tiger) animal; Tiger tiger2 = (Tiger) cat;
}
When narrowing the type, you need to use a type conversion operator, i.e. we perform an explicit conversion.

This causes the Java machine to check whether the object really inherits the type we want to convert it to.

This small innovation produced a manifold reduction in the number of type casting errors, and significantly increased the stability of Java programs.

4
Task
Java Core, level 4, lesson 3
Locked
Code entry
Your attention, please! Now recruiting code entry personnel for CodeGym. Turn up your focus, let your fingers relax, read the code, and then... type it into the appropriate box. Code entry is far from a useless exercise, though it might seem so at first glance: it allows a beginner to get used to and remember syntax (modern IDEs seldom make this possible).
Code Description
public static void main(String[] args)
{
Object obj = new Tiger();
if (obj instanceof Cat)
{
Cat cat = (Cat) obj;
cat.doCatActions();
}}
Better yet, use an instanceof check
public static void main(String[] args)
{
Animal animal = new Tiger();
doAllAction(animal);

Animal animal2 = new Cat();
doAllAction(animal2);

Animal animal3 = new Animal();
doAllAction(animal3);
}

public static void doAllAction(Animal animal)
{
if (animal instanceof Tiger)
{
Tiger tiger = (Tiger) animal;
tiger.doTigerActions();
}

if (animal instanceof Cat)
{
Cat cat = (Cat) animal;
cat.doCatActions();
}

animal.doAnimalActions();
}
And here's why. Take a look at the example on the left.

We (our code) don't always know what type of object we are working with. It could be an object of the same type as the variable (Animal), or any descendant type (Cat, Tiger).

Consider the doAllAction method. It works correctly, regardless of the type of object passed in.

In other words, it works correctly for all three types: Animal, Cat, and Tiger.

public static void main(String[] args)
{
Cat cat = new Tiger(); Animal animal = cat; Object obj = cat;
}
Here we have three assignment operations. All of them are examples of widening conversions.

The type cast operator is not needed here, because no check is necessary. An object reference can always be stored in a variable whose type is one of its ancestors.

"Oh, the second to last example made everything clear: why the check is needed, and why type casting is needed."

"I hope so. I want to draw your attention to this fact:"

None of this causes an object to change in any way! The only thing that changes is the number of methods available to be called on a particular reference variable.

For example, a Cat variable lets you call the doAnimalActions and doCatActions methods. It doesn't know anything about the doTigerActions method, even if it points to a Tiger object.

"Yep, I get it. It was easier than I thought it would be."

Comments (49)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Andrew Evans Level 17, San Jose, Canada
28 February 2022
Think about what set of methods can be called by the reference variable and it makes sense
ImDevin Level 15, Old Town, United States
5 June 2021
lots of comments here about widening & narrowing. I think of it as a giant whirlpool in the ocean. It narrows as you get sucked into it & you can take methods with you. As you come up (if you could :), it widens, but you can't bring any method up with you. Goofy imagery, I know, but it helps me to remember. Happy coding! :)
Karas Level 1, Tampa, United States
14 March 2021
I am just also adding here something that I do not remember being covered and then suddenly you are supposed to know how to use. For Downcasting mainly: Cat tigger= new Tiger(); In this case if you call the methods inside Tiger as an example: tigger.roar(); it will give an error, unless of course there is a roar method in the Cat class, but cats do not roar in this example. So to solve it you can use: ((Tiger) tigger).roar(); Practically simple, but so far I had to google that or seen it in some solutions. Hope it helps.
Gellert Varga Level 23, Szekesfehervar, Hungary
18 May 2021
Yes that's right:) (but a small mistake: "For Downcasting mainly: Cat tigger= new Tiger();" --- this conversion is called Upcasting, not Down.)
Oliver Heintz Level 18, Mustang, United States
5 December 2020
So, if we had: Cat cat = new Tiger(); ...is cat a cat, or is cat a tiger? Can the cat that I created access Tiger methods, or is the new Tiger() object that I created immediately widened to Cat and now can't access Tiger methods? I guess this is where I'm confused. Because why not just Cat cat = new Cat() if all we wanted was a Cat to start with?
Ron R Level 2, Washington, United States
5 January 2021
Hopefully someone will interject, because I am curious as well. My understanding is, when you say Cat cat = new Tiger(), you want a Cat object with access to all the Tiger's methods. For example, if you have a class called, class Tiger extends Cat, then you have a Tiger that inherits all of the Cat's methods as well as all the Tiger's methods. However, following inheritance and the IS-A rule, a Tiger IS-A Cat (so we get all the Cat's methods) but not all Cats are Tigers (so we only have access to Cat methods). The only way for a Cat to implement the Tiger's methods is through polymorphism, i.e., Cat cat = new Tiger(). Now we have a Cat that can also act like a Tiger. Again, I hope someone concurs/non concurs with this, because I'd like some clarity as well.
Gellert Varga Level 23, Szekesfehervar, Hungary
15 February 2021
Hi Ron, You wrote: "When you say: Cat cat = new Tiger(); you want a Cat object with access to all the Tiger's methods. . . . Now we have a Cat that can also act like a Tiger. " I'm sorry, but this is not true at all. This cat is a Tiger but dumbed down. It means this Tiger ("cat") can access only to the Cat-methods. (Can access to all Cat methods, whether inherited from Animal or declared in Cat.) Now it can behave only as a Cat. But you can give back him all of his Tiger-abilities, this way: Tiger tig = (Tiger) cat; This "tig" can behave as a real Tiger now. (But this is the same object, just we assigned it to an other type variable.)
Gellert Varga Level 23, Szekesfehervar, Hungary
15 February 2021
Hi Oliver,

Cat cat = new Tiger();
This cat is a Tiger. Just disguised.:) The new Tiger() object that you created was immediately widened to Cat and now it can't access to Tiger methods.
Samuel Michael Level 16, Stafford, United States Expert
19 February 2021
I like to think of narrowing and widening in terms of the position of ancestors. Example 1. Cat cat = new Tiger(); new Tiger() means a new Tiger is being created. Easy. So is this a widening or narrowing conversion? Well, for one we know that Tiger inherits Cat, that means Cat is more generic relative to a specific class like Tiger, goes up the inheritance hierarchy. Second, think about the variable-value relationship. You have the variable on the left-hand side, and value on the right. It is similar to this in my opinion. The ancestor of tiger, Cat is on the left-hand side. In summary, A new tiger is created but since it is put in a Tiger variable, it lost all its Tiger features and only has access to Cat features, Narrowing Conversion Tiger tiger = (Tiger) animal The position of the ancestor is on the right-hand side, this means it's a narrowing conversion. Since an animal is more generic than a tiger, to make this animal into a tiger it has to be casted (Tiger) and then properly assigned to a Tiger variable In summary, An animal is created, the animal needs to be a tiger, but this cannot happen in its current state. A cast operation (Tiger) has to performed on the animal and assigned to the Tiger variable, thereby descending the inheritance hierarchy and granting the animal Tiger features.
Ron R Level 2, Washington, United States
20 February 2021
Gellert Varga, I disagree. But maybe we're talking over each other. I'm slightly more familiar with the concept now, so I realize there are areas of my original statement that should/could be clarified. However, what I initially said was not "not true at all." For clarity, the Tiger is not a "dumbed down" version of cat. In fact, it's not dumb at all. It's a very specific type of cat, it's a Tiger. Tiger inherits Cat's methods, and overrides them as applicable. You would never be able to do:

Cat cat = new Tiger();
Without the Tiger inheriting the Cat.

public class Tiger extends Cat
This is important because the JVM calls the appropriate method for the object that is referred to in each variable (i.e., Tiger). It does not call the method that is defined by the variable's type (i.e, Cat). Therefore, you can now make a Tiger "snarl" and a Cat "meow."

class Cat{
  public void animalSound() {
    System.out.println("Meow");
  }
}
class Tiger extends Cat{
  public void animalSound() {
    System.out.println("Snarl");
  }
}
Additionally, you can add more methods to the Tiger class to make it more Tiger-like. In your post, you said you couldn't. You said, "It means this Tiger ("cat") can access only to the Cat-methods." The tiger is not a cat in disguise either, as you say. This analogy further convolutes the topic. Cat is just a variable type, it doesn't disguise anything. The variable "cat" (I'm referring to the variable name "cat" not the variable type "Cat") is defined by the object it refers to, which is Tiger(). Regards.
Gellert Varga Level 23, Szekesfehervar, Hungary
26 February 2021
To Samuel: You wrote: "In summary, a new tiger is created but since it is put in a Tiger variable" but i think you wanted to write this: " A new tiger is created but since it is put in a Cat variable"
Gellert Varga Level 23, Szekesfehervar, Hungary
26 February 2021
Ron R, these two terms what i used: - "It means this Tiger ("cat") can access only to the Cat-methods.", and - "dumbed down" need to be more explained. I mean: we reduced its capabilities (fewer methods available) = "dumbed down". For example suppose in Cat class we have 3 methods. In Tiger class we have these 3 inherited methods, plus 2 other own special tiger methods, = five methods.

Cat catTig = new Tiger();
then with this Tiger object referred with catTig variable we can use only 3 methods - which are inherited from Cat. Of course catTig will use that version of these 3 methods what are defined (with override) in Tiger class. The plus 2 special Tiger-methods are not available with using catTig variable.
Oliver Heintz Level 18, Mustang, United States
26 February 2021
Ok, so... For clarity purposes, we have created a new Tiger referenced by cat. The cat reference is of type Cat. cat has only access to Cat methods and fields, not Tiger ones. We'd have to cast cat as Tiger to access Tiger methods or instance variables. Apparently we would do this typically when we would have something, like an Array, that would take Cat references, and also any subclass of Cat, so that it would take Tiger objects too. I guess to follow up this questioning, if Tiger IS-A Cat, and we had an Array of Cat objects, would we have to reference our Tiger object with a Cat object, or could we not just stick a Tiger reference into a Cat Array? So is this valid, and if it is, then why would we ever make a Cat object reference a Tiger object?

ArrayList<Cat> cats = new ArrayList<Cat>;
Tiger cat = new Tiger();
//given that Tiger extends Cat
cats.add(cat);
If the above isn't valid, I guess I've answered all the questions I have on this topic, and if it is valid, I am still confused why we would ever need to use a Superclass reference Object.
Gellert Varga Level 23, Szekesfehervar, Hungary
26 February 2021

Cat stripy = new Tiger;
I don't know the exact reason why would we assign a new Tiger object to a Cat-type reference-variable. Maybe they were just demonstrating examples. But the followings are sure: Yes, we can stick a Tiger object with its own original Tiger-type reference into a Cat Array. To do this, we don't need to change the Tiger-type reference to Cat-type reference. But when you put a Tiger object (more precisely a Tiger-type reference variable) into the Cat Array, the program will do exactly this type casting instead of us. This Cat array can store only Cat-type references, so the added Tiger-type reference will be modified to a Cat-type reference immediately. When you get the elements from the array, you can get only Cat-type references. You need to check them with the instanceof operator (which element belongs to which Cat-subclass?!), and you need to 'convert' them to the corresponding subtypes with narrowing. Look at this example:

import java.util.*;
public class MyClass {
  public static void main(String args[]) {
    ArrayList<Cat> cats = new ArrayList<Cat>();
    Tiger stripy = new Tiger();
    Cat striped = new Tiger();
        
    cats.add(stripy); // index 0 
    cats.add(striped); // index 1
        
    System.out.println(cats);
        
    cats.get(0).behave1();
    cats.get(0).behave2(); // ERROR! "cannot find symbol: method behave2()"
  }
}

class Cat {
  void behave1() {
    System.out.println("say meow");
  }
}
class Tiger extends Cat {
  @Override
  void behave1() {
    System.out.println("run 100km/h");
  }
  void behave2() {
    System.out.println("kills elephant");
  }
}
in line 14 you get an error. We placed a Tiger-type reference into the List, but when we get it from the List you can not run the special tiger-method behave2() on it, because it is already a Cat-type reference.
Oliver Heintz Level 18, Mustang, United States
28 February 2021
Now the anticipation is killing me. When you run behave1() on stripy, does it say meow, or run 100 km/hr? What about striped?
Gellert Varga Level 23, Szekesfehervar, Hungary
28 February 2021
This example is a working program, so you too can easily try out anything on it:) (( For example copy the above prg here: https://www.jdoodle.com/online-java-compiler/ )) Results:

stripy.behave1(); // run 100km/h
striped.behave1(); // run 100km/h
stripy.behave2(); // kills elephant 
// striped.behave2(); // error! : cannot find symbol striped.behave2();
Line-1, Line-2: Tiger objects use the overridden version of the method - both with Cat type variable and with Tiger type variable. Line-3, Line-4: you can use the behave2() method only if the variable is Tiger-type as stripy. With a Cat-type variable (striped), this Tiger method is not accessible.
Sansho Level 19, Bordeaux, France
18 May 2021
Guys, you are saying that doing:

Cat cat = new Tiger();
means that cat can't access to the Tiger methods.... The fact is it can.

public class Test{

public static void main(String[] args)
{
Animal animal = new Tiger();
doAllAction(animal);

Animal animal2 = new Cat();
doAllAction(animal2);

Animal animal3 = new Animal();
doAllAction(animal3);

Cat cat = new Tiger();
doAllAction(cat);
}

public static class Animal{
    void doAnimalActions(){
        System.out.println("Do something");
    }
}
public static class Cat extends Animal{
    void doCatActions(){
        System.out.println("Miaou");
    }
}

public static class Tiger extends Cat{
    void doTigerActions(){
        System.out.println("GRRRRRRRRRRR!");
    }
    
}

public static void doAllAction(Animal animal)
{
if (animal instanceof Tiger)
{
Tiger tiger = (Tiger) animal;
tiger.doTigerActions();
System.out.println("End of Tiger's actions");
}

if (animal instanceof Cat)
{
Cat cat = (Cat) animal;
cat.doCatActions();
System.out.println("End of cat's actions");

}

animal.doAnimalActions();
System.out.println("End of Animal's actions \n");

}
}
https://www.jdoodle.com/online-java-compiler/
Gellert Varga Level 23, Szekesfehervar, Hungary
18 May 2021
The fact persists that this cat variable doesn't have access to the Tiger’s methods:) "Moving up the inheritance chain is called widening, because it leads to a more general type. However, in doing so we lose the ability to invoke the methods added to the class through inheritance." It means the followings: - please try this code in your shared program! -

   Cat cat = new Tiger();
   cat.doTigerActions(); // ERROR!
The error-message from the compiler is: "cannot find symbol cat.doTigerActions(); symbol: method doTigerActions() , location: variable cat of type Cat" It means, directly with a Cat-type variable you can not access a specific Tiger-class method! By the way that method didn't finish its own life. The method is still existing, the Tiger object has it. But with this Cat-type variable nobody can reach it. = "None of this causes an object to change in any way! The only thing that changes is the number of methods available to be called on a particular reference variable." The program you shared is a quite other situation. In every case the doAllAction(Animal animal) method is called. This method receives a reference, and it does a type-casting immediately on it: it makes an Animal-type reference from all the received reference. What's more, with "instanceof" checks inside the method you cast the passed reference back to the original type of the object. In this case, of course the reference regains the reachability of the Tiger-class methods. Inside of doAllAction() you invoked the Tiger-method already on a Tiger-type reference, not on a Cat-type, thus it will be reachable again. Anyway, jdoodle is my favorite online IDE fo me too:)
John Level 17, Mansfield, Philippines
31 May 2021
There`s no sense in this program.the call for the doCatActions(),doTigerActions() will be of no use, since you declared a doAllActions method that calls the doTigerActions() for example if the passed obj is a Tiger refference ;)
Lawson Level 29, Lagos, Nigeria
27 August 2020
i dont really understand this
Agent Smith Level 38
26 August 2020
Here is how to remember this notion easily and never be confused about it again: 1. Object is the WIDEST class in Java. Because it has the biggest iheritance tree under it. 2. So when you go down that tree, you are NARROWING, because less and less ancestors will be there. +Remember that when we are widening with primitives, we don't needs explicit casting. Same here. That's all you practically need. If you want a better understanding why is it so, consider the following: when you have a parent reference variable pointing to any of its children, you can ALWAYS be sure that the child will have all its parents fields (via inheritance). So it is safe to have a more general variable pointing to a more specific child. But the opposite is not true and thus we need a check (cast). Easy! :)
Ryan Level 16, Ashburn, United States
19 July 2020
I feel like this lesson contradicts the previous lesson (Level 10 Lecture 6) on this on what is widening vs what is narrowing: https://codegym.cc/quests/lectures/questsyntax.level10.lecture06 . The examples in the previous lesson (10.6) show going from Object to the String, Integer, etc. is a widening conversion while this lesson says "Moving up the inheritance chain is called widening, because it leads to a more general type." I think both lessons are consistent on when a cast operator is called (going from a parent to a child class) but they seem to contradict which direction is widening and which is narrowing.
Ryan Level 16, Ashburn, United States
19 July 2020
Here is my understanding from reading the Oracle documentation: 1. Moving up the inheritance chain is widening (going from child class to parent class). 2. Moving down the inheritance chain is narrowing (going from parent class to child class). 3. A cast operator is required when narrowing (going from parent class to child class).
Alex Vypirailenko Level 41, USA
20 July 2020
The examples in Level 10 Lesson 6 are incorrect and will be fixed soon. The current lesson is correct.
Thang Za Thang Level 18, Melbourne, Australia
11 July 2020
Too many conflicting on narrowing and widening: Am I right here? Here's a visual in my head: Object 0 --- > If you reach here, it's widening where your access to methods is lost. ..... Animal animal ..... Cat cat ...... Tiger tiger ------> If you reach here, you are narrowing, which means you have more access to methods?
Sela Level 20, Poland
18 July 2020
seems to be a contradict but the "bucket" of Object is the widest in Java. you can pour in it anything
HaeWon Chung Level 17, Boston, United States
17 May 2020
Ok, so my understanding is that

Animal animal = (Animal) obj;
Is narrowing. Then, is this widening?

Object obj = new Tiger();
If the above is widening, does that mean that obj lost all methods that are specific to Tiger and Cat classes? Can I say that if the variable type is not exactly same as assigned object, it is either widening or narrowing. Is that correct?
Brandon Waites Level 26, Cincinnati, United States
19 May 2020
Yes to narrowing, and yes to widening. With widening, you are unable to use the methods within the specific narrowed classes if the ancestor class does not have that same method specified unless you cast down to that class. To your last question, I believe that is a fair assumption to make as long as that object that you are casting to is within the same inheritance chain as the object you are converting from.
Sela Level 20, Poland
18 July 2020
these methods are not lost. they exist in memory but are not accessible to the program until the object is downcast. although the set of methods defined in Object is still accessible in the overridden version like toString(), if Tiger overrides this method of course
TARAS Level 22, Lviv, Украина
11 January 2020
i'm totaly lost here.