-->

Differences between using a method reference and f

2019-04-13 07:57发布

问题:

When using Java 8 streams I often find that I need to refactor a multi-statement lambda expression. I will illustrate this with a simple example. Assume I have started writing this code:

Stream.of(1, 3).map(i -> {
    if (i == 1) {
        return "I";
    } else if (i == 3) {
        return "E";
    }
    return "";
}).forEach(System.out::println);

Now I am not very fond of the large lambda expression in the map call. Hence, I want to refactor it out of there. I see two options, either I make an instance of Function in my class:

private static Function<Integer, String> mapper = i -> {
    if (i == 1) {
        return "I";
    } else if (i == 3) {
        return "E";
    }
    return "";
};

and use it like this:

Stream.of(1, 3).map(mapper).forEach(System.out::println);

Or I simply make a method:

private static String map(Integer i) {
    if (i == 1) {
        return "I";
    } else if (i == 3) {
        return "E";
    }
    return "";
}

and use a method reference:

Stream.of(1, 3).map(Test::map).forEach(System.out::println);

Apart from the obvious matter of taste, are there any advantages or drawbacks to either approach?

For example, I know the stack traces become more readable in the method reference case, which is a small advantage.

回答1:

Unless there's some additional magic I'm unaware of, the current lambda implementation will desugar your non-capturing lambda into a static method and will cache the lambda instance. By doing the same thing explicitly (a static final reference to a lambda), you're basically duplicating that implicit work, so you're ending up with two cached references to the same thing. You are also defeating the lazy initialization of the lambda instance, which you'd otherwise be getting for free.

This is why I would prefer just the method reference: it's simpler to write, more idiomatic, and also seems to be more lightweight in terms of implementation.



回答2:

Some of the reasons to refactor the multi-line lambda expression into an ordinary method, and to use a method reference in the stream, are maintainability and testability.

When the non-trivial mapper function is turned into an ordinary method, it acquires a name, and it becomes available to unit testing frameworks. You can easily write tests that call it directly, and it can be stubbed out if necessary.

The method can also have documentation comments associated with it, including documentation for parameters and return values.

Both of these are quite useful if the mapper function is complicated.

It's not wrong to assign a lambda expression to a field, but I think the main reason for doing so would be if the field is modified at runtime, e.g., when the state of the application changes. Even in these cases I might consider writing ordinary methods and then assigning the field using a method reference instead of a multi-line lambda expression.

One drawback of using a field is that it requires you to declare explicitly the generic type parameters for the functional interface. IDEs can help with this, but it adds clutter to the program.

I would guess that an ordinary method with a method reference is always preferable to using a final field initialized with a lambda expression.