2

I'm not Java developer, but I have many years of experience in C#. I have a List<Foo> that I need to convert to a List<Bar> using ModelMapper where Foo and Bar have essentially identical properties.

Currently I've written this as:

@AutoWired ModelMapper modelMapper;

...

List<Bar> results = repository
        .getFoos()
        .stream()
        .map(x -> modelMapper.map(x, Bar.class))
        .collect(Collectors.toList());

This works fine. However, I feel like that lambda expression could be replaced with just a simple method reference. If this were C#, I'd probably be able to do something along the lines of this:

var results = repository.getFoos().Select(modelMapper.map<Bar>).ToList();

But I can't find the right syntax in Java. I've tried this:

.map(modelMapper::<Bar>map)

But I get the error:

Cannot resolve method 'map'

I am not sure if this is because I've mixed up the syntax somehow, or this is because the map method has a too many overloads to create an unambiguous reference. In case it helps, the overload of map that I'm trying to use is defined as:

public <D> D map(Object source, Class<D> destinationType)

Is there any way to achieve this mapping without a lambda expression?

p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • 2
    No, it can't, since it needs an additional argument: Bar.class. – JB Nizet Jul 17 '19 at 21:56
  • In this case your `modelMapper.map()` method need a signature like `Bar map(Foo foo) {}`. So you need to remove the second parameter for this. It is not possible to use 2 parameters in `modelMapper.map()` with method reference in your case. – Samuel Philipp Jul 17 '19 at 21:56

3 Answers3

3

I am not sure if this is because I've mixed up the syntax somehow, or this is because the map method has a too many overloads to create an unambiguous reference.

You've not messed up the syntax; there's no equivalent in Java (it's often more verbose than C#, and streams are no exception.)

You can only use the method reference (double colon) syntax when the parameters you want to pass to the method are the same, and in the same order, as the parameters in the functional interface.

In the case of map, there's only one parameter, and you need to pass a second parameter (that being Bar.class) to modelMapper.map(), so no method reference syntax is allowed. The only way you could use it were if you were to subclass the modelMapper to work with Bar only, and therefore remove the need for the second explicit class parameter.

I'm pretty confident that the method you're using there is the most concise way of doing things in Java.

Michael Berry
  • 70,193
  • 21
  • 157
  • 216
  • 1
    Thanks for the explanation (and to the other answerers). I had gotten a bit confused by the double declaration of `` and `Class`. I was under the mistaken impression that the second parameter was a kind of syntactic sugar for `map(x)`, but rather, it's a way to infer `` from `Bar.class`. C# can't do this because `typeof(T)` yields a non-generic `Type`. On further inspection, a more accurate C# equivalent would be `.Select(x => modelMapper.map(x, typeof(Bar)))`, which is actually more verbose than the Java due to this fact. – p.s.w.g Jul 17 '19 at 23:46
1

Actually this can not be done, because your modelMapper.map() method requires two parameters. If you really want to use method reference at this point you could create a new method in your modelMapper, which requires only one parameter or wrap the modelMapper.map() call in another method like this:

private Bar mapFooToBar(Foo x) {
    return modelMapper.map(x, Bar.class);
}

You now can use this method with method reference:

List<Bar> results = repository.getFoos().stream()
        .map(this::mapFooToBar)
        .collect(Collectors.toList());

But at the end I don't think that this will make your code better in any way, so I would recommend using the lambda expression you already have.

Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56
1

Java doesn't support partial function application (related to currying), so you can only do that with a lambda.

Of course, since you don't reference any local variables or parameters, you can define a method which you can then reference.

@AutoWired ModelMapper modelMapper;

private Bar mapToBar(Foo x) {
    return modelMapper.map(x, Bar.class);
}

...

List<Bar> results = repository
        .getFoos()
        .stream()
        .map(this::mapToBar)
        .collect(Collectors.toList());

That is actually very close to how the lambda is compiled, except that mapToBar for the lambda would be a hidden (synthetic) method.

Andreas
  • 154,647
  • 11
  • 152
  • 247