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 осигуряват типова безопасност, намаляват нуждата от явно преобразуване и позволяват повторно използване на класове, интерфейси и методи с различни типове.


This site uses Just the Docs, a documentation theme for Jekyll.