17. JAVA – typy generyczne


Typy generyczne w Java to nic innego jak szablony, które możemy wykorzystać dla różnych typów danych.
Metody zdefiniowane w klasie generycznej mogą zostać wykorzystane dla typów złożonych np. Integer, String, Float itd., bez konieczności pisania oddzielnych klas.

Na potrzeby tego przykładu wszystkie klasy umieścimy w tym samym pliku:

plik GenericsExample.java

//stwórzmy klasę generyczną dla jednego typu, która będzie zawierała 2 pola oraz konstruktor, gettery i settery oraz toString

class Test<T>{//w nawiasie ostrym podajemy drukowanymi literami np. T(jak typ), K(klucz, V(wartość), E(element np. kolekcji), N(liczba) - dowolnie, później te litery przyjmą typ np. Integer, String...
private T a;
private T b;

public Test(T a, T b) {
this.a = a;
this.b = b;
}

public T getA() {
return a;
}

public void setA(T a) {
this.a = a;
}

public T getB() {
return b;
}

public void setB(T b) {
this.b = b;
}

@Override
public String toString() {
return "Test{" +
"a=" + a +
", b=" + b +
'}';
}
}


//stwórzmy teraz klasę generyczną dla wielu typów, która będzie zawierała 2 pola oraz konstruktor, gettery i settery oraz toString
class MultiTest<T, U, V>{//w nawiasie ostrym podajemy drukowanymi literami np. T, U, V - dowolnie, później te litery przyjmą typ np. Integer, String...
private T x;
private U y;
private V z;

public MultiTest(T x, U y, V z) {
this.x = x;
this.y = y;
this.z = z;
}

public T getX() {
return x;
}

public void setX(T x) {
this.x = x;
}

public U getY() {
return y;
}

public void setY(U y) {
this.y = y;
}

public V getZ() {
return z;
}

public void setZ(V z) {
this.z = z;
}

@Override
public String toString() {
return "MultiTest{" +
"x=" + x +
", y=" + y +
", z=" + z +
'}';
}
}


public class GenericsExample {
public static void main(String[] args) {
//przykład dla Integer
Test<Integer> gnrInt = new Test<Integer>(1,2); //przyjmujemy typ Integer
System.out.println(gnrInt.toString());

//przykład dla String
Test<String> gnrStr = new Test<String>("a","b"); //przyjmujemy typ String
System.out.println(gnrInt.toString());

//wywołamy teraz klasę generyczną dla wielu typów
MultiTest<Integer, Float, String> multiTest = new MultiTest<>(1,2.5f, "String");
System.out.println(multiTest.toString());
}
}

Metody generyczne

Metody generyczne mogą się znajdować w dowolnej klasie, nie koniecznie generycznej.

Stwórzmy zwykłą klasę, która zawierać będzie metodę generyczną:

import java.util.ArrayList;

class SimpleClass{//zwykła klasa
//metoda generyczna, która zwróci nam ostatni element ArrayList
public static <E> E getLastElement(ArrayList<E> arl) {//przekazujemy ArrayList w argumencie
return arl.get(arl.size() - 1);//pobieramy ostatni element listy
}
}

public class GenericMethod {
public static void main(String[] args) {
//tworzymy ArrayList na bazie Integer
ArrayList<Integer> arl = new ArrayList<>();
arl.add(5);
arl.add(15);
System.out.println(SimpleClass.getLastElement(arl));//wywołujemy metodę z klasy
}
}

 

Ograniczenia

Podczas pisania kodu możemy mieć potrzebę ograniczenia tego, co będzie podstawione pod typ generyczny np.  typ zmiennej lub klasa, która ma dziedziczyć tylko po konkretnej klasie:

plik GenericsExtends

import java.util.ArrayList;
import java.util.Iterator;

//tworzymy klasę bazową
class Person{
private String name;

//dodajemy konstruktor, gettery i settery oraz toString

public Person(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}

//tworzymy klasę Employee, która rozszerzy klasę person
class Employee extends Person{
private String jobTitle;

//dodajemy konstruktor, gettery i settery oraz toString
public Employee(String name, String jobTitle) {
super(name);
this.jobTitle = jobTitle;
}

public String getJobTitle() {
return jobTitle;
}

public void setJobTitle(String jobTitle) {
this.jobTitle = jobTitle;
}

@Override
public String toString() {
return "Employee{" +
//ponieważ klasa rozszerza klasę Person, to mamy dostęp do pola name, które wyświetlimy
"name=" + this.getName() + '\'' +
"jobTitle='" + jobTitle + '\'' +
'}';
}
}

//tworzymy kolejną klasę
class Contractor extends Employee{
public Contractor(String name, String jobTitle) {
super(name, jobTitle);
}
@Override
public String toString() {
return "Contractor{" + super.toString() + "}";//łączymy toString z toString z Employee
}
}

//poniższa klasa nie będzie dziedziczyła po Person, więc jej zastosowanie wygeneruje błąd
class Bug {
private String name;

//dodajemy konstruktor, gettery i settery oraz toString

public Bug(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Bug{" +
"name='" + name + '\'' +
'}';
}
}

//tworzymy klasę generyczną Organisation, która będzie posiadała ograniczenia
//zależy nam na tym, aby klasa Organisation mogła przyjmować jedynie klasy rozszerzające Person, żadne inne

class Organisation<T extends Person>{
private ArrayList<T> participants;

//tworzymy konstruktor, który stworzy zbiór konkretnego typu
public Organisation() {
participants = new ArrayList<T>();
}

//metoda dodająca uczestnika
public void addParticipants(T p){
participants.add(p);
}

//metoda pobierająca index
public T getParticipants(int index){
return participants.get(index);
}

//wyświetlenie uczestników
public void printParticipants(){
Iterator<T> iter = participants.iterator();
while (iter.hasNext()){
System.out.println(iter.next());
}
}
}

public class GenericsExtends {
public static void main(String[] args) {
//tworzymy nowy obiekt na bazie Organisation, który przyjmie typ Employee
Organisation<Employee> org = new Organisation<>();
org.addParticipants(new Employee("Jan","programmer"));
org.addParticipants(new Employee("Iza","programmer"));
org.addParticipants(new Contractor("Alek", "programmer"));

org.printParticipants();

//Organisation<Bug> test = new Organisation<Bug>(); //ta klasa nie rozszerza Person
}
}

i kolejny przykład GenericExtends1.java


import java.util.ArrayList;
import java.util.Iterator;

class Country{
private String name;

public Country(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Country{" +
"name='" + name + '\'' +
'}';
}
}

class Continent extends Country{
private String continentName;

public Continent(String name, String continentName) {
super(name);
this.continentName = continentName;
}

public String getContinentName() {
return continentName;
}

public void setContinentName(String continentName) {
this.continentName = continentName;
}

@Override
public String toString() {
return "Continent{" +
"country='" + super.getName() + '\'' +
", continentName='" + continentName + '\'' +
'}';
}
}

class World<T extends Country>{
private ArrayList<T> parts;

//tworzymy konstruktor, który stworzy zbiór konkretnego typu
public World() {
parts = new ArrayList<T>();
}

//metoda dodająca
public void addParts(T p){
parts.add(p);
}

//metoda pobierająca index
public T getParts(int index){
return parts.get(index);
}

//wyświetlenie uczestników
public void printParts(){
Iterator<T> iter = parts.iterator();
while (iter.hasNext()){
System.out.println(iter.next());
}
}
}

public class GenericExtends1 {
public static void main(String[] args) {
World<Continent> continent = new World<>();
continent.addParts(new Continent("Poland", "Europe"));
continent.addParts(new Continent("USA", "America N"));
continent.addParts(new Continent("Germany", "Europe"));

continent.printParts();;

}
}