Domanda

 1.  List<Car> carList = new ArrayList<Sedan>();

 2.  List<Car> carList = new ArrayList<Car>();
    carList.add(new Sedan());

1 has a compiler error and 2 is legal.

Why the type of variable declaration must match the type we pass to the object type(derived type is not allowed)? I was using Array as following which absolutely correct:

int SIZE = 10;
Car[] carArray = new Sedan[SIZE];

Can anyone tell me why collections must declare as condition 2? Thanks

È stato utile?

Soluzione

Sedan[] is a sub-type of Car[]. But this has a very negative impact on type safety, because the following code is thus legal:

Sedan[] sedans = new Sedan[10];
Car[] cars = sedans; // legal, since Sedan[] extends Car[]
cars[0] = new Car(); // Houston, we have a problem

This code causes an ArrayStoreException at runtime, because an array supposed to only contain sedans would contain a non-Sedan car.

The goal of generics was to have type-safe collections. And the decision was thus taken to design them so that the above problem would be detected at compile time, rather than runtime. Collections are much more used than arrays, and it's quite a good thing to have them type-safe.

So they decided that a List<Sedan> would not be a List<Car>.

Altri suggerimenti

In your Case 1, what you are essentially doing is creating an ArrayListto hold all sorts of car objects, then trying to assign this variable an ArrayList of Sedans. This will not work at its core because you are going from large to small (A Sedan is a Car, but a Car might not be a Sedan).

Basically, your carList in Case 1 can won't work because, with generics, any Car should be able to be put in carList. This means I can call carList.add(new Compact(); right after adding a Sedan.

Case 2 follows this convention.

The way I like to think about it when using generics is that you're essentially creating instances of two separate types of objects in Case 1. Imagine ArrayListCar and ArrayListSedan for a moment, if you will. Each is specialized to take their specific arguments. Confusing, I know, since the arrays will work, but that's just the way it's designed.

It is about type safe in Generics. Assume it is okay:

List<Dog> dogs = new ArrayList<Dog>();
List<Animal> animals = dogs;
animals.add(new Cat());

you definitely can add a new cat object to a list of type Animal, but if the animals refers to dogs, you will messed up a cat with a bunch of dogs, and which is very dangerous, to avoid this, type safe of Generics only allow the declaration type match the object type.

This article and this article explain it in detail. The main idea was explained by Josh Bloch using the PECS principle for generics

Producer Extends > Consumer Super  [PECS]

I most depends on how your are interacting with data stored in a container, as producer or consumer.

Another concern is when is type safety checked, run time or compile time.Consider the following:

// array type safety errors caught at run time
Object[] objArray = new Long[3];
objArray[0] = "i'm a string";
Object notANumber = objArray[0];

// run time exception
Long number = (Long)notANumber;

// generic type errors caught at compile time
// use pecs principle in new ArrayList<...>();
List<Number> list = new ArrayList<Number>();
list.add("not a number"); // compile time error
list.add(Long.valueOf(42L));

Long wontWork = list.get(0);  // compile time error
Number works = list.get(0); 

This compiles:

List<? extends Car> carList = new ArrayList<Sedan>();

but it will not behave as you expect, and the result may be confusing later on if you don't have a sufficient understanding of generics. You should really read a tutorial, there are many of them.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top