Lambda表达式

image

Lambda表达式的格式
  • 格式:(形式参数) ->
  • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
  • ->:有英文中划线和大于符号组成,固定写法。代表指向动作
  • 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
Lambda表达式的使用前提:
  • 有一个接口
  • 接口中有且仅有一个抽象方法
练习演示:
  • 定义一个接口(Addable),里面定义一个抽象方法:int add(int x, int y);
  • 定义一个测试类(AddableDemo),在测试类中提供两个方法
    • 一个方法是:useAddable(Addable a)
    • 一个方法是主方法(main),在主方法中调用useAddable方法
// 接口
public interface Addable{
    int add(int x, int y);
}
// 测试类
public class AddableDemo{
    public static void main(String[] args){
        useAddable((int a, int b) -> {
            return x+y;
        });
    }
    public static void useAddable(Addable a){
        int sum = a.add(10,20);
        System.out.println(sum);
    }
}

// 控制台输出结果
30

Lambda表达式的省略模式

省略规则:
  • 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
  • 如果参数有且仅有一个,那么小括号可以省略
  • 如果代码块的语句只有一条,可以省略大括号和分号,甚至是return关键字
// 接口
public interface Flyable{
    void fly(String s);
}
// 测试类
public class AddableDemo{
    public static void main(String[] args){
        useFlyable((String s) -> {
            System.out.println(s);
        });
        // 参数类型省略
        useFlyable((s) -> {
            System.out.println(s);
        });
        // 小括号可以省略(参数有且仅有一个)
        useFlyable(s -> {
            System.out.println(s);
        });
        // 大括号和分号可以省略(代码块的语句仅有一条--如果有return关键字,则如果省略大括号和分号return关键字也要一起省略掉)
        useFlyable(s -> System.out.println(s));
    }
    public static void useFlyable(Flyable f){
        f.fly("风和日丽,晴空万里");
    }
}

// 控制台输出结果
风和日丽,晴空万里
风和日丽,晴空万里
风和日丽,晴空万里
风和日丽,晴空万里

Lambda表达式的注意事项

image

Lambda表达式和匿名内部类的区别

1.所需类型不同
  • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
  • Lambda表达式:只能是接口
2.使用限制不同
  • 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
  • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda
3.实现原理不同
  • 匿名内部类:编译之后,产生一个单独的.class字节码文件
  • Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成

方法引用符

  • :: 该符号为引用运算符,而它所在的表达式被称为方法引用
举例:
  • Lambda表达式:usePrintable(s -> System.out.println(s));
    • 分析:拿到参数s之后通过lambda表达式,传递给System.out.println方法去处理
  • 方法引用:usePrintable(System.out::println);
    • 分析:直接使用System.out中的println方法来取代Lambda表达式,代码更加的简洁
推导与省略
  • 如果使用Lambda。那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定重载形式,它们都将被自动推导
  • 如果使用方法引用,也是同样可以根据上下文进行推导
  • 方法引用是Lambda的孪生兄弟
// 接口
public interface Printable{
    void printInt(int i);
}
// 测试类
public class PrintableDemo{
    public static void main(String[] args){
        // Lambda表达式
        usePrintable(i -> System.out.println(i));
        // 方法引用
        usePrintable(System.out::println);
    }
    public static void usePrintable(Printable p){
       p.printInt(666);
    }
}

// 控制台输出结果
666
666

Lambda表达式支持的方法引用

常见的引用方式:
  • 引用类方法
  • 引用对象的实例方法
  • 引用类的实例方法
  • 引用构造器

1.引用类方法

引用类方法,其实就是引用类的静态方法
  • 格式:类名::静态方法
  • 范例:Integer::parseInt
    • Integer类的方法:public static int parseInt(String s)将此字符串转换为int类型的数据
// 接口
public interface Converter{
    int converter(String s);
}
// 测试类
public class ConverterDemo{
    public static void main(String[] args){
        // Lambda表达式
        useConverter(s -> Integer.parseInt(s));
        // 引用类方法
        useConverter(Integer::parseInt);
    }
    public static void useConverter(Converter c){
       	int num = c.converter("666");
       	System.out.println(num);
    }
}

// 控制台输出结果
666
666

2.引用对象的实例方法

引用对象的实例方法,其实就是引用类中的成员方法
  • 格式:对象::成员方法
  • 范例:"HelloWorld"::toUpperCase
    • String类中的方法:public String toUpperCase()将此String所有字符转换为大写
// 类
public class PrintString{
    // 把字符串参数变成大写的数据输出到控制台
    public void printUpper(String s){
    	String result = s.toUpperCase();
        System.out.println(result);
    }
}
// 接口
public interface Printer{
    void printUpperCase(String s);
}
// 测试类
public class PrinterDemo{
    public static void main(String[] args){
        // Lambda表达式
        usePrinter((String s) -> {
            String result = s.toUpperCase();
        	System.out.println(result);
        });
        // 引用对象的实例方法
        PrintString ps = new PrintString();
        usePrinter(ps::printUpper);
    }
    public static void usePrinter(Printer p){
       	p.printUpperCase("HelloWorld");
    }
}

// 控制台输出结果
HELLOWORLD
HELLOWORLD

3.引用类的实例方法

引用类的实例方法,其实就是引用类中的成员方法
  • 格式:类名::成员方法
  • 范例:String::substring
    • String类中的方法:public String substring(int beginIndex, int endIndex)
    • 从beginIndex开始到endIndex结束,截取字符串。返回一个子字符串,子字符串的长度为(endIndex - beginIndex)
// 接口
public interface MyString{
    String mySubString(String s, int x, int y);
}
// 测试类
public class MyStringDemo{
    public static void main(String[] args){
        // Lambda表达式
        useMyString((String s, int x, int y) -> {
            return s.substring(x,y);
        });
        // 引用类的实例方法
        useMyString(String::substring);
    }
    public static void useMyString(MyString my){
       	String s = my.mySubString("HelloWorld",2,5);
        System.out.println(s);
    }
}

// 控制台输出结果
llo
llo

4.引用构造器

引用构造器,其实就是引用构造方法
  • 格式:类名::new
  • 范例:Student::new
// Student类
public class Student{
    private String name;
    private int age;
    public Student(){}
    public Student(String name, int age){
        this.name = name;
        this.age = age;
    }
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
}
// 接口
public interface StudentBuilder{
    Student build(String name, int age);
}
// 测试类
public class StudentDemo{
    public static void main(String[] args){
        // Lambda表达式
        useStudentBuilder((String name, int age) -> {
            Student s = new Student(name,age);
            return s;
        });
        // 引用构造器
        useStudentBuilder(Student::new);
    }
    public static void useStudentBuilder(StudentBuilder sb){
       	Student s = sb.build("小明",18);
        System.out.println(s.getName()+","+s.getAge());
    }
}

// 控制台输出结果
小明,18
小明,18

函数式接口

函数式接口:有且仅有一个抽象方法的接口

Java中函数式编程体现的就是Lambda表达式,所以函数式接口就是可以适用于Lambda所使用的接口。

只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导

  • @FunctionalInterface 注解
  • 放在接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败
  • 我们自己定义函数式接口的时候,@FunctionalInterface注解是可选的,就算我们不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。但是,建议加上该注解
常见的函数式接口:
  • Supplier接口
  • Consumer接口
  • Predicate接口
  • Function接口

1.Supplier接口

Supplier :包含一个无参的方法

  • T get():获得结果
  • 该方法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
  • Supplier 接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就生产什么类型的数据供我们使用
练习演示:
  • 定义一个类(SupplierTest),在类中提供两个方法
    • 一个方法是:int getMax(Supplier sup) 用于返回一个int数组中的最大值
    • 一个方法是主方法main,在主方法中调用getMax方法
public class SupplierTest{
    public static void main(String[] args){
        // 定义一个int数组
        int[] arr = {19, 50, 28, 37, 46};
        // 调用方法--参数通过Lambda表达式传递
        int maxValue = getMax(() -> {
            int max = arr[0];
            for(int i=1;i<arr.length;i++){
                if(arr[i]>max){
                    max = arr[i];
                }
            }
            return max;
        });
        System.out.println(maxValue);
    }
    // 定义一个方法,返回一个int数组中的最大值
    private static int  getMax(Supplier<Integer> sup){
        return sup.get();
    }
}

// 控制台输出结果
50

2.Consumer接口

Consumer :包含两个方法

  • void accept(T t):对给定的参数执行操作
  • default Consumer andThen(Consumer after):返回一个组合的Consumer,依次执行此操作,然后执行after操作
  • Consumer 接口也被称为消费型接口,它消费的数据的数据类型由泛型指定
练习演示:
  • String[] strArray = {"小明,18","小兰,19","小茹,20"};
  • 字符串数组中有多条信息,请按照格式:"姓名:XX,年龄:XX"的格式将信息打印出来
  • 要求:
    • 把打印姓名的动作作为第一个Consumer接口的Lambda实例
    • 把打印年龄的动作作为第二个Consumer接口的Lambda实例
    • 将两个Consumer接口按照顺序组合到一起使用
public class ConsumerTest{
    public static void main(String[] args){
        // 定义一个字符串数组
        String[] strArray = {"小明,18","小兰,19","小茹,20"};
        // 调用方法--参数通过Lambda表达式传递
        printInfo(strArray,(String str) -> {
            String name = str.split(",")[0];
            System.out.print("姓名:"+name);
        },(String str) -> {
            int age = Integer.parseInt(str.split(",")[1]);
            System.out.println(",年龄:"+age);
        });
        
        System.out.println("----------------");
        
        // 优化代码版
        printInfo(strArray,str -> System.out.print("姓名:"+str.split(",")[0]),str -> System.out.println(",年龄:"+Integer.parseInt(str.split(",")[1])));
        
    }
    
    private static void printInfo(String[] strArray, Consumer<String> con1, Consumer<String> con2){
        for(String str : strArray){
        	con1.andThen(con2).accept(str);
        }
    }
}

// 控制台输出结果
姓名:小明,年龄:18
姓名:小兰,年龄:19
姓名:小茹,年龄:20
----------------
姓名:小明,年龄:18
姓名:小兰,年龄:19
姓名:小茹,年龄:20

3.Predicate接口

Predicate :常用的四个方法

  • boolean test(T t):对给定的参数进行判断(判断逻辑有Lambda表达式实现),返回一个布尔值
  • default Predicate negate():返回一个逻辑的否定,对应逻辑非 !
  • default Predicate and(Predicate other):返回一个组合判断,对应短路与 &&
  • default Predicate or(Predicate other):返回一个组合判断,对应短路或 ||
  • Predicate 接口通常用于判断参数是否满足指定的条件
练习演示:
  • String[] strArray = {"小明,18","小兰,19","小茹,20","小琪琪,34","小雯雯,35"};
  • 字符串数组中有多条信息,请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,并遍历ArrayList集合
  • 需要同时满足如下要求:
    • 姓名长度大于2
    • 年龄大于33
  • 分析:
    • 有两个判断条件,所以需要使用两个Predicate接口,对条件进行判断
    • 必须同时满足两个条件,所以可以使用and()方法连接两个判断条件
public class PredicateTest{
    public static void main(String[] args){
        // 定义一个字符串数组
        String[] strArray = {"小明,18","小兰,19","小茹,20","小琪琪,34","小雯雯,35"};
        // 调用方法--参数通过Lambda表达式传递
        ArrayList<String> array1 = myFilter(strArray,(String str) -> {
            String name = str.split(",")[0];
            if(name.length()>2){
                return true;
            }
            return false;
        },(String str) -> {
            int age = Integer.parseInt(str.split(",")[1]);
            if(age>33){
            	return true;
            }
            return false;
        });
        // 遍历符合条件存入的集合
        for(String str : array1){
            System.out.println(str);
        }
        
        System.out.println("--------");
        
        // 优化代码版
        ArrayList<String> array2 = myFilter(strArray,str -> str.split(",")[0].length()>2,str -> Integer.parseInt(str.split(",")[1])>33);
        // 遍历符合条件存入的集合
        for(String str : array2){
            System.out.println(str);
        }
    }
    
    // 通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,并遍历ArrayList集合
    private static ArrayList<String> myFilter(String[] strArray, Predicate<String> pre1, Predicate<String> pre2){
        // 定义一个集合
        ArrayList<String> array = new ArrayList<>();
        // 遍历集合
        for(String str : strArray){
            // 判断集合是否满足两个条件
            boolean isSuccess = pre1.and(pre2).test(str);
            // 当两个条件都为true时,添加到新的集合中等待遍历输出
            if(isSuccess){
                array.add(str);
            }
        }
        return array;
    }
}

// 控制台输出结果
小琪琪,34
小雯雯,35
--------
小琪琪,34
小雯雯,35

4.Function接口

Function<T,R> :常用的两个方法

  • R apply(T t):将此函数应用于给定的参数
  • default Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
  • Function<T,R> 接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值
练习演示:
  • String s = "小明,30";
  • 请按照指定的要求进行操作:
    • 将字符串截取得到数字年龄部分
    • 将上一步的年龄字符串转换成int类型的数据
    • 将上一步的int数据加70,得到一个int类型的结果,在控制台输出
  • 通过Function接口来实现函数的拼接
public class FunctionTest{
    public static void main(String[] args){
        String s = "小明,30";
        
        convert(s,(String s) -> {
            return s.split(",")[1];
        },(String ss) -> {
            return Integer.parseInt(ss);
        },(Integer i) -> {
            return i+70;
        })
        
        System.out.println("--------");
        
        // 优化代码版1
        convert(s,s -> s.split(",")[1],ss -> Integer.parseInt(ss),i -> i+70);
        
        System.out.println("--------");
        
        // 优化代码版2
        convert(s,s -> s.split(",")[1],Integer::parseInt,i -> i+70);
    }
    
    private static void convert(String s, Function<String,String> fun1, Function<String,Integer> fun2, Function<Integer,Integer> fun3){
        int i = fun1.andThen(fun2).andThen(fun3).apply(s);
        System.out.println(i);
    }
}

// 控制台输出结果
100
--------
100
--------
100
posted @ 2022-01-16 21:28  早晨9点  阅读(50)  评论(0)    收藏  举报