Jason. Cała informatyka w jednym miejscu!

Przechodzimy do następnej części o typach generycznych w języku Java! Poruszymy temat nakładania ograniczeń na typy generyczne, czyli składowe typu T. Dowiesz się z materiału dlaczego ograniczenia typu generycznego w języku Java są stosowane i co dzięki nim możemy uzyskać 😊.

JAKI JEST SENS OGRANICZENIA TYPU GENERYCZNEGO W JĘZYKU JAVA?

Wyobraź sobie, że masz klasę generyczną i wewnątrz niej musisz skorzystać z metody należącej do innej konkretnej klasy np. "BaseClass". Podanie samego typu T bez żadnych ograniczeń daje Tobie "wolną rękę" do wstawiania takiego typu, jaki Ci się żywnie podoba. Żeby zobrazować przypadek, chodzi mi o domyślną formę jaką przyjmuje typ T:

public class GenericClass<T> {
    
}

Użycie konkretnej metody z konkretnej klasy, bez nałożenia ograniczeń, spowoduje błąd kompilacji. A to dlatego, że typy generyczne stoją na straży zapewnienia bezpieczeństwa typów na etapie kompilacji aniżeli podczas działania programu. Dodanie skonkretyzowanego elementu powoduje samoistnie "zawężenie" kręgu dopuszczalnych typów danych, stąd trzeba nałożyć ograniczenie i postawić granicę co może zostać zaakceptowane podczas tworzenia obiektu, a co nie.

Każda struktura w języku Java, która wspiera typy generyczne, może zawierać ograniczenia nakładane na typ T. Jak wspomniałem, stosujemy je po to, aby wewnątrz klasy móc wywoływać metody (i odwoływać się do danych składowych) pochodzące od konkretnej "rodziny" klas. Wtedy musimy nałożyć ograniczenie, bo w ten sposób nie dochodzi do złamania zasady bezpieczeństwa typów (nie wszystkie klasy mają metodę X lub daną składową Y) - taka jest idea stosowania typów generycznych ☑️.

NAKŁADANIE OGRANICZEŃ NA ZMIENNĄ T

Wyjaśnię zagadnienie na klasie generycznej, lecz to działa tak samo również na metodzie oraz interfejsie. Przypuśćmy, że chcemy dopuścić do wstawienia typu klasy jedynie od określonej z góry klasy bazowej, czyli interesuje nas jedynie podana klasa bazowa np. "BaseClass" i wszystkie wychodzące od niej klasy potomne. Nikt nie powiedział, że to ma być klasa macierzysta, można też podać jedną z klas potomnych! Korzystamy ze znanego już słowa, "extends":

class GenericClass<T extends BaseClass>

Zaraz..."extends" tutaj? Tak ☺️! Twórcy języka Java nie chcieli dodawać odrębnego słowa kluczowego do definiowania ograniczeń typu T, więc piszemy "extends" również w tym kontekście 😄. Po słowie kluczowym, podajemy klasę macierzystą, która ma stanowić "korzeń" dla wpuszczanych typów. Tak tworzymy ograniczenia typu generycznego w języku Java. To da skutek taki, że będziemy mogli tworzyć instancje klas generycznych wyłącznie o typie klasy T jako "BaseClass" albo jej typie potomnym. To działa jak swoista bramka, która sprawdza kto może wejść, a kto nie:

GenericClass<BaseClass> genericBaseClass = new GenericClass<>(new BaseClass());
GenericClass<DerivedClass> genericDerivedClass = new GenericClass<>(new DerivedClass());

System.out.println(genericBaseClass.getValueFromBaseClass());
System.out.println(genericDerivedClass.getValueFromBaseClass());

W pozostałych przypadkach wyrośnie błąd kompilacji (bo powołujemy się na metody, które znajdują się tylko w tej "rodzinie" klas). Typy generyczne mają za zadanie zapewniać bezpieczeństwo typów i wykrywać niepożądane przypadki już na etapie kompilacji, nie podczas działania programu.

KLASA + INTERFEJS

Masz możliwość nałożyć ograniczenie nie tylko poprzez konkretną klasę, ale także poprzez interfejs 💡! Wystarczy połączyć nazwę klasy i interfejsu korzystając ze znaku ampersandu (&):

class GenericClass<T extends BaseClass & Countable>

Wtedy do tworzenia instancji zostaną dopuszczone tylko te klasy pochodzące od "BaseClass", które dodatkowo implementują interfejs "Countable"! To jest konieczne wtedy, gdy klasa generyczna korzysta gdzieś z metody pochodzącej od interfejsu "Countable". W źródłach anglojęzycznych możesz spotkać się z określeniem tego jako "intersection types", czyli "skrzyżowane typy".

Ograniczenia typu generycznego w języku Java

Ograniczenia typu generycznego w języku Java stosowane są w celu umożliwienia powołania się na dane składowe i metody, które pochodzą od konkretnej klasy bądź interfejsu.


Sprawa zamknięta, więc artykuł też można uznać za zamknięty 📕. Dodam, że w kontekście typów generycznych jest to często stosowane i warto to sobie przyswoić jak najprędzej ⏩!

PODOBNE ARTYKUŁY