0Pricing
Java Academy · Lekcja

Symbole wieloznaczne z górnym ograniczeniem

? extends T dla producentów

Symbole wieloznaczne z górnym ograniczeniem to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.

Generyki są niezmienne

W języku Java generyki są niezmienne. List<Integer> nie jest typem List<Number>, mimo że Integer jest typem Number.

Może to zaskakiwać początkujących, ale zapewnia spójność systemu typów. Symbole wieloznaczne bezpiecznie łagodzą to ograniczenie.

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> ints = List.of(1, 2, 3);
        // List<Number> nums = ints; // would NOT compile
        System.out.println("List<Integer> is not List<Number>");
        System.out.println(ints);
    }
}

Wildcard z górnym ograniczeniem

? extends T to wildcard z górnym ograniczeniem. Oznacza „jakiś nieznany typ, który jest typem T lub jego podtypem”.

List<? extends Number> może oznaczać List<Integer>, List<Double> albo List<Number>.

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<? extends Number> a = List.of(1, 2, 3);
        List<? extends Number> b = List.of(1.5, 2.5);
        System.out.println(a.get(0));
        System.out.println(b.get(0));
    }
}

Odczyt z producenta

Za pomocą ? extends Number można bezpiecznie odczytywać elementy jako Number.

Niezależnie od rzeczywistego typu elementów wiadomo, że jest on co najmniej typem Number, więc odczyt jest bezpieczny typowo.

import java.util.List;

public class Main {
    static double sum(List<? extends Number> list) {
        double total = 0;
        for (Number n : list) total += n.doubleValue();
        return total;
    }
    public static void main(String[] args) {
        System.out.println(sum(List.of(1, 2, 3)));
        System.out.println(sum(List.of(1.5, 2.5)));
    }
}

Nie można do niej dodawać elementów

Oto haczyk: do listy ? extends Number nie można dodawać elementów (z wyjątkiem null).

Kompilator nie zna dokładnego typu elementów. Może to być List<Integer>, więc dodanie Double byłoby niebezpieczne.

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<? extends Number> list = new ArrayList<Integer>();
        // list.add(1);   // does NOT compile
        // list.add(1.5); // does NOT compile
        System.out.println("Cannot add to ? extends Number");
        System.out.println("size = " + list.size());
    }
}

Dlaczego zapisywanie jest zabronione

Wyobraźmy sobie, że byłoby to dozwolone. Można by przekazać List<Integer> jako List<? extends Number>, a następnie dodać Double.

Późniejszy kod odczytujący listę jako List<Integer> otrzymałby wyjątek ClassCastException. Kompilator zapobiega temu, zabraniając zapisu.

import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Conceptual: this is why add is blocked
        System.out.println("Writing a Double into a List<Integer> would corrupt it");
        System.out.println("so ? extends bans all adds");
    }
}

Przypadek użycia producenta

Należy używać ? extends T, gdy metoda tylko produkuje dla Państwa wartości, czyli je odczytuje.

Dobrym przykładem jest metoda kopiująca elementy ze źródłowej kolekcji.

import java.util.ArrayList;
import java.util.List;

public class Main {
    static List<Number> copyOut(List<? extends Number> source) {
        List<Number> dest = new ArrayList<>();
        for (Number n : source) dest.add(n);
        return dest;
    }
    public static void main(String[] args) {
        System.out.println(copyOut(List.of(1, 2, 3)));
    }
}

Parametry typów z ograniczeniami a wildcardy

Nie należy mylić <T extends Number> (ograniczonego parametru typu) z ? extends Number (wildcardem).

  • Parametr typu: nadaje typowi nazwę, której można używać w całej metodzie.
  • Wildcard: anonimowy, nieznany podtyp używany wtedy, gdy nie trzeba nadawać mu nazwy.
import java.util.List;

public class Main {
    static <T extends Number> T first(List<T> list) { return list.get(0); }
    public static void main(String[] args) {
        Integer i = first(List.of(10, 20));
        System.out.println(i);
    }
}

Wiele ograniczeń

Parametr typu może mieć wiele ograniczeń połączonych za pomocą &, na przykład <T extends Number & Comparable<T>>.

Wildcardy obsługują tylko jedno górne ograniczenie, dlatego wiele ograniczeń wymaga nazwanego parametru typu.

import java.util.List;

public class Main {
    static <T extends Number & Comparable<T>> T max(List<T> list) {
        T best = list.get(0);
        for (T t : list) if (t.compareTo(best) > 0) best = t;
        return best;
    }
    public static void main(String[] args) {
        System.out.println(max(List.of(3, 9, 1, 7)));
    }
}

Wildcardy z Collections.max

JDK szeroko wykorzystuje górne ograniczenia, na przykład podczas sumowania lub przeszukiwania kolekcji tylko do odczytu.

W tym przykładzie znajdujemy największy element listy określonej jako producent.

import java.util.List;

public class Main {
    static double largest(List<? extends Number> nums) {
        double max = Double.NEGATIVE_INFINITY;
        for (Number n : nums) max = Math.max(max, n.doubleValue());
        return max;
    }
    public static void main(String[] args) {
        System.out.println(largest(List.of(4, 2, 9, 1)));
        System.out.println(largest(List.of(0.5, 9.9, 3.3)));
    }
}

null to jedyna wartość, którą można zapisać

Jedyną wartością, którą można dodać do kolekcji ? extends T, jest null, ponieważ null można przypisać do dowolnego typu referencyjnego.

Rzadko jest to przydatne, ale wyjaśnia, dlaczego metoda add nie jest całkowicie zabroniona, a jedynie praktycznie niedostępna.

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<? extends Number> list = new ArrayList<Number>();
        // Only null is assignable; demonstrated conceptually
        System.out.println("Only null could be added; we avoid that");
        System.out.println("size = " + list.size());
    }
}

Odczyt jako Object zawsze działa

Ponieważ każdy typ rozszerza Object, elementy dowolnej listy z wildcardem można zawsze odczytywać jako Object.

W przypadku górnego ograniczenia Number dodatkowo można odczytywać je bezpośrednio jako Number.

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<? extends Number> nums = List.of(1, 2.0, 3L);
        for (Object o : nums) System.out.println(o.getClass().getSimpleName());
    }
}

Szybkie sprawdzenie

Proszę sprawdzić, jak dobrze rozumieją Państwo górne ograniczenia.

Podsumowanie

Poznali Państwo wildcardy z górnym ograniczeniem:

  • ? extends T oznacza nieznany podtyp typu T.
  • Elementy można odczytywać jako T — kolekcja jest producentem.
  • Nie można dodawać elementów innych niż null.
  • Typy generyczne są niezmiennicze, a wildcardy bezpiecznie łagodzą to ograniczenie.

Następnie poznamy ich lustrzane odbicie: wildcardy z dolnym ograniczeniem.

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<? extends Number> nums = List.of(1, 2, 3);
        System.out.println("Upper bound recap: read " + nums.get(0));
    }
}

Często zadawane pytania

Czy lekcja „Symbole wieloznaczne z górnym ograniczeniem” jest bezpłatna?

Tak — pełny tekst „Symbole wieloznaczne z górnym ograniczeniem” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Symbole wieloznaczne z górnym ograniczeniem”?

? extends T dla producentów Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Java Academy?

Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Symbole wieloznaczne z górnym ograniczeniem”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?

Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Symbole wieloznaczne z górnym ograniczeniem
  2. Symbole wieloznaczne z dolnym ograniczeniem
  3. Zasada PECS
  4. Symbole wieloznaczne w API
← Powrót do Java Academy