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

rascio
  • 8,968
  • 19
  • 68
  • 108
  • Provide the input and the desired output, then will tell you how to implement in with Java 8 streams. – Alboz Aug 25 '15 at 14:01
  • 5
    It's not related to Stream API its related to method reference – rascio Aug 25 '15 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. – Holger Aug 26 '15 at 08:21

7 Answers7

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;     
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 3
    +1 for the second, if this is really just about avoiding the cast (I was going to suggest just `fn(Stream::length)`) – Mark Peters Aug 25 '15 at 14:13
  • There is no need to create a static method. See the answer by JB Nizet below. – pvillela Feb 06 '17 at 23:16
  • @pvillela: True (although then you have a separate variable unnecessarily, or an ugly cast). Will leave it here so that all options are available. – Jon Skeet Feb 07 '17 at 07:04
  • 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 – asgs Jul 11 '17 at 19:58
  • 1
    UPDATE - found the answer to my question from this [SO answer](https://stackoverflow.com/questions/27754335/reference-to-methods-with-different-parameters-in-java8#answer-27754772) which quotes Brian Goetz as saying "if the desugared method is an instance method, the receiver is considered to be the first argument" – asgs Jul 11 '17 at 20:26
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));
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
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.

Clashsoft
  • 11,553
  • 5
  • 40
  • 79
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.

ZhongYu
  • 19,446
  • 5
  • 33
  • 61
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
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
4

You could write:

Stream.of("ciao", "hola", "hello").map(String::length).map(n -> n * 2);
Tunaki
  • 132,869
  • 46
  • 340
  • 423
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
Kanagavelu Sugumar
  • 18,766
  • 20
  • 94
  • 101