Cześć! Na dzisiejszej lekcji będziemy kontynuować naukę nazw generycznych. Tak się składa, że jest to duży temat, ale nie ma dokąd pójść - to niezwykle ważna część języka :) Kiedy będziesz studiować dokumentację Oracle na temat leków generycznych lub czytać poradniki w Internecie, natkniesz się na terminy Typy niepodlegające refinansowaniu i typy podlegające refinansowaniu . Jakiego rodzaju słowem jest „Reableable”? Nawet jeśli z angielskim wszystko jest w porządku, jest mało prawdopodobne, że go spotkałeś. Spróbujmy przetłumaczyć!
*dziękuję Google, bardzo pomogłeś -_-*
Typ reableable to typ, którego informacje są w pełni dostępne w czasie wykonywania. W języku Java są to typy pierwotne, typy surowe i typy nieogólne. Natomiast typy niepodlegające rewizji to typy, których informacje są usuwane i niedostępne w czasie wykonywania. To są po prostu typy ogólne - List<String> , List<Integer> itd.
Swoją drogą, czy pamiętasz, czym są varargi ?
Jeśli zapomniałeś, są to argumenty o zmiennej długości. Przydają się w sytuacjach, gdy nie wiemy dokładnie, ile argumentów można przekazać naszej metodzie. Na przykład, jeśli mamy klasę kalkulatora i ma ona metodęsum
. Do metody sum()
można przekazać 2 liczby, 3, 5 lub dowolną liczbę liczb . Byłoby bardzo dziwnie przeciążać metodę za każdym razem, sum()
aby uwzględnić wszystkie możliwe opcje. Zamiast tego możemy to zrobić:
public class SimpleCalculator {
public static int sum(int...numbers) {
int result = 0;
for(int i : numbers) {
result += i;
}
return result;
}
public static void main(String[] args) {
System.out.println(sum(1,2,3,4,5));
System.out.println(sum(2,9));
}
}
Wyjście konsoli:
15
11
Zatem stosowanie varargs
w połączeniu z lekami generycznymi ma kilka ważnych cech. Spójrzmy na ten kod:
import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
public static void main(String[] args) {
addAll(new ArrayList<String>(), // здесь все нормально
"Leonardo da Vinci",
"Vasco de Gama"
);
// а здесь мы получаем предупреждение
addAll(new ArrayList<Pair<String, String>>(),
new Pair<String, String>("Leonardo", "da Vinci"),
new Pair<String, String>("Vasco", "de Gama")
);
}
}
Metoda pobiera listę i dowolną liczbę obiektów addAll()
jako dane wejściowe , a następnie dodaje wszystkie te obiekty do listy. W metodzie wywołujemy naszą metodę dwukrotnie . Za pierwszym razem dodajemy dwie regularne linie. Tutaj wszystko wporządku. Za drugim razem dodajemy dwa obiekty . I tu nagle dostajemy ostrzeżenie: List<E>
E
main()
addAll()
List
List
Pair<String, String>
Unchecked generics array creation for varargs parameter
Co to znaczy? Dlaczego otrzymujemy ostrzeżenie i co to ma z tym wspólnego array
? Array
- to jest tablica, a w naszym kodzie nie ma żadnych tablic! Zacznijmy od drugiego. Ostrzeżenie wspomina o tablicy, ponieważ kompilator konwertuje argumenty o zmiennej długości (varargs) na tablicę. Innymi słowy, sygnatura naszej metody to addAll()
:
public static <E> void addAll(List<E> list, E... array)
Właściwie wygląda to tak:
public static <E> void addAll(List<E> list, E[] array)
Oznacza to, że w metodzie main()
kompilator przekształci nasz kod w następujący sposób:
public static void main(String[] args) {
addAll(new ArrayList<String>(),
new String[] {
"Leonardo da Vinci",
"Vasco de Gama"
}
);
addAll(new ArrayList<Pair<String,String>>(),
new Pair<String,String>[] {
new Pair<String,String>("Leonardo","da Vinci"),
new Pair<String,String>("Vasco","de Gama")
}
);
}
Wszystko jest w porządku z tablicą String
. Ale z tablicą Pair<String, String>
- nie. Faktem jest, że Pair<String, String>
jest to typ niepodlegający rewizji. Podczas kompilacji wszystkie informacje o typach parametrów (<String, String>) zostaną usunięte. Tworzenie tablic z typu nierewalifikowalnego nie jest dozwolone w Javie . Możesz to sprawdzić, próbując ręcznie utworzyć tablicę Para<String, String>
public static void main(String[] args) {
// ошибка компиляции! Generic array creation
Pair<String, String>[] array = new Pair<String, String>[10];
}
Powód jest oczywisty – bezpieczeństwo typu. Jak pamiętasz, tworząc tablicę, musisz wskazać, jakie obiekty (lub prymitywy) ta tablica będzie przechowywać.
int array[] = new int[10];
Na jednej z poprzednich lekcji szczegółowo zbadaliśmy mechanizm usuwania czcionek. Zatem w tym przypadku w wyniku wymazywania typów straciliśmy informację, że w naszych obiektach Pair
zapisane były pary <String, String>
. Tworzenie tablicy będzie niebezpieczne. Używając metod z varargs
rodzajami i rodzajami, pamiętaj o kasowaniu typów i dokładnym działaniu. Jeśli masz całkowitą pewność co do napisanego przez siebie kodu i wiesz, że nie spowoduje to żadnych problemów, możesz wyłączyć varargs
ostrzeżenia z nim związane za pomocą adnotacji@SafeVarargs
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
Jeśli dodasz tę adnotację do swojej metody, ostrzeżenie, które napotkaliśmy wcześniej, nie pojawi się. Innym możliwym problemem w przypadku varargs
jednoczesnego stosowania leków generycznych jest zanieczyszczenie sterty. Zanieczyszczenie może wystąpić w następujących sytuacjach:
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<String> makeHeapPollution() {
List numbers = new ArrayList<Number>();
numbers.add(1);
List<String> strings = numbers;
strings.add("");
return strings;
}
public static void main(String[] args) {
List<String> stringsWithHeapPollution = makeHeapPollution();
System.out.println(stringsWithHeapPollution.get(0));
}
}
Wyjście konsoli:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Mówiąc najprościej, zanieczyszczenie sterty to sytuacja, w której obiekty typu 1 powinny znajdować się na stercie А
, ale obiekty typu tam trafiają B
z powodu błędów związanych z bezpieczeństwem typów. W naszym przykładzie tak się dzieje. Najpierw utworzyliśmy zmienną Raw numbers
i przypisaliśmy jej kolekcję ogólną ArrayList<Number>
. Następnie dodaliśmy tam numer 1
.
List<String> strings = numbers;
W tej linii kompilator próbował ostrzec nas o możliwych błędach, wydając ostrzeżenie „ Niesprawdzone przypisanie... ”, ale zignorowaliśmy to. W rezultacie mamy zmienną ogólną typu List<String>
, która wskazuje na ogólną kolekcję typu ArrayList<Number>
. Taka sytuacja może wyraźnie prowadzić do kłopotów! Oto co się dzieje. Używając naszej nowej zmiennej, dodajemy ciąg do kolekcji. Sterta została zanieczyszczona - do wpisanej kolekcji dodaliśmy najpierw liczbę, a następnie ciąg znaków. Kompilator nas ostrzegł, ale zignorowaliśmy jego ostrzeżenie i otrzymaliśmy wyniki ClassCastException
tylko wtedy, gdy program był uruchomiony. Co to ma z tym wspólnego varargs
? Używanie varargs
z lekami generycznymi może łatwo doprowadzić do zanieczyszczenia sterty. Oto prosty przykład:
import java.util.Arrays;
import java.util.List;
public class Main {
static void makeHeapPollution(List<String>... stringsLists) {
Object[] array = stringsLists;
List<Integer> numbersList = Arrays.asList(66,22,44,12);
array[0] = numbersList;
String str = stringsLists[0].get(0);
}
public static void main(String[] args) {
List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");
makeHeapPollution(cars1, cars2);
}
}
Co tu się dzieje? Ze względu na wymazanie typów nasze arkusze parametrów (dla wygody będziemy je nazywać „arkuszami” zamiast „listami”):
List<String>...stringsLists
- zamieni się w tablicę arkuszy - List[]
nieznanego typu (pamiętaj, że varargs w wyniku kompilacji zamienia się w zwykłą tablicę). Dzięki temu Object[] array
w pierwszej linijce metody możemy łatwo dokonać przypisania do zmiennej – typy zostały usunięte z naszych arkuszy! A teraz mamy zmienną typu Object[]
, do której możemy w ogóle dodać wszystko - wszystkie obiekty w Javie dziedziczą po Object
! W tej chwili mamy tylko szereg arkuszy sznurków. Ale dzięki zastosowaniu varargs
i wymazaniu typów możemy łatwo dodać do nich arkusz liczb, co właśnie robimy. W rezultacie zanieczyszczamy stertę, mieszając przedmioty różnych typów. Wynikiem będzie ten sam wyjątek ClassCastException
podczas próby odczytania ciągu z tablicy. Wyjście konsoli:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Oto nieoczekiwane konsekwencje, jakie mogą wyniknąć z zastosowania pozornie prostego mechanizmu varargs
:) I na tym kończy się nasz dzisiejszy wykład. Nie zapomnij rozwiązać kilku problemów, a jeśli masz czas i energię, przestudiuj dodatkową literaturę. „ Efektywna Java ” sama się nie przeczyta! :) Do zobaczenia!
GO TO FULL VERSION