Lambda expressions in regards of generic types

In Java, SAM (Single Abstract Method) interfaces are a key feature that enable the use of lambda expressions and functional programming concepts. A SAM is an interface that contains exactly one abstract method, common examples of SAM interfaces in Java include Runnable, Callable, and Comparator.
Any custom interface with a single abstract method can be treated as a functional interface, to be able to costumize code.

The apply method here is generic, meaning it can accept any type T.

To costumize such you can define @FunctionalInterface

@FunctionalInterface
public interface CustomMethodInterface {
    String convert(BigDecimal);
}

What about generics?

Lambda expressions work when the method signature is clear, but with generics in the method signature (like <T>), Java cannot infer the type for the lambda expression. The generic method lacks concrete information at compile time about what type is being passed.

How to resolve this:

you can use a functional interface that doesn’t have type parameters in the method itself:

@FunctionalInterface
public interface MyFunction<T> {
    void apply(T t);
}

MyFunction<String> func = (s) -> System.out.println(s);

Types of Lambda expressions

https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27.3: Lambda expressions in regards of generic types

Functional Interface Parameterization Inference

When a lambda expression with explicit parameter types P1, …, Pn is aimed at a functional interface type F that contains at least one wildcard type argument, a specific parameterization of F can be determined as the ground target type for the lambda expression in the following manner.

Predicate<? super Integer> p = (Number n) -> n.equals(42);

If the ground target type is the non-wildcard parameterization (§9.9) of T

Function<? super Number, ? extends Integer> func;
func = x -> (int) x + 1;

Congruent expression with Function type

A lambda expression is congruent with a function type if all of the following are true:

  • The function type has no type parameters.
  • The number of lambda parameters is the same as the number of parameter types of the function type.
  • If the lambda expression is explicitly typed, its formal parameter types are the same as the parameter types of the function type.
  • If the lambda parameters are assumed to have the same types as the function type’s parameter types
    • If the function type’s result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.
      Consumer printConsumer = s -> System.out.println(s);
    • If the function type’s result is a (non-void) type R, then either i) the lambda body is an expression that is compatible with R in an assignment context, or ii) the lambda body is a value-compatible block, and each result expression (§15.27.2) is compatible with R in an assignment context.
      Function<Integer, String> intToString = x -> "Value is: " + x;

For each non-static member method m of U, if the function type of U has a subsignature of the signature of m, then a notional method whose method type is the function type of U is deemed to override m, and any compile-time error or unchecked warning specified in §8.4.8.3 may occur.

@FunctionalInterface
interface ExampleInterface {
    void performAction(String message);
}

class ExampleClass {
    public void performAction(String message) {
        System.out.println("Message: " + message);
    }
}

In the above case, if such a method isgnature match occurs, the method is considered notionally overridden, which means the functional interface’s abstract method is treated as if it overrides the matching member method.

If the body of a lambda is a statement expression (that is, an expression that would be allowed to stand alone as a statement), it is compatible with a void-producing function type; any result is simply discarded. So, for example, both of the following are legal:

// Predicate has a boolean result
java.util.function.Predicate<String> p = s -> list.add(s);
// Consumer has a void result
java.util.function.Consumer<String> c = s -> list.add(s);

Conclusion

Lambda compatibility involves careful consideration of target type parameterization and congruence with the functional interface’s method signature. This ensures that the lambda can be correctly used within the context where the functional interface is expected, respecting both type inference rules and the method signature requirements.

Leave a Reply

Your email address will not be published. Required fields are marked *