每天学一点之Lambda表达式

Lambda表达式

思想导入:

函数式编程思想: 在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。编程中的函数,也有类似的概念,你调用我的时候,给我实参为形参赋值,然后通过运行方法体,给你返回一个结果。对于调用者来做,关注这个方法具备什么样的功能。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是谁来做

 @Test
    public void test1(){
        String[] arr = {"hello","java","hi","xiao","yu"};
        //把上面的字符串按照长短排序,从短到长
//        Arrays.sort(arr);//按照编码排序
        //public static  void sort(T[] a, Comparator c)
        Arrays.sort(arr, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
//                return o1.length()-o2.length();
                return Integer.compare(o1.length(), o2.length());
            }
        });
        /*
        上面的写法使用了匿名内部类,既声明了一个类,又创建了一个对象。
        创建这个对象的目的是为了给sort方法的第二个形参c赋值。
        声明匿名内部类的目的是为了重写public int compare(String o1, String o2)
         */
        System.out.println(Arrays.toString(arr));
    }
  //这个需求中我们关心的是什么? 如何比较两个字符串的大小,至于对象不重要
        Arrays.sort(arr, (o1, o2) -> Integer.compare(o1.length(), o2.length()));
        System.out.println(Arrays.toString(arr));

做什么,而不是谁来做,怎么做

我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将compareTo方法体内的代码传递给sort方法知晓。

传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。使用Lambda表达式不再有“不得不创建接口对象”的束缚,就是这么简单!

一、函数接口的概念

Lambda表达式其实就是实现SAM接口的语法糖,所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法(可以包含默认方法,静态方法,也就是实现此接口的子类只要求实现一个方法)。只要满足“SAM”特征的接口都可以称为函数式接口,都可以使用Lambda表达式,但是如果要更明确一点,最好在声明接口时,加上 @FunctionalInterface. 一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

虽然Iterable和Comparable接口也只有一个抽象方法,但它们没有@FunctionalInterface注解标记,因此不算。

序号 接口 抽象方法 SAM接口
1 java.lang.Runnable public void run()
2 java.util.Comparator public int compare(T t1, T t2)
3 java.io.FileFilter public boolean accept(File pathname)
4 java.io.FilenameFilter public boolean accept(File dir, String name)

Lambda表达式语法格式

(形参列表) -> {Lambda体}

Lambda表达式的简化

  • 当{Lambda体}中只有一句语句时,可以省略{}和{;}
  • 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,那么{、return、;}三者可以省略。它们三要么一起省略,要么都不省略。
  • 当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
  • 当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
  • 当Lambda表达式(形参列表)是空参时,()不能省略

二、Java8之后引入的函数式接口

Java8在java.util.function新增了很多函数式接口:主要分为四大类,消费型、供给型、判断型、功能型。

1、消费型接口
有形参,但是返回值类型是void

序号 接口名 抽象方法 描述
1 Consumer void accept(T t) 接收一个对象用于完成功能
2 BiConsumer void accept(T t, U u) 接收两个对象用于完成功能
3 DoubleConsumer void accept(double value) 接收一个double值
4 IntConsumer void accept(int value) 接收一个int值
5 LongConsumer void accept(long value) 接收一个long值
6 ObjDoubleConsumer void accept(T t, double value) 接收一个对象和一个double值
7 ObjIntConsumer void accept(T t, int value) 接收一个对象和一个int值
8 ObjLongConsumer void accept(T t, long value) 接收一个对象和一个long值

常用的方法:

public default void forEach(Consumer action) 该方法功能是遍历Collection集合,并将传递给action参数的操作代码应用在每一个元素上。

 List<String> list = Arrays.asList("java","c","python","c++","VB","C#");
        list.forEach(s -> System.out.println(s));

2、供给型接口
无参,但是有返回值

序号 接口名 抽象方法 描述
1 Supplier T get() 返回一个对象
2 BooleanSupplier boolean getAsBoolean() 返回一个boolean值
3 DoubleSupplier double getAsDouble() 返回一个double值
4 IntSupplier int getAsInt() 返回一个int值
5 LongSupplier long getAsLong() 返回一个long值

3、判断型接口
有参,但是返回值类型是boolean结果。

序号 接口名 抽象方法 描述
1 Predicate boolean test(T t) 接收一个对象
2 BiPredicate boolean test(T t, U u) 接收两个对象
3 DoublePredicate boolean test(double value) 接收一个double值
4 IntPredicate boolean test(int value) 接收一个int值
5 LongPredicate boolean test(long value) 接收一个long值

常用的方法

public default boolean removeIf(Predicate filter) 用于删除集合中满足filter指定的条件判断的。

   //删除包含o字母的元素
        list.removeIf(s -> s.contains("o"));

4、功能型接口
既有参数又有返回值

序号 接口名 抽象方法 描述
1 Function R apply(T t) 接收一个T类型对象,返回一个R类型对象结果
2 UnaryOperator T apply(T t) 接收一个T类型对象,返回一个T类型对象结果
3 DoubleFunction R apply(double value) 接收一个double值,返回一个R类型对象
4 IntFunction R apply(int value) 接收一个int值,返回一个R类型对象
5 LongFunction R apply(long value) 接收一个long值,返回一个R类型对象
6 ToDoubleFunction double applyAsDouble(T value) 接收一个T类型对象,返回一个double
7 ToIntFunction int applyAsInt(T value) 接收一个T类型对象,返回一个int
8 ToLongFunction long applyAsLong(T value) 接收一个T类型对象,返回一个long
9 DoubleToIntFunction int applyAsInt(double value) 接收一个double值,返回一个int结果
10 DoubleToLongFunction long applyAsLong(double value) 接收一个double值,返回一个long结果
11 IntToDoubleFunction double applyAsDouble(int value) 接收一个int值,返回一个double结果
12 IntToLongFunction long applyAsLong(int value) 接收一个int值,返回一个long结果
13 LongToDoubleFunction double applyAsDouble(long value) 接收一个long值,返回一个double结果
14 LongToIntFunction int applyAsInt(long value) 接收一个long值,返回一个int结果
15 DoubleUnaryOperator double applyAsDouble(double operand) 接收一个double值,返回一个double
16 IntUnaryOperator int applyAsInt(int operand) 接收一个int值,返回一个int结果
17 LongUnaryOperator long applyAsLong(long operand) 接收一个long值,返回一个long结果
18 BiFunction R apply(T t, U u) 接收一个T类型和一个U类型对象,返回一个R类型对象结果
19 BinaryOperator T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
20 ToDoubleBiFunction double applyAsDouble(T t, U u) 接收一个T类型和一个U类型对象,返回一个double
21 ToIntBiFunction int applyAsInt(T t, U u) 接收一个T类型和一个U类型对象,返回一个int
22 ToLongBiFunction long applyAsLong(T t, U u) 接收一个T类型和一个U类型对象,返回一个long
23 DoubleBinaryOperator double applyAsDouble(double left, double right) 接收两个double值,返回一个double结果
24 IntBinaryOperator int applyAsInt(int left, int right) 接收两个int值,返回一个int结果
25 LongBinaryOperator long applyAsLong(long left, long right) 接收两个long值,返回一个long结果

常用的方法

default void replaceAll(UnaryOperator operator)将该列表的每个元素替换为将该运算符应用于该元素的结果。

 //使用Lambda表达式实现Function接口的子接口UnaryOperator
        // 可以实现将一个字符串首字母转为大写的功能。
        list.replaceAll(s -> s.substring(0,1).toUpperCase() + s.substring(1));

5、自定义函数式接口
只要确保接口中有且仅有一个抽象方法即可:

修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}

接口当中抽象方法的 public abstract 是可以省略的

案例:

  • 声明一个转换器Convertor,包含抽象方法change,可以将参数转换为另一个值,并返回结果。其中T是参数类型,R是返回值类型。
@FunctionalInterface
public interface Convertor<T,R> {
    R change(T t);
}
  //使用Lambda表达式实现Convertor接口,实现取字符串的首字母的功能
        Convertor<String,Character> c1 = (String str)-> {return str.charAt(0);};
        System.out.println(c1.change("hello"));

三、方法引用与构造器引用

Lambda表达式是可以简化函数式接口的变量与形参赋值的语法。而方法引用和构造器引用是为了简化Lambda表达式的。

当Lambda表达式满足一些特殊的情况时,还可以再简化:

(1)Lambda体只有一句语句,并且是通过调用一个对象的/类现有的方法来完成的

(2)并且Lambda表达式的形参正好全部用上,Lambda体中没有额外的数据参与

序号 语法格式 场景
1 实例对象名::实例方法 Lambda表达式有多个形参,Lambda体是调用Lambda体外的某个实例对象的实例方法完成,并且Lambda表达式的形参正好依次按顺序作为该方法调用的实参
2 类名::静态方法 Lambda表达式有多个形参,Lambda体是调用某个类的静态方法完成,并且Lambda表达式的形参正好依次按顺序作为该方法调用的实参
3 类名::实例方法 Lambda表达式只有1个形参,该参数正好Lambda体中调用方法的对象
Lambda表达式有多个形参,其中第1个参数正好是Lambda体中调用方法的对象,其余形参正好依次按顺序作为该方法调用的实参
4 类名::new 当Lambda表达式是一个new表达式,并且Lambda表达式形参正好依次按顺序作为所调用构造器的实参
5 数组类型名::new 当Lambda表达式是一个创建数组对象的new表达式,Lambda表达式的形参正好是创建数组的长度
List<Integer> list = Arrays.asList(1,3,4,8,9);
        //list.forEach(t -> System.out.println(t));
        //用方法引用再简化
        list.forEach(System.out::println);



//        File[] subFiles = dir.listFiles(sub->sub.isFile());
        File[] subFiles = dir.listFiles(File::isFile);

      //这个需求中我们关心的是什么? 如何比较两个字符串的大小,至于对象不重要
//        Arrays.sort(arr, (o1, o2) -> Integer.compare(o1.length(), o2.length()));
//        Arrays.sort(arr, Comparator.comparingInt(s->s.length()));
        Arrays.sort(arr, Comparator.comparingInt(String::length));


  Arrays.sort(arr, (s1,s2) -> s1.compareToIgnoreCase(s2));

        //用方法引用简化
        /*
         * Lambda表达式的形参,第一个(例如:s1),正好是调用方法的对象,剩下的形参(例如:s2)正好是给这个方法的实参
         */
        Arrays.sort(arr, String::compareToIgnoreCase);


 ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list,"张三","李四","王五");
        //用list集合中的姓名,创建一个一个的Student对象
//        ArrayList

     /*   List result = list.stream()
                .map(name -> new Student(name)) //Function R apply(T t)
                .collect(Collectors.toList());*/

        List<Student> result = list.stream()
                .map(Student::new) //Function R apply(T t)
                .collect(Collectors.toList());

        result.forEach(System.out::println);


 Optional<Integer> opt1 = Optional.ofNullable(16);
//        Optional opt2 = opt1.map(len -> new String[len]);
        Optional<String[]> opt2 = opt1.map(String[]::new);
        System.out.println(opt2.orElse(new String[0]).length);

四、Optional类

为了解决空指针异常
Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。

序号 构造器或方法 描述
1 static Optional empty() 用来创建一个空的Optional
2 static Optional of(T value) 用来创建一个非空的Optional
3 static Optional ofNullable(T value) 用来创建一个可能是空,也可能非空的Optional
4 T get() 返回Optional容器中的对象。要求Optional容器必须非空。T get()与of(T value)使用是安全的
5 T orElse(T other) 如果Optional容器中非空,就返回所包装值,如果为空,就用orElse(T other)other指定的默认值(备胎)代替。一般orElse(T other) 与ofNullable(T value)配合使用
6 T orElseGet(Supplier other) 如果Optional容器中非空,就返回所包装值,如果为空,就用Supplier接口的Lambda表达式提供的值代替
7 T orElseThrow(Supplier exceptionSupplier) 如果Optional容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException
8 boolean isPresent() 判断Optional容器中的值是否存在
9 void ifPresent(Consumer consumer) 判断Optional容器中的值是否存在,如果存在,就对它进行Consumer指定的操作,如果不存在就不做
10 Optional map(Function mapper) 判断Optional容器中的值是否存在,如果存在,就对它进行Function接口指定的操作,如果不存在就不做
//其他方法类似
String str = null;
        Optional<String> opt = Optional.ofNullable(str);
//		System.out.println(opt.get());//java.util.NoSuchElementException: No value present
        String string = opt.orElse("javaxiaoyu");
         System.out.println(string);   //javaxiaoyu

你可能感兴趣的:(Java,java,jvm,算法)