Концепции обобщений в Java

JavaBeginner
Практиковаться сейчас

Введение

Обобщения в Java позволяют писать гибкий и повторно используемый код, предоставляя дополнительный уровень абстракции. Они позволяют создавать один алгоритм для нескольких типов объектов. Обобщения обеспечивают безопасность типов и избавляются от ошибок времени выполнения. В этом практическом занятии мы рассмотрим различные аспекты обобщений в Java.

Создание обобщенного класса

В Java мы можем создать обобщенный класс с помощью оператора ромба (<>). В следующем блоке кода мы создадим обобщенный класс под названием MyGenericClass, который может принимать любой тип объекта. Также мы создадим объект типа MyGenericClass с типами integer и string в качестве параметров.

// ~/project/MyGenericClass.java

class MyGenericClass<T> {
    T field1;

    public MyGenericClass(T field1) {
        this.field1 = field1;
    }

    public T getField1() {
        return field1;
    }
}

public class Main {
    public static void main(String[] args) {
        MyGenericClass<Integer> myIntObj = new MyGenericClass<Integer>(100);
        MyGenericClass<String> myStringObj = new MyGenericClass<String>("Hello World");

        System.out.println(myIntObj.getField1());
        System.out.println(myStringObj.getField1());
    }
}

Для запуска кода откройте терминал и перейдите в каталог ~/project. Скомпилируйте код с помощью следующей команды:

javac MyGenericClass.java

После успешной компиляции запустите код с помощью следующей команды:

java Main

Результат выполнения кода будет:

100
Hello World

Создание обобщенного метода

В Java мы также можем создать обобщенный метод. Обобщенный метод может работать с разными типами объектов. В следующем блоке кода мы создадим обобщенный метод под названием printArray, который может выводить любой тип массива.

// ~/project/Main.java

public class Main {
    public static <T> void printArray(T[] array) {
        for (T element : array)
            System.out.println(element);
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4};
        String[] stringArray = {"Hello", "World"};

        printArray(intArray);
        printArray(doubleArray);
        printArray(stringArray);
    }
}

Для запуска кода откройте терминал и перейдите в каталог ~/project. Скомпилируйте код с помощью следующей команды:

javac Main.java

После успешной компиляции запустите код с помощью следующей команды:

java Main

Результат выполнения кода будет:

1
2
3
4
5
1.1
2.2
3.3
4.4
Hello
World

Создание ограниченного обобщенного метода

Мы также можем создать ограниченный обобщенный метод. Ограниченные обобщения ограничивают диапазон типов, которые может принимать метод. Мы используем ключевое слово extends, чтобы установить ограничение. В следующем блоке кода мы создадим ограниченный обобщенный метод под названием printNumbers, который может вывести числа объектов типа Number и его подклассов.

// ~/project/Main.java

public class Main {
    public static <T extends Number> void printNumbers(T[] numbers) {
        for (T number : numbers)
            System.out.println(number);
    }

    public static void main(String[] args) {
        Integer[] intNumbers = {1, 2, 3, 4, 5};
        Double[] doubleNumbers = {1.1, 2.2, 3.3, 4.4};

        printNumbers(intNumbers);
        printNumbers(doubleNumbers);
    }
}

Для запуска кода откройте терминал и перейдите в каталог ~/project. Скомпилируйте код с помощью следующей команды:

javac Main.java

После успешной компиляции запустите код с помощью следующей команды:

java Main

Результат выполнения кода будет:

1
2
3
4
5
1.1
2.2
3.3
4.4

Понимание безопасности типов в обобщениях

Обобщения обеспечивают безопасность типов, гарантируя, что мы определяем только один тип объектов для работы одновременно. В следующем блоке кода мы создадим ArrayList целых чисел без определения его параметра типа. Использовать обобщения без параметров типа - это плохая практика. Код скомпилируется, но при попытке обратиться к элементам другого типа будет возвращена ошибка времени выполнения.

// ~/project/Main.java

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList integerList = new ArrayList();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
        integerList.add("Hello World");

        for (Object element : integerList) {
            System.out.println((Integer)element);
        }
    }
}

Для запуска кода откройте терминал и перейдите в каталог ~/project. Скомпилируйте код с помощью следующей команды:

javac Main.java

После успешной компиляции запустите код с помощью следующей команды:

java Main

Результат выполнения кода будет:

1
2
3
Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.lang.Integer
    at Main.main(Main.java:13)

Мы получаем ошибку времени выполнения, потому что integerList содержит элементы типа Integer и String. Это происходит потому, что мы не определили параметр типа при создании integerList.

Понимание стирания типов в обобщениях

Java использует стирание типов в обобщениях, чтобы обеспечить отсутствие дополнительных накладных расходов во время выполнения. Компилятор заменяет параметр типа классом Object или родительским классом (в случае ограниченных обобщений) в процессе стирания типов. В следующем блоке кода мы увидим, как обобщения работают с примитивными типами на этапе компиляции и выполнения.

// ~/project/Main.java

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);

        int sum = 0;
        for (int i = 0; i < integerList.size(); i++) {
            sum += integerList.get(i);
        }
        System.out.println(sum);
    }
}

Для запуска кода откройте терминал и перейдите в каталог ~/project. Скомпилируйте код с помощью следующей команды:

javac Main.java

После успешной компиляции запустите код с помощью следующей команды:

java Main

Результат выполнения кода будет:

6

В вышеприведенном блоке кода мы пытались добавить элементы integerList. Как мы знаем, примитивные типы, такие как int, не могут использоваться с обобщениями. В силу стирания типов параметр типа Integer заменяется классом Object на этапе компиляции. Однако автоупаковка и распаковка позволили нам выполнять сложение целых чисел на этапе компиляции. Во время выполнения код работал как обычное выполнение кода, без дополнительных накладных расходов.

Резюме

В этом практическом занятии мы изучили обобщения в Java. Мы рассмотрели основные концепции, связанные с обобщенными классами, обобщенными методами, ограниченными обобщенными методами, безопасностью типов в обобщениях и стиранием типов в обобщениях. Мы также увидели, как писать гибкий и повторно используемый код с использованием обобщений. Надеемся, что это практическое занятие помогла вам понять обобщения в Java.