Generics — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Проблемы реализации Generics)
Строка 6: Строка 6:
 
Наиболее распространенными примерами являются Коллекции.
 
Наиболее распространенными примерами являются Коллекции.
  
Вот типичное использование такого рода(без Generics):
+
Вот типичное использование такого рода (без Generics):
  
 
   List myIntList = new LinkedList(); // 1
 
   List myIntList = new LinkedList(); // 1
Строка 30: Строка 30:
 
Он указывает на то, что это не просто произвольный List, а List<Integer>.
 
Он указывает на то, что это не просто произвольный List, а List<Integer>.
 
Мы говорим, что List является generic-интерфейсом, который принимает параметр типа - в этом случае, Integer.
 
Мы говорим, что List является generic-интерфейсом, который принимает параметр типа - в этом случае, Integer.
Кроме того, необходимо обратить внимание на то, что каст выполняется по линии 3' автоматически.
+
Кроме того, необходимо обратить внимание на то, что Cast выполняется по линии 3' автоматически.
  
 
Некоторые могут задуматься, что беспорядок в коде увеличился, но это не так.
 
Некоторые могут задуматься, что беспорядок в коде увеличился, но это не так.
Строка 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;
Строка 105: Строка 106:
 
Пусть мы захотели написать метод, который берет Collection<Object> и выводит на экран. И мы захотели вызвать dump для Integer.  
 
Пусть мы захотели написать метод, который берет Collection<Object> и выводит на экран. И мы захотели вызвать dump для Integer.  
  
'''Проблема'''
+
* '''Проблема'''
  
 
   void dump(Collection<Object> c) {
 
   void dump(Collection<Object> c) {
Строка 116: Строка 117:
 
   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> имеет ограничения.
Строка 121: Строка 124:
 
Для решения этой проблемы используется Wildcard ("?"). Он не имеет ограничения в использовании(то есть имеет соответствие с любым типом) и в этом его плюсы. И теперь, мы можем назвать это с любым типом коллекции.
 
Для решения этой проблемы используется Wildcard ("?"). Он не имеет ограничения в использовании(то есть имеет соответствие с любым типом) и в этом его плюсы. И теперь, мы можем назвать это с любым типом коллекции.
  
В этом примере List<Integer> не может использовать метод dump, так как он не является подтипом List<Object>. 
+
* '''Решение'''  
 
 
'''Решение'''  
 
 
   void dump(Collection<?> c) {
 
   void dump(Collection<?> c) {
 
     for (Iterator<?> i = c.iterator(); i.hasNext(); ) {
 
     for (Iterator<?> i = c.iterator(); i.hasNext(); ) {
Строка 135: Строка 136:
 
* Решение 2 – '''Bounded Wildcard'''
 
* Решение 2 – '''Bounded Wildcard'''
 
Пусть мы захотели написать метод, который рисует List<Shape>. И у Shape есть наследник Circle.  
 
Пусть мы захотели написать метод, который рисует List<Shape>. И у Shape есть наследник Circle.  
И мы хотим вызвать draw для Circle. Но у нас не получиться из-за несовместимости типов.
+
И мы хотим вызвать draw для Circle.
  
'''Проблема'''  
+
* '''Проблема'''  
  
 
   void draw(List<Shape> c) {
 
   void draw(List<Shape> c) {
Строка 149: Строка 150:
 
   List<Circle> l; draw(l); // '''Ошибка'''
 
   List<Circle> l; draw(l); // '''Ошибка'''
  
Это решение используют, если метод который нужно реализовать использовал бы определенный тип и его подтипов. Так называемое "Ограничение сверху". Для этого нужно вместо <Shape> прописать <? extends Shape>.
+
Проблема в том, что у нас не получиться из-за несовместимости типов.
 +
Предложенное решение используется, если метод который нужно реализовать использовал бы определенный тип и его подтипов. Так называемое "Ограничение сверху". Для этого нужно вместо <Shape> прописать <? extends Shape>.
  
'''Решение'''
+
* '''Решение'''
  
 
   void draw(List<? extends Shape> c) {
 
   void draw(List<? extends Shape> c) {
Строка 167: Строка 169:
 
Пусть вы захотели сделать метод, который берет массив Object и переносить их в коллекцию.
 
Пусть вы захотели сделать метод, который берет массив 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++) {
Строка 181: Строка 183:
 
Напомним, что вы не можете просто засунуть Object в коллекции неизвестного типа. Способ решения этой проблемы является использование "Generic-Метод" Для этого перед методом нужно объявить <T> и использовать его.
 
Напомним, что вы не можете просто засунуть 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++) {

Версия 21:49, 1 октября 2013

Generics

Начиная с JDK 1.5, в Java появляются новые возможности для программирования. Одно из таких нововведений являются Generics. Generics являются аналогией с конструкцией "Шаблонов"(template) в С++, но имеет свои нюансы. Generics позволяют абстрагировать множество типов. Наиболее распространенными примерами являются Коллекции.

Вот типичное использование такого рода (без Generics):

 List myIntList = new LinkedList(); // 1
 myIntList.add(new Integer(0)); // 2
 Integer x = (Integer) myIntList.iterator().next(); // 3

Как правило, программист знает, какие данные должны быть в List'e. Тем не менее, Приведение типа ("Cast") в строчке 3 имеет важное очень значение. Компилятор может только гарантировать, что Object будет возвращен итератору, но чтобы обеспечить присвоение переменной типа Integer правильным и безопасным, требуется Cast. Cast не только создает беспорядки, но дает возможность появление ошибки "Runtime Error" из-за невнимательности программиста.

И появляется такой вопрос: "Как с этим бороться? " В частности: "Как же зарезервировать List для определенного типа данных ?"

Как раз такую проблему решают Generics.

 List<Integer> myIntList = new LinkedList<Integer>(); // 1’
 myIntList.add(new Integer(0)); //2’
 Integer x = myIntList.iterator().next(); // 3’

Обратите внимание на объявления типа для переменной 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, мы должны прописать после имени класса <E>. Буква E не обязательна для использование, вместо нее можно подставить любое имя, 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
 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 ("?"). Он не имеет ограничения в использовании(то есть имеет соответствие с любым типом) и в этом его плюсы. И теперь, мы можем назвать это с любым типом коллекции.

  • Решение
 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>()); // Ошибка