Home > Enterprise >  How to cast two classes of the same abstract class back and forth
How to cast two classes of the same abstract class back and forth

Time:12-11

public abstract class Animal {

}

public class Dog implements Animal{

}

public class Cat implements Animal{

}

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

If I have the above structure is there a way to cast an instance of Cat to Dog and vice versa?

CodePudding user response:

No. You misunderstand the cast operator. It is used for three utterly unrelated tasks. You probably know that 5 2 is 7, but "hey" "bye" is "heybye". The operator is used for 2 utterly unrelated tasks. The cast operator is no different.

Hence it's best to reserve the name 'cast' for the actual syntactical construct of (SomeType) someExpr, i.e. use that term very rarely, and instead use the names for the 3 completely unrelated things you can do with it:

Primitive conversion

You get this 'mode' when the thing in the parens is a primitive, and, specifically, a numeric primitive: char, int, long, double, float, short, or byte. That's the whole list - these types are hardcoded in java, you can't make your own primitives, there will never be different primitive types than this1.

In this mode, actual conversion occurs:

double y = 5.5;
int x = (int) y;

The above converts, and this is the only usage of the cast operator that does so.

Type assertion

This mode occurs when you stick a non-primitive type in the parentheses, and applies only to the reified part (the non-generics part, so not anything in the <> part of the type).

What does this is, if everything works out, nothing. It doesn't do anything at all. It is incapable of converting anything.

Specifically, it will inject the following code:

  1. Check if the expression is actually an instance of the type in the parentheses.

2a. If it is, then do absolutely nothing, but for the purposes of parsing your code and knowing what to do, the expression (String) whatever is of type String, even if whatever is not.

2b. If it is not, then throw a ClassCastException on the spot.

That's all it can ever do. So, if it's not throwing an exception, it does nothing.

Type coercion / generics voodoo magic

I'm including this one for completeness: You shouldn't ever be using this particular mode of cast until you're way, way more advanced in java, and none of this is even going to make sense until you know quite a bit of java. If this sounds like gobbledygook to you, that's fine. Just ignore it, it's not important.

For the non-reified stuff in the parentheses (the stuff in the <>), it really really does absolutely nothing. It cannot possibly throw anything, it literally boils down to zero bytecode. It just tells javac to stop whining. It's you telling the compiler: Yes, I know, you cannot guarantee that any of this code makes sense. Shut up, I know what I am doing.

Example:

List<String> listOfStrings = new ArrayList<String>();
listOfStrings.add("Test");
List raw = listOfStrings;
List<Integer> numbers = (List<Integer>) raw;

The above compiles (but with warnings, because this is crazy code that makes no sense), and runs, and doesn't even throw an exception. Even though you now have a variable of type List<Integer> which contains a not-integer. If you interact with this list, ClassCastExceptions will start occurring even though you aren't even writing any casts.

Dogs and Cats

You're doing the second mode: Type assertion. An instance of Dog is not an instance of Cat, therefore:

Dog dog = new Dog();
Cat c = (Cat) dog;

will fail. It will in fact fail with a compile-time error because the compiler knows it'll never work. You can 'fix' that:

Animal a = new Dog();
Cat c = (Cat) a;

Now it will compile, but it'll throw a ClassCastException on the second line.

What does Animal a = new Dog() even mean?

Non-primitives in java are always references. It is impossible in java to have a variable that has a dog in it. I know Dog dog = new Dog() sure looks like you have a variable named dog whose value is a dog, but, it is not. It is a reference to a dog. Dogs are far too weird and large, variables are very simple things. All variables are like postit notes: They have a fixed, small amount of space. For int and boolean and the like, the number fits right on the postit note, but for all the non-primitives, you don't put the dog literally on the note. You write the location of the dog on the note. Dog dog = new Dog() is just syntax sugar for:

Dog dog;   // make a new postit note
new Dog(); // make a dog object
dog = what we just made; // scribble the dog's location on the note

Now Animal a = new Dog() makes sense: Animal a; and Dog dog are both just postit notes. It's just the a postit note here is restricted: It can be blank (a = null), or it can hold the location of an animal. You can write the location of a cat on there, or of a dog, or of a donkey, or of a unicorn. But you can't write the location of a house on it. The dog note can only hold the location of a dog (or be blank).

Hence, the location of a dog can be written on either note.

Cat c = (Cat) animal;

means:

  • Make a new postit note, name it c, and restrict it so that it can only hold the location of a cat, or b e blank.
  • Take the postit note named animal. Follow it and check if the animal it leads you to is, in fact, a cat. If it is NOT, throw a ClassCastException.
  • but if it is, take the location of that cat and scribble it on the postit titled 'c'.

That's a sensible thing to do.

[1] For the purposes of primitive conversion, the future language update known as Project Valhalla is unlikely to change this, even though it will more or less introduce the concept of letting you write your own primitives.

  • Related