Touch-base with Lambda Expressions in Java

Ish Mishra
3 min readJun 16, 2021

Lambda Expression?

Implements a Functional Interface

(if you read somewhere that it’s a way of writing instances of Anonymous classes — wipe out that definition — it is wrong)

But…then what is a Functional Interface?

  • an interface in Java (obviously)
  • but has a single abstract method (SAM)
  • default and static methods of an interface do not count when we talk about SAM (can have as many default/static methods as we like)
  • and nor we count the methods implemented by Object class like toString(), equals() or hashCode()
  • may or may not have an annotation @FunctionalInterface
  • is a method without a name that is used to pass around behaviour as if it’s data ( this statement is too casual but you would get there)

How does a Functional Interface syntax look like?

( zero or more arguments separated by comma)→ {body};
  • ()→{ System.out.println();};
  • () → System.out.println();
  • arg → System.out.println(arg);
  • (arg) → System.out.println(arg);
  • (argA, argB) → { //multiple lines
    System.out.println(argA+ argB);};

If the above confuses you, don’t worry we will see some Functional Interfaces and their implementations by end of this article

Important FI’s in Java?

Let us explore a few in the package: java.util.function

A single article cannot do justice to ways of implementing FI’s but that’s why I gave a title “touch-base” (an evil laugh)

Supplier Interface

@FunctionalInterface
public interface Supplier<T> {

/**
* Gets a result.
*
*
@return a result
*/
T get();
}

When we implement this FI, it would look something like:

Supplier<String> ourFirstSupplier = () -> "Ish loves learning";
String message = ourFirstSupplier.get();
System.out.println(message);
// I so hope you know what this piece of code would do

Consumer Interface

@FunctionalInterface
public interface Consumer<T> {

void accept(T t); //Let's just focus on this one

default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}

Let us implement it in a couple of different ways:

Consumer<String> ourFirstConsumer= (x)->System.out.println(x);
ourFirstConsumer.accept("Never stop learning");
//this would print the string
//########## another way ##########Consumer<String> ourFirstConsumer= x->System.out.println(x);
ourFirstConsumer.accept("Never stop learning");
// ########## and more ways to confuse you ##########Consumer<String> ourFirstConsumer= x -> {
System.out.println(x);
System.out.println("And we can add multiple lines using a block");
};
ourFirstConsumer.accept("Never stop learning");
//TRUST ME I CAN KEEP DOING THIS IN MORE WAYS! FIGURE IT OUT

Function Interface

@FunctionalInterface
public interface Function<T, R> {

/**
* Applies this function to the given argument.
*
*
@param t the function argument
*
@return the function result
*/
R apply(T t);


default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}


default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}

static <T> Function<T, T> identity() {
return t -> t;
}
}

and here we go and implement one:

Function<String, Integer> ourFirstFunction = (param) -> {
//Take an number as String and return as an Integer
return Integer.parseInt(param);
};
Integer receiveAConvertedForm = ourFirstFunction.apply("25");
System.out.println(receiveAConvertedForm);

Predicate Interface

@FunctionalInterface
public interface Predicate<T> {

/**
* Evaluates this predicate on the given argument.
*
*
@param t the input argument
*
@return {@code true} if the input argument matches the predicate,
* otherwise {
@code false}
*/
boolean test(T t);

default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}

default Predicate<T> negate() {
return (t) -> !test(t);
}

default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}

static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}

and here we go again:

Predicate<Integer> isInteger = param -> param instanceof Integer;
System.out.println(isInteger.test(2));

Did you notice?

  • when implementing FI using lambda expressions we did not provide the data type of our parameter.
  • It’s because the Lambda Expression type is context-dependent. In simple terms, it gets inferred by the compiler (remember SAM)
// We did not do this
Consumer<String> ourFirstConsumer=
(String x)->System.out.println(x);
// but wrote
(x)->System.out.println(x);
// data type of parameters are optional

Yes, we did not talk about Method References. Wait for the next article

--

--