Рассмотрим следующую задачу: нам нужно иметь класс/классы, который(-ые) свои полем содержит(-ат) то String, то int[].
Мы можем завести отдельные классы на каждый тип:
Генерики в java - это пример параметрического полиморфизма[1, 2]. Он реализован во многих популярных объектно-ориентированных языках программирования. Но он реализуется различными способами:
- в Java через type erasure
- в C# через reinfication
- в C++ через templates
Что это значит? Почему приняты такие решения в каждом из языков? Какие преимущества и недостатки этих решений?
P.S. В каком случае можно использовать:
- new T();
- new T[];?
Мы можем завести отдельные классы на каждый тип:
public class StringHolder {
    private String data;
    public String getData() {
        return data;
    }
    public void setData(String data) {
        this.data = data;
    }
}
public class IntArrayHolder {
    private int[] data;
    public int[] getData() {
        return data;
    }
    public void setData(int[] data) {
        this.data = data;
    }
}
    Все отлично, за исключением того, что каждого нового хранимого типа придется создавать отдельный класс "хранитель" (holder).
    Можно воспользоваться тем, что у всех ссылочных типов есть общий предок - java.lang.Object и сделать "универсальное хранилище"
public class ObjectHolder {
    private Object data;
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
}
    Оно неплохо работает, хотя и приходится делать явное приведение типов
public class HolderStringTest {
    public static void main(String[] args) {
        ObjectHolder holder = new ObjectHolder();
        holder.setData("Hello!");
        
        String str = (String) holder.getData();
        System.out.println(str);
    }
}
>> Hello!
    Или с int[]   
import java.util.Arrays;
public class HolderIntArrayTest {
    public static void main(String[] args) {
        ObjectHolder holder = new ObjectHolder();
        holder.setData(new int[]{10, 20, 30});
        int[] arr = (int[]) holder.getData();
        System.out.println(Arrays.toString(arr));
    }
}
>> [10, 20, 30]
    Надо понимать, что явные приведения типов это обработка ситуации, когда компилятор не может удостовериться в правильности программы с точки зрения типов. 
    В нашем примере проблемы возникают, когда мы забыли, что положили в хранилище: 
x
public class HolderWrongTest {
    public static void main(String[] args) {
        ObjectHolder holder = new ObjectHolder();
        holder.setData(new int[]{10, 20, 30});
        String str = (String) holder.getData();
        System.out.println(str);
    }
}
>> Exception in thread "main" java.lang.ClassCastException: [I cannot be cast to java.lang.String
    Решением являются generics    
public class GenericHolder<T> {
    private T data;
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
}
    Отметьте отсутствие явного приведения типов в примере использования GenericHolder ниже
import java.util.Arrays;
public class GenericHolderTest<T> {
    public static void main(String[] args) {
        // set String
        GenericHolder<String> strHolder = new GenericHolder<>();
        strHolder.setData("Hello!");
        // get String
        String str = strHolder.getData();
        System.out.println(str);
        // set int[]
        GenericHolder<int[]> intArrayHolder = new GenericHolder<>();
        intArrayHolder.setData(new int[] {10, 20, 30});
        // get int[]
        int[] arr = intArrayHolder.getData();
        System.out.println(Arrays.toString(arr));        
    }
}
    Генерики - это способ полностью определить поведение (класс GenericHolder) не определив для кого он применим. "Доопределение" происходит в момент создания экземпляра.
    Не компилируется!
import java.util.Arrays;
public class WrongGenericHolderTest<T> {
    public static void main(String[] args) {
        GenericHolder<String> holder = new GenericHolder<>();
        holder.setData("Hello!");
        int[] arr = holder.getData();
        System.out.println(Arrays.toString(arr));
    }
}
    Явное приведение типов тоже не помогает, компилятор зорко следит за соответствием типов:
    Не компилируется!
import java.util.Arrays;
public class WrongGenericHolderTest<T> {
    public static void main(String[] args) {
        GenericHolder<String> holder = new GenericHolder<>();
        holder.setData("Hello!");
        int[] arr = (int[]) holder.getData();
        System.out.println(Arrays.toString(arr));
    }
}
x
Обмануть можно, но придется очень сильно постараться:
Обмануть можно, но придется очень сильно постараться:
import java.util.Arrays;
public class WrongGenericHolderTest<T> {
    public static void main(String[] args) {
        GenericHolder<String> holder = new GenericHolder<>();
        holder.setData("Hello!");
        int[] arr = (int[]) (Object) holder.getData();
        System.out.println(Arrays.toString(arr));
    }
}
x
x 
x
x
x 
x
x
x 
x
x
x 
x
x
x 
Генерики в java - это пример параметрического полиморфизма[1, 2]. Он реализован во многих популярных объектно-ориентированных языках программирования. Но он реализуется различными способами:
- в Java через type erasure
- в C# через reinfication
- в C++ через templates
Что это значит? Почему приняты такие решения в каждом из языков? Какие преимущества и недостатки этих решений?
P.S. В каком случае можно использовать:
- new T();
- new T[];?
