02.JDK8-Lambda表达式

81 阅读6分钟

Lambda表达式

1.目录

2.什么是Lambda表达式

Lambda是一种语法糖,是为了替代在某些场景下的匿名内部类而存在的,可以大幅度减少没有必要的编码

它是函数式编程的的一个重要特性,标志着Java向函数式编程迈出了重要的第一步

3.Lambda的标准格式

标准格式介绍

        (参数类型 参数名称)->{
            方法体;
        }

格式说明

  • (参数类型 参数名称):参数列表
  • {代码体;}:方法体
  • ->:箭头,分隔参数列表和方法体

4.常见用法

无参数,无返回值

JDK8之前的做法

当需要启动一个线程去完成任务时,通常会通过Runnable接口来定义任务内容,并使用Thread类来启动该线程

/**
 * @author sgy
 * @date 2022/5/30 14:03
 * @description 演示匿名内部类的问题
 */
public class Demo01 {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新线程任务执行");
            }
        }).start();
    }
}
Lambda写法
/**
 * @author sgy
 * @date 2022/5/30 14:05
 * @description 使用Lambda来初步优化匿名内部类
 */
public class Demo02 {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("新线程任务执行!")).start();
    }
}

有参数,有返回值

/**
 * @author sgy
 * @date 2022/5/30 14:05
 * @description 有参数有返回值的Lambda
 */
public class Demo04 {
    public static void main(String[] args) {
        List<Person> persons = new ArrayList<>();
        persons.add(new Person("刘德华", 2, "男"));
        persons.add(new Person("张学友", 1, "女"));
        persons.add(new Person("刘德华", 4, "男"));
        persons.add(new Person("黎明", 9, "男"));

        // 原生方式按照从小到大排序(id)
        Collections.sort(persons, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getId() - o2.getId();
            }
        });
        for (Person person : persons) {
            System.out.println(person);
        }

        // Lambda排序
        Collections.sort(persons, (o1, o2) -> {
            return o1.getId() - o2.getId();
        });
        for (Person person : persons) {
            System.out.println(person);
        }
    }
}

5.Lambda的省略格式

说明

Lambda的标准格式是可以进一步被优化的,只不过要满足一些特殊条件

省略规则

  1. 小括号内参数的类型可以省略
  2. 如果小括号内有且仅有一个参数,则小括号可以省略
  3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号

省略前代码

(int a)->{
    return new person();
}

省略后代码

a->new Person()

6.使用Lambda的前提条件

  1. 方法的参数或局部变量类型必须为接口才能使用Lambda
  2. 接口中有且仅有一个抽象方法

7.函数式接口

什么是函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导

FunctionalInterface注解

与@Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface。该注解可用于一个接口的定义上:

@FunctionalInterface 
public interface Operator { 
    void myMethod(); 
}

一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

8.Lambda和匿名内部类对比

  1. 所需的类型不一样:
    • 匿名内部类,需要的类型可以是类,抽象类,接口
    • Lambda表达式,需要的类型必须是接口
  2. 抽象方法的数量不一样
    • 匿名内部类所需的接口中抽象方法的数量随意
    • Lambda表达式所需的接口只能有一个抽象方法

9.常用内置函数式接口

介绍

我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口

常用内置函数式接口

它们主要在 java.util.function 包中

  • Supplier:一般用于无参创建某对象
  • Consumer:进T无返回值
  • Function:进T返R
  • Predicate:进T返回Boolean,一般用于判断

Supplier接口

介绍

它意味着供给,对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据

代码
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
案例
目标

使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值

提示:接口的泛型请使用java.lang.Integer类

代码

Consumer接口

介绍

它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定

代码
@FunctionalInterface
public interface Consumer<T> {


    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
案例
目标

使用Lambda表达式将一个字符串转成大写的字符串

代码

Function接口

介绍

接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有参数有返回值

代码
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }


    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
案例
目标

使用Lambda表达式将字符串转成数字

代码

Predicate接口

介绍

有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用它

代码
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
案例
目标

使用Lambda判断一个人名如果超过3个字就认为是很长的名字

代码

10.方法引用

为什么需要方法引用

进一步简化Lambda表达式

如果我们在Lambda中所指定的功能,已经有其他方法存在相同方案,那是否还有必要再写重复逻辑?可以直接“引用”过去就好了

应用场景

如果Lambda所要实现的方案 ,已经有其他方法存在相同方案,那么则可以使用方法引用

语法定义

  1. instanceName::methodName 对象::方法名
  2. ClassName::staticMethodName 类名::静态方法
  3. ClassName::methodName 类名::普通方法
  4. ClassName::new 类名::new 调用的构造器
  5. TypeName[]::new String[]::new 调用数组的构造器

第一种语法

对象::方法名

    @Test
    public void test01() {
        Date now = new Date();
        Supplier<Long> supp = () -> {
            return now.getTime();
        };
        System.out.println(supp.get());
        Supplier<Long> supp2 = now::getTime;
        System.out.println(supp2.get());
    }

第二种语法

类名::静态方法

    @Test
    public void test02() {
        Supplier<Long> supp = () -> {
            return System.currentTimeMillis();
        };
        System.out.println(supp.get());
        Supplier<Long> supp2 = System::currentTimeMillis;
        System.out.println(supp2.get());
    }

第三种语法

类名::引用实例方法

Java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者

    @Test
    public void test03() {
        Function<String, Integer> f1 = (s) -> {
            return s.length();
        };
        System.out.println(f1.apply("abc"));
        Function<String, Integer> f2 = String::length;
        System.out.println(f2.apply("abc"));
        BiFunction<String, Integer, String> bif = String::substring;
        String hello = bif.apply("hello", 2);
        System.out.println("hello = " + hello);
    }

第四种语法

类名::new引用构造器

    @Test
    public void test04() {
        Supplier<Person> sup = () -> {
            return new Person();
        };
        System.out.println(sup.get());
        Supplier<Person> sup2 = Person::new;
        System.out.println(sup2.get());
        BiFunction<String, Integer, Person> fun2 = Person::new;
        System.out.println(fun2.apply("张三", 18));
    }

第五种语法

数组::new 引用数组构造器

    @Test
    public void test05() {
        Function<Integer, String[]> fun = (len) -> {
            return new String[len];
        };
        String[] arr1 = fun.apply(10);
        System.out.println(arr1 + ", " + arr1.length);
        Function<Integer, String[]> fun2 = String[]::new;
        String[] arr2 = fun.apply(5);
        System.out.println(arr2 + ", " + arr2.length);
    }