Chain of Map method references

2019-02-02 22:02发布

问题:

I'm working with Twitter4J. But the question I'm asking is more general. I want to access the user id of a given tweet. Currently, I have the following two options:

//Option 1
stream.map(status -> status.getUser().getId())
      .forEach(System.out::println);

//Option 2:
stream.map(Status::getUser)
      .map(User:getId)
      .forEach(System.out::println);

I don't like the lambda expression in the first option, nor being forced to call two maps in the second one. Is there a way to make a chain of method references? I know that Status::getUser::getId does not work, but am wondering if there is an alternative.

回答1:

Nope, these are the two ways of doing it. Anything else would end up being only less clear.

But, since you asked, here are some options.

static<T,U,R> Function<T,R> chain(
        Function<? super T, ? extends U> f1,
        Function<? super U, ? extends R> f2) {
    return t -> f2.apply(f1.apply(t));
}

stream.map(chain(Status::getUser, User::getId))

Or

static<T,R> Function<T,R> func(Function<T,R> f) {
    return f;
}

stream.map(func(Status::getUser).andThen(User::getId))


回答2:

Of the two options proposed, I tend to use the second, chaining calls to map().

There are other options, which uses built-in API instead of helper methods, as shown here. With the Function.compose() or Function.andThen() methods, you can chain function invocations. Predicate and other functional interfaces implement similar default methods.

stream.map(((Function<Status, User>) Status::getUser).andThen(User::getId))
  .forEach(System.out::println);

Alternatively

Function<Status, User> toUser = Status::getUser;
stream.map(toUser.andThen(User::getId)).forEach(System.out::println);

The drawback is that, in order to use these, the functional interface type has to be given explicitly. That requires a cast or an assignment, and that looks a bit ugly. So for a mapping chain, I usually use multiple map() calls.