Reference to method with .apply

Viewed 106

I have a List called "racers" of simple class

class Racer {
    private String name; 
    private String teamName;
    // and getters
}

I am using а method to find maximum lengths of the fields in the list:

int maxRacerNameLength = getMaxFieldLength(racers, Racer::getName);
int maxTeamNameLength = getMaxFieldLength(racers, Racer::getTeamName);

Method implementation:

private int getMaxFieldLength(List<Racer> racers, Function<Racer, String> getter) {
    return racers.stream().mapToInt(racer -> getter.apply(racer).length()).max().getAsInt();
}

How do I get rid of this last lambda using method reference?

3 Answers

You can do it by breaking mapToInt(racer->getter.apply(racer).length()) into two steps:

return racers.stream().map(getter::apply).mapToInt(String::length).max().getAsInt();

Demo:

public class Main {
    public static void main(String[] args) {
        List<Racer> racers = List.of(
                                        new Racer("Andy", "One"), 
                                        new Racer("Thomas", "Four"), 
                                        new Racer("John", "One"),
                                        new Racer("Newton", "Four")
                                    );

        System.out.println(getMaxFieldLength(racers, Racer::getName));
        System.out.println(getMaxFieldLength(racers, Racer::getTeamName));
    }

    static int getMaxFieldLength(List<Racer> racers, Function<Racer, String> getter) {
        return racers.stream().map(getter::apply).mapToInt(String::length).max().getAsInt();
    }
}

Output:

6
4

Kinds of Method References:

Kind Example
Reference to a static method ContainingClass::staticMethodName
Reference to an instance method of a particular object containingObject::instanceMethodName
Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName
Reference to a constructor ClassName::new

Learn more about it from Method References.

You can achieve what you want as follows:

private int getMaxFieldLength(List<Racer> racers, Function<Racer, String> getter) {
    return racers.stream()
                 .map(getter)
                 .mapToInt(String::length)
                 .max()
                 .getAsInt();
}

However, I wouldn't recommend getting the value of OptionalInt using getAsInt. That's a slippery slope. The list of racers might be empty, that will cause NoSuchElementException.

You should either return a default value in that case (0 seems fine here):

private int getMaxFieldLength(List<Racer> racers, Function<Racer, String> getter) {
    return racers.stream()
                 .map(getter)
                 .mapToInt(String::length)
                 .max()
                 .orElse(0);
}

or return OptionalInt and let the method caller deal with it:

private OptionalInt getMaxFieldLength(List<Racer> racers, Function<Racer, String> getter) {
    return racers.stream()
                 .map(getter)
                 .mapToInt(String::length)
                 .max();
}

If the implementation is bound to always find the max of a Stream of integers, you could rather prefer a ToIntFunction over a Function<Racer, String>. This would change the implementation and signature of the method to:

private int getMaxFieldLength(List<Racer> racers, ToIntFunction<Racer> toLength) {
    return racers.stream()
            .mapToInt(toLength)
            .max()
            .orElse(Integer.MIN_VALUE); // or throw exception for empty racers
}

This would in turn the usage of the method to something like:

int maxRacerNameLength = getMaxFieldLength(racers, racer -> racer.getName().length());
int maxTeamNameLength = getMaxFieldLength(racers,racer -> racer.getTeamName().length());
Related