Рассмотрим следующую задачу: нам нужно иметь класс/классы, который(-ые) свои полем содержит(-ат) то 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[];?