2012-05-24 23 views
7

Kto może mi to wyjaśnić?Java Generics: dodanie niewłaściwego typu w kolekcji

mam te kilka klas:

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"); 
    } 
} 

I to jest akcja:

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(); 
      } 
     } 
    } 
} 

Ten przykład skompilować bez błędów, a wyjście jest:

woof 
woof 
meow 

Ale jak czy mogę dodać na liście psa kota? A jak Cat jest rzucony na psa?

używam: java version "1.6.0_24". OpenJDK Runtime Environment (IcedTea6 1.11.1) (6b24-1.11.1-4ubuntu3)

+1

Istnieją ostrzeżenia kompilatora, nie? –

+1

Na pewno pojawi się ostrzeżenie kompilatora na animals.add ((T) new Cat()) ;. – tjg184

+1

I tak zaczyna się szlak łez, czyli generics Java. –

Odpowiedz

6

Ok tu chodzi z rodzajowych (cokolwiek to zastosowania odlewania hackery może nie być bezpieczne w czasie wykonywania, ponieważ rodzajowych praca erasure):

można przypisać podtyp parametryzowane w ten sam sposób np

List<Animal> l = new ArrayList<Animal>(); 

i można dodać elementy, które są typem tego parametru lub jego podklasy np

l.add(new Cat()); 
l.add(new Dog()); 

ale można dostać tylko na tY PE parametru:

Animal a = l.get(0); 
Cat c = l.get(0); //disallowed 
Dog d = l.get(1); //disallowed 

Teraz można korzystać z dziką kartę, aby ustawić górną granicę od typu parametru

List<? extends Animal> l = new ArrayList<Animal>(); 
List<? extends Animal> l = new ArrayList<Cat>(); 
List<? extends Animal> l = new ArrayList<Dog>(); 

Ale nie można dodać nowe elementy do tej listy

l.add(new Cat()); // disallowed 
l.add(new Dog()); // disallowed 

W twoim przypadku masz List<T>, więc ma on metodę add(T t), którą możesz dodać, jeśli przesyłasz do T. Ale T ma typ ograniczony przez Animal, więc nie powinieneś nawet próbować dodawać do tej listy, ale jest traktowany jako konkretny typ i dlatego pozwala na rzutowanie. Może to jednak spowodować wyświetlenie ClassCastException.

I można tylko odzyskać elementy, które stanowią górną granicę type

Animal a = l.get(0); 
Cat c = l.get(0); //disallowed 
Dog d = l.get(1); //disallowed 

Albo można ustawić dolną granicę parametru typ

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>(); 

i można dodać obiekty, które są podtypy dolna typ powiązany

l1.add(new Cat()); 
l1.add(new Dog()); 
l1.add(new Object()); //disallowed 

Ale wszystkie wyszukane obiekty są typu Obiekt

Object o = l1.get(0); 
Animal a = l1.get(0); //disallowed 
Cat c = l2.get(0); //disallowed 
Dog d = l3.get(0); //disallowed 
+0

+1 za podanie tych przykładów. – dragon66

0

rodzajowych pracować tylko dla kompilacji bezpieczeństwa czasowym. W twoim przypadku, jak kompilator może wiedzieć, że coś złego się stanie? Przyjmuje definicje typów i wychodzi z tego. Wykonanie więcej oznaczałoby znacznie bardziej skomplikowaną pracę dla kompilatora, ale istnieją rozszerzone statyczne warcaby i inne narzędzia, które mogłyby złapać to przedwstępne środowisko wykonawcze.

1

Wiąże się to wpisać skasowaniem. Typ nie jest zachowywany w środowisku wykonawczym. Naprawdę, lista staje się listą typu obiektu w czasie wykonywania. Właśnie dlatego otrzymujesz ostrzeżenie kompilatora lub powinno być na animals.add ((T) new Cat()); Podczas kompilacji Cat rozszerza zwierzę, które jest typu T. Jednak nie może wymusić, że pies jest na liście w tym czasie, dlatego ostrzeżenie kompilatora.

0

Wystarczy powiedzieć, że istnieje taka T dla których obsada może odnieść sukces. Co do skompilowanego kodu, to będzie dokładnie taki sam, jak gdybyś napisał animals.add((Animal)new Cat());

2

Nie oczekuj rodzajowych wykonać sprawdzanie typu Runtime. Podczas kompilacji Java wykonuje wszystkie typy wnioskowania, tworzy instancje wszystkich typów, ... a następnie usuwa cały ślad typów ogólnych z kodu. W czasie wykonywania typem jest Lista, a nie Lista < T> lub Lista < Pies>.

Główne pytanie, dlaczego pozwala rzucać new Cat() na typ T extends Animal, z ostrzeżeniem tylko o niezaznaczonych konwersjach, jest ważne. Pewne niesłuszne cechy systemu typu sprawiają, że legalizacja takich podejrzanych rzutów jest niezbędna.

Jeśli chcesz kompilatora, aby zapobiec dodawania niczego do listy, należy użyć symbolu wieloznacznego:

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(); 
     } 
    } 
} 

PS: Wątpliwą downcasts w ciele pętli są doskonałym przykładem tego, jak kulawy Java jest na rozłączne suma (Variant) typów.

+0

1. nadal można rzutować na 'List ' i dodawać do niego 2 rzeczy.nadal możesz przekazać tę listę do oryginalnej metody 'doAction' od góry (sparametryzowanej przez' T'), która wciąż może do niej dodać – newacct

0

Twoja funkcja doAction jest parametryzowana przez typ T, który rozszerza klasę Animal. Może to być obiekt typu Dog lub Cat.
Należy również zwrócić uwagę na różnicę pomiędzy parametru formalnego i skutecznego parametru.
Parametr formalny jest używany podczas definiowania metody, która w twoim przypadku to List <T> (używając jej jako skrótu).
Efektywna parametr jest tym, który „daje” na swojej metodzie, gdy nazywają go tutaj List<Dog>.
.
Pozwala spojrzeć na animals.add((T) new Cat()) w doAction(). zwierzęta to lista, których elementy są typu T, który jest albo Dog lub Cat, więc nie ma błędu, ponieważ jest to typ formalnego parametru.
To jest powód. Jest to jedna z korzyści używania klas parametrycznych.

+1

Java nie zdaje sobie sprawy, że jedynymi zwierzętami są psy i koty. Rzeczywiście tak nigdy nie może być, ponieważ zawsze można dodać kolejne. Co się dzieje, jest to, że rzutowanie jest sprawdzane (przez kompilator) niezależnie od typu wnioskowania, które występuje, gdy jest wywoływane z 'List < Dog >'. Najwyraźniej zasada jest taka, że ​​obsada jest dozwolona (z ostrzeżeniem), jeśli mogła * zawsze * być ważna. –

+0

To, co powiedziałem, było oparte na kodzie, który podał plakat. I tak, oczywiście, zawsze można dodać kolejne. Chodzi mi o to, że "Kot" rozszerza "Zwierzęta", więc dodanie go do listy elementów, które rozszerza Zwierzęta, nie jest złe. –