Generics — различия между версиями
(→Несовместимость generic-типов) |
м (rollbackEdits.php mass rollback) |
||
| (не показаны 23 промежуточные версии 9 участников) | |||
| Строка 1: | Строка 1: | ||
== Generics == | == Generics == | ||
| − | Начиная с JDK 1.5, в Java появляются новые возможности для программирования. | + | Начиная с JDK 1.5, в Java появляются новые возможности для программирования. Одним из таких нововведений являются Generics. |
Generics являются аналогией с конструкцией "Шаблонов"(template) в С++, но имеет свои нюансы. | Generics являются аналогией с конструкцией "Шаблонов"(template) в С++, но имеет свои нюансы. | ||
Generics позволяют абстрагировать множество типов. | Generics позволяют абстрагировать множество типов. | ||
Наиболее распространенными примерами являются Коллекции. | Наиболее распространенными примерами являются Коллекции. | ||
| − | Вот типичное использование такого рода(без Generics): | + | Вот типичное использование такого рода (без Generics): |
| − | List myIntList = new LinkedList(); | + | 1. List myIntList = new LinkedList(); |
| − | myIntList.add(new Integer(0)); | + | 2. myIntList.add(new Integer(0)); |
| − | Integer x = '''(Integer)''' myIntList.iterator().next(); | + | 3. Integer x = '''(Integer)''' myIntList.iterator().next(); |
Как правило, программист знает, какие данные должны быть в List'e. | Как правило, программист знает, какие данные должны быть в List'e. | ||
| − | Тем не менее, Приведение типа ('''"Cast"''') в строчке 3 | + | Тем не менее, стоит обратить особое внимание на Приведение типа ('''"Cast"''') в строчке 3. |
| − | Компилятор может | + | Компилятор может лишь гарантировать, что метод next() вернёт Object, |
но чтобы обеспечить присвоение переменной типа Integer правильным и безопасным, требуется Cast. | но чтобы обеспечить присвоение переменной типа Integer правильным и безопасным, требуется Cast. | ||
Cast не только создает беспорядки, но дает возможность появление ошибки "Runtime Error" из-за невнимательности программиста. | Cast не только создает беспорядки, но дает возможность появление ошибки "Runtime Error" из-за невнимательности программиста. | ||
И появляется такой вопрос: "Как с этим бороться? " | И появляется такой вопрос: "Как с этим бороться? " | ||
| − | В частности: "Как же зарезервировать List для определенного типа данных ?" | + | В частности: "Как же зарезервировать List для определенного типа данных?" |
Как раз такую проблему решают Generics. | Как раз такую проблему решают Generics. | ||
| − | List'''<Integer>''' myIntList = new LinkedList'''<Integer>'''(); | + | 1. List'''<Integer>''' myIntList = new LinkedList'''<Integer>'''(); |
| − | myIntList.add(new Integer(0)); | + | 2. myIntList.add(new Integer(0)); |
| − | Integer x = myIntList.iterator().next(); | + | 3. Integer x = myIntList.iterator().next(); |
Обратите внимание на объявления типа для переменной myIntList. | Обратите внимание на объявления типа для переменной myIntList. | ||
Он указывает на то, что это не просто произвольный List, а List<Integer>. | Он указывает на то, что это не просто произвольный List, а List<Integer>. | ||
Мы говорим, что List является generic-интерфейсом, который принимает параметр типа - в этом случае, Integer. | Мы говорим, что List является generic-интерфейсом, который принимает параметр типа - в этом случае, Integer. | ||
| − | Кроме того, необходимо обратить внимание на то, что | + | Кроме того, необходимо обратить внимание на то, что теперь Cast выполняется в строчке 3 автоматически. |
Некоторые могут задуматься, что беспорядок в коде увеличился, но это не так. | Некоторые могут задуматься, что беспорядок в коде увеличился, но это не так. | ||
| − | Вместо приведения к Integer в строчке 3, у нас теперь есть Integer в качестве параметра в строчке 1 | + | Вместо приведения к Integer в строчке 3, у нас теперь есть Integer в качестве параметра в строчке 1. |
| − | + | Здесь существенное отличие. Теперь компилятор может проверить этот тип на корректность во время компиляции. | |
'''И когда мы говорим''', что myIntList объявлен как List<Integer>, это будет '''''справедливо''''' во всем коде и компилятор это гарантирует. | '''И когда мы говорим''', что myIntList объявлен как List<Integer>, это будет '''''справедливо''''' во всем коде и компилятор это гарантирует. | ||
* На заметку: | * На заметку: | ||
| − | Эффект от Generics особенно проявляется в крупных проектах | + | Эффект от Generics особенно проявляется в крупных проектах: он улучшает читаемость и надежность кода в целом. |
== Свойства == | == Свойства == | ||
| Строка 57: | Строка 57: | ||
} | } | ||
| − | Для того чтобы использовать класс как Generics, мы должны прописать после имени класса < | + | Для того чтобы использовать класс как Generics, мы должны прописать после имени класса '''<...>''', куда можно подставить любое имя, wildcard и т.д. |
| − | После того как было | + | После того как было объявлено имя generic-типа его можно использовать как обычный тип внутри метода. И когда в коде будет объявлен, к примеру, List<Integer>, то Е станет Integer для переменной list (как показано ниже). |
| − | Теперь рассмотрим чем старая реализация кода отличается от новой : | + | Теперь рассмотрим чем старая реализация кода отличается от новой: |
List<E> ─ список элементов E | List<E> ─ список элементов E | ||
| Строка 83: | Строка 83: | ||
Это одна из самых важных вещей, которую вы должны узнать о Generics | Это одна из самых важных вещей, которую вы должны узнать о Generics | ||
| − | Как | + | Как говорится: "В бочке мёда есть ложка дегтя". Для того чтобы сохранить целостности и независимости друг от друга Коллекции, у Generics существует так называемая "Несовместимость generic-типов". |
* Суть такова: | * Суть такова: | ||
| Строка 90: | Строка 90: | ||
То G<Foo> '''не является''' наследником G<Bar>. | То G<Foo> '''не является''' наследником G<Bar>. | ||
| + | * '''Пример''': | ||
List<Integer> li = new ArrayList<Integer>(); | List<Integer> li = new ArrayList<Integer>(); | ||
List<Object> lo = li; | List<Object> lo = li; | ||
| Строка 95: | Строка 96: | ||
Иначе — ошибки | Иначе — ошибки | ||
lo.add(“hello”); | lo.add(“hello”); | ||
| − | // ClassCastException | + | // '''ClassCastException''': String -> int |
Integer li = lo.get(0); | Integer li = lo.get(0); | ||
| Строка 101: | Строка 102: | ||
* Решение 1 - '''Wildcard''' | * Решение 1 - '''Wildcard''' | ||
| − | '''Проблема''' | + | Пусть мы захотели написать метод, который берет Collection<Object> и выводит на экран. И мы захотели вызвать dump для Integer. |
| + | |||
| + | * '''Проблема''' | ||
void dump(Collection<Object> c) { | void dump(Collection<Object> c) { | ||
| Строка 112: | Строка 115: | ||
List<Object> l; dump(l); | List<Object> l; dump(l); | ||
List<Integer> l; dump(l); // '''Ошибка''' | List<Integer> l; dump(l); // '''Ошибка''' | ||
| + | |||
| + | В этом примере List<Integer> не может использовать метод dump, так как он не является подтипом List<Object>. | ||
Проблема в том что эта реализация кода не эффективна, так как Collection<Object> не является полностью родительской коллекцией всех остальных коллекции, грубо говоря Collection<Object> имеет ограничения. | Проблема в том что эта реализация кода не эффективна, так как Collection<Object> не является полностью родительской коллекцией всех остальных коллекции, грубо говоря Collection<Object> имеет ограничения. | ||
| − | Для решения этой проблемы используется Wildcard ("?"). Он не имеет ограничения в использовании(то есть имеет соответствие с любым типом) и в этом его плюсы. И теперь, мы можем | + | Для решения этой проблемы используется Wildcard ("?"). Он не имеет ограничения в использовании(то есть имеет соответствие с любым типом) и в этом его плюсы. И теперь, мы можем вызвать dump с любым типом коллекции. |
| − | + | * '''Решение''' | |
| − | |||
| − | '''Решение''' | ||
void dump(Collection<?> c) { | void dump(Collection<?> c) { | ||
for (Iterator<?> i = c.iterator(); i.hasNext(); ) { | for (Iterator<?> i = c.iterator(); i.hasNext(); ) { | ||
| Строка 126: | Строка 129: | ||
} | } | ||
} | } | ||
| + | |||
| + | ---- | ||
* Решение 2 – '''Bounded Wildcard''' | * Решение 2 – '''Bounded Wildcard''' | ||
| − | '''Проблема''' | + | Пусть мы захотели написать метод, который рисует List<Shape>. И у Shape есть наследник Circle. |
| + | И мы хотим вызвать draw для Circle. | ||
| + | |||
| + | * '''Проблема''' | ||
void draw(List<Shape> c) { | void draw(List<Shape> c) { | ||
| Строка 140: | Строка 148: | ||
List<Circle> l; draw(l); // '''Ошибка''' | List<Circle> l; draw(l); // '''Ошибка''' | ||
| − | + | Проблема в том, что у нас не получится из-за несовместимости типов. | |
| + | Предложенное решение используется, если метод который нужно реализовать использовал бы определенный тип и его подтипов. Так называемое "Ограничение сверху". Для этого нужно вместо <Shape> прописать <? extends Shape>. | ||
| − | '''Решение''' | + | * '''Решение''' |
void draw(List<? extends Shape> c) { | void draw(List<? extends Shape> c) { | ||
| Строка 151: | Строка 160: | ||
} | } | ||
} | } | ||
| + | |||
| + | ---- | ||
* Решение 3 – '''Generic-Метод''' | * Решение 3 – '''Generic-Метод''' | ||
| − | '''Проблема''' | + | Пусть вы захотели сделать метод, который берет массив Object и переносить их в коллекцию. |
| + | |||
| + | * '''Проблема''' | ||
void addAll(Object[] a, Collection<?> c) { | void addAll(Object[] a, Collection<?> c) { | ||
for (int i = 0; i < a.length; i++) { | for (int i = 0; i < a.length; i++) { | ||
| Строка 166: | Строка 179: | ||
addAll(new String[10], new ArrayList<Object>()); // '''Ошибка''' | addAll(new String[10], new ArrayList<Object>()); // '''Ошибка''' | ||
| − | '''Решение''' | + | Напомним, что вы не можете просто засунуть Object в коллекции неизвестного типа. Способ решения этой проблемы является использование "Generic-Метод" Для этого перед методом нужно объявить <T> и использовать его. |
| + | |||
| + | * '''Решение''' | ||
<T> void addAll(T[] a, Collection<T> c) { | <T> void addAll(T[] a, Collection<T> c) { | ||
for (int i = 0; i < a.length; i++) { | for (int i = 0; i < a.length; i++) { | ||
| Строка 172: | Строка 187: | ||
} | } | ||
} | } | ||
| − | Но все равно после выполнение останется ошибка в | + | Но все равно после выполнение останется ошибка в третьей строчке : |
addAll(new Object[10], new ArrayList<String>()); // '''Ошибка''' | addAll(new Object[10], new ArrayList<String>()); // '''Ошибка''' | ||
| + | |||
| + | |||
| + | ---- | ||
| + | |||
| + | * Решение 4 – '''Bounded type argument''' | ||
| + | Реализуем метод копирование из одной коллекции в другую | ||
| + | |||
| + | * '''Проблема''' | ||
| + | <M> void addAll(Collection<M> c, Collection<M> c2) { | ||
| + | for (Iterator<M> i = c.iterator(); i.hasNext(); ) { | ||
| + | M o = i.next(); | ||
| + | c2.add(o); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | addAll(new AL<Integer>(), new AL<Integer>()); | ||
| + | addAll(new AL<Integer>(), new AL<Object>()); //Ошибка | ||
| + | |||
| + | Проблема в том что две Коллекции могут быть разных типов (несовместимость generic-типов). Для таких случаев было придуман Bounded type argument. Он нужен если метод ,который мы пишем использовал бы определенный тип данных. Для этого нужно ввести <N extends M> (N принимает только значения M). | ||
| + | Также можно корректно писать <T extends A & B & C>. (Принимает значения нескольких переменных) | ||
| + | |||
| + | * '''Решение''' | ||
| + | |||
| + | <M, N extends M> void addAll(Collection<N> c, Collection<M> c2) { | ||
| + | for (Iterator<N> i = c.iterator(); i.hasNext(); ) { | ||
| + | N o = i.next(); | ||
| + | c2.add(o); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | ---- | ||
| + | |||
| + | * Решение 5 – '''Lower bounded wcard''' | ||
| + | Реализуем метод нахождение максимума в коллекции. | ||
| + | |||
| + | * '''Проблема''' | ||
| + | <T extends Comparable<T>> | ||
| + | T max(Collection<T> c) { | ||
| + | … | ||
| + | } | ||
| + | |||
| + | List<Integer> il; Integer I = max(il); | ||
| + | class Test implements Comparable<Object> {…} | ||
| + | List<Test> tl; Test t = max(tl); // Ошибка | ||
| + | |||
| + | * <T extends Comparable<T>> обозначает что Т обязан реализовывать интерфейс Comparable<T>. | ||
| + | Ошибка возникает из за того что Test реализует интерфейс Comparable<Object>. Решение этой проблемы - Lower bounded wcard("Ограничение снизу"). Суть в том что мы будет реализовывать метод не только для Т, но и для его Супер-типов(Родительских типов). | ||
| + | Например: Если мы напишем | ||
| + | |||
| + | List<T super Integer> list; | ||
| + | |||
| + | Мы можем заполнить его List<Integer>, List<Number> или List<Object>. | ||
| + | |||
| + | * '''Решение''' | ||
| + | |||
| + | <T extends Comparable<? super T>> | ||
| + | T max(Collection<T> c) { | ||
| + | … | ||
| + | } | ||
| + | ---- | ||
| + | * Решение 6 – '''Wildcard Capture''' | ||
| + | Реализуем метод Swap в List<?> | ||
| + | |||
| + | * '''Проблема''' | ||
| + | |||
| + | void swap(List<?> list, int i, int j) { | ||
| + | list.set(i, list.get(j)); // Ошибка | ||
| + | } | ||
| + | |||
| + | Проблема в том, что метод List.set() не может работать с List<?>, так как ему не известно какой он List. Для решение этой проблемы используют "Wildcard Capture" (или "Capture helpers"). Суть заключается в том, чтобы обмануть компилятор. Напишем еще один метод с параметризованной переменной и будем его использовать внутри нашего метода. | ||
| + | |||
| + | * '''Решение''' | ||
| + | |||
| + | void swap(List<?> list, int i, int j) { | ||
| + | swapImpl(list, i, j); | ||
| + | } | ||
| + | <T> void swapImpl(List<T> list, int i, int j) { | ||
| + | T temp = list.get(i); | ||
| + | list.set(i, list.get(j)); | ||
| + | list.set(j, temp); | ||
| + | } | ||
| + | |||
| + | == Ограничения Generic == | ||
| + | Также нужно запомнить простые правила для работы с Generics. | ||
| + | |||
| + | * Невозможно создать массив параметра типа | ||
| + | Collection<T> c; | ||
| + | T[] ta; | ||
| + | new T[10]; // Ошибка !! | ||
| + | |||
| + | * Невозможно создать массив Generic-классов | ||
| + | new ArrayList<List<Integer>>(); | ||
| + | List<?>[] la = new List<?>[10]; // Ошибка !! | ||
| + | |||
| + | |||
| + | == Преобразование типов == | ||
| + | В Generics также можно манипулировать с информацией, хранящийся в переменных. | ||
| + | |||
| + | * Уничтожение информации о типе | ||
| + | |||
| + | List l = new ArrayList<String>(); | ||
| + | |||
| + | * Добавление информации о типе | ||
| + | |||
| + | List<String> l = (List<String>) new ArrayList(); | ||
| + | List<String> l1 = new ArrayList(); | ||
| + | |||
| + | == Примеры кода == | ||
| + | * Первый пример: | ||
| + | |||
| + | List<String> ls; | ||
| + | List<Integer> li; | ||
| + | ls.getClass() == li.getClass() // True | ||
| + | ls instanceof List // True | ||
| + | ls instanceof List<String> // Запрещено | ||
| + | |||
| + | * Второй пример: | ||
| + | |||
| + | Нахождение максимума в Коллекции Integer. | ||
| + | |||
| + | * Без Generics: | ||
| + | Collection c; | ||
| + | Iterator i = c.iterator(); | ||
| + | Integer max = '''(Integer)''' i.next(); | ||
| + | while(i.hasNext()) { | ||
| + | Integer next = '''(Integer)''' i.next(); | ||
| + | if (next.compareTo(max) > 0) { | ||
| + | max = next; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | * С помощью Generics | ||
| + | Collection'''<Integer>''' c; | ||
| + | Iterator'''<Integer>''' i = c.iterator(); | ||
| + | Integer max = i.next(); | ||
| + | while(i.hasNext()) { | ||
| + | Integer next = i.next(); | ||
| + | if (next.compareTo(max) > 0) { | ||
| + | max = next; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | == Источники == | ||
| + | [http://www.kgeorgiy.info/ Сайт Георгия Корнеева] | ||
| + | |||
| + | [http://docs.oracle.com/javase/tutorial/java/generics/ Java tutorial. Generics] | ||
| + | |||
| + | [http://www.kgeorgiy.info/courses/java-intro/slides/pics/generics-tutorial.pdf Generics tutorial] | ||
Текущая версия на 19:03, 4 сентября 2022
Содержание
Generics
Начиная с JDK 1.5, в Java появляются новые возможности для программирования. Одним из таких нововведений являются Generics. Generics являются аналогией с конструкцией "Шаблонов"(template) в С++, но имеет свои нюансы. Generics позволяют абстрагировать множество типов. Наиболее распространенными примерами являются Коллекции.
Вот типичное использование такого рода (без Generics):
1. List myIntList = new LinkedList(); 2. myIntList.add(new Integer(0)); 3. Integer x = (Integer) myIntList.iterator().next();
Как правило, программист знает, какие данные должны быть в List'e. Тем не менее, стоит обратить особое внимание на Приведение типа ("Cast") в строчке 3. Компилятор может лишь гарантировать, что метод next() вернёт Object, но чтобы обеспечить присвоение переменной типа Integer правильным и безопасным, требуется Cast. Cast не только создает беспорядки, но дает возможность появление ошибки "Runtime Error" из-за невнимательности программиста.
И появляется такой вопрос: "Как с этим бороться? " В частности: "Как же зарезервировать List для определенного типа данных?"
Как раз такую проблему решают Generics.
1. List<Integer> myIntList = new LinkedList<Integer>(); 2. myIntList.add(new Integer(0)); 3. Integer x = myIntList.iterator().next();
Обратите внимание на объявления типа для переменной myIntList. Он указывает на то, что это не просто произвольный List, а List<Integer>. Мы говорим, что List является generic-интерфейсом, который принимает параметр типа - в этом случае, Integer. Кроме того, необходимо обратить внимание на то, что теперь Cast выполняется в строчке 3 автоматически.
Некоторые могут задуматься, что беспорядок в коде увеличился, но это не так. Вместо приведения к Integer в строчке 3, у нас теперь есть Integer в качестве параметра в строчке 1. Здесь существенное отличие. Теперь компилятор может проверить этот тип на корректность во время компиляции.
И когда мы говорим, что myIntList объявлен как List<Integer>, это будет справедливо во всем коде и компилятор это гарантирует.
- На заметку:
Эффект от Generics особенно проявляется в крупных проектах: он улучшает читаемость и надежность кода в целом.
Свойства
- Строгая типизация
- Единая реализация
- Отсутствие информации о типе
Пример реализации Generic-класса
public interface List<E> {
E get(int i);
set(int i, E e);
add(E e);
Iterator<E> iterator();
…
}
Для того чтобы использовать класс как Generics, мы должны прописать после имени класса <...>, куда можно подставить любое имя, wildcard и т.д.
После того как было объявлено имя generic-типа его можно использовать как обычный тип внутри метода. И когда в коде будет объявлен, к примеру, List<Integer>, то Е станет Integer для переменной list (как показано ниже).
Теперь рассмотрим чем старая реализация кода отличается от новой:
List<E> ─ список элементов E
Раньше :
List list = new List(); list.add(new Integer(1)); Integer i = (Integer) list.get(0);
Теперь :
List<Integer> list = new List<Integer>(); list.add(new Integer(1)); Integer i = list.get(0);
Как видите, больше не нужно приводить Integer, так как метод get() возвращает ссылку на объект конкретного типа (в данном случае – Integer).
Несовместимость generic-типов
Это одна из самых важных вещей, которую вы должны узнать о Generics
Как говорится: "В бочке мёда есть ложка дегтя". Для того чтобы сохранить целостности и независимости друг от друга Коллекции, у Generics существует так называемая "Несовместимость generic-типов".
- Суть такова:
Пусть у нас есть тип Foo, который является подтипом Bar, и еще G - наследник Коллекции. То G<Foo> не является наследником G<Bar>.
- Пример:
List<Integer> li = new ArrayList<Integer>(); List<Object> lo = li;
Иначе — ошибки
lo.add(“hello”); // ClassCastException: String -> int Integer li = lo.get(0);
Проблемы реализации Generics
- Решение 1 - Wildcard
Пусть мы захотели написать метод, который берет Collection<Object> и выводит на экран. И мы захотели вызвать dump для Integer.
- Проблема
void dump(Collection<Object> c) {
for (Iterator<Object> i = c.iterator(); i.hasNext(); ) {
Object o = i.next();
System.out.println(o);
}
}
List<Object> l; dump(l); List<Integer> l; dump(l); // Ошибка
В этом примере List<Integer> не может использовать метод dump, так как он не является подтипом List<Object>.
Проблема в том что эта реализация кода не эффективна, так как Collection<Object> не является полностью родительской коллекцией всех остальных коллекции, грубо говоря Collection<Object> имеет ограничения.
Для решения этой проблемы используется Wildcard ("?"). Он не имеет ограничения в использовании(то есть имеет соответствие с любым типом) и в этом его плюсы. И теперь, мы можем вызвать dump с любым типом коллекции.
- Решение
void dump(Collection<?> c) {
for (Iterator<?> i = c.iterator(); i.hasNext(); ) {
Object o = i.next();
System.out.println(o);
}
}
- Решение 2 – Bounded Wildcard
Пусть мы захотели написать метод, который рисует List<Shape>. И у Shape есть наследник Circle. И мы хотим вызвать draw для Circle.
- Проблема
void draw(List<Shape> c) {
for (Iterator<Shape> i = c.iterator(); i.hasNext(); ) {
Shape s = i.next();
s.draw();
}
}
List<Shape> l; draw(l); List<Circle> l; draw(l); // Ошибка
Проблема в том, что у нас не получится из-за несовместимости типов. Предложенное решение используется, если метод который нужно реализовать использовал бы определенный тип и его подтипов. Так называемое "Ограничение сверху". Для этого нужно вместо <Shape> прописать <? extends Shape>.
- Решение
void draw(List<? extends Shape> c) {
for (Iterator<? extends Shape> i = c.iterator();
i.hasNext(); ) {
Shape s = i.next();
s.draw();
}
}
- Решение 3 – Generic-Метод
Пусть вы захотели сделать метод, который берет массив Object и переносить их в коллекцию.
- Проблема
void addAll(Object[] a, Collection<?> c) {
for (int i = 0; i < a.length; i++) {
c.add(a[i]);
}
}
addAll(new String[10], new ArrayList<String>()); addAll(new Object[10], new ArrayList<Object>()); addAll(new Object[10], new ArrayList<String>()); // Ошибка addAll(new String[10], new ArrayList<Object>()); // Ошибка
Напомним, что вы не можете просто засунуть Object в коллекции неизвестного типа. Способ решения этой проблемы является использование "Generic-Метод" Для этого перед методом нужно объявить <T> и использовать его.
- Решение
<T> void addAll(T[] a, Collection<T> c) {
for (int i = 0; i < a.length; i++) {
c.add(a[i]);
}
}
Но все равно после выполнение останется ошибка в третьей строчке :
addAll(new Object[10], new ArrayList<String>()); // Ошибка
- Решение 4 – Bounded type argument
Реализуем метод копирование из одной коллекции в другую
- Проблема
<M> void addAll(Collection<M> c, Collection<M> c2) {
for (Iterator<M> i = c.iterator(); i.hasNext(); ) {
M o = i.next();
c2.add(o);
}
}
addAll(new AL<Integer>(), new AL<Integer>()); addAll(new AL<Integer>(), new AL<Object>()); //Ошибка
Проблема в том что две Коллекции могут быть разных типов (несовместимость generic-типов). Для таких случаев было придуман Bounded type argument. Он нужен если метод ,который мы пишем использовал бы определенный тип данных. Для этого нужно ввести <N extends M> (N принимает только значения M). Также можно корректно писать <T extends A & B & C>. (Принимает значения нескольких переменных)
- Решение
<M, N extends M> void addAll(Collection<N> c, Collection<M> c2) {
for (Iterator<N> i = c.iterator(); i.hasNext(); ) {
N o = i.next();
c2.add(o);
}
}
- Решение 5 – Lower bounded wcard
Реализуем метод нахождение максимума в коллекции.
- Проблема
<T extends Comparable<T>>
T max(Collection<T> c) {
…
}
List<Integer> il; Integer I = max(il);
class Test implements Comparable<Object> {…}
List<Test> tl; Test t = max(tl); // Ошибка
- <T extends Comparable<T>> обозначает что Т обязан реализовывать интерфейс Comparable<T>.
Ошибка возникает из за того что Test реализует интерфейс Comparable<Object>. Решение этой проблемы - Lower bounded wcard("Ограничение снизу"). Суть в том что мы будет реализовывать метод не только для Т, но и для его Супер-типов(Родительских типов). Например: Если мы напишем
List<T super Integer> list;
Мы можем заполнить его List<Integer>, List<Number> или List<Object>.
- Решение
<T extends Comparable<? super T>>
T max(Collection<T> c) {
…
}
- Решение 6 – Wildcard Capture
Реализуем метод Swap в List<?>
- Проблема
void swap(List<?> list, int i, int j) {
list.set(i, list.get(j)); // Ошибка
}
Проблема в том, что метод List.set() не может работать с List<?>, так как ему не известно какой он List. Для решение этой проблемы используют "Wildcard Capture" (или "Capture helpers"). Суть заключается в том, чтобы обмануть компилятор. Напишем еще один метод с параметризованной переменной и будем его использовать внутри нашего метода.
- Решение
void swap(List<?> list, int i, int j) {
swapImpl(list, i, j);
}
<T> void swapImpl(List<T> list, int i, int j) {
T temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
Ограничения Generic
Также нужно запомнить простые правила для работы с Generics.
- Невозможно создать массив параметра типа
Collection<T> c; T[] ta; new T[10]; // Ошибка !!
- Невозможно создать массив Generic-классов
new ArrayList<List<Integer>>(); List<?>[] la = new List<?>[10]; // Ошибка !!
Преобразование типов
В Generics также можно манипулировать с информацией, хранящийся в переменных.
- Уничтожение информации о типе
List l = new ArrayList<String>();
- Добавление информации о типе
List<String> l = (List<String>) new ArrayList(); List<String> l1 = new ArrayList();
Примеры кода
- Первый пример:
List<String> ls; List<Integer> li; ls.getClass() == li.getClass() // True ls instanceof List // True ls instanceof List<String> // Запрещено
- Второй пример:
Нахождение максимума в Коллекции Integer.
- Без Generics:
Collection c;
Iterator i = c.iterator();
Integer max = (Integer) i.next();
while(i.hasNext()) {
Integer next = (Integer) i.next();
if (next.compareTo(max) > 0) {
max = next;
}
}
- С помощью Generics
Collection<Integer> c;
Iterator<Integer> i = c.iterator();
Integer max = i.next();
while(i.hasNext()) {
Integer next = i.next();
if (next.compareTo(max) > 0) {
max = next;
}
}