Reputation: 781
Who could me explain this?
I have these couple of classes:
abstract class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
public void woof() {
System.out.println("woof");
}
}
class Cat extends Animal {
public void meow() {
System.out.println("meow");
}
}
And this is the action:
import java.util.ArrayList;
import java.util.List;
public class TestClass {
public static void main(String[] args) {
new TestClass().go();
}
public void go() {
List<Dog> animals = new ArrayList<Dog>();
animals.add(new Dog());
animals.add(new Dog());
doAction(animals);
}
public <T extends Animal> void doAction(List<T> animals) {
animals.add((T) new Cat()); // why is it possible?
// Variable **animals** is List<Dog>,
// it is wrong, that I can add a Cat!
for (Animal animal: animals) {
if (animal instanceof Cat) {
((Cat)animal).meow();
}
if (animal instanceof Dog) {
((Dog)animal).woof();
}
}
}
}
This example compile without errors, and output is:
woof
woof
meow
But how can I add in list of Dog a Cat? And how the Cat is casted to Dog?
I use: java version "1.6.0_24". OpenJDK Runtime Environment (IcedTea6 1.11.1) (6b24-1.11.1-4ubuntu3)
Upvotes: 8
Views: 2750
Reputation: 8619
Ok here's the deal with generics (anything that uses casting hackery might not be safe at runtime, because generics work by erasure):
You can assign a subtype parameterised the same way e.g
List<Animal> l = new ArrayList<Animal>();
and you can add items that are the type of this parameter or its subclasses e.g
l.add(new Cat());
l.add(new Dog());
but you can only get out the type of the parameter:
Animal a = l.get(0);
Cat c = l.get(0); //disallowed
Dog d = l.get(1); //disallowed
Now, you can use a wild card to set an upper bound on the parameter type
List<? extends Animal> l = new ArrayList<Animal>();
List<? extends Animal> l = new ArrayList<Cat>();
List<? extends Animal> l = new ArrayList<Dog>();
But you can't add new items to this list
l.add(new Cat()); // disallowed
l.add(new Dog()); // disallowed
In your case you have a List<T>
so it has a method add(T t)
so you can add if you cast to T
. But T
has type bounded above by Animal
so you shouldn't even be trying to add to this list, but it is treated as a concrete type and that's why it allows the cast. However this may throw a ClassCastException
.
And you can only retrieve items that are the upper bound type
Animal a = l.get(0);
Cat c = l.get(0); //disallowed
Dog d = l.get(1); //disallowed
Or you can set the lower bound parameter type
List<? super Animal> l1 = new ArrayList<Object>();
List<? super Animal> l1 = new ArrayList<Animal>();
List<? super Cat> l2 = new ArrayList<Animal>();
List<? super Cat> l2 = new ArrayList<Cat>();
List<? super Dog> l3 = new ArrayList<Animal>();
List<? super Dog> l3 = new ArrayList<Dog>();
And you can add objects that are subtypes of the lower bound type
l1.add(new Cat());
l1.add(new Dog());
l1.add(new Object()); //disallowed
But all objects retrieved are of type Object
Object o = l1.get(0);
Animal a = l1.get(0); //disallowed
Cat c = l2.get(0); //disallowed
Dog d = l3.get(0); //disallowed
Upvotes: 8
Reputation: 877
Your function doAction
is parametrized by a type T
that extends the class Animal
. So it can be either an object of type Dog
or Cat
.
You should also note the difference between formal parameter and effective parameter.
The formal parameter is the one you use when defining your method, which in your case is List <T>
(using it as a shortcut).
The effective parameter is the one you "give" to your method when call it, here List<Dog>
.
.
Lets take a look at animals.add((T) new Cat())
in doAction()
. animals is a list which elements are of type T
which is either Dog
or Cat
, so there is no mistake, since that's the type of the formal parameter.
So that's the reason. It is one of the benefits of using parametrize classes.
Upvotes: 0
Reputation: 5239
Do not expect generics to perform runtime type checking. During compilation, Java performs all the type inference, instantiates all the types, ... and then erases all trace of generic types from the code. At runtime, the type is List, not List< T > or List< Dog >.
The main question, why it allows you to cast new Cat()
to type T extends Animal
, with only a warning about unchecked conversions, is valid. Certain unsound features of the type system make legalizing such dubious casts necessary.
If you want the compiler to prevent the addition of anything to the list, you should use a wildcard:
public void doAction(List< ? extends Animal > animals) {
animals.add(new Cat()); // disallowed by compiler
animals.add((Animal)new Cat()); // disallowed by compiler
for (Animal animal: animals) {
if (animal instanceof Cat) {
((Cat)animal).meow();
}
if (animal instanceof Dog) {
((Dog)animal).woof();
}
}
}
P.S. The dubious downcasts in the loop body are a perfect example of how lame Java is for disjoint sum (variant) types.
Upvotes: 2
Reputation: 200306
Suffice it to say that there is such a T
for which the cast can succeed. As for compiled code, it will be exactly the same as if you wrote animals.add((Animal)new Cat());
Upvotes: 0
Reputation: 4686
This is related to type erasure. The type is not preserved at runtime. Really, List becomes List of Type Object at runtime. This is why you're getting a compiler warning or should be on animals.add((T) new Cat()); At compile time, Cat does extend animal which is of type T. However, it cannot enforce that a Dog is in the list at that time, therefore the compiler warning.
Upvotes: 1
Reputation: 35106
Generics only work for compile time safety. In your case, how can the compiler know that something bad will happen? It assumes your type definitions, and proceeds off of that. To do more would mean much more elaborate work for the compiler, but there are extended static checkers and other tools that could catch this pre-runtime.
Upvotes: 0