Z książki Effective Java Joshua Blocha,
- Tablice różnią się od typu ogólnego na dwa ważne sposoby. Pierwsze tablice są kowariantne. Typy generyczne są niezmienne.
Kowariantny oznacza po prostu, że jeśli X jest podtypem Y, to X [] będzie również podtypem Y []. Tablice są kowariantne, ponieważ łańcuch jest podtypem obiektu So
String[] is subtype of Object[]
Niezmienny oznacza po prostu niezależnie od tego, czy X jest podtypem Y, czy nie,
List<X> will not be subType of List<Y>.
Moje pytanie brzmi: dlaczego podjęto decyzję o kowariantności tablic w Javie? Istnieją inne posty SO, takie jak Dlaczego tablice są niezmienne, ale listy są kowariantne? , ale wydają się być skupieni na Scali i nie jestem w stanie nadążyć.
java
arrays
generics
language-design
covariance
chętny do nauki
źródło
źródło
LinkedList
.Odpowiedzi:
Przez wikipedię :
Odpowiada to na pytanie „Dlaczego tablice są kowariantne?”, A dokładniej: „Dlaczego w tamtym czasie tablice były kowariantne ?”
Kiedy wprowadzono leki generyczne, celowo nie uczyniono ich kowariantnymi z powodów wskazanych w tej odpowiedzi przez Jona Skeeta :
Oryginalna motywacja do tworzenia kowariantnych tablic opisana w artykule na Wikipedii nie dotyczyła typów ogólnych, ponieważ symbole wieloznaczne umożliwiały wyrażenie kowariancji (i kontrawariancji), na przykład:
źródło
Object[] num = new Number[4]; num[1]= 5; num[2] = 5.0f; num[3]=43.4; System.out.println(Arrays.toString(num)); num[0]="hello";
ArrayStoreException
w razie potrzeby. Najwyraźniej uznano to wówczas za godny kompromis. Porównajmy to ze współczesnością: z perspektywy czasu wielu uważa kowariancję tablic za błąd.Powodem jest to, że każda tablica zna swój typ elementu w czasie wykonywania, a kolekcja ogólna nie z powodu wymazywania typów.
Na przykład:
Jeśli było to dozwolone w przypadku kolekcji ogólnych:
Ale spowodowałoby to problemy później, gdy ktoś próbowałby uzyskać dostęp do listy:
źródło
Może ta pomoc: -
Leki generyczne nie są kowariantne
Tablice w języku Java są kowariantne - co oznacza, że jeśli Integer rozszerza liczbę (co robi), to nie tylko liczba całkowita jest również liczbą, ale liczba całkowita [] jest również a
Number[]
, a Ty możesz podać lub przypisaćInteger[]
gdzieNumber[]
jest wezwany. (Bardziej formalnie, jeśli liczba jest nadtypem liczby całkowitej, toNumber[]
jest nadtypemInteger[]
). Można by pomyśleć, że to samo dotyczy typów ogólnych - toList<Number>
jest nadtypList<Integer>
i że można przekazać aList<Integer>
tam, gdzieList<Number>
oczekuje się a. Niestety to tak nie działa.Okazuje się, że jest dobry powód, dla którego nie działa to w ten sposób: złamałoby to typowe zabezpieczenia, które miały zapewniać. Wyobraź sobie, że możesz przypisać a
List<Integer>
doList<Number>
. Następnie poniższy kod pozwoliłby ci wstawić coś, co nie było liczbą całkowitą, doList<Integer>
:Ponieważ ln to a
List<Number>
, dodanie do niego Float wydaje się całkowicie legalne. Ale gdyby ln był aliasowany zli
, to złamałoby to obietnicę bezpieczeństwa typu zawartą w definicji li - że jest to lista liczb całkowitych, dlatego typy rodzajowe nie mogą być kowariantne.źródło
ArrayStoreException
at runtime.WHY
tablice wykonane jako kowariantne. jak wspomniał Sotirios, z Arraysami można by uzyskać ArrayStoreException w czasie wykonywania, gdyby Arrays były niezmienne, to moglibyśmy wykryć ten błąd w czasie kompilacji, prawda?Animal
, która nie musi akceptować żadnych elementów otrzymanych z innego miejsca” z „tablicy, która nie może zawierać nic opróczAnimal
, i musi być skłonny zaakceptować zewnętrzne odniesienia doAnimal
. Kod, który wymaga tego pierwszego, powinien akceptować tablicęCat
, ale kod, który potrzebuje drugiego, nie powinien. Jeśli kompilator mógłby rozróżnić te dwa typy, mógłby zapewnić sprawdzanie podczas kompilacji. Niestety jedyne, co je wyróżnia ...Tablice są kowariantne z co najmniej dwóch powodów:
Jest to przydatne w przypadku zbiorów zawierających informacje, które nigdy nie ulegną zmianie na kowariantne. Aby zbiór T był kowariantny, jego magazyn zapasowy również musi być kowariantny. Chociaż można by zaprojektować niezmienną
T
kolekcję, która nie używałabyT[]
jako magazynu zapasowego (np. Używając drzewa lub połączonej listy), jest mało prawdopodobne, aby taka kolekcja działała tak dobrze, jak ta obsługiwana przez tablicę. Ktoś mógłby argumentować, że lepszym sposobem zapewnienia kowariantnych niezmiennych kolekcji byłoby zdefiniowanie typu „kowariantnej niezmiennej tablicy”, z której mogliby korzystać, ale po prostu zezwolenie na kowariancję tablic było prawdopodobnie łatwiejsze.Tablice są często modyfikowane przez kod, który nie wie, jaki typ rzeczy się w nich znajdzie, ale nie umieści w tablicy niczego, co nie zostało odczytane z tej samej tablicy. Doskonałym tego przykładem jest kod sortujący. Koncepcyjnie możliwe, że typy tablicowe zawierałyby metody do zamiany lub permutacji elementów (takie metody mogą być w równym stopniu stosowane do dowolnego typu tablicy) lub zdefiniowanie obiektu „manipulatora tablicami”, który zawiera odniesienie do tablicy i co najmniej jedną rzecz które zostały z niego odczytane i mogą zawierać metody do przechowywania wcześniej odczytanych elementów w tablicy, z której pochodzą. Gdyby tablice nie były kowariantne, kod użytkownika nie byłby w stanie zdefiniować takiego typu, ale środowisko wykonawcze mogłoby zawierać pewne wyspecjalizowane metody.
Fakt, że tablice są kowariantne, może być postrzegany jako brzydki hack, ale w większości przypadków ułatwia to tworzenie działającego kodu.
źródło
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.
- słuszna uwagaWażną cechą typów parametrycznych jest możliwość pisania algorytmów polimorficznych, czyli algorytmów, które operują na strukturze danych niezależnie od wartości jej parametru, np
Arrays.sort()
.W przypadku typów ogólnych odbywa się to za pomocą typów symboli wieloznacznych:
Aby były naprawdę użyteczne, typy symboli wieloznacznych wymagają przechwytywania symboli wieloznacznych, a to wymaga pojęcia parametru typu. Żadna z tych opcji nie była dostępna w czasie dodawania tablic do języka Java, a tworzenie tablic o kowariantach typu referencyjnego pozwoliło na znacznie prostszy sposób dopuszczenia algorytmów polimorficznych:
Jednak ta prostota otworzyła lukę w systemie typów statycznych:
wymagające sprawdzenia w czasie wykonywania każdego dostępu do zapisu w tablicy typu referencyjnego.
Krótko mówiąc, nowsze podejście zawarte w rodzajach generycznych sprawia, że system typów jest bardziej złożony, ale także statycznie bezpieczniejszy dla typów, podczas gdy starsze podejście było prostsze i mniej statycznie bezpieczne. Projektanci języka zdecydowali się na prostsze podejście, mając do zrobienia ważniejsze rzeczy niż zamknięcie małej luki w systemie czcionek, która rzadko powoduje problemy. Później, kiedy powstała Java i zadbano o pilne potrzeby, mieli zasoby, aby zrobić to dobrze dla generycznych (ale zmiana dla tablic zepsułaby istniejące programy Java).
źródło
Typy generyczne są niezmienne : od JSL 4.10 :
i kilka wierszy dalej, JLS wyjaśnia również, że
tablice są kowariantne (pierwszy punktor):
4.10.3 Podpisywanie między typami tablic
źródło
Myślę, że na początku podjęli złą decyzję, która spowodowała, że tablica stała się kowariantna. Łamie to bezpieczeństwo typów, jak opisano tutaj, i utknęli z tym z powodu kompatybilności wstecznej, a potem próbowali nie popełnić tego samego błędu dla generycznego. I to jest jeden z powodów, dla których Joshua Bloch woli listy od tablic w pozycji 25 książki „Efektywna Java (drugie wydanie)”
źródło
Moje podejście: Kiedy kod oczekuje tablicy A [] i dajesz jej B [], gdzie B jest podklasą A, są tylko dwie rzeczy, o które musisz się martwić: co się dzieje, gdy czytasz element tablicy i co się dzieje, gdy piszesz to. Dlatego nie jest trudno napisać reguły języka, aby zapewnić, że bezpieczeństwo typów jest zachowane we wszystkich przypadkach (główna zasada jest taka, że
ArrayStoreException
jeśli spróbujesz włożyć A do B []), może zostać wyrzucone. Jednak w przypadku klasy ogólnej, kiedy deklarujesz klasęSomeClass<T>
, może istnieć wiele sposobówT
użycia w treści klasy i myślę, że jest to zbyt skomplikowane, aby wypracować wszystkie możliwe kombinacje, aby napisać reguły o tym, kiedy rzeczy są dozwolone, a kiedy nie.źródło