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

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) {

       // Create a list, aim to contain usernames
       ArrayList userNames = new ArrayList();

       // Add username to list.
       userNames.add("tom");
       userNames.add("jerry");


       // You accidentally add an element not String to the list.
       // (This is allowed).
       userNames.add(new Integer(100));


       // And get the first element
       // It is an Object (But you know it is a String)
       // ==> Tom
       Object obj1 = userNames.get(0);

       // Cast to String.
       String userName1 = (String) obj1;

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


       // And get the second element
       // (You know it is a String)
       // ==> jerry
       String userName2 = (String) userNames.get(1);

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

     
       // Get the 3rd element (Actually it is an Integer).
       // (Error casts happen here).
       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) {
       
       // Create a list, aim to contain usernames
       ArrayList<String> userNames = new ArrayList<String>();

        // Add string to list
       userNames.add("tom");
       userNames.add("jerry");

        // You can not add an element not a String
       userNames.add(new Integer(100)); // Compile Error!

       // You do not need to 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) {

     
       // Create KeyValue object.
       // Integer: Phone Number (K = Integer)
       // String: Name (V = String)
       KeyValue<Integer, String> entry = new KeyValue<Integer, String>(12000111, "Tom");
       
       // Java understands that the return type is a Integer
       // (K = Integer)
       Integer phone = entry.getKey();
       
       // Java understands that the return type is a 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;

// This class extends KeyValue<K,V>
// And specify K, V
// K = Integer (Phone Number)
// V = String (Name)
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 understands that the return type is Integer.
       Integer phone = entry.getKey();

       // Java understands that the return type is String.
       String name = entry.getValue();

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

   }

}

Пример 2:

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


// This class extends KeyValue<K,V>
// Specify the parameter K is String.
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) {

       // (Emp Number, Employee Name)
       // V = String (Employee Name)
       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;


// This class extends KeyValue<K,V>
// It has added a parameter 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) {
    // If exceptions Mistake occurs, this block will be executed
    ...
} catch( Mistake<User> eu) {
     // This block is never executed
    ...
}

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>: To say that this method has two parameters K, V
   // Method returns K.
   public static <K, V> K getKey(KeyValue<K, V> entry) {
       K key = entry.getKey();
       return key;
   }

 
   // <K, V>: To say that this method has two parameters K, V
   // Method returns V.    
   public static <K, V> V getValue(KeyValue<K, V> entry) {
       V value = entry.getValue();
       return value;
   }

 
   // ArrayList <E>: The list contains the elements of type E
   // Method returns the type 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);

       // A list containing the element type KeyValue<Integer, String>.
       ArrayList<KeyValue<Integer, String>> list = new ArrayList<KeyValue<Integer, String>>();

       // Add element to list
       list.add(entry1);
       list.add(entry2);

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

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

}

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

Иногда вы хотите инициировать объект  Generic:
// Generic Object Initialization

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

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

import java.util.Date;

public class Bar {

   // Class này phải có cấu tử mặc định
   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.
// You can declare a generic array of generic.

T[] myarray;

// But you can not initialize a generic array.
// (This is not allowed).

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;
   }

   // Returns the last element of the 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[] names = new String[] { "Tom", "Jerry" };

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

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

}
Возвращаясь к вопросу, почему Java не поддерживает инициализацию массива  Generic:
// Why Java don't support to initialize Generic array?

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

T[]  tarray = new T[10];


// At the time of compilation, the compiler will consider T as Object.
// This command is equivalent to.

T[] tarray  = new Object[10];

// At runtime of the application, if T is defined as String.
// It means that:

String[] tarray = new Object[10];

// The above is not allowed. Reason:
// 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>();

// list type where the element type is a subtype of Number.
List<? extends Number> list = new ArrayList<Long>();

// A wildcard parameterized type.
Pair<String,?> pair = new Pair<String,Integer>();
Некоторые недействительные объявления.
// String is not subclass of Number; so error
List<? extends Number> list = new ArrayList<String>();  

// String is not superclass of 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) {

     
       // A list containing the elements of type String.
       ArrayList<String> listString = new ArrayList<String>();
       
       listString.add("Tom");
       listString.add("Jerry");

 
       // A list containing the elements of type Integer.
       ArrayList<Integer> listInteger = new ArrayList<Integer>();
       
       listInteger.add(100);

   
       // You can not declare:
       // ArrayList<Object> list = listString;        

       // A wildcard parameterized object
       ArrayList<? extends Object> list;

       // You can declare:
       list = listString;

       // Or
       list = 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) {

       // ArrayList<E>    
       // A list containing the elements of type String.
       ArrayList<String> listString
                           = new ArrayList<String>();

     
       // Using generic method: add(E)
       // Add not null element to list
       listString.add("Tom");

       listString.add("Jerry");


       // Add null element to list
       listString.add(null);
   }

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

import java.util.ArrayList;

public class InvalidWildcard1 {

   public static void main(String[] args) {

       // ArrayList<E>    
       // A list containing the elements of type String.
       ArrayList<String> listString = new ArrayList<String>();

       // A wildcard parameterized type
       ArrayList<? extends Object> listWildcard = listString;

 
       // Using generic method: add(E)
       // A wildcard parameterized object can not be used
       // generic method with generic parameter not null.
       listWildcard.add("Tom"); // Error!

       listWildcard.add("Jerry"); // Error!
       
       // But can use generic methods
       // with null parameter.
       listWildcard.add(null);

   }

}

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

Wildcard вида параметра(wildcard parameterized type) не является конкретным видом, и не может появиться в операторе  new.
// Wildcard can not participate in the 'new' operator.
List<? extends Object> list= new ArrayList<? extends Object>();