Поиск…


Вступление

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

Синтаксис

  • class ArrayList <E> {} // общий класс с параметром типа E
  • класс HashMap <K, V> {} // общий класс с двумя параметрами типа K и V
  • <E> void print (элемент E) {} // общий метод с параметром типа E
  • ArrayList <String> имена; // объявление общего класса
  • ArrayList <?> Объекты; // объявление общего класса с неизвестным параметром типа
  • new ArrayList <String> () // создание экземпляра универсального класса
  • new ArrayList <> () // экземпляр с типом вывода "diamond" (Java 7 или новее)

замечания

Генерики реализуются в Java через стирание типа, что означает, что во время выполнения информация типа, указанная в создании экземпляра универсального класса, недоступна. Например, оператор List<String> names = new ArrayList<>(); создает объект списка, из которого тип элемента String не может быть восстановлен во время выполнения. Однако, если список хранится в поле типа List<String> или передается параметру method / constructor этого же типа или возвращается из метода этого типа возврата, то полная информация о типе может быть восстановлена ​​во время выполнения через API Java Reflection.

Это также означает, что при выдаче родовому типу (например: (List<String>) list ), литой является непроверенный бросок . Поскольку параметр <String> стирается, JVM не может проверить правильность приведения из List<?> List<String> ; JVM видит только показ List для List во время выполнения.

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

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

В этом примере используется общий класс Param для принятия одного параметра типа T , ограниченного угловыми скобками ( <> ):

public class Param<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

Чтобы создать экземпляр этого класса, укажите аргумент типа вместо T Например, Integer :

Param<Integer> integerParam = new Param<Integer>();

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

Param<String[]> stringArrayParam;
Param<int[][]> int2dArrayParam;
Param<Param<Object>> objectNestedParam;

В Java SE 7 и более поздних версиях аргумент типа может быть заменен пустым набором аргументов типа ( <> ), называемым алмазом :

Java SE 7
Param<Integer> integerParam = new Param<>();

В отличие от других идентификаторов, параметры типа не имеют ограничений именования. Однако их имена обычно являются первой буквой их цели в верхнем регистре. (Это верно даже во всех официальных JavaDocs.)
Примеры включают T для «type» , E для «element» и K / V для «key» / «value» .


Расширение общего класса

public abstract class AbstractParam<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

AbstractParam - абстрактный класс, объявленный с параметром типа T При расширении этого класса этот параметр типа может быть заменен аргументом типа, записанным внутри <> , или параметр типа может оставаться неизменным. В первом и втором примерах ниже String и Integer заменяют параметр типа. В третьем примере параметр типа остается неизменным. В четвертом примере вообще не используются обобщения, поэтому он аналогичен классу Object . Компилятор будет предупреждать о том, что AbstractParam является сырым типом, но он скомпилирует класс ObjectParam . Пятый пример имеет 2 типа параметров (см. «Параметры нескольких типов» ниже), выбирая второй параметр как параметр типа, переданный суперклассу.

public class Email extends AbstractParam<String> {
    // ...
}

public class Age extends AbstractParam<Integer> {
    // ...
}

public class Height<T> extends AbstractParam<T> {
    // ...
}

public class ObjectParam extends AbstractParam {
    // ...
}

public class MultiParam<T, E> extends AbstractParam<E> {
    // ...
}

Ниже приведено использование:

Email email = new Email();
email.setValue("[email protected]");
String retrievedEmail = email.getValue();

Age age = new Age();
age.setValue(25);
Integer retrievedAge = age.getValue();
int autounboxedAge = age.getValue();

Height<Integer> heightInInt = new Height<>();
heightInInt.setValue(125);

Height<Float> heightInFloat = new Height<>();
heightInFloat.setValue(120.3f);

MultiParam<String, Double> multiParam = new MultiParam<>();
multiParam.setValue(3.3);

Обратите внимание, что в классе Email метод T getValue() действует так, как будто он имеет подпись String getValue() , а метод void setValue(T) действует так, как если бы он был объявлен void setValue(String) .

Также возможно создать экземпляр с анонимным внутренним классом с пустыми фигурными фигурными скобками ( {} ):

AbstractParam<Double> height = new AbstractParam<Double>(){};
height.setValue(198.6);

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


Множественные параметры типа

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

public class MultiGenericParam<T, S> {
    private T firstParam;
    private S secondParam;
   
    public MultiGenericParam(T firstParam, S secondParam) {
        this.firstParam = firstParam;
        this.secondParam = secondParam;
    }
    
    public T getFirstParam() {
        return firstParam;
    }
    
    public void setFirstParam(T firstParam) {
        this.firstParam = firstParam;
    }
    
    public S getSecondParam() {
        return secondParam;
    }
    
    public void setSecondParam(S secondParam) {
        this.secondParam = secondParam;
    }
}

Использование может быть выполнено следующим образом:

MultiGenericParam<String, String> aParam = new MultiGenericParam<String, String>("value1", "value2");
MultiGenericParam<Integer, Double> dayOfWeekDegrees = new MultiGenericParam<Integer, Double>(1, 2.6);

Объявление общего метода

Методы также могут иметь общие параметры типа.

public class Example {

    // The type parameter T is scoped to the method
    // and is independent of type parameters of other methods.
    public <T> List<T> makeList(T t1, T t2) {
        List<T> result = new ArrayList<T>();
        result.add(t1);
        result.add(t2);
        return result;
    }

    public void usage() {
        List<String> listString = makeList("Jeff", "Atwood");
        List<Integer> listInteger = makeList(1, 2);
    }
}

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

Иногда, хотя и редко, может быть необходимо переопределить этот тип вывода с явными аргументами типа:

void usage() {
    consumeObjects(this.<Object>makeList("Jeff", "Atwood").stream());
}

void consumeObjects(Stream<Object> stream) { ... }

Это необходимо в этом примере, потому что компилятор не может «смотреть вперед», чтобы увидеть, что Object нужен для T после вызова stream() и в противном случае он makeList бы String на makeList аргументов makeList . Обратите внимание, что язык Java не поддерживает исключение класса или объекта, на который вызывается метод ( this в приведенном выше примере), когда аргументы типа явно указаны.

Бриллиант

Java SE 7

Java 7 представила Diamond 1, чтобы удалить некоторую котельную плиту вокруг создания экземпляра универсального класса. С Java 7+ вы можете написать:

List<String> list = new LinkedList<>();

Там, где вам приходилось писать в предыдущих версиях, это:

List<String> list = new LinkedList<String>();

Одно ограничение - для анонимных классов , где вы все равно должны указывать параметр типа в экземпляре:

// This will compile:

Comparator<String> caseInsensitiveComparator = new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareToIgnoreCase(s2);
    }
};

// But this will not:

Comparator<String> caseInsensitiveComparator = new Comparator<>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareToIgnoreCase(s2);
    }
};
Java SE 8

Хотя использование алмаза с анонимными внутренними классами не поддерживается в Java 7 и 8, оно будет включено как новая функция в Java 9 .


Сноска:

1 - Некоторые люди называют <> использование «алмазного оператора ». Это неверно. Алмаз не ведет себя как оператор и не описан или не указан нигде в JLS или (официальном) Java-учебном пособии как оператор. Действительно, <> не является даже явным символом Java. Скорее это < токен, за которым следует токен > , и это законный (хотя и плохой стиль), чтобы иметь пробелы или комментарии между ними. JLS и Tutorials последовательно ссылаются на <> как на «алмаз», и поэтому это правильный термин для него.

Требование нескольких верхних границ («расширяет A & B»)

Вы можете потребовать, чтобы общий тип расширил несколько верхних границ.

Пример: мы хотим отсортировать список чисел, но Number не реализует Comparable .

public <T extends Number & Comparable<T>> void sortNumbers( List<T> n ) {
  Collections.sort( n );
}

В этом примере T должен расширять Number и реализовывать Comparable<T> который должен соответствовать всем «нормальным» реализациям встроенных чисел, таких как Integer или BigDecimal но не подходит для более экзотических, таких как Striped64 .

Поскольку множественное наследование не разрешено, вы можете использовать не более одного класса в качестве привязки и должны быть первыми перечислены. Например, <T extends Comparable<T> & Number> не допускается, потому что Comparable является интерфейсом, а не классом.

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

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

public abstract class Animal {
    public abstract String getSound();
}

public class Cat extends Animal {
    public String getSound() {
        return "Meow";
    }
}

public class Dog extends Animal {
    public String getSound() {
        return "Woof";
    }
}

Без ограниченных дженериков мы не можем создать класс-контейнер, который является общим и знает, что каждый элемент является животным:

public class AnimalContainer<T> {

    private Collection<T> col;

    public AnimalContainer() {
        col = new ArrayList<T>();
    }

    public void add(T t) {
        col.add(t);
    }

    public void printAllSounds() {
        for (T t : col) {
            // Illegal, type T doesn't have makeSound()
            // it is used as an java.lang.Object here
            System.out.println(t.makeSound()); 
        }
    }
}

С общим ограничением в определении класса это теперь возможно.

public class BoundedAnimalContainer<T extends Animal> { // Note bound here.

    private Collection<T> col;

    public BoundedAnimalContainer() {
        col = new ArrayList<T>();
    }

    public void add(T t) {
        col.add(t);
    }

    public void printAllSounds() {
        for (T t : col) {
            // Now works because T is extending Animal
            System.out.println(t.makeSound()); 
        }
    }
}

Это также ограничивает действительные экземпляры родового типа:

// Legal
AnimalContainer<Cat> a = new AnimalContainer<Cat>();

// Legal
AnimalContainer<String> a = new AnimalContainer<String>();
// Legal because Cat extends Animal
BoundedAnimalContainer<Cat> b = new BoundedAnimalContainer<Cat>();

// Illegal because String doesn't extends Animal
BoundedAnimalContainer<String> b = new BoundedAnimalContainer<String>();

Решаем между `T`,`? супер T` и `? расширяет T`

Синтаксис Java генерирует ограниченные подстановочные знаки, представляя неизвестный тип ? является:

  • ? extends T представляет собой верхний ограниченный шаблон. Неизвестный тип представляет тип, который должен быть подтипом T или сам тип T.

  • ? super T представляет собой нижний ограниченный шаблон. Неизвестный тип представляет тип, который должен быть супертипом T или самим типом T.

Как правило, вы должны использовать

  • ? extends T если вам нужен только «чтение» доступа («ввод»)
  • ? super T если вам нужно «написать» доступ («выход»)
  • T если вам нужны оба («изменить»)

Использование extends или super обычно лучше, потому что это делает ваш код более гибким (как в: разрешении использования подтипов и супертипов), как вы увидите ниже.

class Shoe {}
class IPhone {}
interface Fruit {}
class Apple implements Fruit {}
class Banana implements Fruit {}
class GrannySmith extends Apple {}

   public class FruitHelper {

        public void eatAll(Collection<? extends Fruit> fruits) {}

        public void addApple(Collection<? super Apple> apples) {}
}

Теперь компилятор сможет обнаружить определенное плохое использование:

 public class GenericsTest {
      public static void main(String[] args){
  FruitHelper fruitHelper = new FruitHelper() ;
    List<Fruit> fruits = new ArrayList<Fruit>();
    fruits.add(new Apple()); // Allowed, as Apple is a Fruit
    fruits.add(new Banana()); // Allowed, as Banana is a Fruit
    fruitHelper.addApple(fruits); // Allowed, as "Fruit super Apple"
    fruitHelper.eatAll(fruits); // Allowed

    Collection<Banana> bananas = new ArrayList<>();
    bananas.add(new Banana()); // Allowed
    //fruitHelper.addApple(bananas); // Compile error: may only contain Bananas!
    fruitHelper.eatAll(bananas); // Allowed, as all Bananas are Fruits

    Collection<Apple> apples = new ArrayList<>();
    fruitHelper.addApple(apples); // Allowed
    apples.add(new GrannySmith()); // Allowed, as this is an Apple
    fruitHelper.eatAll(apples); // Allowed, as all Apples are Fruits.
    
    Collection<GrannySmith> grannySmithApples = new ArrayList<>();
    fruitHelper.addApple(grannySmithApples); //Compile error: Not allowed.
                                   // GrannySmith is not a supertype of Apple
    apples.add(new GrannySmith()); //Still allowed, GrannySmith is an Apple
    fruitHelper.eatAll(grannySmithApples);//Still allowed, GrannySmith is a Fruit

    Collection<Object> objects = new ArrayList<>();
    fruitHelper.addApple(objects); // Allowed, as Object super Apple
    objects.add(new Shoe()); // Not a fruit
    objects.add(new IPhone()); // Not a fruit
    //fruitHelper.eatAll(objects); // Compile error: may contain a Shoe, too!
}

Выбрав правильный T ? super T или ? extends T необходимо для использования с подтипами. Затем компилятор может обеспечить безопасность типа; вам не нужно бросать (который не является безопасным по типу и может вызвать ошибки программирования), если вы используете их правильно.

Если это непросто понять, пожалуйста, помните правило PECS :

P roducer использует « E xtends», а C onsumer использует « S uper».

(У производителя есть только доступ на запись, и у Потребителя есть только доступ для чтения)

Преимущества универсального класса и интерфейса

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


Более строгие проверки во время компиляции

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


Устранение отливок

Следующий фрагмент кода без дженериков требует кастинга:

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);

При повторном написании для использования дженериков код не требует кастинга:

List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);   // no cast

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

Используя generics, программисты могут реализовывать общие алгоритмы, которые работают с коллекциями разных типов, могут быть настроены и безопасны в типе и их легче читать.

Связывание общего параметра с более чем 1 типом

Общие параметры также могут быть привязаны к нескольким типам с использованием синтаксиса T extends Type1 & Type2 & ...

Предположим, вы хотите создать класс, универсальный тип которого должен реализовывать как Flushable и Closeable , вы можете написать

class ExampleClass<T extends Flushable & Closeable> {
}

Теперь ExampleClass принимает только общие параметры, типы, которые реализуют как Flushable и Closeable .

ExampleClass<BufferedWriter> arg1; // Works because BufferedWriter implements both Flushable and Closeable

ExampleClass<Console> arg4; // Does NOT work because Console only implements Flushable
ExampleClass<ZipFile> arg5; // Does NOT work because ZipFile only implements Closeable

ExampleClass<Flushable> arg2; // Does NOT work because Closeable bound is not satisfied.
ExampleClass<Closeable> arg3; // Does NOT work because Flushable bound is not satisfied.

Методы класса могут выбирать вывод общих аргументов типа как Closeable и Flushable .

class ExampleClass<T extends Flushable & Closeable> {
    /* Assign it to a valid type as you want. */
    public void test (T param) {
        Flushable arg1 = param; // Works
        Closeable arg2 = param; // Works too.
    }

    /* You can even invoke the methods of any valid type directly. */
    public void test2 (T param) {
        param.flush(); // Method of Flushable called on T and works fine.
        param.close(); // Method of Closeable called on T and works fine too.
    }
}

Замечания:

Вы не можете привязать общий параметр к любому типу, используя предложение OR ( | ). Поддерживается только предложение AND ( & ). Общий тип может расширять только один класс и многие интерфейсы. Класс должен быть помещен в начало списка.

Создание экземпляра родового типа

Из-за стирания типа следующее не будет работать:

public <T> void genericMethod() {
    T t = new T(); // Can not instantiate the type T.
}

Тип T стирается. Поскольку во время выполнения JVM не знает, что изначально было T , он не знает, какой конструктор должен вызывать.


обходные

  1. Передача класса T при вызове genericMethod :

    public <T> void genericMethod(Class<T> cls) {
        try {
            T t = cls.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
             System.err.println("Could not instantiate: " + cls.getName());
        }
    }
    
    genericMethod(String.class);
    

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

Java SE 8
  1. Передача ссылки на конструктор T :

    public <T> void genericMethod(Supplier<T> cons) {
        T t = cons.get();
    }
    
    genericMethod(String::new);
    

Обращаясь к объявленному типовому типу в пределах его собственного объявления

Как вы собираетесь использовать экземпляр (возможно, далее) унаследованного типичного типа в объявлении метода в объявлении общего типа? Это одна из проблем, с которыми вам придется столкнуться, когда вы копаете немного глубже в дженериках, но все же довольно распространенный.

Предположим, что у нас есть DataSeries<T> (здесь), который определяет общий ряд данных, содержащий значения типа T Очень сложно работать с этим типом непосредственно, когда мы хотим выполнить много операций, например, с двойными значениями, поэтому мы определяем DoubleSeries extends DataSeries<Double> . Предположим, что исходный DataSeries<T> имеет метод add(values) который добавляет другую серию одинаковой длины и возвращает новую. Как мы применяем тип values и тип возврата как DoubleSeries а не DataSeries<Double> в нашем производном классе?

Проблема может быть решена путем добавления параметра универсального типа, возвращающего назад и расширяющего объявляемый тип (применяется к интерфейсу здесь, но то же самое относится к классам):

public interface DataSeries<T, DS extends DataSeries<T, DS>> {
    DS add(DS values);
    List<T> data();
}

Здесь T представляет собой тип данных, который имеет ряд, например Double и DS . Унаследовал тип (или типы) теперь могут быть легко реализованы путем замены упомянутого выше параметра, соответствующего производного типа, таким образом, получая конкретное Double основанное определение формы:

public interface DoubleSeries extends DataSeries<Double, DoubleSeries> {
    static DoubleSeries instance(Collection<Double> data) {
        return new DoubleSeriesImpl(data);
    }
}

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

class DoubleSeriesImpl implements DoubleSeries {
    private final List<Double> data;

    DoubleSeriesImpl(Collection<Double> data) {
        this.data = new ArrayList<>(data);
    }

    @Override
    public DoubleSeries add(DoubleSeries values) {
        List<Double> incoming = values != null ? values.data() : null;
        if (incoming == null || incoming.size() != data.size()) {
            throw new IllegalArgumentException("bad series");
        }
        List<Double> newdata = new ArrayList<>(data.size());
        for (int i = 0; i < data.size(); i++) {
            newdata.add(this.data.get(i) + incoming.get(i)); // beware autoboxing
        }
        return DoubleSeries.instance(newdata);
    }

    @Override
    public List<Double> data() {
        return Collections.unmodifiableList(data);
    }
}

Как вы можете видеть, метод add объявлен как DoubleSeries add(DoubleSeries values) и компилятор счастлив.

При необходимости шаблон может быть дополнительно вложен.

Использование экземпляра с Generics

Использование дженериков для определения типа в instanceof

Рассмотрим следующий общий класс Example объявленный с формальным параметром <T> :

class Example<T> {
    public boolean isTypeAString(String s) {
        return s instanceof T; // Compilation error, cannot use T as class type here
    }
}

Это всегда даст ошибку компиляции, поскольку, как только компилятор компилирует исходный код Java в байт-код Java, он применяет процесс, известный как стирание типа , который преобразует весь общий код в не общий код, что делает невозможным отличать T-типы во время выполнения. Тип, используемый с instanceof должен быть повторно подкрепляемым , что означает, что вся информация о типе должна быть доступна во время выполнения, и обычно это не относится к родовым типам.

Следующий класс представляет, что два разных класса Example , Example<String> и Example<Number> , выглядят так, что после того, как дженерики разделились стиранием типа :

class Example { // formal parameter is gone
    public boolean isTypeAString(String s) {
        return s instanceof Object; // Both <String> and <Number> are now Object
    }
}

Поскольку типы исчезли, JVM не знает, какой тип T


Исключение из предыдущего правила

Вы всегда можете использовать неограниченный шаблон (?) Для указания типа в instanceof следующим образом:

    public boolean isAList(Object obj) {
        return obj instanceof List<?>;
    }

Это может быть полезно для оценки того, является ли экземпляр obj List или нет:

System.out.println(isAList("foo")); // prints false
System.out.println(isAList(new ArrayList<String>()); // prints true
System.out.println(isAList(new ArrayList<Float>()); // prints true

На самом деле неограниченный подстановочный знак считается воспроизводимым типом.


Использование общего экземпляра с instanceof

Другая сторона монеты заключается в том, что использование экземпляра t из T с instanceof является законным, как показано в следующем примере:

class Example<T> {
    public boolean isTypeAString(T t) {
        return t instanceof String; // No compilation error this time
    }
}

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

class Example { // formal parameter is gone
    public boolean isTypeAString(Object t) {
        return t instanceof String; // No compilation error this time
    }
}

Так как даже если стирание типа происходит в любом случае, теперь JVM может различать разные типы в памяти, даже если они используют один и тот же ссылочный тип ( Object ), как показано в следующем фрагменте:

Object obj1 = new String("foo"); // reference type Object, object type String
Object obj2 = new Integer(11); // reference type Object, object type Integer
System.out.println(obj1 instanceof String); // true
System.out.println(obj2 instanceof String); // false, it's an Integer, not a String

Различные способы реализации универсального интерфейса (или расширения общего класса)

Предположим, что был указан следующий общий интерфейс:

public interface MyGenericInterface<T> {
    public void foo(T t);
}

Ниже перечислены возможные способы его реализации.


Внеродная реализация класса с определенным типом

Выберите определенный тип, чтобы заменить параметр формального типа <T> MyGenericClass и реализовать его, как MyGenericClass в следующем примере:

public class NonGenericClass implements MyGenericInterface<String> {
    public void foo(String t) { } // type T has been replaced by String
}

Этот класс относится только к String , и это означает, что использование MyGenericInterface с различными параметрами (например, Integer , Object и т. Д.) Не будет компилироваться, как показано в следующем фрагменте:

NonGenericClass myClass = new NonGenericClass();
myClass.foo("foo_string"); // OK, legal
myClass.foo(11); // NOT OK, does not compile
myClass.foo(new Object()); // NOT OK, does not compile

Общая реализация класса

Объявите другой общий интерфейс с формальным параметром типа <T> который реализует MyGenericInterface следующим образом:

public class MyGenericSubclass<T> implements MyGenericInterface<T> {
    public void foo(T t) { } // type T is still the same
    // other methods...
}

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

public class MyGenericSubclass<U> implements MyGenericInterface<U> { // equivalent to the previous declaration
    public void foo(U t) { }
    // other methods...
}

Внедрение класса Raw

Объявите не-общий класс, который реализует MyGenericInteface как необработанный тип (вообще не используя общий):

public class MyGenericSubclass implements MyGenericInterface {
    public void foo(Object t) { } // type T has been replaced by Object
    // other possible methods
}

Этот способ не рекомендуется, так как он не на 100% безопасен во время выполнения, потому что он смешивает сырой тип (подкласса) с дженериками (интерфейса), и это также запутывает. Современные компиляторы Java поднимут предупреждение с такой реализацией, однако код - по соображениям совместимости с более старым JVM (1.4 или более ранним) - скомпилируется.


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

Использование Generics для автоматического создания

С помощью дженериков можно вернуть все, что ожидает абонент:

private Map<String, Object> data;
public <T> T get(String key) {
    return (T) data.get(key);
}

Метод будет скомпилирован с предупреждением. Код на самом деле более безопасен, чем выглядит, потому что время выполнения Java будет выполняться при его использовании:

Bar bar = foo.get("bar");

Это менее безопасно, если вы используете общие типы:

List<Bar> bars = foo.get("bars");

Здесь приведение будет выполняться, когда возвращаемый тип представляет собой какой-либо List (т. ClassCastException Возвращаемый List<String> не приведет к ClassCastException , в конечном итоге вы получите его, когда ClassCastException элементы из списка).

Чтобы обойти эту проблему, вы можете создать API, который использует типизированные ключи:

public final static Key<List<Bar>> BARS = new Key<>("BARS");

наряду с этим методом put() :

public <T> T put(Key<T> key, T value);

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

Связанные с:

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

Многие несвязанные общие параметры, такие как те, которые используются в статическом методе, не могут быть восстановлены во время выполнения (см. Другие темы на Erasure ). Однако существует общая стратегия, используемая для доступа к типу, удовлетворяющему параметру generic для класса во время выполнения. Это позволяет использовать общий код, который зависит от доступа к типу, без необходимости передавать информацию типа типа через каждый вызов.

Фон

Общая параметризация в классе может быть проверена путем создания анонимного внутреннего класса. Этот класс будет захватывать информацию о типе. В общем, этот механизм упоминается как токены супертекста , которые подробно описаны в блоге Neal Gafter .

Реализации

Три общие реализации на Java:

Пример использования

public class DataService<MODEL_TYPE> {
     private final DataDao dataDao = new DataDao();
     private final Class<MODEL_TYPE> type = (Class<MODEL_TYPE>) new TypeToken<MODEL_TYPE>
                                                                (getClass()){}.getRawType();
     public List<MODEL_TYPE> getAll() {
         return dataDao.getAllOfType(type);
    }
}

// the subclass definitively binds the parameterization to User
// for all instances of this class, so that information can be 
// recovered at runtime
public class UserService extends DataService<User> {}

public class Main {
    public static void main(String[] args) {
          UserService service = new UserService();
          List<User> users = service.getAll();
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow