Понимать и использовать Lambda в Java

задняя часть

помещение

Передумать,JDK8Официальная версия была выпущена в 2014 году, она и сейчас (2020-02-08) прошло более 5 лет.JDK8Две из наиболее мощных новых представленных функций:Lambdaвыражение (нижеLambdaСпецификаторJDKкоторый предоставилLambdaStream, эти две мощные функции превращают функциональное программирование вJavaразвиваться и процветать. В этой статье будут представлены основные понятия, использование, принципы реализации и реальные боевые сценарии.Lambda, который также затрагивает некоторые концепции функционального программирования,JVMнекоторые знания и тд.

Базовые концепты

Далее вводятся некоторые основные понятия и вводятся их шаг за шагом.LambdaКонцепция чего-либо.

функциональный интерфейс

И функциональные интерфейсы, и методы интерфейса по умолчаниюJDK8Представлены новые функции. Концепция функционального интерфейса может быть получена изjava.lang.FunctionalInterfaceаннотированныйAPIКомментарии, которые:

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification.

Conceptually, a functional interface has exactly one abstract method. Since {@linkplain java.lang.reflect.Method#isDefault() default methods} have an implementation, they are not abstract.

Проще говоря:@FunctionalInterfaceпредставляет собой интерфейс, предоставляющий информацию (фактическиИнтерфейс идентификации), используемый для указания того, что соответствующее объявление типа интерфейса являетсяJavaФункциональный интерфейс, определенный спецификацией языка. Концептуально функциональный интерфейс имеет один и только один абстрактный метод, потому что методы интерфейса по умолчанию должны быть реализованы, они не являются абстрактными методами.

Таким образом, вы можете определить функциональный интерфейс следующим образом: Если интерфейс объявленимеет ровно один абстрактный метод, то это функциональный интерфейс и его можно использовать с@FunctionalInterfaceИдентификатор аннотации.

JDKМногие встроенные функциональные интерфейсы были определены в , например:

// java.lang.Runnable
@FunctionalInterface
public interface Runnable {

    public abstract void run();
}  

// java.util.function.Supplier
@FunctionalInterface
public interface Supplier<T> {

    T get();
}

Также есть возможность настроить функциональный интерфейс, например:

@FunctionalInterface
public interface CustomFunctionalInterface {
    
    // 可以缩写为void process();  接口方法定义的时候,默认使用public abstract修饰
    public abstract void process();
}

метод интерфейса по умолчанию

Значение метода интерфейса по умолчанию можно увидетьJavaДля соответствующих глав в официальном руководстве вы можете просмотреть конкретные ссылки в ссылках в конце статьи:

Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces.

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

Метод интерфейса по умолчанию (далее метод по умолчанию) передается черезdefaultобъявление ключевого слова, тело метода может быть написано непосредственно в интерфейсе.То есть метод по умолчанию как объявляет метод, так и реализует его.. Это важно, прежде чем появилась функция метода по умолчанию,JavaВ спецификации языка программирования сущность интерфейса представляет собой набор объявлений методов, и с момента появления функции метода по умолчанию изменилась и сущность интерфейса. Пример метода по умолчанию выглядит следующим образом:

public interface DefaultMethod {

    default void defaultVoidMethod() {

    }

    default String sayHello(String name) {
        return String.format("%s say hello!", name);
    }

    static void main(String[] args) throws Exception {
        class Impl implements DefaultMethod {

        }
        DefaultMethod defaultMethod = new Impl();
        System.out.println(defaultMethod.sayHello("throwable"));  // throwable say hello!
    }
}

Если вы наследуете интерфейс, который определяет методы по умолчанию, вы можете сделать следующее:

  • Полностью игнорировать метод по умолчанию родительского интерфейса, тогда это эквивалентно прямому наследованию реализации метода по умолчанию родительского интерфейса (наследование методов).
  • Переобъявите метод по умолчанию, здесь специально удалите егоdefaultключевое слово, сpublic abstractКлючевое слово повторно объявляет соответствующий метод, что эквивалентно превращению метода по умолчанию в абстрактный метод, и подкласс должен реализовать его (абстракция метода).
  • Переопределить метод по умолчанию, то есть напрямую переопределить реализацию в родительском интерфейсе (переопределение метода).

В сочетании с функциональным интерфейсом, упомянутым в предыдущем разделе, здесь можно сделать вывод:Функциональный интерфейс, то есть интерфейс с одним и только одним абстрактным методом, может определять 0 или N (N >= 1) методов по умолчанию.. ЭтоStream特性引入的理论基础。 Например:

@FunctionalInterface
public interface CustomFunctionalInterface {

    public abstract void process();

    default void defaultVoidMethod() {

    }

    default String sayHello(String name) {
        return String.format("%s say hello!", name);
    }
}

Здесь небольшое отступление.

При написании этой статьи я вспомнил слова бывшего коллеги, суть которых такова:В программной инженерии, если вы начинаете с нуля, разработка любой новой функции очень проста, но трудно повторять новые функции при условии совместимости со всеми историческими функциями.. Только представь,JavaПрошло более десяти лет с тех пор, как он был повторен и по сей день,Hotspot VMПроект с исходным кодом уже очень большой (собран вручнуюOpenJDK Hotspot VMЛюбой, кто владеет исходным кодом, знает всю боль процесса), любые новые функции должны быть совместимы с предыдущими версиями, иначе многие используют историческую версию.JavaПриложение не сможет обновить новыйJDKВерсия. И бинарная прямая совместимость, и новые функции должны быть повторены.JavaЕго нужно отбросить.Метод по умолчанию является примером.Интерфейс должен быть отброшен и могут быть определены только абстрактные методы.Это длилось много лет.JavaГлубоко укоренившаяся среди разработчиков концепция охватила систему потокового программирования, построенную на основе реализации метода по умолчанию. Автор иногда тоже думает: если я хочу развитьStreamЭта новая функция, что мне делать или что я могу сделать?

Вложенные классы

Вложенные классы (Nested Classes), проще говоря: определить еще один класс в одном классе, затем в классебыть определенКласс является вложенным классом, а самый внешний класс обычно называется включающим классом (Enclosing Class). Вложенные классы в основном делятся на два типа: статические вложенные классы и нестатические вложенные классы.Нестатические вложенные классы также известны как внутренние классы (Inner Classes).

// 封闭类
class OuterClass {
    ...
    // 静态嵌套类
    static class StaticNestedClass {
        ...
    }
    
    // 内部类
    class InnerClass {
        ...
    }
}

Доступ к статическим вложенным классам можно получить напрямую, используя имя окружающего класса, например:OuterClass.StaticNestedClass x = new OuterClass.StaticNestedClass();, эта форма использования в основном ничем не отличается от общего экземпляра класса.

Существование экземпляра внутреннего класса должно зависеть от существования экземпляра вмещающего класса, а внутренний класс может напрямую обращаться к любым свойствам и методам включающего класса, синтаксис объявления немного своеобразен:

public class OuterClass {

    int x = 1;

    static class StaticNestedClass {

    }

    class InnerClass {
        // 内部类可以访问封闭类的属性
        int y = x;
    }

    public static void main(String[] args) throws Exception {
        OuterClass outerClass = new OuterClass();

        // 必须这样实例化内部类 - 声明的语法相对奇特
        OuterClass.InnerClass innerClass = outerClass.new InnerClass();

        // 静态嵌套类可以一般实例化,形式为:封闭类.静态嵌套类
        OuterClass.StaticNestedClass staticNestedClass = new OuterClass.StaticNestedClass();

        // 如果main方法在封闭类内,可以直接使用静态嵌套类进行实例化
        StaticNestedClass x = new StaticNestedClass();
    }
}

Существует два специальных типа внутренних классов: локальные классы (Local Classes) и анонимные классы (Anonymous Classes).

Локальный класс — это класс, объявленный в произвольном блоке (block) класс, например, объявленный в блоке кода, статическом блоке кода, методе экземпляра или статическом методе, он может получить доступ ко всем свойствам-членам и методам включающего класса, и его область действия находится внутри блока и не может использоваться вне блока. Например:

public class OuterClass {

    static int y = 1;
    
    {    
        // 本地类A
        class A{
            int z = y;
        }
        A a = new A();
    }

    static {
        // 本地类B
        class B{
            int z = y;
        }
        B b = new B();
    }

    private void method(){
        // 本地类C
        class C{
            int z = y;
        }
        C c = new C();
    }
}

Анонимные классы могут сделать код более кратким, позволяя пользователямРеализовать класс при его определенииАнонимные классы и другие внутренние разницы в классе: этовыражение, а не объявление класса. Например:

public class OuterClass {

    interface In {

        void method(String value);
    }
    
    public void sayHello(){
        // 本地类 - 类声明
        class LocalClass{
            
        }
        
        // 匿名类 - 是一个表达式
        In in = new In() {
            
            @Override
            public void method(String value) {
                
            }
        };
    }
}

Если вы используетеJavaсделалGUIразработка, анонимный класс находится вSwingилиJavaFxОн часто используется в обратном вызове события, и вы часто видите такой код:

JButton button = new JButton();
button.addActionListener(new AbstractAction() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("按钮事件被触发...");
    }
});

Диаграмма типов вложенного класса выглядит следующим образом:

Nested Classes
  - Static Nested Classes
  - None Nested Classes
    - Local Classes
    - Anonymous Classes
    - Other Inner Classes

Лямбда-выражения

Нижеследующее из энциклопедии поисковой системы оLambdaОпределение выражения:

Lambdaвыражение(Lambda Expression) является анонимной функцией,LambdaВыражения основаны на математическихλИсчисление названо, прямо соответствующееLambdaАннотация(Lambda Abstraction), является анонимной функцией, то есть функцией без имени функции.LambdaВыражения могут представлять замыкания (обратите внимание на отличие от традиционного математического смысла).

JavaсерединаLambdaвыражение (далее именуемоеLambda) внешне похож на вышеприведенное определение, и по сути тоже является анонимной функцией, но принцип его реализации отличается от реализации анонимных функций в обычных анонимных классах.JDK8Введен новый синтаксический сахар.

Первоначальное намерение ввести лямбда-выражения

Если интерфейс содержит только один метод, то синтаксис анонимных классов станет очень громоздким и непонятным, что приведет к большому количеству шаблонного кода, и сводится к следующему: избыточность кода — самый большой недостаток анонимных классов. При программировании мы часто хотим передать функцию в качестве параметра другому методу,Lambdaрожден для этого,LambdaПозволяет потребителям видеть функциональность как параметры метода и код как данные. вводитьLambdaПриносит следующие преимущества:

  • Упрощает код, вводит мощные функции вывода типов и ссылок на методы, а простые функции могут быть решены даже в одной строке кода, освобождая оковы анонимных классов.
  • Передача функций в качестве параметров обеспечивает поддержку функционального программирования.

На данный момент сделан вывод:LambdaПрименяется только к реализации функционального интерфейса, соответствующего уникальному абстрактному методу..

Синтаксис определения лямбда-выражения

LambdaПодробное определение грамматики выглядит следующим образом:

// en_US
InterfaceType interfaceObject = [Method Argument List] -> Method Body

// zh_CN
接口类型 接口实例 = [方法参数列表] -> 方法体

Более конкретное описание должно быть:

接口类型 接口实例临时变量 = (方法参数类型X 方法参数类型X临时变量 , 方法参数类型Y 方法参数类型Y临时变量...) -> { 方法体... return 接口抽象方法返回值对应类型类型实例;}

ОдинLambdaВыражение состоит из пяти частей:

  • Возвращаемое значение: тип интерфейса и временная переменная экземпляра, соответствующая типу интерфейса.
  • знак равенства:=.
  • Список параметров метода: обычно заключен в квадратные скобки.()упаковка, формат такой(类型1 类型1的临时变量,...,类型N 类型N的临时变量), когда метод не перегружен и можно четко определить тип параметра, тип параметра можно не указывать, оставив только список временных переменных. В частности, пустой список параметров используется с()Указывает, что если есть только один параметр, его можно опустить.().
  • стрелка:->.
  • Тело метода: обычно состоит из фигурных скобок{}упаковка, формат такой{方法逻辑... return 函数式接口方法返回值类型的值;}, есть несколько вещей, чтобы отметить:
    • Если тело метода является пустой реализацией, используйте{}средства, такие какRunnable runnable = () -> {};.
    • Если возвращаемое значение абстрактного метода функционального интерфейсаvoidтип, не надоreturnоператоры ключевых слов, такие какRunnable runnable = () -> {int i=0; i++;};.
    • Если метод интерфейса функций абстрактный метод содержит только выражение, не нужно использовать{}пакет, напримерRunnable runnable = () -> System.out.println("Hello World!");.

Вот несколько примеров:

// Function - 具体
java.util.function.Function<String, Integer> functionY = (String string) -> {
    return Integer.parseInt(string);
};
// Function - 简化
java.util.function.Function<String, Integer> functionX = string -> Integer.parseInt(string);

// Runnable - 具体
Runnable runnableX = () -> {
    System.out.println("Hello World!");
};
// Runnable - 简化
Runnable runnableY = () -> System.out.println("Hello World!");

// 整数1-100的和 - 具体
int reduceX = IntStream.range(1, 101).reduce(0, (int addend, int augend) -> {
    return addend + augend;
});
// 整数1-100的和 - 简化
int reduceY = IntStream.range(1, 101).reduce(0, Integer::sum);

Целевые типы и определение типа

Сначала представим следующий сценарий:

// club.throwable.Runnable
@FunctionalInterface
public interface Runnable {

    void run();

    static void main(String[] args) throws Exception {
        java.lang.Runnable langRunnable = () -> {};
        club.throwable.Runnable customRunnable = () -> {};
        langRunnable.run();
        customRunnable.run();
    }
}

Автор определяет иjava.lang.RunnableПолностью согласованный функциональный интерфейсclub.throwable.Runnable,вышеmain()В методе видно, что два интерфейса соответствуютLambdaРеализация тела метода выражения также точно такая же, но очевидно, что для получения возвращаемых значений могут использоваться разные типы интерфейсов, то есть эти дваLambdaТипы не одинаковые. И эти дваLambdaТип возвращаемого значения выражения является тип возвращаемого значения, которое мы в конечном итоге ожидаем (expecting a data type of XX),ТакLambdaВыражение является соответствующиможидаемый тип, ожидаемый типLambdaвыразительныйтип цели.

подтвердитьLambdaВыразительный тип цели,JavaКомпилятор будет основываться на соответствующемLambdaвыражение, используяконтекстИли сцена выводится всесторонне, и одним из факторов суждения является контекст, в котором происходит действие.LambdaТип, ожидаемый выражением. следовательно,только вJavaКомпилятор может правильно вывестиLambdaЕго можно использовать только в сценарии целевого типа выражения.Lambdaвыражение, эти сценарии включают:

  • объявление переменной.
  • Назначение.
  • заявление о возврате.
  • Инициализатор массива.
  • LambdaТело функции выражения.
  • условное выражение (condition ? processIfTrue() : processIfFalse()).
  • Выражения преобразования типов (Cast).

LambdaВ дополнение к целевому типу выражение также включает список параметров и тело метода, а тело метода необходимо реализовать в зависимости от списка параметров, поэтому параметры метода также являются фактором при определении целевого типа.

Процесс вывода типа для параметров метода в основном зависит от двух особенностей языка: разрешения перегрузки (Overload Resolution) и вывод типа параметра (Type Argument Inference).

Оригинал: для аргументов метода компилятор Java определяет целевой тип с помощью двух других функций языка: разрешения перегрузки и вывода аргумента типа.

Для данного метода будет вызвано разрешение перегрузки (Method Invocation), чтобы найти наиболее подходящее объявление метода (Method Declaration). Поскольку разные объявления имеют разные подписи, когдаLambdaКогда выражение используется в качестве параметра метода, разрешение перегрузки влияет наLambdaЦелевой тип выражения. Компилятор будетLambdaПонимание информации, предоставленной выражением, принимает решение. еслиLambdaВыражение имеет явный тип (тип параметра указывается явно), и компилятор может использовать его напрямуюLambdaвозвращаемый тип выражения; еслиLambdaВыражение имеет неявный тип (выводится тип параметра) и игнорируется разрешением перегрузки.Lambdaтело функции-выражения и зависят только отLambdaКоличество аргументов выражения.

Например:

// 显式类型
Function<String, String> functionX = (String x) -> x;

// 隐式类型
Function<String, Integer> functionY = x -> Integer.parseInt(x);

Если в выводе типа есть неоднозначность, которая зависит от параметров метода для объявления лучшего метода (Ambiguous), нам нужно использовать преобразование (Cast) или явноLambdaвыражения, чтобы предоставить больше информации о типе, таким образомLambdaЦелевой тип выражения. Например:

// 编译不通过
Object runnableX = () -> {};

// 编译通过 - Cast
Object runnableY = (Runnable) () -> {};


// 静态方法入参类型是函数式接口
public static void function(java.util.function.Function function) {

}

function((Function<String, Long>) (x) -> Long.parseLong(x));

сфера

Есть несколько вещей, которые следует помнить о масштабе:

  • <1>:Lambdaв выраженииthisСсылки и вложенные классыthisЦитата же.
  • <2>:LambdaВыражение основано на лексической области видимости, оно не наследует никаких переменных от суперкласса, переменные в теле метода имеют ту же семантику, что и переменные в его внешней среде.
  • <3>:Lambda expressions close over values, not variables, это,LambdaВыражения закрыты для типов значений и открыты для переменных (ссылочных) типов (что объясняет, как именноLambdaКогда выражение внутренне ссылается на внешнее свойство, свойство должно быть определено какfinal).

во-первых<1>Возьмите пример:

public class LambdaThis {

    int x = 1;

    public void method() {
        Runnable runnable = () -> {
            int y = this.x;
            y++;
            System.out.println(y);
        };
        runnable.run();
    }

    public static void main(String[] args) throws Exception {
        LambdaThis lambdaThis = new LambdaThis();
        lambdaThis.method();   // 2
    }
}

во-первых<2>Возьмите пример:

public class LambdaScope {
    
    public void method() {
        int x = 1;
        Runnable runnable = () -> {
            // 编译不通过 - Lambda方法体外部已经定义了同名变量
            int x = 2;
        };
        runnable.run();
    }
}

во-первых<3>Возьмите пример:

public class LambdaValue {

    public void method() {
        (final) int x = 1;
        Runnable runnable = () -> {
            // 编译不通过 - 外部值类型使用了final
            x ++;
        };
        runnable.run();
    }
}

public class LambdaValue {

    public void method() {
        (final) IntHolder holder = new IntHolder();
        Runnable runnable = () -> {
            // 编译通过 - 使用了引用类型
            holder.x++;
        };
        runnable.run();
    }

    private static class IntHolder {

        int x = 1;
    }
}

ссылка на метод

ссылка на метод (Method Reference) является функцией иLambdaВыражение, подобное выражению, требует целевого типа и реализации функционального интерфейса, но эта форма реализации связана с существующим методом не через тело метода, а через имя метода (или ключевое слово), которое по сути является компиляцией- уровневая технология, целью которой является дальнейшее упрощениеLambdaТела методов выражения и реализации некоторых конкретных выражений. Типы ссылок на методы сводятся к следующему:

тип пример
ссылка на статический метод ClassName::methodName
Указывает ссылку на метод экземпляра объекта instanceRef::methodName
Любая ссылка на метод объекта определенного типа ContainingType::methodName
ссылка на метод суперкласса supper::methodName
Справочник по методу конструктора ClassName::new
Справочник по методам конструктора массива TypeName[]::new

Можно видеть, что его основная форма:方法容器::方法名称或者关键字.

Вот несколько основных примеров использования:

// 静态方法引用
public class StaticMethodRef {

    public static void main(String[] args) throws Exception {
        Function<String, Integer> function = StaticMethodRef::staticMethod;
        Integer result = function.apply("10086");
        System.out.println(result);  // 10086
    }

    public static Integer staticMethod(String value) {
        return Integer.parseInt(value);
    }
}

// 指定对象实例方法引用
public class ParticularInstanceRef {

    public Integer refMethod(String value) {
        return Integer.parseInt(value);
    }

    public static void main(String[] args) throws Exception{
        ParticularInstanceRef ref = new ParticularInstanceRef();
        Function<String, Integer> function = ref::refMethod;
        Integer result = function.apply("10086");
        System.out.println(result);  // 10086
    }
}

// 特定类型任意对象方法引用
String[] stringArray = {"C", "a", "B"};
Arrays.sort(stringArray, String::compareToIgnoreCase);
System.out.println(Arrays.toString(stringArray)); // [a, B, C]

// 超类方法引用
public class SupperRef {

    public static void main(String[] args) throws Exception {
        Sub sub = new Sub();
        System.out.println(sub.refMethod("10086")); // 10086
    }

    private static class Supper {

        private Integer supperRefMethod(String value) {
            return Integer.parseInt(value);
        }
    }

    private static class Sub extends Supper {

        private Integer refMethod(String value) {
            Function<String, Integer> function = super::supperRefMethod;
            return function.apply(value);
        }
    }
}

// 构造器方法引用
public class ConstructorRef {

    public static void main(String[] args) throws Exception {
        Function<String, Person> function = Person::new;
        Person person = function.apply("doge");
        System.out.println(person.getName()); // doge
    }

    private static class Person {

        private final String name;

        public Person(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }
}

// 数组构造器方法引用
Function<Integer, Integer[]> function = Integer[]::new;
Integer[] array = function.apply(10);
System.out.println(array.length); // 10

Основной принцип реализации Lambda в Java

Суть в том, чтобы сказать три раза:

  • Lambdaнижний слой выражениянетРеализация анонимного класса.
  • Lambdaнижний слой выражениянетРеализация анонимного класса.
  • Lambdaнижний слой выражениянетРеализация анонимного класса.

в глубоком обученииLambdaДо выражения автор тоже думал, чтоLambdaЭто синтаксический сахар для анонимных классов:

// Lambda
Function<String, String> functionX = (String x) -> x;

// 错误认知
Function<String, String> functionX = new Function<String, String>() {
    @Override public Void apply(String x) {
        return x;
    }
}

LambdaЭто синтаксический сахар анонимных классов.Это познание неверно.. Давайте возьмем пример и проанализируем его с точки зрения исходного кода и байт-кода.LambdaВесь поток компиляции и выполнения выражений.

public class Sample {

    public static void main(String[] args) throws Exception {
        Runnable runnable = () -> {
            System.out.println("Hello World!");
        };
        runnable.run();
        String hello = "Hello ";
        Function<String, String> function = string -> hello + string;
        function.apply("Doge");
    }
}

Добавить кVMпараметр-Djdk.internal.lambda.dumpProxyClasses=.запустите вышеуказанноеSample#main()метод, корневой каталог проекта динамически генерирует два класса следующим образом:

import java.lang.invoke.LambdaForm.Hidden;

// $FF: synthetic class
final class Sample?Lambda$14 implements Runnable {
    private Sample?Lambda$14() {
    }

    @Hidden
    public void run() {
        Sample.lambda$main$0();
    }
}

import java.lang.invoke.LambdaForm.Hidden;
import java.util.function.Function;

// $FF: synthetic class
final class Sample?Lambda$15 implements Function {
    private final String arg$1;

    private Sample?Lambda$15(String var1) {
        this.arg$1 = var1;
    }

    private static Function get$Lambda(String var0) {
        return new Sample?Lambda$15(var0);
    }

    @Hidden
    public Object apply(Object var1) {
        return Sample.lambda$main$1(this.arg$1, (String)var1);
    }
}

Проверьте байт-код двух классов и обнаружите, что модификатор классаfinal synthetic. Затем посмотрите прямо на закрытый классSampleБайт-код:

public class club/throwable/Sample {
     <ClassVersion=52>
     <SourceFile=Sample.java>

     public Sample() { // <init> //()V
         <localVar:index=0 , name=this , desc=Lclub/throwable/Sample;, sig=null, start=L1, end=L2>

         L1 {
             aload0 // reference to self
             invokespecial java/lang/Object.<init>()V
             return
         }
         L2 {
         }
     }

     public static main(java.lang.String[] arg0) throws java/lang/Exception { //([Ljava/lang/String;)V
         <localVar:index=0 , name=args , desc=[Ljava/lang/String;, sig=null, start=L1, end=L2>
         <localVar:index=1 , name=runnable , desc=Lclub/throwable/Runnable;, sig=null, start=L3, end=L2>
         <localVar:index=2 , name=hello , desc=Ljava/lang/String;, sig=null, start=L4, end=L2>
         <localVar:index=3 , name=function , desc=Ljava/util/function/Function;, sig=Ljava/util/function/Function<Ljava/lang/String;Ljava/lang/String;>;, start=L5, end=L2>

         L1 {
             invokedynamic 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; : run()Lclub/throwable/Runnable; ()V club/throwable/Sample.lambda$main$0()V (6) ()V
             astore1
         }
         L3 {
             aload1
             invokeinterface club/throwable/Runnable.run()V
         }
         L6 {
             ldc "Hello " (java.lang.String)
             astore2
         }
         L4 {
             aload2
             invokedynamic 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; : apply(Ljava/lang/String;)Ljava/util/function/Function; (Ljava/lang/Object;)Ljava/lang/Object; club/throwable/Sample.lambda$main$1(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; (6) (Ljava/lang/String;)Ljava/lang/String;
             astore3
         }
         L5 {
             aload3
             ldc "Doge" (java.lang.String)
             invokeinterface java/util/function/Function.apply(Ljava/lang/Object;)Ljava/lang/Object;
             pop
         }
         L7 {
             return
         }
         L2 {
         }
     }

     private static synthetic lambda$main$1(java.lang.String arg0, java.lang.String arg1) { //(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
         <localVar:index=0 , name=hello , desc=Ljava/lang/String;, sig=null, start=L1, end=L2>
         <localVar:index=1 , name=string , desc=Ljava/lang/String;, sig=null, start=L1, end=L2>

         L1 {
             new java/lang/StringBuilder
             dup
             invokespecial java/lang/StringBuilder.<init>()V
             aload0 // reference to arg0
             invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
             aload1
             invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
             invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
             areturn
         }
         L2 {
         }
     }

     private static synthetic lambda$main$0() { //()V
         L1 {
             getstatic java/lang/System.out:java.io.PrintStream
             ldc "Hello World!" (java.lang.String)
             invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
         }
         L2 {
             return
         }
     }
// The following inner classes couldn't be decompiled: java/lang/invoke/MethodHandles$Lookup 
}

Вышеуказанный базовый код былBytecode-ViewerИнструменты отформатированы, в соответствии с привычками чтения людей, чтение от байтового кода, в сочетании с предыдущим анализом, вероятно, может нарисовать следующие выводы:

  • <1>:LambdaВыражение добавляет класс шаблона для реализации соответствующего типа интерфейса с помощью технологии улучшения байт-кода во время компиляции.Класс шаблонаИспользуются все свойстваfinalУкрашенные классы шаблонов определяются ключевыми словамиfinal syntheticретушь.
  • <2>: Закрытые занятия основаны наLambdaТипы выражений генерируютсяprivate static syntheticМодифицированный статический метод, тело метода статического метода получено изLambdaтела методов, имена этих статических методовlambda$封闭类方法名$递增数字.
  • <3>:LambdaВызовы выражений заканчиваются инструкциями байт-кодаinvokedynamic, Игнорируя середину процесса, первый до последнего звонка<2>Метод, в котором путь находится на шаге.

Из-за нехватки места здесьLambdaКратко разбирается основной принцип выражения (этот процесс вывода ограничен личным пониманием, и основа еще не достаточна):

  • <1>: объемлющий класс будет основан наLambdaгенерация типа выраженияprivate static syntheticМодифицированный статический метод, тело метода статического метода получено изLambdaтела методов, имена этих статических методовlambda$封闭类方法名$递增数字.
  • <2>:Lambdaвыражение пройдетLambdaMetafactory#metafactory()метод, сгенерируйте класс шаблона, соответствующий функциональному интерфейсу, а реализация метода интерфейса класса шаблона ссылается на первый<1>Статический метод, определенный на шаге при создании сайта вызоваConstantCallSiteпример, который будет передан позжеUnsafe#defineAnonymousClass()Создайте экземпляр класса шаблона. .
  • <3>: call point.ConstantCallSiteдескриптор метода в экземпляреMethodHandleРазличные реализации будут выбраны в соответствии с различными сценариями.MethodHandleСуществует множество подклассов , которые здесь не могут быть расширены.
  • <4>:пройти черезinvokedynamiceинструкция, основанная на<1>экземпляр класса шаблона в шаге,<3>Дескриптор метода в шаге и входные параметры метода используются для вызова дескриптора метода, фактически последний делегат относится к первому<1>Выполняется в статическом методе, определенном на шаге.

Если вы хотите отслеживатьLambdaПолное время жизни вызова выражения, которое можно указать с помощьюLambdaMetafactory#metafactory()метод начинается с записиDEBUG, ссылка на звонок очень большая, и нужно набраться терпения. В целом это:Lambdaвыражение основано наJSR-292Представлен пакет вызова динамического языка.java.lang.invokeиUnsafe#defineAnonymousClass()Определенный облегченный класс шаблона реализован, в основном используетсяinvokedynamiceИнструкции байт-кода, связанные с дескрипторами методовMethodHandle, точка вызоваCallSiteТакие относительно сложные точки знаний не будут здесь подробно излагаться.

настоящий бой

Облегченный пакет DAO на основе JDBCTEMPLATE

Предположим, что таблица заказовDDLследующее:

CREATE TABLE `t_order`
(
    id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    create_time DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    edit_time   DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    user_id     BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
    order_id    VARCHAR(64)     NOT NULL COMMENT '订单ID',
    amount      DECIMAL(12, 2)  NOT NULL DEFAULT 0 COMMENT '订单金额',
    INDEX idx_user_id (user_id),
    UNIQUE uniq_order_id (order_id)
) COMMENT '订单表';

Следующее основано наJdbcTemplateупаковать легкийOrderDao:

// 辅助接口
@FunctionalInterface
public interface PreparedStatementProcessor {

    void process(PreparedStatement ps) throws SQLException;
}

@FunctionalInterface
public interface ResultSetConverter<T> {

    T convert(ResultSet resultSet) throws SQLException;
}

// OrderDao接口
public interface OrderDao {

    int insertSelective(Order record);

    int updateSelective(Order record);

    Order selectOneByOrderId(String orderId);

    List<Order> selectByUserId(Long userId);
}

// OrderDao实现
@Repository
@RequiredArgsConstructor
public class MySqlOrderDao implements OrderDao {

    private final JdbcTemplate jdbcTemplate;

    private static final ResultSetConverter<Order> CONVERTER = r -> {
        Order order = new Order();
        order.setId(r.getLong("id"));
        order.setCreateTime(r.getTimestamp("create_time").toLocalDateTime());
        order.setEditTime(r.getTimestamp("edit_time").toLocalDateTime());
        order.setUserId(r.getLong("user_id"));
        order.setAmount(r.getBigDecimal("amount"));
        order.setOrderId(r.getString("order_id"));
        return order;
    };

    private static final ResultSetExtractor<List<Order>> MULTI = r -> {
        List<Order> list = new ArrayList<>();
        while (r.next()) {
            list.add(CONVERTER.convert(r));
        }
        return list;
    };

    private static final ResultSetExtractor<Order> SINGLE = r -> {
        if (r.next()) {
            return CONVERTER.convert(r);
        }
        return null;
    };

    @Override
    public int insertSelective(Order record) {
        List<PreparedStatementProcessor> processors = new ArrayList<>();
        StringBuilder sql = new StringBuilder("INSERT INTO t_order(");
        Cursor cursor = new Cursor();
        if (null != record.getId()) {
            int idx = cursor.add();
            sql.append("id,");
            processors.add(p -> p.setLong(idx, record.getId()));
        }
        if (null != record.getOrderId()) {
            int idx = cursor.add();
            sql.append("order_id,");
            processors.add(p -> p.setString(idx, record.getOrderId()));
        }
        if (null != record.getUserId()) {
            int idx = cursor.add();
            sql.append("user_id,");
            processors.add(p -> p.setLong(idx, record.getUserId()));
        }
        if (null != record.getAmount()) {
            int idx = cursor.add();
            sql.append("amount,");
            processors.add(p -> p.setBigDecimal(idx, record.getAmount()));
        }
        if (null != record.getCreateTime()) {
            int idx = cursor.add();
            sql.append("create_time,");
            processors.add(p -> p.setTimestamp(idx, Timestamp.valueOf(record.getCreateTime())));
        }
        if (null != record.getEditTime()) {
            int idx = cursor.add();
            sql.append("edit_time,");
            processors.add(p -> p.setTimestamp(idx, Timestamp.valueOf(record.getEditTime())));
        }
        StringBuilder realSql = new StringBuilder(sql.substring(0, sql.lastIndexOf(",")));
        realSql.append(") VALUES (");
        int idx = cursor.idx();
        for (int i = 0; i < idx; i++) {
            if (i != idx - 1) {
                realSql.append("?,");
            } else {
                realSql.append("?");
            }
        }
        realSql.append(")");
        // 传入主键的情况
        if (null != record.getId()) {
            return jdbcTemplate.update(realSql.toString(), p -> {
                for (PreparedStatementProcessor processor : processors) {
                    processor.process(p);
                }
            });
        } else {
            // 自增主键的情况
            KeyHolder keyHolder = new GeneratedKeyHolder();
            int count = jdbcTemplate.update(p -> {
                PreparedStatement ps = p.prepareStatement(realSql.toString(), Statement.RETURN_GENERATED_KEYS);
                for (PreparedStatementProcessor processor : processors) {
                    processor.process(ps);
                }
                return ps;
            }, keyHolder);
            record.setId(Objects.requireNonNull(keyHolder.getKey()).longValue());
            return count;
        }
    }

    @Override
    public int updateSelective(Order record) {
        List<PreparedStatementProcessor> processors = new ArrayList<>();
        StringBuilder sql = new StringBuilder("UPDATE t_order SET ");
        Cursor cursor = new Cursor();
        if (null != record.getOrderId()) {
            int idx = cursor.add();
            sql.append("order_id = ?,");
            processors.add(p -> p.setString(idx, record.getOrderId()));
        }
        if (null != record.getUserId()) {
            int idx = cursor.add();
            sql.append("user_id = ?,");
            processors.add(p -> p.setLong(idx, record.getUserId()));
        }
        if (null != record.getAmount()) {
            int idx = cursor.add();
            sql.append("amount = ?,");
            processors.add(p -> p.setBigDecimal(idx, record.getAmount()));
        }
        if (null != record.getCreateTime()) {
            int idx = cursor.add();
            sql.append("create_time = ?,");
            processors.add(p -> p.setTimestamp(idx, Timestamp.valueOf(record.getCreateTime())));
        }
        if (null != record.getEditTime()) {
            int idx = cursor.add();
            sql.append("edit_time = ?,");
            processors.add(p -> p.setTimestamp(idx, Timestamp.valueOf(record.getEditTime())));
        }
        StringBuilder realSql = new StringBuilder(sql.substring(0, sql.lastIndexOf(",")));
        int idx = cursor.add();
        processors.add(p -> p.setLong(idx, record.getId()));
        realSql.append(" WHERE id = ?");
        return jdbcTemplate.update(realSql.toString(), p -> {
            for (PreparedStatementProcessor processor : processors) {
                processor.process(p);
            }
        });
    }

    @Override
    public Order selectOneByOrderId(String orderId) {
        return jdbcTemplate.query("SELECT * FROM t_order WHERE order_id = ?", p -> p.setString(1, orderId), SINGLE);
    }

    @Override
    public List<Order> selectByUserId(Long userId) {
        return jdbcTemplate.query("SELECT * FROM t_order WHERE order_id = ?", p -> p.setLong(1, userId), MULTI);
    }

    private static class Cursor {

        private int idx;

        public int add() {
            idx++;
            return idx;
        }

        public int idx() {
            return idx;
        }
    }
}

похожий наMybatis Generator, вышесказанноеDAOДля достижения автора сделали простой генератор, пока атрибуты соединения и правила фильтрации таблицы настраиваются, соответствующие классы сущности иDAOсвоего рода.

Значение настройки VO на основе опционального

// 假设VO有多个层级,每个层级都不知道父节点是否为NULL,如下
// - OrderInfoVo
//   - UserInfoVo
//     - AddressInfoVo
//        - address(属性)
// 假设我要为address属性赋值,那么就会产生箭头型代码。

// 常规方法
String address = "xxx";
OrderInfoVo o = ...;
if(null != o){
    UserInfoVo uiv = o.getUserInfoVo();
    if (null != uiv){
        AddressInfoVo aiv = uiv.getAddressInfoVo();
        if (null != aiv){
            aiv.setAddress(address);
        }
    }
}

// 使用Optional和Lambda
String address = "xxx";
OrderInfoVo o = ...;
Optional.ofNullable(o).map(OrderInfoVo::getUserInfoVo).map(UserInfoVo::getAddressInfoVo).ifPresent(a -> a.setAddress(address));

резюме

LambdaдаJavaВ сладком синтаксическом сахаре обнимиLambda, охватывая функциональное программирование, автор также испытал на себе процесс сопротивления, не оптимистичный, начинающий и действительно ароматный, и в настоящее время много его используетStreamиLambda, который может максимально упростить код и высвободить труд при условии обеспечения производительности. Времена идут вперед,JavaОн также прогрессирует, в чем верят многие люди, которые живут и упорствуют в своей карьере программиста.

Использованная литература:

личный блог

(Конец этой статьи e-a-20200208 c-5-d)

Технический публичный аккаунт ("Throwable Digest"), который время от времени выкладывает оригинальные технические статьи автора (никогда не занимайтесь плагиатом и не перепечатывайте):