Создать метод, возвращающий конкретный тип потомка обобщенного типа
Столкнулся с непониманием между мной и явой в вопросе вывода типа. А именно: не могу написать метод, возвращающий не просто коллекцию, а конкретного потомка коллекции.
public static <T, L extends Collection<T>> L genList(){
return (L)new ArrayList<T>();
}
По задумке, этот метод вернет потомка Collection, причем все методы потомка будут доступны. На практике, этот код вообще не работает, пока не кастанешь в L. А если кастанешь - распрощаешься с методами потомка. Как же так, если по-идее ArrayList является потомком Collection?
На код выше меня сподвиг рабочий код:
public static <T, L extends Collection<T>> L getCollection(L list, T t1, T t2, T t3){
list.add(t1);
list.add(t2);
list.add(t3);
return list;
}
Смысл этого кода в том же - получить объект, с которым можно работать, как с коллекцией. А на выходе получить этот же объект с сохранением типа.
Почему этот код работает, а код выше - нет? Не пойму.
Ну и на закуску очень странный для меня код шреденгера, который работает и не работает одновременно.
public static <T, L extends Collection<T>> L genCollection(Supplier<L> gen, T t1, T t2, T t3){
L list = gen.get();
list.add(t1);
list.add(t2);
list.add(t3);
return list;
}
Отличие от предыдущего кода в том, что мы на вход подаем не коллекцию, а лямбду, возвращающую коллекцию. И, в зависимости от того, что мы туда передадим, у нас код будет возвращать как конкретный тип, так и обобщенный.
Возьмем к примеру, следующий тип:
public class ItWorks<T> extends ArrayList<T> {
public void itWorks(){
System.out.println("It works!");
}
}
И применим к нему методы:
getCollection(new ItWorks<>(), 1,2,3).itWorks();
genCollection(ItWorks::new,1,2,3);
genCollection(() -> new ItWorks<>(),1,2,3).itWorks();
Первый и третий методы возвращают сам тип, а второй - обобщенную коллекцию. Что не так?
Ответы (1 шт):
В java generics invariant-ны, к сожалению. И еще их постигает type erasure,
В scala (covariant/contravariant) и kotlin (in/out), которые работают на JVM это улучшили: https://kotlinlang.org/docs/reference/generics.html
Вообще тема сложная.
Есть время компиляции (при которой происходит проверка типов) и время исполнения программы.
Во время исполнения классы есть, а дженериков уже нет: Collection и ArrayList отличаются, а Collection<String> от Collection<Item> не отличается.
При этом если куда-то в качестве Collection-а пришел ArrayList, то всё нормально, будут работать методы, переопределенные для ArrayList.
И скастить в ArrayList тоже получится (хотя увлекаться не стоит).
Есть время компиляции типы проверяются более основательно, с дженериками.
getCollection компилится, т. к. на входе и выходе один и тот же объект одинакового типа L.
genList без кастинга не компилится, т. к. (хотя и ArrayList и L являются подтипами Collection) компилятор не может доказать взаимозаменяемость ArrayList и L.
Предположим, в конкретном вызове genList захочется L как Set (он же тоже подтип Collection), а вы ему ArrayList возвратите.
Эту ошибку компилятор не пропускает. А если сделать каст (L), то компилятор успокоится. А потом исполнение упадет где-то, т. к. окажется, что ArrayList не умеет быть Set.
применяем genList в методах, где тип выводится однозначно, таким образом он передается в метод genList()
Чтобы создать коллекцию нужного типа, нужен конструктор/фабрика для этого типа. Это можно наблюдать в genCollection(Supplier.
Tип не передается, у genList() аргументов нет.
Можно в genList() передать Class<...> вместо фабрики:
- и сделать реестр фабрик, из которого по классу брать фабрику
- или применить к классу рефлекшн