Что такое «тип SAM» в Java?

133

Читая спецификацию Java-8, я постоянно вижу ссылки на «типы SAM». Я не смог найти четкого объяснения, что это такое.

Что такое тип SAM и каков пример его использования?

Cody
источник
4
См. Cr.openjdk.java.net/~briangoetz/lambda/lambda-state-3.html (который я нашел после одного поиска, который привел меня к другому вопросу SO).
Jon Skeet
2
Пожалуйста, не отсылайте людей к устаревшей информации. Немного более длинный поиск привел бы вас к более свежей версии: cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html , которой 18 месяцев, а сейчас она во многих мест. Ответ на вопрос OP можно найти на lambdafaq.org/what-is-a-functional-interface , одной странице в FAQ, который я стараюсь поддерживать в актуальном состоянии в том, что до недавнего времени было быстро меняющимся языком и разработкой API.
Морис Нафталин
1
@MauriceNaftalin Вы также можете просто связать след Java , который постоянно обновляется командой разработчиков.
Брайан
1
Текущий термин - «функциональный интерфейс».
newacct
1
@MauriceNaftalin: Простите, я пропустил это. Несомненно, связался бы с этим, если бы нашел это.
Jon Skeet

Ответы:

142

Подводя итог ссылке, которую Джон опубликовал 1 на случай, если она когда-нибудь выйдет из строя, «SAM» означает «единственный абстрактный метод», а «SAM-type» относится к таким интерфейсам, как Runnable,Callable и т. Д. Лямбда-выражения, новая функция в Java 8, являются считается типом SAM и может быть свободно преобразован в них.

Например, с таким интерфейсом:

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

Вы можете объявить Callableлямбда-выражения следующим образом:

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

Лямбда-выражения в этом контексте в основном представляют собой синтаксический сахар. Они выглядят лучше в коде, чем анонимные классы, и менее строгие при именовании методов. Возьмите этот пример по ссылке:

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);

Это создает Comparatorиспользование определенного метода, который не имеет того же имени, что иComparator.compare , что и, таким образом, вам не нужно следовать интерфейсу именования методов, и вы можете иметь несколько переопределений сравнения в классе, а затем создавать компараторы на лету через лямбда-выражения.

Идем глубже ...

На более глубоком уровне Java реализует их с помощью invokedynamicинструкции байт-кода, добавленной в Java 7. Я сказал ранее, что объявление Lambda создает экземпляр анонимного класса Callableили Comparableаналогичный ему, но это не совсем так. Вместо этого, при первом invokedynamicвызове, он создает обработчик функции Lambda с помощью LambdaMetafactory.metafactoryметода , а затем использует этот кешированный экземпляр в будущих вызовах Lambda. Более подробную информацию можно найти в этом ответе .

Этот подход сложен и даже включает в себя код, который может считывать примитивные значения и ссылки непосредственно из памяти стека для передачи в ваш код Lambda (например, чтобы обойти необходимость выделения Object[]массива для вызова вашей Lambda), но он позволяет будущие итерации реализации Lambda. заменить старые реализации, не беспокоясь о совместимости байт-кода. Если инженеры Oracle изменят базовую реализацию Lambda в более новой версии JVM, Lambdas, скомпилированные на более старой JVM, автоматически будут использовать новую реализацию без каких-либо изменений со стороны разработчика.


1 Синтаксис ссылки устарел. Взгляните на тропу Java по лямбда-выражениям, чтобы увидеть текущий синтаксис.

Брайан
источник