Natrafiam na kod Java w następujący sposób:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
Jaka jest różnica między wszystkimi trzema powyższymi i jak nazywają ten typ deklaracji klasy lub interfejsu w Javie?
Natrafiam na kod Java w następujący sposób:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
Jaka jest różnica między wszystkimi trzema powyższymi i jak nazywają ten typ deklaracji klasy lub interfejsu w Javie?
Odpowiedzi:
Cóż, nie ma różnicy między pierwszymi dwoma - po prostu używają różnych nazw dla parametru type ( Elub T).
Trzecia nie jest prawidłową deklaracją - ?jest używana jako symbol wieloznaczny, który jest używany, gdy podaje się argument typu , np. List<?> foo = ...Oznacza, że fooodnosi się do listy jakiegoś typu, ale nie wiemy co.
Wszystko to jest ogólne , co jest dość dużym tematem. Możesz dowiedzieć się o tym za pomocą następujących zasobów, chociaż oczywiście dostępnych jest więcej:
Ti E- to tylko identyfikatory. Możesz KeyValuePair<K, V>na przykład pisać . ?ma jednak szczególne znaczenie.
To bardziej konwencja niż cokolwiek innego.
T ma być typem Ema być elementem ( List<E>: lista elementów) Kjest kluczem (w Map<K,V>) V to Wartość (jako wartość zwracana lub wartość odwzorowana) Są one w pełni zamienne (pomimo konfliktów w tej samej deklaracji).
Poprzednie odpowiedzi wyjaśniają parametry typu (T, E itp.), Ale nie wyjaśniają symboli wieloznacznych „?” Ani różnic między nimi, więc zajmę się tym.
Po pierwsze, żeby być jasnym: symbole wieloznaczne i parametry typu nie są takie same. Gdy parametry typu definiują rodzaj zmiennej (np. T), która reprezentuje typ dla zakresu, symbol wieloznaczny nie: symbol wieloznaczny po prostu określa zestaw dopuszczalnych typów, których można użyć dla typu ogólnego. Bez żadnych ograniczeń ( extendslub super), symbol wieloznaczny oznacza „użyj tutaj dowolnego typu”.
Symbol wieloznaczny zawsze znajduje się między nawiasami kątowymi i ma znaczenie tylko w kontekście typu ogólnego:
public void foo(List<?> listOfAnyType) {...} // pass a List of any type
nigdy
public <?> ? bar(? someType) {...} // error. Must use type params here
lub
public class MyGeneric ? { // error
public ? getFoo() { ... } // error
...
}
Staje się bardziej mylące, gdy się pokrywają. Na przykład:
List<T> fooList; // A list which will be of type T, when T is chosen.
// Requires T was defined above in this scope
List<?> barList; // A list of some type, decided elsewhere. You can do
// this anywhere, no T required.
Definicje metod nakładają się na siebie. Są funkcjonalnie identyczne:
public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething) {...}
Jeśli więc zachodzą na siebie, po co korzystać z jednego lub drugiego? Czasami jest to po prostu styl: niektórzy twierdzą, że jeśli nie potrzebujesz parametru typu, powinieneś użyć symbolu wieloznacznego, aby kod był prostszy / bardziej czytelny. Jedną główną różnicę wyjaśniłem powyżej: parametry par definiują zmienną typu (np. T), której można użyć w innym miejscu zakresu; symbol wieloznaczny nie. W przeciwnym razie istnieją dwie duże różnice między parametrami typu a symbolem wieloznacznym:
Parametry typu mogą mieć wiele klas granicznych; symbol wieloznaczny nie może:
public class Foo <T extends Comparable<T> & Cloneable> {...}
Symbol wieloznaczny może mieć dolne granice; parametry typu nie mogą:
public void bar(List<? super Integer> list) {...}
W powyższym List<? super Integer>zdefiniowano Integerdolną granicę symbolu wieloznacznego, co oznacza, że typem listy musi być liczba całkowita lub supertyp typu liczba całkowita. Granice typów ogólnych wykraczają poza to, co chcę szczegółowo omówić. Krótko mówiąc, pozwala określić, jakie typy mogą być typami ogólnymi. Dzięki temu można traktować leki generyczne polimorficznie. Np. Z:
public void foo(List<? extends Number> numbers) {...}
Można przekazać List<Integer>, List<Float>, List<Byte>, itd. Dla numbers. Bez ograniczania typów to nie zadziała - takie właśnie są ogólne.
Na koniec, oto definicja metody, która używa znaku wieloznacznego do zrobienia czegoś, czego nie sądzę, że można zrobić w inny sposób:
public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
numberSuper.add(elem);
}
numberSupermoże być liczbą lub dowolnym nadtypem liczby (np. List<Object>) i elemmusi być liczbą lub dowolnym podtypem. Przy wszystkich ograniczeniach kompilator może być pewien, że .add()jest bezpieczny.
Zmienna typu, <T>, może być dowolnym nie-pierwotnym typem, który określisz: dowolnym typem klasy, dowolnym typem interfejsu, dowolnym typem tablicy, a nawet inną zmienną typu.
Najczęściej używane nazwy parametrów typu to:
W Javie 7 dozwolone jest tworzenie instancji w następujący sposób:
Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
Najczęściej używane nazwy parametrów typu to:
E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types
Zobaczysz te nazwy używane w interfejsie API Java SE
kompilator wykona przechwytywanie dla każdego znaku wieloznacznego (np. znaku zapytania na liście), gdy utworzy funkcję taką jak:
foo(List<?> list) {
list.put(list.get()) // ERROR: capture and Object are not identical type.
}
Jednak ogólny typ, taki jak V, byłby w porządku i czyniąc go ogólną metodą :
<V>void foo(List<V> list) {
list.put(list.get())
}