Java документация гласит, что джинерики работают с помощью механизма "стирания", т.е. это:
class Test<T> {
T foo;
T bar;
Test(T o) {
}
}
Будет превращено во время компиляции в это:
class Test {
Object foo;
Object bar;
Test(Object o) {
}
}
Ведь так? Хорошо, тогда почему такой код не работает?
class Test<T> {
T[] foo;
T bar;
Test(T o) {
foo = new T[5];
bar = new T();
}
}
Ведь, по идеи, этот код во время компиляции станет таким:
class Test {
Object[] foo;
Object bar;
Test(Object o) {
foo = new Object[5];
bar = new Object();
}
}
И никаких проблем быть не должно, но они есть. Почему так происходит?
Массивы отличаются от обобщённых типов в двух важных аспектах. Во-первых, массивы ковариантны (covariant). Это жуткое слово значит просто, что если Sub является подтипом Super, тогда тип массива Sub[] является подтипом Supeг[]. Средства обобщённого программирования, напротив, инвариантны (invariant): для любых двух отдельных типов Туре1 и Туре2 тип List не является ни подтипом, ни супертипом для List [JLS, 4.10; Naftalin07, 2.5]. Вы можете подумать, что это недостаток средств обобщённого программирования, но можно поспорить, что недостатком, напротив, обладают именно массивы.
Вторым важным отличием массивов от обобщённых типов является то, что массивы реифицированы (reified) [JLS, 4.7]. Это значит, что массивы знают и проверяют тип своих элементов при выполнении. Как выше было сказано, если вы попытаетесь сохранить String в массив Long, вы получите исключение ArrayStoreException. Обобщённые типы, напротив, реализуются стиранием (erasure) [JLS, 4.6]. Это значит, что они проверяют свои ограничения типов только на этапе компиляции и затем выбрасывают (или стирают) информацию о типах элементов при выполнении. Стирание позволяет обобщённым типам легко взаимодействовать со старым кодом, который не использует средства обобщённого программирования.
По причине этих фундаментальных различий массивы и обобщённые типы не могут применяться одновременно. Например, нельзя создавать массив обобщённых типов, параметризованных типов или параметров типа. Ни одно из этих выражений создания массивов не является разрешённым: new List[], new List[], new E[]. Все выражения приведут к ошибкам создания обобщённых массивов на этапе компиляции. Почему нельзя создавать обобщённые массивы? Потому что это небезопасно. Если бы это было разрешено, приведение типов, генерируемое компилятором в правильно написанной программе, могло бы вызвать исключение ClassCastException. Это бы нарушило фундаментальные гарантии, которые даёт система обобщённых типов.
© Joshua Bloch "Effective Java", глава 5, статья 25
T
- это тип. Ключевое слово new
вызывает конструктор класса. Но у типов нет ни методов, ни констукторов.
Ради интереса представим что new T()
работало бы. Пусть у нас будет дженерик метод вызывающий конструктор внутри, и пусть будет класс User
с нестандартным конструктором:
class User {
String name;
User(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
User user = Test.<User>genericFoo();
System.out.println(user.name);
}
static <T> T genericFoo() {
return new T();
}
}
Получается что дженерик метод вызывает конструктор который не существует в природе!
К моему сожалению, в Джаве порой трудно различить типы и классы. Например, вызов статического метода класса выглядит так, будто метод вызывается из типа - Test.<User>genericFoo()
. Но типы не имеют методов.
У типов и классов есть свои иерархии. Типы там связаны между собой через сабтайпинг, классы связаны через наследование. Часто эти иерархии похожи.
Рассмотрим типичный случай:
interface Animal {
default String makeVoice() {
return "Grrrr";
}
}
class Dog implements Animal { }
class Cat implements Animal { }
1) Благодаря наследованию мы можем вызвать методы полученные по наследству.
2) Благодаря сабтайпингу мы можем положить "котов" в переменную для "животных".
public class Test2 {
public static void main(String[] args) {
String voiсe = new Dog().makeVoice();
Cat cat = new Cat();
Animal animal = cat;
}
}
Другой пример, когда иерархия типов не совпадает с наследованием:
public class Test3 {
public static void main(String[] args) {
Dog[] dogs = {};
Animal[] animals = dogs;
LinkedList<Dog> dogsList = new LinkedList<Dog>();
LinkedList<Animal> animalsList = dogsList; // тут не компилируется
}
}
Массив собак никогда не наследовал массив животных, но в иерархии типов массив собак - является подтипом массива животных. Благодаря чему компилятор разрешает нам класть массив собак в переменную для массива животных. Как писали другом ответе - это свойство называется ковариантность.
Листы (как и остальные дженерик классы) - инвариантны, т.е. так как лист собак не наследует лист животных, то и в иерархии типов - они не связаны. А значит присваивать их компилятор не разрешает.
Надеюсь этот опус немного помог различать типы и классы.
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Виртуальный выделенный сервер (VDS) становится отличным выбором
Используя сторонние библиотеки, или нативные библиотеки Java, можно сделать цветной шрифт в терминале? Может что-то наподобие printf? И есть возможность...
интересует момент: для каждого репозитори - свой сервис? И потом уже создавать общий сервис, связывая другие сервисы, или же, все таки можно...
Пишу не большого бота с базой sqliteВ базе две таблицы: products и categories_to_subcategories