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

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

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

Ответ 1

Чтобы суммировать ссылку, которую Джон опубликовал 1 на случай, если она когда-нибудь выйдет из строя, "SAM" означает "единственный абстрактный метод", а "SAM-тип" относится к таким интерфейсам, как 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 метода LambdaMetafactory.metafactory, а затем использует этот кэшированный экземпляр в будущих вызовах Lambda. Больше информации можно найти в этом ответе.

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


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

Ответ 2

Интерфейсы, имеющие только один абстрактный метод, известный как Интерфейсы с одним абстрактным методом (SAM Interfaces).

Обычно они называются функциональными интерфейсами.

  • Требуется только один абстрактный метод
  • @FunctionalInterface аннотация может быть добавлена
  • Методы по умолчанию не являются абстрактными, поэтому мы можем добавить любое количество методов по умолчанию
  • Позволяет переопределять методы класса Object как абстрактные
  • Функциональные (SAM) интерфейсы очень важны для написания лямбда-выражения
  • Многие функциональные (SAM) интерфейсы добавляются в java 8 в пакете java.util.function.

Вы можете прочитать больше на Что такое интерфейс SAM?