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)
Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.
The generic collections are introduced in Java 5 Version. The generic collections disable the type-casting and there is no use of type-casting when it is used in generics. The generic collections are type-safe and checked at compile-time. These generic collections allow the datatypes to pass as parameters to classes.
So, anything that is used as generics has to be convertable to Object (in this example get(0) returns an Object ), and the primitive types aren't. So they can't be used in generics.
Type erasure is a process in which compiler replaces a generic parameter with actual class or bridge method. In type erasure, compiler ensures that no extra classes are created and there is no runtime overhead.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With