Общие сведения о Java-дженериках в краткой форме с примерами.

Пожалуйста, прочитайте этот FAQ для получения дополнительной информации:

ОЗYX0Z

Вот несколько важных моментов из этого FAQ.

Пример (общий тип):

класс Pair<X,Y> { 
  сначала частный X;
  частный Y второй;   общественная пара (X a1, Y a2) {
    первый = а1;
    второй = а2;
  }
  public X getFirst() {сначала вернуться; }
  общественный Y getSecond () {вернуть второй; }
  public void setFirst(X arg)  { first = arg; }
  общественный недействительный setSecond (Y arg) {второй = arg; }
}

Класс Пара имеет два параметра типа Х и Да .  Они заменяются аргументами типа, когда универсальный тип Пара создается экземпляр. Например, в декларации Пара<Строка, Дата> параметр типа Х заменяется аргументом типа Нить и Да заменяется на  Дата .
Идентификатор области Х и Да является полное определение класса.  В этой области два параметра типа Х и Да используются так же, как и типы (с некоторыми ограничениями). В приведенном выше примере параметры типа используются в качестве аргументов и типа возвращаемого значения методов экземпляра, а также типа полей экземпляра.
Параметры типа могут быть объявлены с границами. Границы предоставляют доступ к методам неизвестного типа, который указывает параметр типа. В нашем примере мы не вызываем методы неизвестных типов. Х и Да . По этой причине два параметра типа не ограничены.

Пример (определенного параметризованного типа):

общественная пустота printPair( Пара<Строка,Long> пара) {
  System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Пара<Строка,Long> лимит = новый Пара<Строка,Long> («максимум», 1024 л);
printPair (ограничение);

Пример (параметризованного типа с подстановочными знаками):

общественная пустота printPair( Пара<?,?> пара) {
  System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Пара<?,?> лимит = новый Пара<Строка,Long> («максимум», 1024 л);
printPair (ограничение);

Все экземпляры универсального типа используют одно и то же представление типа во время выполнения, а именно представление необработанного типа. Например, экземпляры универсального типа Список , такой как Список<Дата> , Список<String> , Список<Long> и т. д. имеют разные статические типы во время компиляции, но один и тот же динамический тип. Список во время исполнения.

Актерский состав состоит из двух частей:

  • проверка статического типа, выполняемая компилятором во время компиляции и
  • динамическая проверка типа, выполняемая виртуальной машиной во время выполнения.

Статическая часть сортирует бессмысленные приведения, которые не могут быть успешными, например, приведение из Нить что Дата или из Список<String> что Список<Дата> .

Динамическая часть использует информацию о типах времени выполнения и выполняет проверку типов во время выполнения.  Это вызывает КлассКастИсключение если динамический тип объекта не является целевым типом (или подтипом целевого типа) приведения. Примерами приведения типов с динамической частью являются приведения из Объект что Нить или из Объект что Список<String> .  Это так называемые преобразования супертипа в подтип.

Примеры конкретных параметризованных типов: Список<String> , Map<String,Date> , но не Список<? расширяет Number> или Map<String,?> .

Иногда они этого ожидают Список<Объект> был бы супертипом Список<String> , потому что Объект является супертипом Нить .  Это ожидание связано с тем фактом, что для массивов существует такой тип отношений:  Объект[] является супертипом Нить[] , потому что Объект является супертипом Нить . (Такой тип отношений известен как ковариация .) Отношения супер-подтипов типов компонентов распространяются на соответствующие типы массивов. Для экземпляров универсальных типов такой связи типов не существует. (Параметризованные типы нет ковариантный.)

Отсутствие надподтиповых отношений между экземплярами одного родового типа имеет различные последствия.  Вот пример.

Пример:

void printAll(ArrayList<Object> c) {
  для (Объект o: c) 
    System.out.println(о);
} Список ArrayList<String> = новый ArrayList<String> ();
... заполнить список ...
РаспечататьВсе (список);   // ошибка

А ArrayList<String> объект не может быть передан в качестве аргумента методу, который запрашивает ArrayList<Object> поскольку эти два типа являются экземплярами одного и того же универсального типа, но для другой аргументы типа, и по этой причине они несовместимы друг с другом.

Конкретные параметризованные типы — это конкретные реализации универсального типа.  Они почти как типы; есть лишь несколько ограничений.  Их НЕ ДОЛЖНО использовать для следующих целей:

  • для создания массивов
  • в обработке исключений
  • в литерале класса
  • в экземпляр выражение

Массивы, содержащие элементы, тип которых является определенным параметризованным типом, не допускаются.

Пример (недопустимого типа массива):

статический тест пустоты() {
  Пара<Целое,Целое>[] intPairArr = новый Пара<Целое,Целое>[10] ; // ошибка
  addElements (intPairArr); 
  Pair<Integer,Integer> пара = intPairArr[1];
  Целое число я = пара.getFirst();
  пара.setSecond(я);
}
статическая пустота addElements( Объект[] objArr) {
  objArr[0] = новая пара<Integer,Integer>(0,0);
  objArr[1] = новая пара<String,String>("","");      // должно произойти сбой с ArrayStoreException
}

Компилятор запрещает создание массивов, тип компонента которых является конкретным параметризованным типом, например Пара<Целое,Целое> в нашем примере.  В предыдущей статье мы обсуждали, почему разумно, чтобы компилятор квалифицировал Пара<Целое,Целое>[] как незаконное.  Ключевая проблема заключается в том, что компилятор и система выполнения должны гарантировать, что массив является однородный последовательность элементов одного типа.  Одна из проверок типа, а именно проверка хранилища массива, выполняемая виртуальной машиной во время выполнения, не позволяет обнаружить некорректную вставку чужеродного элемента. В примере вторая вставка в добавитьЭлементы метод должен завершиться неудачей, поскольку они добавили пару строк в массив целочисленных значений, но он не дает ожидаемого сбоя. Причины обсуждались в предыдущих статьях.

Если мы не можем использовать массивы, содержащие элементы, тип которых является определенным параметризованным типом, что мы можем использовать в качестве обходного пути?

Давайте рассмотрим 3 возможных обходных пути:

  • массив необработанных типов
  • массив неограниченного параметризованного типа с подстановочными знаками
  • коллекция вместо массива

Хотя массивы конкретных параметризованных типов недопустимы, коллекции конкретных параметризованных типов разрешены.

Параметризованный тип с подстановочными знаками — это экземпляр универсального типа, в котором хотя бы один аргумент типа является подстановочным знаком.  Примеры параметризованных типов с подстановочными знаками: Коллекция<?> , Список<? расширяет Number> , Компаратор<? супер Стринг> и Пара<Строка,?> . Параметризованный тип с подстановочными знаками обозначает семейство типов, включающее конкретные экземпляры универсального типа.  Тип используемого подстановочного знака определяет, какие конкретные параметризованные типы принадлежат семейству.  Например, параметризованный тип с подстановочными знаками Коллекция<?> обозначает семейство всех экземпляров Коллекция интерфейс независимо от аргумента типа.  Параметризованный тип с подстановочными знаками Список<? расширяет Number> обозначает семейство всех типов списков, где тип элемента является подтипом  Число .  Параметризованный тип с подстановочными знаками Компаратор<? супер Стринг> является семейством всех экземпляров Компаратор интерфейс для типов аргументов типа, которые являются супертипами Нить.

Параметризованный тип с подстановочными знаками не является типом в обычном смысле (отличается от непараметризованного класса/интерфейса или необработанного типа).

Для ввода можно использовать параметризованные типы с подстановочными знаками (например, непараметризованные классы и интерфейсы):

  • как типы аргументов и возвращаемых значений методов
  • как тип поля или локальной ссылочной переменной
  • как тип компонента массива как аргумент типа других параметризованных типов
  • как тип цели в приведениях

Универсальными могут быть не только типы, но и методы. Статические и нестатические методы, а также конструкторы могут иметь параметры типа. Синтаксис параметров формального типа аналогичен синтаксису универсальных типов. Раздел параметров типа заключен в угловые скобки и отображается перед типом возвращаемого значения метода. Синтаксис и значение Ego идентичны типу списка параметров универсального типа.

Вот общий пример Макс метод, который вычисляет наибольшее значение в коллекции элементов неизвестного типа А .

Пример (общий метод):

Коллекции классов {
  общественный статический <A расширяет Comparable<A>> A max(Коллекция< А > хз) {
    Итератор< А > xi = xs.iterator();
    А ш = xi.next();
    в то время как (xi.hasNext()) {
      А х = xi.next();
      если (w.compareTo(x) < 0) w = x;
    }
    вернуть w;
  }
} 

The Макс метод имеет один параметр типа с именем А .  Это заполнитель для типа элемента коллекции, над которой работает метод. Параметр типа имеет границу; это должен быть тип, который является подтипом Сопоставимое<A>

Пример (того же общего типа, на этот раз с границы):

общедоступный интерфейс Comparable<T> {
  public int CompareTo(T arg);
}
общедоступный класс TreeMap<Key расширяет Comparable<Key> ,Данные>{
  частный статический класс Entry<K,V> {
     ключ К;
     значение В;
     Entry<K,V> слева = ноль;
     Entry<K,V> справа = ноль;
     Entry<K,родитель V>;
  }
  частный переходный Entry<Key, Data> root = null;
  ...
  частный Entry<Key, Data> getEntry (ключевой ключ) {
     Entry<Key,Data> p = корень;
     Ключ k = ключ;
     в то время как (p != ноль) {
       int cmp = k. сравнить с (p.key) ; 
       если (cmp == 0)
         вернуть р;
       иначе, если (cmp < 0)
         р = р.левый;
       еще 
         р = р.право; 
     }
     вернуть ноль;
  }
  общедоступное логическое значение containsKey (ключ ключа) {
     вернуть getEntry (ключ)! = null;
  }
  ...
}

В приведенном выше примере параметр типа Ключ имеет границы Сопоставимый<Key> .  Спецификация границы имеет два эффекта:

  • Он предоставляет доступ к методам, которые определяет граница. .  В примере граница Сопоставимый<Key> дает доступ к сравнить с метод, который мы хотим вызвать в реализации нашего ДеревоКарта сорт.
  • Для создания экземпляра универсального типа можно использовать только типы «в пределах границ».   В этом примере параметризованный тип, такой как TreeMap<Number,String> будет отклонено, поскольку тип Число не является подтипом Сопоставимый<Номер> .  Параметризованный тип, например TreeMap<String,String> будет принят, поскольку тип String находится в пределах границ, т. е. является подтипом Сопоставимый<String> .

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

Примеры (параметры типа лимита):

класс X0 <T расширяется интервал > { ... }      // ошибка
класс X1 <T расширяется Объект[] > { ... } // ошибка
класс X2 <T расширяется Число > { ... }
класс X3 <T расширяется Нить > { ... }
класс X4 <T расширяется работоспособный > { ... }
класс X5 <T расширяется Поток.Состояние > { ... }
класс X6 <T расширяется Список > { ... }
класс X7 <T расширяется Список<String> > { ... }
класс X8 <T расширяется Список<? расширяет Number> > { ... }
класс X9 <T расширяется Сопоставимо<? супер номер> > { ... }
класс X10<T расширяется Map.Entry<?,?> > { ... }

В примере кода показано, что примитивные типы, такие как интервал и типы массивов, такие как Объект[] не допускается в качестве привязки к параметру типа.

Типы классов, такие как Число или Нить и типы интерфейсов, такие как работоспособный , разрешенный как привязанный к параметру типа.

Типы объявлений, например Поток.Состояние также разрешены как привязанные к параметру типа. Поток.Состояние — это пример вложенного типа, используемого в качестве привязки параметра типа.  Также разрешены нестатические внутренние типы.

Необработанные типы разрешены в качестве привязок к параметру типа; Список это пример.

Допускаются параметризованные типы, привязанные к параметру типа, включая конкретные параметризованные типы, такие как Список<String> , ограниченные параметризованные типы с подстановочными знаками, такие как Список<? расширяет Number> и Сопоставимо<? супер Long> и неограниченные параметризованные типы с подстановочными знаками, такие как Map.Entry<?,?> .  Привязка, представляющая параметризованный тип с подстановочными знаками, принимает в качестве аргумента типа все типы, принадлежащие к семейству типов, которое обозначает подстановочный знак.  Привязка параметризованного типа с помощью подстановочных знаков обеспечивает лишь ограниченный доступ к полям и методам; ограничения зависят от типа подстановочного знака.

Пример (параметризованный тип с подстановочными знаками в качестве привязки параметра типа):

класс X< T расширяет List<? расширяет Number> ОЗYX0Z {
  общественный недействительный someMethod (T t) {
    t.add(новый длинный(0L));    // ошибка
    Номер n = t.remove(0);
  }
}
класс Тест {
  public static void main(String[] args) {
     X<ArrayList< Длинный >> x1 = новый X<ArrayList<Long>>(); 
     X<ArrayList< Нить >> x2 = новый X<ArrayList<String>>(); // ошибка
  }
}

Ссылочные переменные типа Т (параметр типа) обрабатываются как ссылочные переменные подстановочного типа (привязка параметра типа).  В нашем примере последствием этого является то, что компилятор отклоняет вызов методов, которые принимают аргумент «неизвестного» типа, который обозначает параметр типа, например: Список.добавить , поскольку граница представляет собой параметризованный тип с подстановочными знаками и верхней границей.
В то же время граница Список<? расширяет Number> определяет типы, которые можно использовать в качестве аргументов типа. Компилятор принимает все аргументы типа, принадлежащие семейству типов. Список<? расширяет Number> , то есть все подтипы Список с аргументом типа, который является подтипом Число .

Параметр типа можно использовать в качестве границы для другого параметра типа.

Пример (параметр типа, используемый в качестве привязки параметра типа):

класс Трехместный <T> {
  частный Т fst, snd, trd;
  общественный < ты расширяет Т , С расширяет Т , Вт расширяет Т > Triple(U arg1, V arg2, W arg3) {
    фст = arg1;
    снд = arg2;
    трд = arg3;
  }
}

В этом примере параметр типа Т параметризованного класса используется как привязка параметров типа ты ,  С и Вт параметризованный метод экземпляра этого класса.

Дополнительные возможности использования параметров типа в качестве границ для других параметров типа включают ситуации, когда вложенный тип определен внутри универсального типа или локальный класс определен в универсальном методе.  Разрешено даже использовать параметр типа, поскольку я связываю другой параметр типа в том же разделе параметров типа.

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

Пример (класс, используемый в качестве привязки параметра типа):

общественный класс СуперКласс {     // статические члены
    общедоступное перечисление EnumType {ЭТО, ЭТО}
    общедоступный статический объект staticField;
    public static void staticMethod() { ... }

    // нестатические члены
    общественный класс InnerClass { ... }
    общедоступный объект NonStaticField;
    общественный недействительный nonStaticMethod() { ... }

    // конструкторы
    общественный суперкласс() { ... }

    // частные члены
    частный объект PrivateField; 

    ...
}

публичный финальный класс SomeClass<T расширяется СуперКласс ОЗYX0Z {
    частный объект Т;
    общественный SomeClass (T т) {объект = т; } 

    публичная строка toString() {
        возвращаться
         "статический вложенный тип    : "+T.EnumType.class+"\n" 
        +"static field          : "+T.staticField+"\n"
        +"статический метод         : "+T.staticMethod()+"\n"
        +"нестатический вложенный тип: "+T.InnerClass.class+"\n"
        +"нестатическое поле      : "+object.nonStaticField+"\n"
        +"нестатический метод     : "+object.nonStaticMethod()+"\n"
        +"constructor           : "+(new T())+"\n"                     // ошибка
        +"privatemember        : "+object.privateField+"\n"          // ошибка 
        ;
    }
}

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

Контекст, в котором Enum<E расширяет Enum<E>> “Появляется декларация Перечисление класс в упаковке java.lang :

публичный абстрактный класс Enum<E расширяет Enum<E>> {
  ...
}

Тип Перечисление является общим базовым классом для всех типов перечислений.  В Java тип перечисления, такой как Цвет переводится в класс Цвет который простирается Enum<Цвет> . Цель суперкласса Перечисление заключается в обеспечении функциональности, общей для всех типов объявлений.

Вот схема урока Перечисление :

публичный абстрактный класс Энум< Э расширяет Enum<E>> реализует Comparable< Э >, сериализуемый {
  частное последнее имя строки;
  публичное окончательное имя строки() { ... }   частный финал int порядковый номер;
  public Final int ordinal() { ... }

  protected Enum (имя строки, порядковый номер int) { ... }

  public String           toString() { ... }
  общедоступное окончательное логическое значение равно (Объект другое) { ... }
  публичный финал int hashCode() { ... }
  защищенный финальный объект clone() выдает CloneNotSupportedException { ... }
  public Final int CompareTo( Э о) { ... }

  публичный финал Class< Э > getDeclaringClass() { ... }
  public static <T расширяет Enum<T>> T valueOf(Class<T> enumType, String name) { ... }
}

Удивительная особенность в декларации» Enum<E расширяет Enum<E>> ” — это тот факт, что недавно определенный класс Перечисление и его новый определенный параметр типа Э появляются в границе того же параметра типа.  Это означает, что Перечисление type должен быть создан для одного из его подтипов.  Чтобы понять, почему это имеет смысл, учтите, что каждый тип перечисления преобразуется в подтип Перечисление .

Вот надуманный тип перечисления Цвет :

перечисление Цвет {КРАСНЫЙ, СИНИЙ, ЗЕЛЕНЫЙ}

Компилятор преобразует его в следующий класс:

публичный выпускной класс Цвет простирается Enum<Цвет> {
  public static окончательные значения Color[]() { return (Color[])$VALUES.clone(); }
  public static Color valueOf (имя строки) { ... }   частный Color (String s, int i) {super (s, i); }

  общедоступный статический окончательный цвет RED;
  общедоступный статический окончательный цвет СИНИЙ;
  общедоступный статический окончательный цвет ЗЕЛЕНЫЙ;

  частный статический окончательный цвет $VALUES[];

  статический {
    КРАСНЫЙ = новый цвет («КРАСНЫЙ», 0);
    СИНИЙ = новый цвет («СИНИЙ», 1);
    ЗЕЛЕНЫЙ = новый цвет («ЗЕЛЕНЫЙ», 2);
    $VALUES = (новый цвет[] {КРАСНЫЙ, СИНИЙ, ЗЕЛЕНЫЙ});
  }
}

Наследование приводит к тому, что Цвет type наследует все методы, реализованные в Enum<Цвет> .  Среди них есть сравнить с метод.  Цвет. сравнить с метод, вероятно, должен занять Цвет в качестве аргумента.  Чтобы это произошло, класс Перечисление является универсальным и Enum.compareTo метод принимает Перечисление параметр типа Э в качестве аргумента.  В результате введите Цвет получено из Enum<Цвет> наследует сравнить с метод, который принимает Цвет а также аргумент, именно так и довольно быть.
Если мы понимаем декларацию “ Enum<E расширяет Enum<E>> «Мы видим, что эта закономерность имеет несколько аспектов.

Во-первых, параметр типа — это сам тип: “. Перечисление <E расширяет Перечисление <E>> “. Он гарантирует, что только подтипы типа Перечисление разрешены в качестве аргументов типа. (Теоретически, типа Перечисление может быть создан сам по себе, как в Enum<Enum> , но это конечно не предназначено, и трудно представить ситуацию, в которой создание такого экземпляра было бы полезно.)

Во-вторых, существует тот факт, что тип связанного параметра является параметризованным типом. Перечисление <E> , который использует параметр типа Э в качестве аргумента типа границы. Это объявление гарантирует, что отношения наследования между подтипом и экземпляром Перечисление имеет форму X расширяет Enum<X> “. Подтип, например “ X расширяет Enum<Y> “невозможно объявить из-за аргумента типа Да не было бы в рамках; только подтипы Enum<X> находятся в пределах.

В-третьих, существует тот факт, что Перечисление носит общий характер в первую очередь.  Это означает, что некоторые методы класса Перечисление принять аргумент или вернуть значение неизвестного типа (или иным образом зависеть от неизвестного типа). Как мы уже знаем, этот неизвестный тип позже станет подтипом Х из Enum<X> .  Следовательно, в параметризованном типе Enum<X> , эти методы включают подтип Х , и они наследуются в подтип Х .  сравнить с метод является примером такого метода; он наследуется от суперкласса в каждом подклассе и в каждом случае имеет подпись, специфичную для подкласса.

Подведение итогов, заявление “ Enum<E расширяет Enum<E>> ” можно расшифровать так: Перечисление является универсальным типом, экземпляр которого может быть создан только для его подтипов, и эти подтипы будут наследовать некоторые полезные методы, некоторые из которых принимают аргументы, специфичные для подтипа (или иным образом зависят от подтипа).

Параметры типов нижних границ классов

Параметры типа могут иметь несколько границ, как в класс Box<T расширяет Number> {...} . Но параметр типа не может иметь нижней границы, то есть такая конструкция, как класс Коробка<T большой Номер> {...} не разрешено.  Почему нет?  Ответ: это бессмысленно, потому что если бы это было разрешено, вы бы ничего не купили.  Давайте посмотрим, почему параметры типа нижней границы сбивают с толку после выяснения того, что означает параметр типа верхней границы.

Верхний предел параметра типа имеет три эффекта:

    1. Ограниченное создание экземпляров. Верхняя граница ограничивает набор типов, которые можно использовать для создания экземпляра универсального типа.  Если мы объявим класс Box<T расширяет Number> {...} тогда компилятор будет гарантировать, что только подтипы Число может использоваться как аргумент типа.  То есть Коробка<Номер> или Коробка<Long> разрешено, но Коробка<Объект> или Коробка<Строка> будет отклонен

Пример (ограниченное создание экземпляров из-за верхней границы параметра типа):
класс Box<T расширяет Number> { 
   частное значение Т; 
   общественный ящик (T t) {значение = t; } 
   ...
}  класс Тест { 
   public static void main(String[] args) { 
       Box<Long> boxOfLong = новый Box<Long>(0L);   // отлично
       Box<String> boxOfString = новый Box<String>(""); // ошибка: строка выходит за пределы
   }
}

  1. Доступ к нестатическим членам. Верхняя граница предоставляет доступ ко всем общедоступным нестатическим методам и полям верхней границы.  В реализации нашего класс Box<T расширяет Number> {...} мы можем вызывать все общедоступные нестатические методы, определенные в классе Число , такой как intValue() например.  Без верхней границы компилятор отклонил бы любой такой вызов. Пример (доступа к нестатическим членам из-за верхней границы параметра типа):

класс Box<T расширяет Number> { 
   частное значение Т; 
   общественный ящик (T t) {значение = t; } 
   public int приращение() { return value.intValue()+1; } // <= будет ошибкой без привязки к числу
   ...
} 

    1. Введите Стирание. Крайняя левая верхняя граница используется для стирания типа и заменяет параметр типа в байт-коде.  В нашем класс Box<T расширяет Number> {...} все случаи Т будет заменен верхней границей Число .  Например, если класс Коробка имеет частное поле типа Т и метод недействительный набор (содержимое T) для установки этого частного поля, тогда поле будет иметь тип Число после стирания типа метод будет преобразован в метод void set (числовое содержимое) .

Пример (использования верхней границы параметра типа при стирании типа – до типа стирание):
класс Box< T расширяет номер ОЗYX0Z { 
   частный Т ценить; 
   общественный ящик( Т т) {значение = т; } 
   ...
} 

      Пример (использования верхней границы параметра типа при стирании типа –

после

      типа стирание):

ОЗYX0Z

класс Коробка { 
   частный Число ценить; 
   общественный ящик( Число т) {значение = т; } 
   ...
}

    Кроме того, крайняя левая верхняя граница появляется в других местах, таких как автоматически вставленные приведения и методы моста.

Если бы для параметров типа были разрешены нижние границы, какие побочные эффекты они имели бы или должны были бы иметь? Если такая конструкция, как класс Коробка<T большой Номер> {...} были разрешены, что бы это значило?  Что означают три побочных эффекта верхней границы параметра типа — ограниченное создание экземпляров, доступ к нестатическому элементу, стирание типа — для нижней границы?

Ограниченные экземпляры. Компилятор может ограничить набор типов, которые можно использовать для создания экземпляра универсального типа, с помощью параметра типа нижней границы.  Например, компилятор может разрешить такие экземпляры, как Коробка<Номер> и Коробка<Объект> из Коробка<T большой Номер> и отклонять экземпляры, такие как Коробка<Long> или Коробка<Короткая> .  Это будет эффект, соответствующий ограничительному побочному эффекту, описанному для верхних границ параметра типа.

Доступ к нестатическим членам. Нижняя граница параметра типа не дает доступа к каким-либо конкретным методам, кроме тех, которые унаследованы от класса. Объект . На примере Коробка<T большой Номер> супертипы Число не имеют ничего общего, за исключением того, что они являются ссылочными типами и, следовательно, подтипами Объект .  Компилятор не может предположить, что поле типа Т имеет тип Число или его подтип.  Вместо этого поле типа Т может быть любого супертипа Число , такой как Сериализуемый или Объект .  Вызов такого метода, как intValue() больше не является типобезопасным, и компилятору придется его отклонить. Как следствие, нижняя граница параметра типа не будет предоставлять доступ к нестатическим членам, кроме тех, которые определены в классе. Объект и, таким образом, имеет тот же эффект, что и «нет границ».

Тип Стирание .  Следуя этой логике, не имеет смысла заменять вхождения параметра типа его крайней левой нижней границей.  Объявление метода, подобного конструктору Коробка( Т т) как конструктор Коробка( Число т) не имеет смысла, учитывая, что Т заменяется супертипом Число .  Ан Объект может быть правильно передан конструктору в экземпляре Коробка<Объект> и конструктор отклонит его.  Это было бы совершенно неправильно.   Таким образом, стирание типа заменит все вхождения переменной типа. Т по типу Объект , а не по нижней границе эго.  Опять же, нижний предел будет иметь тот же эффект, что и «без ограничения».

Хотите понять, что значит, если оба ниже _и_ верхние пределы были разрешены?   Лично я даже не хочу об этом думать и предпочитаю классифицировать это как «неуправляемое», если хотите.

Суть в следующем: это все “ большой «Обязанное купит вас» — это ограничение, согласно которому только супертипы Число могут использоваться в качестве аргументов типа.  И даже это часто понимают неправильно.  Это НЕ будет означать, что класс Box<T супер Number> {...} содержит только экземпляры супертипов Число .  Совсем наоборот – как показывает пример ниже!

Пример (использования верхней границы параметра типа при стирании типа – до типа стирание):

класс Box< Т супер номер ОЗYX0Z {
   частный Т ценить;
   общественный ящик( Т т) {значение = т; }
   ...
}

Пример (использования верхней границы параметра типа при стирании типа – после типа стирание):

класс Коробка {
   частный Объект ценить;
   общественный ящик( Объект т) {значение = т; }
   ...
}

А класс Коробка<T большой Номер> {...} будет преобразовано путем стирания типа в Коробка содержащий Объект поле, и его конструктор будет переведен на Коробка (Объект т) . Это принципиально отличается от класс Коробка<T простирается Номер> {...} , который будет переведен в Коробка содержащий Число поле, и его конструктор будет переведен на Коробка(номер т) . Следовательно, Коробка<Номер> созданный из класс Box<T расширяет Number> {...} будет отличаться от Коробка<Номер> создание экземпляра из класс Box<T супер Number> {...} , что может вызвать путаницу.  По этой причине нижние границы не имеют смысла для параметров типа класса.

ОЗYX0Z


ОЗYX0Z

Параметры типа нижней границы методов

В сочетании с методами и типами их аргументов иногда может быть полезен параметр типа нижней границы.

Пример (метод, использующий параметр типа с нижней границей):

класс Pair<X,Y> {
   сначала частный X;
   частный Y второй;
   ...
   общественный < Супер Х , Б супер Ю > B addToMap(Map<A,B> map) { // ошибка: параметр типа не может иметь нижнюю границу
      вернуть map.put(первый, второй);
   }
}
класс Тест { 
   public static void main(String[] args) { 
     Pair<String,Long> пара = новая пара<>("ABC",42L);
     Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
     Номер номер = пара.addToMap(карта);
   }
}

The добавитьToMap() Метод добавляет содержимое пары на карту.  Любая карта, которая может содержать супертипы Х и Да сделал бы.  Карта помещать() Метод возвращает значение, найденное на карте для данного ключа, если для ключа на карте уже существует запись значения ключа.  Возвращаемое значение карты помещать() метод должен быть возвращен из добавитьToMap() метод.  В этих обстоятельствах хотелось бы объявить метод, как показано выше: Карта параметризуется супертипами параметров типа пары и добавитьToMap() Тип возвращаемого значения метода — это тип значения карты.

Поскольку компилятор не допускает нижних границ параметров типа, нам нужен обходной путь.

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

Пример (обходное решение предыдущего примера с использованием подстановочных знаков):

класс Pair<X,Y> {
   сначала частный X;
   частный Y второй;
   ...
   общественный Объект addToMap(Map< ? Супер Икс , ? супер Й Карта >) { 
      вернуть map.put(первый, второй);
   }
}
класс Тест { 
   public static void main(String[] args) { 
     Pair<String,Long> пара = новая пара<>("ABC",42L);
     Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
     Номер номер = (Число) пара.addToMap(карта);
   }
}

Это работает, за исключением того, что нет возможности объявить тип возвращаемого значения по желанию.  Это был бы супертип Да который компилятор извлекает из типа карты, но синтаксиса для его указания не существует.  Нам не следует объявлять возвращаемый тип “. ? супер Й “, потому что “ ? супер Й ” — это подстановочный знак, а не тип, поэтому его нельзя использовать в качестве возвращаемого типа. У нас нет выбора, и мы должны его использовать. Объект вместо этого в качестве возвращаемого типа нашего метода.  Этот довольно неспецифичный тип возвращаемого значения, в свою очередь, вынуждает вызывающих добавитьToMap() метод для приведения возвращаемого значения вниз от Объект к его фактическому типу.  Мы имели в виду не совсем это.

Другой обходной путь — использование статических методов.  Вот обходной путь с использованием статического метода вместо нестатического.

Пример (обход решения предыдущего примера статическим методом):

класс Pair<X,Y> {
   сначала частный X;
   частный Y второй;
   ...
   общественный статический ОЗYX0Z А , Б ,Х расширяет А ,Ю расширяет B > B addToMap(Pair<X, пара Y>, Map< А , Б Карта >) {
      return map.put(пара.первая,пара.вторая);
   }
}
класс Тест { 
   public static void main(String[] args) { 
     Pair<String,Long> пара = новая пара<>("ABC",42L);
     Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
     Номер номер = Пара .addToMap( пара ,карта);
   }
}

Общий добавитьToMap() метод имеет четыре параметра типа: два заполнителя Х и Да для типа пары и двух заполнителей А и Б для типа карты. А и Б являются супертипами Х и Да , потому что Х и Да   объявляются с А и Б в качестве их верхних границ. (Обратите внимание, что параметры типа универсального метода Х и Да не имеют ничего общего с Пара класс Х и Да параметры.  Имена Х и Да повторно используются в универсальном методе, поэтому их можно легко распознать как параметры парного типа. Используя четыре параметра типа, мы можем опционально объявить точный тип возвращаемого значения: это тот же тип, что и тип значения карты.

Пример (незаконное использование параметра типа в статическом контексте универсального класса):

класс SomeClass <T> {
  // статический инициализатор, статическое поле, статический метод
  статический {
    НекоторыеКласс< Т Тест > = новый SomeClass< Т ОЗYX0Z(); // ошибка
  }
  частный статический Т глобальнаяинформация;          // ошибка
  общественный  статический Т getGlobalInfo() {        // ошибка
    вернуть глобальную информацию;
  }
  // нестатический инициализатор, нестатическое поле, нестатический метод
  { 
    НекоторыеКласс< Т Тест > = новый SomeClass< Т ОЗYX0Z();
  }
  частный        Т локальная информация;
  общественный         Т getLocalInfo() { 
    вернуть локальную информацию;
  }
  // статические вложенные типы
  общественный статический сорт Неудача расширяет исключение {
    частный финал Т информация;           // ошибка
    публичный провал( Т т) {информация = т; }      // ошибка
    общественный Т getInfo() { вернуть информацию; }    // ошибка
   }
  частный интерфейс Копируемый {
    Т копировать();                       // ошибка
  }
  частный перечисление Состояние {
    ДЕЙСТВИТЕЛЬНЫЙ, НЕДЕЙСТВИТЕЛЬНЫЙ;
    частный Т информация; // ошибка
    общественная недействительность setInfo( Т т) {информация = т; } // ошибка
    общественный Т getInfo() { вернуть информацию; }    // ошибка
  }
  // нестатические вложенные типы
  общественный сорт Аксессор {
    общественный Т getInfo() {вернуть localInfo; }
  }
}

Пример показывает, что параметр типа нельзя использовать в статическом контексте универсального класса.  Это также показывает, что вложенные интерфейсы и типы перечисления считаются членами статического типа класса.  Только внутренние классы, то есть нестатические вложенные классы, могут использовать параметр типа включающего универсального класса.

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

Пример (незаконное использование параметра типа в универсальном интерфейсе):

интерфейс SomeInterface <T> {
  // поле
  НекоторыеКласс< Т Значение > = новый SomeClass< Т ОЗYX0Z();  // ошибка
  // вложенный тип
  сорт Аксессор {
    общественный Т получитьИнформацию() {            // ошибка
      возвращаемое значение.getGlobalInfo();
    }
  }
  // методы
  Т получитьЗначение();
}

В примере показано, что поля интерфейса неявно статичны, поэтому параметр типа нельзя использовать где-либо в объявлении поля универсального интерфейса.  Аналогично, вложенный класс считается статическим вложенным классом, а не внутренним классом, и по этой причине использование параметра типа где-либо во вложенном классе является незаконным.

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

В случае со статическими вложенными классами и интерфейсами это не является серьёзной проблемой.  В качестве обходного пути мы можем сами сгенерировать статический класс или интерфейс.

Пример (обходной путь — создание вложенного статического типа):

класс-оболочка <T> {
  частный финал Т Объект;

  публичная оболочка( Т т) {Объект = т; }
  общественный Т getWrapper () {вернуть объект; }   общедоступный неизменяемый <T> makeImmutable() {
        вернуть новый неизменяемый <T> (Объект);
  }
  общедоступный изменяемый <T> makeMutable() {
        вернуть новый Mutable <T> (Объект);
  }
  частный статический <A> A makeClone(A theObject) { ... }

  публичный статический окончательный класс, неизменяемый <A> { // теперь является универсальным классом
        частный финал А Объект;

        публичный неизменяемый( А арг) {
            theObject = makeClone(arg);
        }
        общественный А getWrappedItem() {
            вернуть makeClone(Объект);
        }
  }
  публичный статический класс Mutable <A> {
        ... похожий ...
  }
}

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

Обходной путь для вложенных статических классов:

Если вложенный статический тип является классом, альтернативным решением может быть преобразование статического класса в нестатический внутренний класс.

Пример (обходной путь — используйте внутренний класс):

класс-оболочка <T> {
  частный финал Т Объект;

  публичная оболочка( Т т) {Объект = т; }
  общественный Т getWrapper () {вернуть объект; }   общественный изменяемый makeMutable() {
      возвращаться этот. новый Mutable(theObject);
  }
  общественный неизменяемый makeImmutable() {
      возвращаться этот. новый неизменяемый (Объект);
  }
  частный статический <A> A makeClone(A theObject) { ... }

  публичный финальный класс Immutable {  // больше не является статическим классом
      частный финал Т Объект;

      публичный неизменяемый( Т арг) {
          theObject = makeClone(arg);
      }
      общественный Т getWrappedItem() {
          вернуть makeClone(Объект);
      }
  }
  публичный класс Mutable 
        ... похожий ...
  }
}

Этот обходной путь сопряжен с некоторыми накладными расходами, поскольку все внутренние классы имеют скрытую ссылку на экземпляр внешнего типа, который им не нужен в этом примере и который они не используют; скрытая ссылка - это просто багаж.  Часто внутренние классы объединяются с интерфейсами, чтобы сохранить внутренний класс как частную деталь реализации включающего его класса.  Мы можем сделать то же самое здесь.

Пример (усовершенствованное предыдущее решение обхода):

класс-оболочка <T> {
  частный финал Т Объект;

  публичная оболочка( Т т) {Объект = т; }
  общественный Т getWrapper () {вернуть объект; }   общедоступный изменяемый <T> makeMutable() {
      вернуть this.new Mutable(theObject);
  }
  общедоступный неизменяемый <T> makeImmutable() {
      вернуть this.new Immutable(theObject);
  }
  частный статический <A> A makeClone(A theObject) { ... }

  публичный интерфейс Неизменяемый<S> {
      S getWrappedItem();
  }
  публичный интерфейс Mutable<S> {
      S getWrappedItem();
      void setWrappedItem (S arg);
  }
  частный последний класс ImmutableImplementation реализует Immutable<T> { 

      частный финал T theObject;

      public ImmutableImplementation(T arg) {
          theObject = makeClone(arg);
      }
      общественный T getWrappedItem () {
          вернуть makeClone(Объект);
      }
  }
  частный класс MutableImplementation реализует Mutable<T> { 
        ... похожий ...
  }
}

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

ОЗYX0Z

ОЗYX1Z

ОЗYX2Z

 

ОЗYX0Z

Оставить ответ