3

Java 8 allows you to use the same lambda expression in different contexts. Consider the following code that demonstrates this :

   public class LambdasTypeCheck {
        public static void main(String []args) {
            //Same lambda expression can be used in different contexts.
            Predicate<List<String>> predicate = (l) -> l.add("1");
            Consumer<List<String>>  consumer = (l) -> l.add("1");
        }
    }

The lambda expression (l) -> l.add("1");is duplicated in two places. How can I avoid this duplication? One way I can reuse this lambda expression is if there is some interface/class which is a parent of all lambda expressions such that I can assign any lambda expression to a reference of such an interface/class.

Chetan Kinger
  • 15,069
  • 6
  • 45
  • 82
  • 2
    Do you have a real life scenario in which the same lambda expression can be assigned to different functional interfaces? The example you gave makes little sense. `(l) -> l.add("1")` shouldn't be used as a Predicate. – Eran Feb 16 '15 at 16:22
  • At the moment I don't. Does that mean I shouldn't be curious about it? I want to know if it "can" be done rather than whether it "should" be done. Also, that example is only for supporting my question and not something I am using in the real world. – Chetan Kinger Feb 16 '15 at 16:26
  • 1
    Although those two lambda expressions look the same, they are not the same. Their target types are totally different, and so it is their intent. I daresay the predicate lambda is pretty bad, since it has side effects. To keep referential transparency it should be `l::contains` – Edwin Dalorzo Feb 16 '15 at 19:19

2 Answers2

6

You didn’t define which “duplication” you are worrying about.

If it is the code duplication (or say, the fear about possible inconsistencies between the expressions which intentionally ought to be the same) that bothers you, you can use

Predicate<List<String>> predicate = (l) -> l.add("1");
Consumer<List<String>>  consumer = predicate::test;

to avoid it.

If you want the lambda expressions to be really represented by the same instance, you may created a combined interface:

interface PredicateAndConsumer<T> extends Predicate<T>, Consumer<T> {
    public default void accept(T t) { test(t); }
}

 

PredicateAndConsumer<List<String>> pAndC = (l) -> l.add("1");
Predicate<List<String>> predicate = pAndC;
Consumer<List<String>>  consumer = pAndC;

So, as you can see, you can use pAndC in both contexts where either Predicate<List<String>> or Consumer<List<String>> is required.

However, there is little benefit in doing this. In this special case we can safely assume that for most JREs the overhead of introducing another interface is higher than the saving of implementing it via one lambda expression.


You may also consider the answers to “Is method reference caching a good idea in Java 8?”. To summarize it, your example expressions do not capture values and therefore will be implemented as singletons in Oracle’s reference implementation (and will likely be as well in all alternative implementations). So here we are talking about whether there are one or two immutable constants created in the runtime environment…

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Can you elaborate on what you mean by "do not capture values?" – Chetan Kinger Feb 16 '15 at 17:35
  • 1
    Simplified, they don’t access variables from their surrounding scope, see http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables – Holger Feb 16 '15 at 17:38
2

The chances for using the same code to implement two different interfaces within the same method are, let's face it, practically zero. But even then you can do what you probably always do when you encounter duplicate code: Create a new method / class.

public class LambdasTypeCheck {
  public static void main(String []args) {
    Predicate<List<String>> predicate = LambdasTypeCheck::addOne;
    Consumer<List<String>>  consumer = LambdasTypeCheck::addOne;
  }

  public static boolean addOne(List<String> list) {
    return list.add("1");
  }
}

That doesn't work when you want to capture values, of course:

String one = "1";
Predicate<List<String>> predicate = (l) -> l.add(one);
Consumer<List<String>>  consumer = (l) -> l.add(one);

Here you have to use lambda expressions and you could only resort to the solution suggested by @Holger. But then again: This (the code duplication within a method) is a very, very unlikely scenario :)

a better oliver
  • 26,330
  • 2
  • 58
  • 66