37

Is there a way in Java8 to use a method reference as a Function object to use its methods, something like:

Stream.of("ciao", "hola", "hello")
    .map(String::length.andThen(n -> n * 2))

This question is not related to the Stream, it is used just as example, I would like to have answer about the method reference

3
  • Provide the input and the desired output, then will tell you how to implement in with Java 8 streams. Commented Aug 25, 2015 at 14:01
  • 5
    It's not related to Stream API its related to method reference Commented Aug 25, 2015 at 14:02
  • 1
    the canonical solution is .map(s -> s.length() * 2) as there is no reason to express it as two functions just to combine them immediately. If you have an already existing Function instance or want to keep at least one of them for later re-use, the problem does not exist as then you have an object you can invoke either andThen or compose on. In all other cases, there is no reason to do it that complicated. Commented Aug 26, 2015 at 8:21

7 Answers 7

32

You can write a static method to do this:

import java.util.function.*;

class Test {
    public static void main(String[] args) {
        Function<String, Integer> function = combine(String::length, n -> n * 2);
        System.out.println(function.apply("foo"));
    }

    public static <T1, T2, T3> Function<T1, T3> combine(
        Function<T1, T2> first,
        Function<T2, T3> second) {
        return first.andThen(second);
    }
}

You could then put it in a utility class and import it statically.

Alternatively, create a simpler static method which just returns the function it's given, for the sake of the compiler knowing what you're doing:

import java.util.function.*;

class Test {
    public static void main(String[] args) {
        Function<String, Integer> function = asFunction(String::length).andThen(n -> n * 2);
        System.out.println(function.apply("foo"));
    }

    public static <T1, T2> Function<T1, T2> asFunction(Function<T1, T2> function) {
        return function;     
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

+1 for the second, if this is really just about avoiding the cast (I was going to suggest just fn(Stream::length))
There is no need to create a static method. See the answer by JB Nizet below.
@pvillela: True (although then you have a separate variable unnecessarily, or an ugly cast). Will leave it here so that all options are available.
A question irrelevant to this discussion - how can the method reference String::length be passed to a method which accepts a Function? Functions accept a parameter and return a value, where as the length method doesn't accept anything while returning a value which makes it a Supplier. May be I completely misunderstood how Method references work
UPDATE - found the answer to my question from this SO answer which quotes Brian Goetz as saying "if the desugared method is an instance method, the receiver is considered to be the first argument"
23

You can just save it into a variable:

Function<String, Integer> toLength = String::length;
Stream.of("ciao", "hola", "hello")
      .map(toLength.andThen(n -> n * 2));

Or you can use a cast, but it's less readable, IMO:

Stream.of("ciao", "hola", "hello")
      .map(((Function<String, Integer>) String::length).andThen(n -> n * 2));

Comments

11

You should be able to achieve what you want inline by using casts:

Stream.of("ciao", "hola", "hello")
      .map(((Function<String, Integer>) String::length).andThen(n -> n * 2))

There are only 'type hints' for the compiler, so they don't actually 'cast' the object and don't have the overhead of an actual cast.


Alternatively, you can use a local variable for readability:

Function<String, Integer> fun = String::length

Stream.of("ciao", "hola", "hello")
      .map(fun.andThen(n -> n * 2));

A third way that may be more concise is with a utility method:

public static <T, X, U> Function<T, U> chain(Function<T, X> fun1, Function<X, U> fun2)
{
    return fun1.andThen(fun2);
}

Stream.of("ciao", "hola", "hello")
      .map(chain(String::length, n -> n * 2));

Please note that this is not tested, thus I don't know if type inference works correctly in this case.

1 Comment

So, there is not sugar syntax to have it inline without cast and parenthesis...thx
8

You may also use

Function.identity().andThen(String::length).andThen(n -> n * 2)

The problem is, String::length is not necessarily a Function; it can conform to many functional interfaces. It must be used in a context that provides target type, and the context could be - assignment, method invocation, casting.

If Function could provide a static method just for the sake of target typing, we could do

    Function.by(String::length).andThen(n->n*2)

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

For example, I use this technique in a functional interface

static <T> AsyncIterator<T> by(AsyncIterator<T> asyncIterator)

Syntax sugar to create an AsyncIterator from a lambda expression or a method reference.

This method simply returns the argument asyncIterator, which seems a little odd. Explanation:

Since AsyncIterator is a functional interface, an instance can be created by a lambda expression or a method reference, in 3 contexts:

 // Assignment Context
 AsyncIterator<ByteBuffer> asyncIter = source::read;
 asyncIter.forEach(...);

 // Casting Context
 ((AsyncIterator<ByteBuffer>)source::read)
     .forEach(...);

 // Invocation Context
 AsyncIterator.by(source::read)
     .forEach(...);

The 3rd option looks better than the other two, and that's the purpose of this method.

Comments

5

You can use a cast

Stream.of("ciao", "hola", "hello")
        .map(((Function<String, Integer>) String::length)
                .andThen(n -> n * 2))
        .forEach(System.out::println);

prints

8
8
10

Comments

4

You could write:

Stream.of("ciao", "hola", "hello").map(String::length).map(n -> n * 2);

Comments

0

We can use reduce function to combine multiple functions into one.

public static void main(String[] args) {
    List<Function<String, String>> normalizers = Arrays.asList(
    str -> {
        System.out.println(str);
        return str;
    }, 
    String::toLowerCase,
    str -> {
        System.out.println(str);
        return str;
    },
    String::trim,
    str -> {
        System.out.println(str);
        return str;
    });

    String input = " Hello World ";
    normalizers.stream()
               .reduce(Function.identity(), (a, b) -> a.andThen(b))
               .apply(input);
}

Output:

 Hello World 
 hello world 
hello world

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.