-1

I've been trying to find a way to write a generic function -- possibly not using generics -- to map over a collection.

Let's say I have a function A to B, I'd like to write a function that takes a Collection<A> and returns a Collection<B>. NOTE that A and B are not generics, just a way to express a general pattern.

What I have so far is

public static Collection<Point> points2dToPoints(Collection<Point2D> points) {
    return points.stream()
            .map(Utils::point2dToPoint)
            .collect(Collectors.toCollection(() -> points));

}

However, I get a type error in the .collect, because obviously I want the new collection to be Collection<Point> but I'm not sure how to get a Supplier for that?

Edit: I would like to be able to use this function in a generic way: if I pass it a Set I get a Set in return, but if I pass it a List I get a list in return. is it even possible to do this?

dreamcrash
  • 47,137
  • 25
  • 94
  • 117
Lorenzo
  • 2,160
  • 12
  • 29
  • 6
    Just use `Collectors.toList()`. – Louis Wasserman Nov 27 '20 at 20:22
  • @LouisWasserman what if I don't know if it's a list? I would like to be able to pass it a Set, and get a Set in return, but when I pass it a List I get a List – Lorenzo Nov 27 '20 at 21:54
  • that is an important detail to specify. It is _impossible_ to solve that problem in general: you can't guarantee that the kind of collection you get in can even be created by other code at all. All you can do is attempt to catch common cases: `if (points instanceof Set) { // use toSet... }` – Louis Wasserman Nov 27 '20 at 22:12

2 Answers2

3

The best option is to not overcomplicate, and simply do:

public static Collection<Point> points2dToPoints(Collection<Point2D> points) {
    return points.stream()
            .map(Utils::point2dToPoint)
            .collect(Collectors.toList());
}

returning a concrete implement of interface Collection (e.g., Collectors.toList()) while hiding from the outside the implementation details, (i.e., having Collection in the method signature).

You can, however, make your method more generic by passing to it -- as Supplier -- which concrete implementation of the interface Collection you want it to return, namely

 public static Collection<Point> points2dToPoints(Collection<Point2D> points, Supplier<Collection<Point>> aNew) {
        return points.stream()
                .map(Utils::point2dToPoint)
                .collect(toCollection(aNew));

In this way you can pass the concrete Collection implementation that will be returned, for example:

points2dToPoints(.., ArrayList::new);
points2dToPoints(.., TreeSet::new);
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
dreamcrash
  • 47,137
  • 25
  • 94
  • 117
2

Replace the statement Collectors.toCollection(() -> points) with Collectors.toList().

Demo:

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

class Car {
    String name;

    public Car(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Car [name=" + name + "]";
    }
}

class Book {
    Car car;

    public Book(Car car) {
        this.car = car;
    }

    public Car getCar() {
        return car;
    }
}

public class Main {
    public static void main(String[] args) {
        // Test
        Collection<Car> list = points2dToPoints(
                List.of(new Book(new Car("Toyota")), new Book(new Car("Ford")), new Book(new Car("Suzuki"))));

        list.forEach(System.out::println);
    }

    public static Collection<Car> points2dToPoints(Collection<Book> points) {
        return points.stream().map(Book::getCar).collect(Collectors.toList());    
    }
}

Output:

Car [name=Toyota]
Car [name=Ford]
Car [name=Suzuki]
dreamcrash
  • 47,137
  • 25
  • 94
  • 117
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110