Generics в Java
Generics позволяват класове, интерфейси и методи да работят с тип, който се задава при използване. Така един и същ код може да бъде използван с различни типове, без да се губи типова безопасност.
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
T е параметър на типа. Той се заменя с конкретен тип при създаване на обект.
Box<String> textBox = new Box<>();
textBox.setValue("Java");
String value = textBox.getValue();
В този пример T се заменя със String.
Параметри на типа
Параметърът на типа е име, което временно представлява конкретен тип. Той се записва в ъглови скоби след името на класа, интерфейса или метода.
class Box<T> {
private T value;
}
В примера T не е конкретен клас. Той е параметър, който ще бъде заменен при използване на Box.
Box<String> textBox = new Box<>();
Box<Integer> numberBox = new Box<>();
При Box<String> параметърът T се разглежда като String. При Box<Integer> параметърът T се разглежда като Integer.
В Java съществуват утвърдени конвенции за именуване на параметрите на типа.
| Име | Значение | Къде се използва |
|---|---|---|
T | Type | общ тип |
E | Element | елементи в колекции като List и Set |
K | Key | ключове в Map |
V | Value | стойности в Map |
R | Result | резултат от операция или функция |
U | Втори свободен тип | когато T вече се използва |
Тези имена са конвенция. Те не променят поведението на програмата, но правят generic декларациите по-разпознаваеми.
Необходимост от generics
Без generics стойностите често трябва да се съхраняват като Object. Това позволява запис на всякакъв тип и измества грешките към времето на изпълнение.
class Box {
private Object value;
public void setValue(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
}
При такъв клас е необходимо явно преобразуване.
Box box = new Box();
box.setValue("Java");
String value = (String) box.getValue();
Generics премахват нуждата от такова преобразуване и позволяват компилаторът да проверява типовете.
Типова безопасност
Типова безопасност означава, че компилаторът проверява дали се използват стойности от правилния тип. При generics грешка от несъвместим тип се открива при компилация, а не чак при изпълнение.
Box<String> box = new Box<>();
box.setValue("Java");
// box.setValue(10); // не се компилира
В примера box е деклариран като Box<String>. Затова в него може да бъде записан String, но не и int или Integer.
Типовата безопасност намалява нуждата от явно преобразуване и намалява риска от грешки по време на изпълнение.
Генеричен клас с повече от един параметър
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
Класът Pair<K, V> използва два параметъра на типа. K може да представлява тип на ключ, а V тип на стойност.
Pair<String, Integer> grade = new Pair<>("Ivan", 6);
Генеричен метод
Генеричен метод декларира собствен параметър на типа преди типа на връщаната стойност.
class Printer {
public static <T> void print(T value) {
System.out.println(value);
}
}
Методът може да бъде извикан с различни типове.
Printer.print("Java");
Printer.print(100);
Printer.print(12.5);
Генеричен интерфейс
Интерфейс също може да има параметър на типа.
interface Repository<T> {
void save(T item);
T findById(int id);
}
Клас, който имплементира интерфейса, задава конкретен тип или остава генеричен.
class StudentRepository implements Repository<Student> {
@Override
public void save(Student item) {
}
@Override
public Student findById(int id) {
return null;
}
}
Generics и референтни типове
Generics работят с референтни типове. Не може да се използва примитивен тип като int, double или boolean.
// Box<int> box = new Box<>(); // не се компилира
Box<Integer> box = new Box<>();
За примитивни стойности се използват обгръщащи класове като Integer, Double и Boolean.
Raw типове
Raw тип се получава, когато generic клас се използва без параметър на типа.
Box box = new Box();
Този подход премахва част от проверките на компилатора и не трябва да се използва в нов код. Правилната форма е:
Box<String> box = new Box<>();
Предимства
Generics осигуряват типова безопасност, намаляват нуждата от явно преобразуване и позволяват повторно използване на класове, интерфейси и методи с различни типове.