javalambdajava-8terminology

What is a 'SAM type' in Java?


Reading up on the Java-8 spec, I keep seeing references to 'SAM types'. I haven't been able to find a clear explanation of what this is.

What is a SAM type and what is an example scenario of when one might be used?


Solution

  • To summarize the link Jon posted1 in case it ever goes down, "SAM" stands for "single abstract method", and "SAM-type" refers to interfaces like Runnable, Callable, etc. Lambda expressions, a new feature in Java 8, are considered a SAM type and can be freely converted to them.

    For example, with an interface like this:

    public interface Callable<T> {
        public T call();
    }
    

    You can declare a Callable using lambda expressions like this:

    Callable<String> strCallable = () -> "Hello world!";
    System.out.println(strCallable.call()); // prints "Hello world!"
    

    Lambda expressions in this context are mostly just syntactic sugar. They look better in code than anonymous classes and are less restrictive on method naming. Take this example from the link:

    class Person { 
        private final String name;
        private final int age;
    
        public static int compareByAge(Person a, Person b) { ... }
    
        public static int compareByName(Person a, Person b) { ... }
    }
    
    Person[] people = ...
    Arrays.sort(people, Person::compareByAge);
    

    This creates a Comparator using a specific method that doesn't share the same name as Comparator.compare, that way you don't have to follow the interface naming of methods and you can have multiple comparison overrides in a class, then create the comparators on the fly via the lambda expressions.

    Going Deeper...

    On a deeper level, Java implements these using the invokedynamic bytecode instruction added in Java 7. I said earlier that declaring a Lambda creates an instance of Callable or Comparable similar to an anonymous class, but that's not strictly true. Instead, the first time the invokedynamic is called, it creates a Lambda function handler using the LambdaMetafactory.metafactory method2, then uses this cached instance in future invocations of the Lambda. More info can be found in this answer.

    This approach is complex and even includes code that can read primitive values and references directly from stack memory to pass into your Lambda code (e.g. to get around needing to allocate an Object[] array to invoke your Lambda), but it allows future iterations of the Lambda implementation to replace old implementations without having to worry about bytecode compatibility. If the engineers at Oracle change the underlying Lambda implementation in a newer version of the JVM, Lambdas compiled on an older JVM will automatically use the newer implementation without any changes on the developer's part.


    1 The syntax on the link is out of date. Take a look at the Lambda Expressions Java Trail to see the current syntax.

    2 11 years on, and LambdaMetafactory.metafactory is still how this is implemented in JDK 23!