Symbol wieloznaczny (Java)
W języku programowania Java symbol wieloznaczny ?
jest specjalnym rodzajem argumentu typu, który kontroluje bezpieczeństwo typów przy użyciu typów ogólnych (sparametryzowanych). Może być używany w deklaracjach i instancjach zmiennych, a także w definicjach metod, ale nie w definicji typu ogólnego. Jest to forma adnotacji wariancji miejsca użycia , w przeciwieństwie do adnotacji wariancji miejsca definicji, które można znaleźć w językach C# i Scala .
Kowariancja dla typów ogólnych
W przeciwieństwie do tablic (które w Javie są kowariantne ), różne instancje typu ogólnego nie są ze sobą kompatybilne, nawet jawnie: Z deklaracją Generic<Supertype> superGeneric; Ogólny<podtyp> podogólny;
kompilator zgłosiłby błąd konwersji dla obu rzutowań (Generic<Subtype>)superGeneric
i (Generic<Supertype>)subGeneric
.
Ta niezgodność może zostać złagodzona przez symbol wieloznaczny, jeśli ?
jest używany jako rzeczywisty parametr typu: Generic<?>
jest nadtypem wszystkich parametryzacji typu ogólnego Generic
. Umożliwia to bezpieczne przypisanie obiektów typu Generic<Supertype>
i Generic<Subtype> do zmiennej lub parametru metody typu
Generic<?>
. Korzystanie z wersji ogólnej<? extends Supertype>
pozwala na to samo, ograniczając kompatybilność do Supertype
i jego elementów podrzędnych. Inna możliwość jest Ogólny<? super Subtype>
, który również akceptuje oba obiekty i ogranicza kompatybilność do Subtype
i wszystkich jego rodziców.
Wildcard jako typ parametru
W treści jednostki ogólnej (formalny) parametr typu jest traktowany tak, jak jego górna granica (wyrażona za pomocą extends
; Obiekt
, jeśli nie jest ograniczony). Jeśli zwracanym typem metody jest parametr typu, wynik (np. typu ?
) może być przywoływany przez zmienną typu górna granica (lub Object
). Z drugiej strony symbol wieloznaczny nie pasuje do żadnego innego typu, nawet Object
: If ?
został zastosowany jako parametr typu formalnego metody, nie można do niego przekazać żadnych rzeczywistych parametrów. Jednak obiekty nieznanego typu można odczytać z obiektu ogólnego i przypisać do zmiennej nadtypu górnej granicy.
class Generic < T extends UpperBound > { private T t ; nieważne napisz ( T t ) { to . t = t ; } T przeczytaj () { zwróć t ; } } ... Ogólny < UpperBound > konkretna referencja typu = nowy Ogólny <
Górna granica > (); Ogólny <?> symbol wieloznacznyReference = konkretnyReferencjaTypu ; UpperBound ub = WildcardReference . czytać (); // Obiekt również byłby OK wildcardReference . napisz ( nowy obiekt ()); // wpisz błąd wildcardReference . napisz ( nowy UpperBound ()); // błąd wpisz konkretnyTypeReference . pisać ( nowy UpperBound ()); // OK
Ograniczone symbole wieloznaczne
Ograniczony symbol wieloznaczny to taki, który ma górne lub dolne ograniczenie dziedziczenia . Ograniczeniem symbolu wieloznacznego może być typ klasy, interfejsu , typ tablicy lub zmienna typu. Górne granice są wyrażane za pomocą extends , a dolne za pomocą super słowa kluczowego. Symbole wieloznaczne mogą określać górną lub dolną granicę, ale nie obie.
Górne granice
Górna granica symbolu wieloznacznego musi być podtypem górnej granicy odpowiedniego parametru typu zadeklarowanego w odpowiednim typie ogólnym. Przykładem symbolu wieloznacznego, który wyraźnie określa górną granicę, jest:
Ogólny<? extends SubtypeOfUpperBound> referenceConstrainedFromAbove;
To odwołanie może zawierać dowolną parametryzację Generic
, której typem argumentu jest podtyp SubtypeOfUpperBound
. Symbol wieloznaczny, który nie określa jawnie górnej granicy, jest faktycznie taki sam, jak ten, który ma ograniczenie rozszerza Object
, ponieważ wszystkie typy referencyjne w Javie są podtypami Object.
Dolne granice
Symbol wieloznaczny z dolną granicą, np
Ogólny<? super SubtypeOfUpperBound> referenceConstrainedFromBelow;
może przechowywać dowolną parametryzację Generic
, której dowolny argument typu jest zarówno podtypem odpowiedniej górnej granicy parametru typu, jak i nadtypem SubtypeOfUpperBound
.
Tworzenie obiektów za pomocą symboli wieloznacznych
Żadne obiekty nie mogą być tworzone z argumentem typu wieloznacznego: na przykład new Generic<?>()
jest zabronione. W praktyce jest to niepotrzebne, ponieważ gdyby ktoś chciał utworzyć obiekt, który można by przypisać do zmiennej typu Generic<?>
, mógłby po prostu użyć dowolnego dowolnego typu (który mieści się w ograniczeniach symbolu wieloznacznego, jeśli taki istnieje) jako typu argument.
Jednak new ArrayList<Generic<?>>()
jest dozwolona, ponieważ symbol wieloznaczny nie jest parametrem typu instancji ArrayList
. To samo dotyczy new ArrayList<List<?>>()
.
W wyrażeniu tworzenia tablicy typ komponentu tablicy musi być możliwy do ponownego wykorzystania, zgodnie z definicją w specyfikacji języka Java, sekcja 4.7. Oznacza to, że jeśli typ komponentu tablicy ma jakiekolwiek argumenty typu, wszystkie muszą być nieograniczonymi symbolami wieloznacznymi (symbolami wieloznacznymi składającymi się tylko z ? )
. Na przykład nowy Generic<?>[20]
jest poprawny, a nowy Generic<JakiśType>[20]
nie.
W obu przypadkach brak parametrów to kolejna opcja. Spowoduje to wygenerowanie ostrzeżenia, ponieważ jest mniej bezpieczne dla typów (patrz Raw type ).
Przykład: listy
W środowisku Java Collections Framework klasa List<MyClass>
reprezentuje uporządkowaną kolekcję obiektów typu MyClass
. Górne granice są określane za pomocą rozszerzeń
: A List<? extends MyClass>
to lista obiektów pewnej podklasy MyClass
, tj. każdy obiekt na liście ma gwarancję typu MyClass
, więc można go iterować za pomocą zmiennej typu MyClass
public void zróbCoś ( Lista <? extends MojaKlasa > list ) { for ( Obiekt MojejKlasy : lista ) { // OK // zrób coś } }
Jednak nie ma gwarancji, że można dodać dowolny obiekt typu MyClass
do tej listy:
public void zrób coś ( Lista <? rozszerza MojaKlasa > lista ) { MojaKlasa m = nowa MojaKlasa (); lista . dodać ( m ); // Błąd kompilacji }
Odwrotna sytuacja dotyczy dolnych granic, które są określone za pomocą super
: A List<? super MyClass>
jest listą obiektów jakiejś nadklasy MyClass
, tzn. lista gwarantuje, że będzie mogła zawierać dowolny obiekt typu MyClass
, więc można dodać dowolny obiekt typu MyClass
:
public void zrób coś ( Lista <? super MojaKlasa > lista ) { MojaKlasa m = nowa MojaKlasa (); lista . dodać ( m ); // OK }
Jednak nie ma gwarancji, że można iterować po tej liście za pomocą zmiennej typu MyClass
:
public void doSomething ( List <? super MojaKlasa > list ) { for ( Obiekt MyClass : lista ) { // Błąd kompilacji // zrób coś } }
Aby móc zarówno dodawać obiekty typu MyClass
do listy, jak i iterować po niej za pomocą zmiennej typu MyClass
, potrzebny jest List<MyClass>
, który jest jedynym typem List
, który jest jednocześnie List<? rozszerza MyClass>
i List <? super MojaKlasa>
.
Mnemoniki PECS (Producer Extends, Consumer Super) z książki Effective Java autorstwa Joshua Blocha dają łatwy sposób na zapamiętanie, kiedy używać symboli wieloznacznych (odpowiadających kowariancji i kontrawariancji) w Javie.
Zobacz też
- Kwantyfikacja ograniczona
- Kowariancja i kontrawariancja (informatyka)
- Generics in Java#Type wildcards wyjaśnia dolne i górne granice symboli wieloznacznych
- Specyfikacja języka Java, wydanie trzecie (Sun), ISBN 978-0-321-24678-3 http://java.sun.com/docs/books/jls/
- Samouczki Java, lekcje ogólne http://download.oracle.com/javase/tutorial/java/generics/index.html
- Przechwytywanie symboli wieloznacznych, http://bayou.io/draft/Capturing_Wildcards.html
- Typkompatibilität w Javie http://public.beuth-hochschule.de/~solymosi/veroeff/typkompatibilitaet/Typkompatibilitaet.html#Joker (w języku niemieckim)