Когда я впервые столкнулся с лямбда-выражениями, я почувствовал, что это штука очень волшебная (высокоуровневая), один() плюс -> может передать кусок кода, в то время я взял на себя код коллеги в проект компании, и я не разбирался в особенностях java8., я тоже был в недоумении, когда читал, а потом быстро прочитал книгу "война java8" и решил написать блог о серии фич java8, что не только углубило мое впечатление, но и поделилось им с вами. Надеюсь, вы дадите мне еще совет 😄.
Что такое лямбда?
Лямбда — это анонимная функция, мы можем понимать лямбда-выражение как фрагмент кода, который можно передать (передача кода как параметра называется параметризацией поведения). Lambda позволяет использовать функции в качестве параметров метода (функции передаются в методы в качестве параметров), для этого необходимо понимать, что такое функциональный интерфейс, который здесь не будет представлен и будет объяснен в следующей статье. .
Во-первых, давайте посмотрим, как выглядит лямбда?
Обычное письмо:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello lambda");
}
}).start();
лямбда пишет:
new Thread(
() -> System.out.println("hello lambda")
).start();
Как насчет этого? Это кажется очень кратким, да, в этом прелесть лямбды, она может сделать код, который вы пишете, более простым и гибким.
Как написать лямбда?
Давайте посмотрим на некоторые из приведенных выше диаграмм.Это синтаксис лямбда.Лямбда разделена на три части: список параметров, оператор и тело лямбда. Ниже приведены важные характеристики лямбда-выражений:
可选类型声明:Нет необходимости объявлять тип параметра, компилятор может единообразно идентифицировать значение параметра. То есть (s) -> System.out.println(s) и
(String s) -> System.out.println(s) — то же самое. Компилятор выполнит вывод типов, поэтому нет необходимости добавлять типы параметров.
可选的参数圆括号:Параметр не должен определять круглые скобки, но несколько параметров должны определять круглые скобки. Например:
s -> System.out.println(s) Параметру не нужны круглые скобки.
(x, y) -> Integer.compare(y, x) к двум параметрам добавляются круглые скобки, иначе компилятор сообщит об ошибке.
可选的大括号:Если тело содержит оператор, фигурные скобки не требуются.
s -> System.out.println(s) , фигурные скобки не нужны.
可选的返回关键字:Если тело имеет только одно возвращаемое значение выражения, компилятор автоматически вернет значение, а в фигурных скобках нужно указать, что выражение возвращает значение.
Если тело Lambda не добавляет { }, нет необходимости писать return:
Comparator<Integer> com = (x, y) -> Integer.compare(y, x);
Добавление { } в тело Lambda требует добавления возврата:
Comparator<Integer> com = (x, y) -> {
int compare = Integer.compare(y, x);
return compare;
};
вывод типа
Выше мы видели, как должно быть написано лямбда-выражение, но важной особенностью лямбда является то, что可选参数类型声明, то есть вам не нужно писать тип параметра, так почему бы не написать его? Как он узнает тип параметра? Это включает вывод типа.
Общие улучшения вывода типов для java8:
Поддержка вывода универсального целевого типа из контекста метода.
Поддержка вывода универсального типа, передаваемого последнему методу в цепочке вызовов методов.
В приведенном выше коде типом ps является список.<Person>, поэтому возвращаемый тип ps.stream() — Stream<Person>. Метод map() принимает функциональный интерфейс типа Function, где тип T — это тип элемента Stream, которым является Person, а тип R неизвестен. Поскольку целевой тип лямбда-выражения все еще неизвестен после разрешения перегрузки, нам нужно вывести тип R: проверяя тип лямбда-выражения, мы обнаруживаем, что тело лямбда-выражения возвращает String, поэтому тип R — String, и таким образом, map() возвращает Stream<String>. В большинстве случаев компилятор может разрешить правильный тип, но если он не может быть разрешен, нам необходимо:
используйте явное лямбда-выражение (предоставив явный тип для параметра p), чтобы предоставить дополнительную информацию о типе
Преобразование лямбда-выражения в Function
Укажите фактический тип для универсального параметра R. (<String>карта (p -> p.getName ()))
ссылка на метод
Ссылки на методы используются для прямого доступа к существующим методам или конструкторам класса или экземпляра, обеспечивая способ ссылки без выполнения метода. Это более краткое и простое для понимания лямбда-выражение.Когда в лямбда-выражении выполняется только один вызов метода, форма прямого использования ссылки на метод более удобочитаема.
Ссылки на методы представлены с помощью оператора "::" с именем класса или экземпляра слева и именем метода справа.(注意:方法引用::右边的方法名是不需要加()的,例:User::getName)
Список параметров и тип возвращаемого значения вызывающего метода в теле Lambda должны быть согласованы со списком функций и типом возвращаемого значения абстрактного метода в функциональном интерфейсе.
Если первый параметр в списке параметров Lambda является вызывающей стороной метода экземпляра, а второй параметр является параметром метода экземпляра, вы можете использовать ClassName::method
注意:Метод конструктора, который необходимо вызвать, совпадает со списком параметров абстрактного метода в функциональном интерфейсе.
Как реализована лямбда?
Я давно учился писать лямбда, но в чем ее принцип? Давайте просто посмотрим на пример, чтобы увидеть, что правда:
public class StreamTest {
public static void main(String[] args) {
printString("hello lambda", (String s) -> System.out.println(s));
}
public static void printString(String s, Print<String> print) {
print.print(s);
}
}
@FunctionalInterface
interface Print<T> {
public void print(T t);
}
Приведенный выше код настраивает функциональный интерфейс, определяет статический метод и затем использует этот функциональный интерфейс для получения параметров. После написания этого класса мы переходим к терминальному интерфейсу javac для компиляции, а затем используем javap (javap — это инструмент защиты от синтаксического анализа, который поставляется с jdk. Его функция заключается в обратном разборе области кода (сборки), соответствующей текущему классу в соответствии с файлом байт-кода класса. инструкция), таблица локальных переменных, таблица исключений и таблица сопоставления смещений строк кода, пул констант и т. д.) для разбора, как показано ниже:
Выполните команду javap -p ( -p -private показывает все классы и члены)Глядя на рисунок выше, видно, что при компиляции генерируется лямбда-выражение.lambda$main$0Статический метод, этот статический метод реализует логику лямбда-выражения, теперь мы знаем, что исходное лямбда-выражение скомпилировано в статический метод, так как же вызывается этот статический метод? мы продолжаем
Здесь размещена только часть структуры байт-кода, поскольку определение пула констант слишком длинное, оно не вставляется.
InnerClasses:
public static final #58= #57 of #61; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#28 (Ljava/lang/Object;)V
#29 invokestatic com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;)V
#30 (Ljava/lang/String;)V
Благодаря этой структуре байт-кода обнаруживается, что должен быть сгенерирован внутренний класс, метод LambdaMetafactory.metafactory вызывается с помощью invokestatic, а методlambda$main$0Передано в качестве параметра, давайте посмотрим на код реализации в методе метафабрики:
В функции buildCallSite именно функция spinInnerClass создает этот внутренний класс. То есть генерируется внутренний класс, такой как StreamTest?Lambda$1.class Этот класс создается во время выполнения и не сохраняется на диске.
Если вы хотите увидеть этот встроенный класс, вы можете установить параметры среды с помощью
System.setProperty("jdk.internal.lambda.dumpProxyClasses", " . ");
Этот внутренний класс будет сгенерирован по указанному вами пути Текущий путь выполнения. Посмотрим, как выглядит сгенерированный класс.
Из рисунка видно, что динамически сгенерированный внутренний класс реализует мой пользовательский функциональный интерфейс и переписывает методы в функциональном интерфейсе.
Давайте посмотрим на javap -v -p StreamTest?Lambda$1.class:
Было обнаружено, что метод lambda$main$0 вызывался с помощью директивы invokestatic в переопределенном методе parint.
Суммировать:Таким образом, реализуется лямбда-выражение, используется инструкция invokedynamic, во время выполнения вызывается LambdaMetafactory.metafactory для динамической генерации внутреннего класса, реализуется функциональный интерфейс, а метод в функциональном интерфейсе переписывается, и метод вызывается внутри метода.lambda$main$0, блок вызывающего метода во внутреннем классе не генерируется динамически, но статический метод был скомпилирован и сгенерирован в исходном классе, и внутреннему классу нужно только вызвать статический метод.
После просмотра ставьте лайки и подписывайтесь! Другие блоги будут следовать. Если есть какая-то ошибка, пожалуйста, поправьте меня.