Шаг 64.
Язык программирования Java.
Ограничение на переменные типа

На этом шаге мы рассмотрим ограничение на переменные типа

Иногда класс или метод нуждается в наложении ограничений на переменные типа. Приведем характерный тому пример, в котором требуется вычислить наименьший элемент массива следующим образом:

public class ArrayAlg {
    public static <T> T min(T[] a) {
        if (a == null || a.length == 0) {
            return null;
        }

        T smallest = a[0];
        for (int i = 1; i < a.length; i++) {
            if (smallest.compareTo(a[i]) > 0) {
                smallest = a[i];
            }
        }
        return smallest;
    }
}

Но такой код имеет одну проблему. Переменная smallest относится к типу T, а это означает, что она может быть объектом любого класса. И этот класс вполне может не содержать метод compareTo().

Выход из этого затруднительного положения состоит в том, чтобы наложить ограничение на тип T и вместо него подставлять только класс, реализующий Comparable - стандартный интерфейс с единственным методом compareTo(). Для этого достаточно наложить ограничение на переменную типа следующим образом:

public static <T extends Comparable> T min(T[] a) {...}

Теперь обобщенный метод min() может вызываться только с массивами классов, реализующих интерфейс Comparable. Если указать массив других классов произойдет ошибка компиляции.

Заметим, что хоть Comparable является интерфейсом, мы использовали ключевое слово extends.

Переменная типа или подстановки может иметь несколько ограничений. Ограничивающие типы разделяются знаком &. Ниже приведен пример для нескольких ограничений:

T extends Comparable & Serializable

Как и в механизме наследования в Java, подтипов интерфейсов может быть сколько угодно, то только один из ограничивающих типов может быть классом. Если для ограничения служит класс, он должен быть первым в списке накладываемых ограничений.

Ниже приведем исправленную версию для нахождения минимального и максимального элемента в массиве.

import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 * <p>Класс для тестирования программы</p>
 * */
public class PairTest {
    public static void main(String[] args) {
        String[] words = {"name1", "b", "name3", "c", "name2", "a"};
        Pair<String> mms = ArrayAlg.minmax(words);
        System.out.println("min = " + mms.getFirst());
        System.out.println("max = " + mms.getSecond());

        GregorianCalendar[] birthdays = {
            new GregorianCalendar(1992, Calendar.SEPTEMBER, 2),
            new GregorianCalendar(1993, Calendar.SEPTEMBER, 3),
            new GregorianCalendar(1995, Calendar.SEPTEMBER, 5),
            new GregorianCalendar(1994, Calendar.SEPTEMBER, 4),
            new GregorianCalendar(1991, Calendar.SEPTEMBER, 1)
        };

        Pair<GregorianCalendar> mmg = ArrayAlg.minmax(birthdays);
        System.out.println("min = " + mmg.getFirst().getTime());
        System.out.println("max = " + mmg.getSecond().getTime());
    }
}

/**
 * <p>Класс, который представляет из себя пару обобщенных значений</p>
 * */
class Pair<T> {
    private T first;
    private T second;

    /**
     * <p>Конструктор по умолчанию</p>
     * */
    public Pair() {
        this.first = null;
        this.second = null;
    }

   /**
     * <p>Конструктор, который принимает два значения</p>
     * @param first Первое значение
     * @param second Второе значение
     * */
    public Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }

    /**
     * <p>Функция для получения первого значения</p>
     * @return Первое значение
     * */
    public T getFirst() {
        return first;
    }

    /**
     * <p>Функция для изменения первого значения</p>
     * @param first Первое значение
     * */
    public void setFirst(T first) {
        this.first = first;
    }

    /**
     * <p>Функция для получения второго значения</p>
     * @return Второе значение
     * */
    public T getSecond() {
        return second;
    }

    /**
     * <p>Функция для изменения второго значения</p>
     * @param second Второе значение
     * */
    public void setSecond(T second) {
        this.second = second;
    }
}

/**
 * <p>Класс для поиска минимального и максимального элемента в массиве</p>
 * */
class ArrayAlg {
    /**
     * <p>Функция для поиска минимального и максимального элемента в массиве</p>
     * @param a Массив Объектов, тип которых должен реализовывать интерфейс Comparable
     * @return Минимальное и максимальное значение, упакованные в пару
     * */
    public static <T extends Comparable> Pair<T> minmax(T[] a) {
        if (a == null || a.length == 0) {
            return null;
        }

        T min = a[0];
        T max = a[0];

        for (int i = 1; i < a.length; i++) {
            if (min.compareTo(a[i]) > 0) {
                min = a[i];
            }

            if (max.compareTo(a[i]) < 0) {
                max = a[i];
            }
        }

        return new Pair<>(min, max);
    }
}

Проект можно взять здесь


Рис. 1. Вывод программы

На следующем шаге мы поговорим про то, как виртуальная машина понимает обобщенный код

Предыдущий шаг Содержание Следующий шаг