Возврат определенного типа коллекции в зависимости от типа аргумента

Вопрос или проблема

Я пытаюсь написать маппер, который может принимать 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;
    }
}

Объяснение кода

  1. Типы: Мы используем два параметра типа: C, который расширяет Collection<A>, и B, который является типом, в который мы хотим преобразовать элементы.

  2. Метод aToB:

    • Принимает collection – вашу входную коллекцию типа C.
    • Вызывает mapper, чтобы преобразовать каждый элемент из A в B.
    • Принимает collectionFactorySupplier, который будет использоваться для создания новой коллекции нужного типа.
  3. Использование:
    Чтобы использовать этот метод, вы можете вызвать его следующим образом:
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, вы сможете реализовать необходимую функциональность с учетом типов. Это позволяет писать более обобщенный и гибкий код. Надеюсь, данное объяснение поможет вам лучше понять ситуацию и найти оптимальное решение!

Оцените материал
Добавить комментарий

Капча загружается...