Skip to content

SAM转换 SAM Conversions

Just like Java 8, Kotlin supports SAM conversions. This means that Kotlin function literals can be automatically converted into implementations of Java interfaces with a single non-default method, as long as the parameter types of the interface method match the parameter types of the Kotlin function.

You can use this for creating instances of SAM interfaces:

val runnable = Runnable { println("This runs in a runnable") }

…and in method calls:

val executor = ThreadPoolExecutor()
// Java signature: void execute(Runnable command)
executor.execute { println("This runs in a thread pool") }

If the Java class has multiple methods taking functional interfaces, you can choose the one you need to call by using an adapter function that converts a lambda to a specific SAM type. Those adapter functions are also generated by the compiler when needed:

executor.execute(Runnable { println("This runs in a thread pool") })

Note that SAM conversions only work for interfaces, not for abstract classes, even if those also have just a single abstract method.

Also note that this feature works only for Java interop; since Kotlin has proper function types, automatic conversion of functions into implementations of Kotlin interfaces is unnecessary and therefore unsupported.

参考 https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions


SAM

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 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 method, 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.

https://stackoverflow.com/questions/17913409/what-is-a-sam-type-in-java