5

I was learning about Streams in Java 8.

For example, If I have to double a number:

Arrays.stream(intArray).map(e->e*2).forEach(System.out::println);

If I have to square a number, then I can use below:

Arrays.stream(intArray).map(e->e*e).forEach(System.out::println);

But If I have to apply both functions on same Integer array using "andThen" method java.util.function.Function, I am doing it via:

  Function<Integer, Integer> times2 = e -> e * 2;

  Function<Integer, Integer> squared = e -> e * e;  

 Arrays.stream(intArray).map(times2.andThen(squared)).forEach(System.out::println);

Is it possible to rewrite this (3 statements) in single line like :

Arrays.stream(intArray).map(e->e*2.andThen(f->f*f)).forEach(System.out::println);

This is giving me a compiler error. Is it possible to do it?

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
Nicky
  • 1,025
  • 3
  • 15
  • 29
  • 2
    Another approach is to use 2 map calls map(e->e*2).map(e->e*e). I am sure internally Java will do andThen on them. – tsolakp Jun 04 '17 at 16:28
  • 2
    can't you `map(e -> Math.pow(2*e, 2))` (easier to read later IMHO) –  Jun 04 '17 at 16:30
  • @tsolakp I know it can be done. But my aim is to understand the use of `andThen` method – Nicky Jun 04 '17 at 16:32
  • 1
    See also https://stackoverflow.com/questions/24436871/very-confused-by-java-8-comparator-type-inference/24442897#24442897 – Brian Goetz Jun 04 '17 at 23:01

3 Answers3

4

Looks like Java does not implicitly assume lambda expression of being a specific Functional type. I had to add casting to make it work:

Arrays.stream( new int[]{ 1, 2, 3, 4 } )
.map( ( (IntUnaryOperator)( e -> e*2 ) ).andThen(f->f*f) )
.forEach(System.out::println);

I, personally don't like this and would prefer to use double map calls. I am curious to see a better idea.

tsolakp
  • 5,858
  • 1
  • 22
  • 28
  • 3
    Lambda expressions have no intrinsic type; they require a _target type_ (which must be a _functional interface_.) The same lambda expression could be compatible with many functional interfaces; `s -> s.isEmpty()` could be a `Function` or a `Predicate`. You need to provide some type information; type inference != mind reading. – Brian Goetz Jun 04 '17 at 22:14
  • The compiler can see that map is taking IntUnfaryOperator. Cant it infer the type for it? – tsolakp Jun 04 '17 at 22:24
  • 2
    The problem is the chaining. If you provided a single lambda as the argument to `map()`, it would work exactly as you expect. But with the chained expression, it can infer only that the chained expression `e->e*2.andThen(f->f*f)` is an `IntUnaryOperator`. But, what is the type of `e->e*2` ? It has no idea. The only constraint the compiler can derive is that it is some type that has a method called `andThen()`. – Brian Goetz Jun 04 '17 at 22:26
  • 2
    It would also have no problem with `.map(times2.andThen(e -> e*e))`, because it knows the type of `times2`. – Brian Goetz Jun 04 '17 at 22:28
  • Even in chained form thr compiler can see that we are passing lambda expression and calling method on it. Can't it assume that that lambda expression is IntUnaryType and then check if it has 'andThen' method on it ? – tsolakp Jun 04 '17 at 22:55
  • 2
    No. You're asking for mind-reading, based on the one example you happen to have in mind now. There are infinitely many possible functional interface types that might have a method called `andThen()`, which could have an arbitrary signature. What possible reason can it have for assuming that it is `IntUnaryOperator`, and not something else? – Brian Goetz Jun 04 '17 at 22:59
  • Because the lambda is passed into 'map' method that only takes IntUnaryOperator. – tsolakp Jun 04 '17 at 23:01
  • 2
    Sorry, that's pure wishful thinking (mostly motivated by only having one this one example in mind.) That's really just asking the compiler to take a wild guess, because it saw that type somewhere in proximity. But there's no credible reason to guess this type, and one can easily construct examples where this guess would be wrong. This isn't how one designs a language. – Brian Goetz Jun 04 '17 at 23:08
  • Yes, this is a wishful thinking but I would like to see example that this wont work. – tsolakp Jun 04 '17 at 23:11
  • 1
    @BrianGoetz and tsolakp Please, allow me to mediate here... tsolakp, in my answer I'm defining a `Whatever` functional interface and using a lambda whose target type could be either `Whatever` or `Function`. Now imagine that I add a default method `andThen` with the same signature to my `Whatever` interface (this means that it receives a `Function` and returns another `Function`). How would the compiler now know that the first lambda is a `Function` and not a `Whatever`? Because, with method chaining, every class could return the function that `Stream.map` is expecting as an argument... – fps Jun 05 '17 at 02:25
  • 1
    My point is that you're assuming that, as `map` expects a function and, as `andThen` appears after the first lambda, then the compiler should infer that the first lambda is actually a `Function`. But I've already shown an example that would cause type ambiguity by using a custom functional interface in my previous comment. – fps Jun 05 '17 at 02:31
  • @BrianGoetz Is there any chance that each functional interface in the jdk had a static method to allow type inference in method chain calls? I'm thinking in something like i.e. for `Function`: `static Function of(Function f) { return f; }` Maybe this has been already discussed somewhere... If yes, is there any email archive or log with the discussion available? Thanks in advance! – fps Jun 05 '17 at 02:35
  • 1
    @FedericoPeraltaSchaffner So, you're saying instead of `(Function) lambda`, you'd say `Function.of(lambda)` instead. Syntactically, that saves you relatively little -- just naming the type arguments. But many users find using casts to introduce a target type to be nonintuitive -- is that your motivation? – Brian Goetz Jun 05 '17 at 14:04
  • @BrianGoetz If the generic type parameters can be inferred, then I'd find it better than a cast. I admit it's just my personal taste. Casting is not only unintuitive but also ugly, I think. I've seen many developers complaining about this, saying that "the compiler is dumb, or should infer everything", etc. – fps Jun 05 '17 at 14:23
  • @BrianGoetz On the other hand, if the types of the arguments of the lambda expression need to be specified, even when using this inference method, then it would be useless – fps Jun 05 '17 at 14:29
  • @FedericoPeraltaSchaffner 1) Compiler sees that 'map' is taking Whatever. 2) Since there is no cast to first lambda it can assume that first lambda is of Whatever type. 3) It will next look at 'andThen' method of Whatever. 4) If 'andThen' is returning Function then it will complain because 'map' takes Whatever. 5) If it returns Whatever then all is good. – tsolakp Jun 05 '17 at 15:36
  • 1
    tsolakp Then you're suggesting that the compiler should infer the type of the lambda in a method chain call *only* when the same type is at both ends of the chain? – fps Jun 05 '17 at 15:43
  • @FedericoPeraltaSchaffner. It first infers the type and then checks if the call chain returns the correct type. – tsolakp Jun 05 '17 at 15:45
  • 1
    tsolakp But you know that the whole language works the other way round, don't you? It always starts evaluating chains from left to right, and it needs a type to start the chain. Consider this example: `void method(String s) { }`. Suppose you have a `Map>`. You can call the method as follows: `method(myMap.get(key).get(0).substring(2))`. That method call chain in the argument works because the type of the map, the list and the elements are all known in advance. However, `method` receives a `String` and the chain starts with a `Map`, goes on with a `List` and then a `String`. – fps Jun 05 '17 at 15:59
  • 1
    My point is that the compiler can't infer that, as the `Stream.map` method expects a `Function`, then the first lambda should be a `Function`, because the only object that needs to be of type `Function` is the one returned by the last method in the call chain (in this case `andThen`) – fps Jun 05 '17 at 16:01
  • 1
    If you changed the language so that the first element of the call chain must be of the same type of the argument of the method, then the example `void method(String s) { }` wouldn't work when called with `method(myMap.get(key).get(0).substring(2))`, because `myMap` is of type `Map`, while the argument that `method` is expecting is of type `String`. – fps Jun 05 '17 at 16:05
  • Maybe, only just in case of lambdas, it can look at the other end just to try to get type of first object in the chain and then proceed as usual? – tsolakp Jun 05 '17 at 16:42
3

If you absolutely must use the .andThen method, then the other answers here are what you are looking for. But if you are looking for a simple way to combine two functions in a single-line fluent stream operation, then just calling .map twice is the most readable and compact form that I can think of:

Arrays.stream(intArray).map(e->e*2).map(f->f*f).forEach(System.out::println);
Matthew Leidholm
  • 4,199
  • 3
  • 24
  • 26
2

@tsolakp's answer shows how to create an inlined lambda of a specific type by casting the lambda expression to the desired functional type.


As to why you can't do:

Arrays.stream(intArray)
    .boxed()
    .map(e -> e * 2.andThen(e -> e * e)) // wrong! does not compile!
    .forEach(System.out::println);

The reason is that in Java, lambdas and method references must have a target type, because they are not first citizens of the language. In fact, a lambda must be of a specific SAM type (single abstract method type). A SAM type is represented by a functional interface, i.e. an interface that has only one abstract method. Examples of functional interfaces are Function, Predicate, Consumer, Supplier, Runnable, etc., or any SAM type declared by yourself, such as:

@FunctionalInterface
public interface Whatever<T, R> {

    R thisIsMySingleAbstractMethod(T argument);
}

A lambda expression that matches the Whatever functional type can be:

Whatever<Integer, String> whatever = number -> number.toString();

But the same lambda expression could have been used to declare a Function<Integer, String>:

Function<Integer, String> whatever = number -> number.toString();

This example shows that lambdas have no type of their own until they are targeted to a SAM type. Only after this point, the methods of the type are available for chaining.

In a stream, the map method expects a Function as an argument. If you can express such function with an inlined lambda expression, then this lambda expression will be automatically inferred to be of the type of the argument of the map method. This will happen (at least conceptually) after the lambda expression has been evaluated by the compiler.

However, if you want to use a method of the lambda expression's target type, (this would be andThen in your example), it is not possible, because the compiler hasn't figured out the target type yet.

That's why casting the lambda expression works: it tells the compiler in advance what the target type of the lambda expression will be.

fps
  • 33,623
  • 8
  • 55
  • 110
  • Like the utilities method approach but it requires for every single functional interface that does not extend Function class. I still think that compiler should be able to handle this case without requiring cast. – tsolakp Jun 04 '17 at 19:55
  • 1
    @Eugene Done, I edited the question to remove that part of the utility method, but I wanted to preserve the explanation about lambdas and target types, etc. – fps Jun 05 '17 at 05:00