5

Let's say I have this code:

Map<String, String> map;
// later on
map.entrySet().stream().map(MyObject::new).collect(Collectors.toList());

And I have a MyObject Constructor which takes two arguments of type String. I want to be able to do this but I cannot. I know I can do e -> new MyObject(e.getKey(), e.getValue()) but prefer MyObject::new.

Similar code works for Set<String> and List<String> with one argument constructor of MyObject class.

fastcodejava
  • 39,895
  • 28
  • 133
  • 186
  • 2
    the ``map`` function is not aware that the 2 necessary ``String``s are somewhere inside that ``Map.Entry`` instance. You have to manually extract key and value, thus you need to use ``e.getKey()`` and ``e.getValue()``. – f1sh Dec 12 '18 at 19:31
  • 1
    Why do you prefer a method reference? It's elegant when it fits the requirements, but here it doesn't. – RealSkeptic Dec 12 '18 at 19:33
  • 1
    see [this](https://stackoverflow.com/questions/29835382/use-method-reference-with-parameter) – Hadi J Dec 12 '18 at 19:33
  • 2
    You can use the `PairStream` from [this answer](https://stackoverflow.com/a/29254246/2711488); `PairStream.from(map).map(MyObject::new).collect(Collectors.toList());` – Holger Dec 13 '18 at 07:32

4 Answers4

10

use a lambda:

map.entrySet()
   .stream()
   .map(e -> new MyObject(e.getKey(), e.getValue()))
   .collect(Collectors.toList());

otherwise the only way to use a method reference is by creating a function as such:

private static MyObject apply(Map.Entry<String, String> e) {
      return new MyObject(e.getKey(), e.getValue());
}

then do something like:

map.entrySet()
   .stream()
   .map(Main::apply)
   .collect(Collectors.toList());

Where Main is the class containing the apply method.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
3
map.entrySet().stream().map(MyObject::new).collect(Collectors.toList()));

And I have a MyObject Constructor which takes two arguments of type String. I want to be able to do this but I cannot.

In map.entrySet().stream().map(...), Java is expecting a Function, mapping one value to another value. One value. From the stream, the function receives a value of type Map.Entry<String, String>, but your constructor takes two String arguments. Java doesn't automagically expand a Map.Entry<String, String> to a pair of Strings to pass to your constructor. You have to do that unwrapping manually.

janos
  • 120,954
  • 29
  • 226
  • 236
2

The problem with the constructor is that it defines two parameters, while Function#apply demanded by Stream#map accepts only one.

You could write a static factory method for MyObject

class MyObject {
    public static MyObject of(Map.Entry<String, String> e) {
        return new MyObject(e.getKey(), e.getValue());
    }
}

and refer to it like

map(MyObject::of)

Before you do so, ask yourself if one pretty-looking line in a plain processing chain somewhere is worthy of a new constructor or utility method.

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
2

Add a Map.Entry constructor

class MyObject {
        private final String k;
        private final String v;

        MyObject(String s1, String s2) {
            k = s1;
            v = s2;
        }

        MyObject(Map.Entry<String, String> e) {
            this(e.getKey(), e.getValue());
        }

        public String toString() {
            return "key: " + k + ' ' + "value: " + v;
        }
    }

You will be able to call

List<MyObject> myObjectList = map.entrySet().stream().map(MyObject::new).collect(Collectors.toList());