Урок 16

Обобщения


1. Что такое обобщения?

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

Пример 1. Матрица

public class Matrix<T> {
    private T[] array;

    public Matrix(T[] array) {
        this.array = array;
    }

    public static void main(String[] args) {
        Matrix<Double> doubleMatrix = new Matrix<>(new Double[2]);
        Matrix<Integer> integerMatrix = new Matrix<>(new Integer[4]);
        Matrix<Byte> byteMatrix = new Matrix<>(new Byte[7]);
    }
}

Matrix<Integer> integerMatrix - Integer является аргументом типа.

Java не создает разные версии класса Matrix или любого другого обобщенного класса. Имеется только одна версия класса Matrix, которая существует в прикладной программе.

Обобщения работают только с объектами! Следующий код является неправильным:

Gen<int> strOb = new  Gen<int> (53);
// Ошибка, нельзя использовать примитивные типы 

Т обозначает имя параметра типа. Это имя используется в качестве заполнителя вместо которого в дальнейшем подставляется имя конкретного типа, передаваемого классу Matrix при создании объекта. Это означает, что обозначение Т применяется в классе Matrix всякий раз, когда требуется параметр типа. Всякий раз, когда объявляется параметр типа, он указывается в угловых скобках.

Обобщенные типы отличаются в зависимости от типов-аргументов. Следующий код не допустим:

doubleMatrix = integerMatrix; // Не верно! 

Несмотря на то, что doubleMatrix и integerMatrix имеют тип Matrix<T>, они являются ссылками на разные типы, потому что типы их параметров отличаются.

Пример 2. Обобщенный класс с двумя параметрами типа

public class TwoGen<T, V> {
    T ob1;
    V ob2;

    TwoGen(T o1, V o2) {
        ob1 = o1;
        ob2 = o2;
    }

    void showTypes() {
        System.out.println("Type of T is " +
                ob1.getClass().getName());

        System.out.println("Type of V is " +
                ob2.getClass().getName());
    }

    T getOb1() {
        return ob1;
    }

    V getOb2() {
        return ob2;
    }
}


public class SimpleGen {
    public static void main(String args[]) {
        TwoGen<Integer, String> tgObj = new TwoGen<>(88, "Generics");

        tgObj.showTypes();

        int v = tgObj.getOb1();
        System.out.println("value: " + v);

        String str = tgObj.getOb2();
        System.out.println("value: " + str);
    }
}

2. Ограниченные типы

Указывая параметр типа, можно наложить ограничение сверху в виде верхней границы, где объявляется суперкласс, от которого должны быть унаследованы все аргументы типов. С этой целью вместе с параметром указывается ключевое слово extends:

class Gen <Т extends Superclass> 

Параметр типа Т может быть заменен только указанным суперклассом или его подклассами.

Пример 3. Использования ограниченного типа

public class Average<T extends Number> {
    private T[] array;

    public Average(T[] array) {
        this.array = array.clone();
    }

    public double average() {
        double sum = 0.0;

        for (T value : array) {
            sum += value.doubleValue();
        }

        return sum / array.length;
    }
}



public class BoundsDemo {
    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Average<Integer> integerAverage = new Average<Integer>(intArray);
        double v = integerAverage.average();
        System.out.println("Integer average is " + v);

        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
        Average<Double> doubleAverage = new Average<Double>(doubleArray);
        double w = doubleAverage.average();
        System.out.println("DoubleAverage is " + w);

        // This won't compile because String is not a
        // subclass of Number.
       /* String[] strs = {"1", "2", "3", "4", "5"};
        Average<String> strob = new Average<String>(strs);

        double x = strob.average();
        System.out.println("strob average is " + v);*/
    }
}

В виде ограничения можно накладывать не только тип класса, но и тип интерфейса:

public class MyClass<T extends Serializable> 

Ограничение может включать в себя как тип класса, так и типы одного или нескольких интерфейсов:

class Gen <T extends MyClass & MyInterface1 & MyInterface2>

Тип класса должен быть задан первым. Накладывая на обобщенный тип ограничение, состоящее из класса и одного или нескольких интерфейсов, для их объединения следует воспользоваться логической операцией &: Таким образом, любой тип, передаваемый параметру Т, должен быть подклассом, производным от класса MyClass и реализующим интерфейсы MyInterface1 и MyInterface2.

3. Применение метасимвольных аргументов

Представьте, что мы хотим добавить метод для сравнения средних значений массивов в класс Average из примера 3. Причем типы массивов могут быть разные:

Integer intArray[] = {1, 2, 3, 4, 5};
Double doubleArray[] = {1.1, 2.2, 3.3, 4.4, 5.5};

Average<Integer> iob = new Average<>(intArray);
Average<Double> dob = new Average<>(doubleArray);

if (iob.sameAvg(dob)) {
    System.out.println("are the same.");} 
else {   
    System.out.println("differ.");
}

Так как Average параметризованный тип, какой тип параметра вы укажете для Average, когда создадите параметр метода типа Average? Напрашивается следующий вариант:

boolean sameAvg(Average<T> ob) {
    return average() == ob.average();
}

Но это не сработает, так как в этом случае метод sameAvg будет принимать аргументы только того же типа, что и существующий объект:

if (iob.sameAvg(iob)) {
    System.out.println("are the same.");} 
else {   
    System.out.println("differ.");
}

Чтобы создать обобщенную версию метода sameAvg(), следует воспользоваться другим средством обобщений Jаvа – метасимвольным аргументом. Метасимвольный аргумент обозначается знаком ? и представляет неизвестный тип.

boolean sameAvg(Average<?> ob) {
    return average() == ob.average();
}

Метасимвол не оказывает никакого влияния на тип создаваемых объектов класса Average. Это определяется оператором extends в объявлении класса Average. Метасимвол просто совпадает c любым достоверным объектом класса Average.

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

Пример 4. Применение метасимвольного аргумента

public class Average2<T extends Number> {
    private T[] array;

    public Average2(T[] array) {
        this.array = array.clone();
    }

    public double average() {
        double sum = 0.0;

        for (T value : array) {
            sum += value.doubleValue();
        }

        return sum / array.length;
    }

    boolean sameAvg(Average2<?> ob) {
        return average() == ob.average();
    }
}

public class BoundsDemo2 {
    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Average2<Integer> iob = new Average2<>(intArray);
        double v = iob.average();
        System.out.println("Integer average is " + v);

        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
        Average2<Double> dob = new Average2<>(doubleArray);
        double w = dob.average();
        System.out.println("Double average is " + w);

        Float[] floatArray = {1.0F, 2.0F, 3.0F, 4.0F, 5.0F};
        Average2<Float> fob = new Average2<>(floatArray);
        double x = fob.average();
        System.out.println("Float average is " + x);

        System.out.print("Averages of iob and dob ");
        if (iob.sameAvg(dob)) {
            System.out.println("are the same.");
        } else {
            System.out.println("differ.");
        }

        System.out.print("Averages of iob and fob ");
        if (iob.sameAvg(fob)) {
            System.out.println("are the same.");
        } else {
            System.out.println("differ.");
        }
    }
}

4. Обобщенные методы и конструкторы

В методах обобщенного класса можно использовать параметр типа, а следовательно, они становятся обобщенными относительно параметра типа. Но можно объявить обобщенный метод, в котором непосредственно используется один или несколько параметров типа. Более того, можно объявить обобщенный метод, входящий в необобщенный класс. Например:

Пример 5. Обобщенный метод

public class GenMethodDemo {
    // Determine if an object is in an array.
    static <T, V> boolean isIn(T x, V[] array) {
        for (V element : array) {
            if (x.equals(element)) {
                return true;
            }
        }
        return false;
    }

    public static void main(String args[]) {
        Integer nums[] = {1, 2, 3, 4, 5};

        if (isIn(2, nums)) {
            System.out.println("2 is in nums");
        }

        if (!isIn(7, nums)) {
            System.out.println("7 is not in nums");
        }
        System.out.println();

        String strs[] = {"one", "two", "three",
                "four", "five"};

        if (isIn("two", strs)) {
            System.out.println("two is in strs");
        }

        if (!isIn("seven", strs)) {
            System.out.println("seven is not in strs");
        }
    }
}

Конструкторы также могут быть обобщенными, даже если их классы таковыми не являются. Например:

Пример 6. Обобщенный конструктор

public class GenConstructor {
    private double val;

    <T extends Number> GenConstructor(T arg) {
        val = arg.doubleValue();
    }

    void showValue() {
        System.out.println("value: " + val);
    }
}

public class GenConstructorDemo {
    public static void main(String args[]) {
        GenConstructor test1 = new GenConstructor(100);
        GenConstructor test2 = new GenConstructor(123.5F);

        test1.showValue();
        test2.showValue();
    }
}

5. Обобщенные интерфейсы

В дополнение к обобщенным классам и методам вы можете объявлять обобщенные интерфейсы. Обобщенные интерфейсы специфицируются так же, как и обобщенные классы:

Пример 7. Обобщенный интерфейс

public interface MyInterface<T> {
    T someMethod(T t);
}

public class MyClass<T> implements MyInterface<T> {
    @Override
    public T someMethod(T t) {
        return t;
    }

    public static void main(String[] args) {
        MyInterface<String> object = new MyClass<>();
        String str = object.someMethod("some string");
    }
}

6. Иерархии обобщенных классов

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

Пример 8. Иерархия обобщенных классов

public class GenericSuper<T> {
    T ob;

    GenericSuper(T ob) {
        this.ob = ob;
    }

    T getOb() {
        return ob;
    }
}

public class GenericSub<T> extends GenericSuper<T>{
    public GenericSub(T ob) {
        super(ob);
    }
}
public class HierarchyDemo {
    public static void main(String args[]) {
        GenericSuper<Integer> iOb = new GenericSuper<>(88);
        GenericSub<Integer> iOb2 = new GenericSub<>(99);
        GenericSub<String> strOb2 = new GenericSub<>("Generics Test");

        if (iOb2 instanceof GenericSub<?>) {
            System.out.println("iOb2 is instance of GenericSub");
        }
        if (iOb2 instanceof GenericSuper<?>) {
            System.out.println("iOb2 is instance of GenericSuper");
        }
        System.out.println();

        if (strOb2 instanceof GenericSub<?>) {
            System.out.println("strOb is instance of GenericSub");
        }

        if (strOb2 instanceof GenericSuper<?>) {
            System.out.println("strOb is instance of GenericSuper");
        }
        System.out.println();

        if (iOb instanceof GenericSub<?>) {
            System.out.println("iOb is instance of GenericSub");
        }

        if (iOb instanceof GenericSuper<?>) {
            System.out.println("iOb is instance of GenericSuper");
        }

        // The following can't be compiled because
        // generic type info does not exist at run-time.
       /* if (iOb2 instanceof GenericSub<Integer>) {
            System.out.println("iOb2 is instance of Gen2<Integer>");
        }*/
    }
}

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

7. Ограничения присущие обобщениям

Ограничениям присущи некоторые ограничения. Рассмотрим их.

1. Нельзя создавать экземпляр по параметру типа.

class Gen<T> 
  T оb; 

  Gen () {
    оb = new Т(); // Недопустимо!!!
 }

2. Ограничения на статические члены

Никакой stаtiс член не может использовать тип параметра, объявленный в eго классе. Например, все stаtiс члены этого класса являются недопустимыми:

class Wrong<T> { 
   // Неверно, нельзя создать статические переменные типа Т. 
   static Т оb; 

  // Неверно, ни один статический метод не может использовать Т. 
  static T getOb() { 
     return оb; 
   } 
}

Но объявить статические обобщенные методы со своими параметрами типа все же можно.

3. Ограничения на обобщенные массивы.

Нельзя создать экземпляр массива, тип элемента которого определяется параметром типа.

public class Gen2<T extends Number> {
    T оb;
    T vals[]; // ОК

    Gen2(T о, T[] nums) {
        оb = о;
        // Этот оператор неверен.
        // vals = new Т[10]; // нельзя создавать массив объектов Т
        // Однако этот оператор верен.
        vals = nums; // можно присвоить ссылку существующему массиву
    }
}

Нельзя создать массив специфических для типа обобщенных ссылок:

public class GenArrays {
    public static void main(String args[]) {
        Integer n[] = {1, 2, 3, 4, 5};
        // Нельзя создать массив специфичных для типа обобщенных ссылок.
        // Gen2<Integer> gens[] = new Gen2<Integer>[10]; // Неверно!
        // Это верно.
        Gen2<?> gens[] = new Gen2<?>[10]; // ОК
        gens[0] = new Gen2<>(34, n);
    }
}


0 comments
Leave your comment: