Руководство Java Generics

View more categories:

1- Почему Java Generics?

Generics это понятие, введенное в Java, начиная с версии 5. Перед тем, как ознакомить вас с понятием  Generics, посмотрим на код Java перед версией 5.
В данном примере,  ArrayList это списко, вы можете добавить., удалить, изменить в списке, и иметь доступ к элементам в списке.
BeforeJ5Example.java
package org.o7planning.tutorial.generics;

import java.util.ArrayList;

public class BeforeJ5Example {

	public static void main(String[] args) {

		// Создать объект ArrayList (Список).
		// Чтобы содержать имена пользователей.
		ArrayList userNames = new ArrayList();

		// Добавить String в список.
		userNames.add("tom");
		userNames.add("jerry");

		// Вы случайно добавляете элемент не String вида в список.
		// (Это вполне позволено).
		userNames.add(new Integer(100));

		// И получить первый элемент
		// Он является Object (Но вы знаете, что он является String)
		// ==> tom
		Object obj1 = userNames.get(0);

		// Сделать cast в String.
		String userName1 = (String) obj1;

		System.out.println("userName1 = " + userName1);

		// Получить элемент 2.
		// (Вы знаете, что он является String)
		// ==> jerry
		String userName2 = (String) userNames.get(1);

		System.out.println("userName2 = " + userName2);

		// Получить 3-ий элемент и сделать cast чтобы он стал String.
		// (На самом деле он является Integer).
		// (Ошибка cast происходит здесь).
		String userName3 = (String) userNames.get(2);

		System.out.println("userName3 = " + userName3);
	}

}
Ситуация в Java перед версией 5:

Вам нужно создать объект  ArrayList с целью хранения только элементов вида  String, при этом добавить в этот список элемент не вида String где-нибудь в программе (Это вполне возможно), когда вы получаете эти элементы и приводите к виду  String, выбрасывается исключение.
  • TODO (Image)
Java 5 вводит понятие  Generics. С помощью  Generics, вы можете создать объект  ArrayList который позволяет содержать только объекты вида  String, и не позволяет иметь другие виды объектов.
J5Example.java
package org.o7planning.tutorial.generics;

import java.util.ArrayList;

public class J5Example {

	public static void main(String[] args) {

		// Создать ArrayList (Список)
		// Этот список только позволяет содержать элементы вида String.
		ArrayList<String> userNames = new ArrayList<String>();

		// Добавить String в список.
		userNames.add("tom");
		userNames.add("jerry");

		// Вы не можете добавить элементы не String вида в список.
		// (Ошибка при компиляции).
		userNames.add(new Integer(100)); // Compile Error!

		// Вам не нужно делать cast элемента.
		String userName1 = userNames.get(0);

		System.out.println("userName1 = " + userName1);

	}

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

2- ВидGeneric для Class & Interface

2.1- Generics Class

Пример ниже определяет class generics. KeyValueэто class generics который содержит пару ключей/значения (key/value).
 
KeyValue.java
package org.o7planning.tutorial.generics.ci;

public class KeyValue<K, V> {

   private K key;
   private V value;

   public KeyValue(K key, V value) {
       this.key = key;
       this.value = value;
   }

   public K getKey() {
       return key;
   }

   public void setKey(K key) {
       this.key = key;
   }

   public V getValue() {
       return value;
   }

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

}
K, V в class KeyValue<K,V> называется параметорм generics, который является определенной ссылкой. При использовании этого class-а вам нужно определить точный параметр.

Смотрите пример использования class KeyValue.
 
KeyValueDemo.java
package org.o7planning.tutorial.generics.ci;

public class KeyValueDemo {

	public static void main(String[] args) {

		// Создать объект KeyValue
		// Integer: Номер телефона (K = Integer)
		// String: Имя пользователя. (V = String).
		KeyValue<Integer, String> entry = new KeyValue<Integer, String>(12000111, "Tom");

		// Java понимает вид возврата как Integer (K = Integer).
		Integer phone = entry.getKey();

		// Java понимает вид возврата как String (V = String).
		String name = entry.getValue();

		System.out.println("Phone = " + phone + " / name = " + name);
	}

}
Запуск примера:

2.2- Наследование класса Generics

Сlass расширенный из class generics, может определить вид параметра generics, сохранить параметры generics или добавить параметры generics.

Пример 1:

PhoneNameEntry.java
package org.o7planning.tutorial.generics.ci;

// Этот класс расширенный (extends) из класса KeyValue<K,V>.
// И ясно определяет K,V:
// K = Integer  (Номер телефона).
// V = String   (Имя пользователя).
public class PhoneNameEntry extends KeyValue<Integer, String> {

	public PhoneNameEntry(Integer key, String value) {
		super(key, value);
	}

}
Пример использования  PhoneNameEntry:
PhoneNameEntryDemo.java
package org.o7planning.tutorial.generics.ci;

public class PhoneNameEntryDemo {

	public static void main(String[] args) {

		PhoneNameEntry entry = new PhoneNameEntry(12000111, "Tom");

		// Java понимает вид возврата как Integer.
		Integer phone = entry.getKey();

		// Java понимает вид возврата как String.
		String name = entry.getValue();

		System.out.println("Phone = " + phone + " / name = " + name);

	}

}

Пример 2:

StringAndValueEntry.java
package org.o7planning.tutorial.generics.ci;

// Этот класс расширен (extends) из класса KeyValue<K,V>.
// Ясно определить вид параметра <K> как String.
// Сохранить вид параметра Generic <V>.
public class StringAndValueEntry<V> extends KeyValue<String, V> {

	public StringAndValueEntry(String key, V value) {
		super(key, value);
	}

}
Пример использования  StringAndValueEntry class:
StringAndValueEntryDemo.java
package org.o7planning.tutorial.generics.ci;

public class StringAndValueEntryDemo {

	public static void main(String[] args) {

		// (Код сотрудника, Имя сотрудника).
		// V = String (Имя сотрудника)
		StringAndValueEntry<String> entry = new StringAndValueEntry<String>("E001", "Tom");

		String empNumber = entry.getKey();

		String empName = entry.getValue();

		System.out.println("Emp Number = " + empNumber);
		System.out.println("Emp Name = " + empName);

	}

}

Пример 3:

KeyValueInfo.java
package org.o7planning.tutorial.generics.ci;

// Этот класс расширен (extends) из класса KeyValue<K,V>
// Он имеет еще один параметр Generics <I>.
public class KeyValueInfo<K, V, I> extends KeyValue<K, V> {

	private I info;

	public KeyValueInfo(K key, V value) {
		super(key, value);
	}

	public KeyValueInfo(K key, V value, I info) {
		super(key, value);
		this.info = info;
	}

	public I getInfo() {
		return info;
	}

	public void setInfo(I info) {
		this.info = info;
	}

}

2.3- Generics Interface

Интерфейс с параметром Generics:
GenericInterface.java
package org.o7planning.tutorial.generics.ci;

public interface GenericInterface<G> {

 
  public G doSomething();
 
}
Пример class-а, применяющего интерфейс:
GenericInterfaceImpl.java
package org.o7planning.tutorial.generics.ci;

public class GenericInterfaceImpl<G> implements GenericInterface<G>{

   private G something;
   
   @Override
   public G doSomething() {
       return something;
   }

}

2.4- Java не поддерживает Generics Throwable

Вы не можете создать class generic который является наследником  Throwable, java не поддерживет создание такого class-а.
Сообщение ошибки компилятора:
- The generic class MyException<E> may not subclass java.lang.Throwable
Java не поддерживает создание class-а  Throwable generic, так как это не приносит никакую пользу. Причиной является информация Generic только используется компилятором для управления кодом программиста. В процессе запуска Java информация Generic не существует, объект  Mistake<Account> или  Mistake<User> являются объектом  Mistake.
} catch( Mistake<Account> ea) {
    // Если происходит исключение Mistake, данный блок будет выполнен.
    ...
} catch( Mistake<User> eu) {
     // Данный блок никогда не выполняется
    ...
}

3- Методы generics

Метод в class или интерфейсе, который может стать generic (generify).
MyUtils.java
package org.o7planning.tutorial.generics.m;

import java.util.ArrayList;

import org.o7planning.tutorial.generics.ci.KeyValue;

public class MyUtils {

	// <K,V> : Говорит этот метод имеет 2 вида параметра K,V
	// Метод возвращает объект вида K.
	public static <K, V> K getKey(KeyValue<K, V> entry) {
		K key = entry.getKey();
		return key;
	}

	// <K,V> : Говорит этот метод имеет 2 вида параметра K,V
	// Метод возвращает объект вида V.
	public static <K, V> V getValue(KeyValue<K, V> entry) {
		V value = entry.getValue();
		return value;
	}

	// ArrayList<E>: Список содержит элемент вида E.
	// Метод возвращает объект вида E.
	public static <E> E getFirstElement(ArrayList<E> list) {
		if (list == null || list.isEmpty()) {
			return null;
		}
		E first = list.get(0);
		return first;
	}

}
Например, используя метод generics:
MyUtilsDemo.java
package org.o7planning.tutorial.generics.m;

import java.util.ArrayList;

import org.o7planning.tutorial.generics.ci.KeyValue;

public class MyUtilsDemo {

	public static void main(String[] args) {

		// K = Integer: Phone
		// V = String: Name
		KeyValue<Integer, String> entry1 = new KeyValue<Integer, String>(12000111, "Tom");
		KeyValue<Integer, String> entry2 = new KeyValue<Integer, String>(12000112, "Jerry");

		// (K = Integer).
		Integer phone = MyUtils.getKey(entry1);
		System.out.println("Phone = " + phone);

		// Список содержит элементы вида KeyValue<Integer,String>.
		ArrayList<KeyValue<Integer, String>> list = new ArrayList<KeyValue<Integer, String>>();

		// Добавить элемент в список.
		list.add(entry1);
		list.add(entry2);

		KeyValue<Integer, String> firstEntry = MyUtils.getFirstElement(list);

		System.out.println("Value = " + firstEntry.getValue());
	}

}

4- Инициализация объекта Generic

Иногда вы хотите инициировать объект  Generic:
// Создать объект Generic.
T t = new T(); // Error
Инициализация объекта generic, как выше не позволено, так как <T> не существует в момент запуска Java. Он имеет значение только для компилятора, управляющего кодом программиста. Все виды   <T> похожи друг на друга и понимаются как Object в момент запуска Java.

Для инициализации объекта generic <T> вам нужно предоставить Java объект Class<T>, Java создаст объект <T> в момент запуска используя  Java Reflection.
Bar.java
package org.o7planning.tutorial.generics.o;

import java.util.Date;

public class Bar {

	// Этот класс должен иметь Constructor (конструктор) по умолчанию .
	public Bar() {

	}

	public void currentDate() {
		System.out.println("Now is: " + new Date());
	}

}
MyGeneric.java
package org.o7planning.tutorial.generics.o;

public class MyGeneric<T> {

   private T tobject;

   public MyGeneric(Class<T> tclass)
           throws InstantiationException, IllegalAccessException {
       
       this.tobject = (T) tclass.newInstance();
       
   }

   public T getTObject() {
       return this.tobject;
   }
}
MyGenericDemo.java
package org.o7planning.tutorial.generics.o;

public class MyGenericDemo {

   public static void main(String[] args) throws Exception {

       MyGeneric<Bar> mg = new MyGeneric<Bar>(Bar.class);

       Bar bar = mg.getTObject();

       bar.currentDate();
   }
}

5- Массив Generic

Вы можете объявить массив generic, но вы не можете создать массив generic.
// Вы можете объявить массив generic.
T[] myarray;

// Но не можете инициализировать массив generic.
// (Это не разрешено).
T[] myarray = new T[5];  // Error!
Пример:
GenericArray.java
package org.o7planning.tutorial.generics.a;

public class GenericArray<T> {

	private T[] array;

	// Contructor.
	public GenericArray(T[] array) {
		this.array = array;
	}

	public T[] getArray() {
		return array;
	}

	// Возвращает последний элемент массива.
	public T getLastElement() {
		if (this.array == null || this.array.length == 0) {
			return null;
		}
		return this.array[this.array.length - 1];
	}

}
GenericArrayDemo.java
package org.o7planning.tutorial.generics.a;

public class GenericArrayDemo {

	public static void main(String[] args) {

		// Массив String.
		String[] names = new String[] { "Tom", "Jerry" };

		GenericArray<String> gArray = new GenericArray<String>(names);

		String last = gArray.getLastElement();
		
		System.out.println("Last Element = " + last);
	}

}
Возвращаясь к вопросу, почему Java не поддерживает инициализацию массива  Generic:
// Почему Java не поддерживает инициализировать массив Generic?
T[] genericArray = new T[10]; // Error!
Это потому что вид generic не существует в момент запуска, List<String> или  List<Integer>  это  List. Generic только работает с компиляторами для управления кодом программиста. Это означает, что компилятору Java нужно знать точно, что такое  <T> для компиляции (compile) new T[10];. Если не знает точно, то по умолчанию считает T как Object. Тогда:
 
// Допустим Java позволяет инициализировать массив Generic:
T[]  tarray = new T[10];

// Во время компиляции (Compile-time)
// компилятор будет считать <T> как Object.
// Команда выше индентична:
T[] tarray  = new Object[10];

// Если во время запуска приложения, вы определяете <T> как String.
// Значит:
String[] tarray = new Object[10];

// Пункт выше не разрешен. Причина:
// Type mismatch: cannot convert from Object[] to String[]
Если вы хотите инициализировать массив Generic вам нужно передать Java объект  Class<T>, помогая Java создать массив generic в момент запуска используя  Java Reflection. Смотрите изображенный пример:
GArray.java
package org.o7planning.tutorial.generics.a;

import java.lang.reflect.Array;

public class GArray<T> {

  private Class<T> tclass;

  private T[] myArray;

  public GArray(Class<T> tclass) {
      this.tclass = tclass;

      final int size = 10;
      myArray = (T[]) Array.newInstance(tclass, size);
  }

  public T[] getMyArray() {
      return this.myArray;
  }

}
GArrayDemo.java
package org.o7planning.tutorial.generics.a;

public class GArrayDemo {

   public static void main(String[] args) {

       GArray<Integer> garray = new GArray<Integer>(Integer.class);

       Integer[] myArray = garray.getMyArray();

       myArray[0] = 1;
       myArray[2] = 0;
   }

}

6- Generics с Wildcards

В коде Generic, знак вопроса (?), называется wildcard, который представляет неопределенный вид. Вид параметра wildcard (wildcard parameterized type) это случай вида Generic, там где минимум один параметр является wildcard.
Пример вида параметра wildcard (wildcard parameterized) :
  • Collection<?>
  • List<? extends Number>
  • Comparator<? super String>
  • Pair<String,?>.
Wildcard могут использованы в разныъ ситуациях: как вид параметра, поле (field), или локальная переменная; иногда как возвратный вид (Будет объяснено в практических примерах). Wildcard никогда не использованы как аргумент для вызова метода Generic, создания объекта class generic, или супертип (supertype).
Wildcard находящиеся в разных позициях имеют разные значения:
  • Collection<?> denotes all instantiations of the Collection interface regardless of the type argument.
  • List<? extends Number> denotes all list types where the element type is a subtype of Number.
  • Comparator<? super String> denotes all instantiations of the Comparator interface for type argument types that are supertypes of String.
Wildcard вида параметра не является конкретным видом, который может появиться в операторе new. Он только является подсказкой применяющегося правила, с помощью generics java, что какой вид имеет действие в определенном случае, где wildcard был использован. 
  • Пример:
Collection<?> coll = new ArrayList<String>();

// Набор содержит только вид Number или подвид Number
List<? extends Number> list = new ArrayList<Long>();

// Объект с подстановочным видом параметра.
// (A wildcard parameterized type)
Pair<String,?> pair = new Pair<String,Integer>();
Некоторые недействительные объявления.
// String не является подвидом Number, поэтому ошибка.
List<? extends Number> list = new ArrayList<String>();  

// String не является родительским видом Integer, поэтому ошибка.
ArrayList<? super String> cmp = new ArrayList<Integer>();

6.1- Examples with wildcard

WildCardExample1.java
package org.o7planning.tutorial.generics.w;

import java.util.ArrayList;

public class WildCardExample1 {

	public static void main(String[] args) {

		// Список содержащий элементы вида String.
		ArrayList<String> listString = new ArrayList<String>();

		listString.add("Tom");
		listString.add("Jerry");

		// Список содержащий элементы вида Integer
		ArrayList<Integer> listInteger = new ArrayList<Integer>();

		listInteger.add(100);

		// Вы не можете объявить:
		ArrayList<Object> list1 = listString; // ==> Error!

		// Объект с подстановочным видом параметра.
		// (wildcard parameterized object).
		ArrayList<? extends Object> list2;

		// Вы можете объявить:
		// ​​​​​​​
		list2 = listString;

		// Или
		list2 = listInteger;

	}

}
WildCardExample2.java
package org.o7planning.tutorial.generics.w;

import java.util.ArrayList;
import java.util.List;

public class WildCardExample2 {

   public static void printElement(List<?> list) {
       for (Object e : list) {
           System.out.println(e);
       }
   }

   public static void main(String[] args) {

       List<String> names = new ArrayList<String>();
       names.add("Tom");
       names.add("Jerry");
       names.add("Donald");

       List<Integer> values = new ArrayList<Integer>();

       values.add(100);
       values.add(120);

       System.out.println("--- Names --");

       printElement(names);

       System.out.println("-- Values --");

       printElement(values);

   }

}

6.2- Тип wildcard parameterized не может использовать методы Generics

ValidWildcard1.java
package org.o7planning.tutorial.generics.w;

import java.util.ArrayList;

public class ValidWildcard1 {

	public static void main(String[] args) {

		// Список содержащий элементы вида String.
		ArrayList<String> listString = new ArrayList<String>();

		// Использовать метод generic: add(E).
		// Добавить элемент не null в список
		listString.add("Tom");

		listString.add("Jerry");

		// Добавить элемент null в список.
		listString.add(null);
	}

}
InvalidWildcard1.java
package org.o7planning.tutorial.generics.w;

import java.util.ArrayList;

public class InvalidWildcard1 {

	public static void main(String[] args) {
 
		// Список с подстановочным видом параметра.
		// (wildcard parameterized type).
		ArrayList<? extends Object> listWildcard = listString;

		// Вы не можете использовать метод add(E)
		// с параметром отличающимся от null.
		listWildcard.add("Tom"); // ==> Error!

		listWildcard.add("Jerry"); // ==> Error!

		// Добавить элемент null в список.
		listWildcard.add(null);

	}

}

6.3- Wildcard не может участвовать в new операторе

Wildcard вида параметра(wildcard parameterized type) не является конкретным видом, и не может появиться в операторе  new.
// Параметр Wildcard не может участвовать в операторе new.
List<? extends Object> list= new ArrayList<? extends Object>();

View more categories: