Вопрос или проблема
Я пытаюсь написать маппер, который может принимать Collection<A>
и возвращать Collection<B>
. Эта часть простая, но я пытаюсь сделать так, чтобы возвращаемая коллекция имела тот же тип, что и входящая.
Таким образом, вызов aToB(Set.of(new A()))
должен возвращать, например, Set<B>
. И более важно, я хочу, чтобы компилятор об этом знал.
Я пытался использовать дженерики, но, похоже, это не совсем возможно из-за того, что я не могу параметризовать дженерик. Следующее не работает:
public <C extends Collection> C<B> aToB(C<A> coll) {}
Что-то подобное теряет факт того, что коллекции должны быть одного типа, и компилятор тоже не в восторге:
public <C extends Collection<A>, D extends Collection<B>> D aToB(C coll) {}
Я понимаю, что это не практичный случай и что я должен просто написать несколько методов, что я и сделал, но мне интересно, возможно ли это.
В качестве дополнительного вопроса, мне также интересно, почему следующий код не компилируется, сообщая, что я предоставляю List, когда требуется C.
public <C extends Collection<String>> C aaa() {
List<String> a = List.of("AAA");
return a;
}
Спасибо
Это невозможно в Java.
Вам нужны отдельные методы для каждого типа коллекции.
PS: Вы можете попробовать Scala, если хотите, чтобы это работало.
PPS: Ваш дополнительный вопрос не работает, потому что есть другие классы, реализующие Collection
.
Я понимаю, что это не практичный случай и что я должен просто написать несколько методов, что я и сделал, но мне интересно, возможно ли это.
Это не так. Ближайшее, что можно сделать, – это потребовать от пользователя передать Supplier<Collection<B>>
, что не так уж и плохо, например, пользователи могут передавать ArrayList::new
.
В качестве дополнительного вопроса, мне также интересно, почему следующий код не компилируется, сообщая, что я предоставляю List, когда требуется C.
class Foo {
// допустим, это компилируется
public <C extends Collection<String>> C aaa() {
List<String> a = List.of("AAA");
return a;
}
// так что вы думаете, что произойдет здесь? это допустимый Java
public void bbb() {
HashSet<String> set = Foo.<HashSet<String>>aaa();
}
}
C
определяется вызовущим кодом. Если вы хотите пообещать пользователю, что собираетесь вернуть некоторый подтип Collection<String>
, но не указывать, что именно, то соответствующий тип метода—это
public Collection<String> aaa() { ... }
Ответ или решение
Конечно! Давайте рассмотрим вашу задачу и разберемся в нюансах работы с обобщениями (generics) в Java, чтобы вы могли создать метод, который будет возвращать коллекцию нужного типа в зависимости от входной коллекции.
Задача
Ваша цель состоит в том, чтобы написать метод, который принимает Collection<A>
и возвращает Collection<B>
, сохраняя при этом тип коллекции, переданной в качестве аргумента. Это действительно интересная задача, но в Java есть определенные ограничения, которые мы должны учесть.
Ограничения Java Generics
В Java нельзя напрямую параметризовать тот же тип коллекции, что и входной — то есть вы не можете создать общий метод, который будет возвращать коллекцию того же типа, что и входная, без использования Supplier
или аналогичных решений.
Решение с использованием Supplier
Чтобы реализовать необходимую функциональность, вы можете передавать Supplier
для создания новой коллекции типа B
. Вот пример:
import java.util.*;
import java.util.function.Supplier;
public class Mapper {
public <C extends Collection<A>, B> C aToB(C collection, Function<A, B> mapper, Supplier<C> collectionFactory) {
C resultCollection = collectionFactory.get();
for (A element : collection) {
resultCollection.add(mapper.apply(element));
}
return resultCollection;
}
}
Объяснение кода
-
Типы: Мы используем два параметра типа:
C
, который расширяетCollection<A>
, иB
, который является типом, в который мы хотим преобразовать элементы. -
Метод
aToB
:- Принимает
collection
– вашу входную коллекцию типаC
. - Вызывает
mapper
, чтобы преобразовать каждый элемент изA
вB
. - Принимает
collectionFactory
–Supplier
, который будет использоваться для создания новой коллекции нужного типа.
- Принимает
- Использование:
Чтобы использовать этот метод, вы можете вызвать его следующим образом:
Set<A> setOfA = Set.of(new A());
Mapper mapper = new Mapper();
Set<B> setOfB = mapper.aToB(setOfA, a -> new B(a), HashSet::new);
Отвечая на побочный вопрос
Ваш код не компилируется, потому что тип параметра C
должен быть задействован в контексте метода. Когда вы объявляете public <C extends Collection<String>> C aaa()
, компилятор требует, чтобы метод возвращал именно тот тип, который указан при вызове метода. Однако, если вы хотите, чтобы метод возвращал коллекцию типа Collection<String>
, проще реализовать метод так:
public Collection<String> aaa() {
List<String> a = List.of("AAA");
return a; // это допустимо, так как List является подтипом Collection
}
Заключение
Java обобщения имеют свои ограничения, и вы не можете создать универсальный метод, который будет возвращать коллекцию точно того же типа, что и входная. Однако, используя Supplier
, вы сможете реализовать необходимую функциональность с учетом типов. Это позволяет писать более обобщенный и гибкий код. Надеюсь, данное объяснение поможет вам лучше понять ситуацию и найти оптимальное решение!