Lambda表达式超详细总结

    技术2023-12-24  84

    文章目录

    一、什么是Lambda表达式二、为什么使用Lambda表达式三、Lambda表达式语法四、函数式接口1.什么是函数式接口2.自定义函数式接口3.Java内置函数式接口 五、方法引用六、构造器引用与数组引用1.构造器引用2.数组引用

    一、什么是Lambda表达式

    Lambda表达式,也可称为闭包。类似于JavaScript中的闭包,它是推动Java8发布的最重要的新特性。

    二、为什么使用Lambda表达式

    我们可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样进行传递)。Lambda允许把函数作为一个方法的参数,使用Lambda表达式可以写出更简洁、更灵活的代码,而其作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

    一个简单示例:

    分别使用局部内部类、静态内部类、匿名内部类方式实现Runnable的run()方法并创建和启动线程,如下所示:

    public class LambdaDemo { /** * 局部内部类 */ class MyThread01 implements Runnable{ @Override public void run() { System.out.println("局部内部类:用Lambda语法创建线程吧!"); } } /** * 静态内部类 */ static class MyThread02 implements Runnable{ @Override public void run() { System.out.println("静态内部类:对啊,用Lambda语法创建线程吧!"); } } public static void main(String[] args) { /** * 匿名内部类 */ Runnable runnable = new Runnable(){ @Override public void run() { System.out.println("匿名内部类:求求你,用Lambda语法创建线程吧!"); } }; //局部内部类方式 LambdaDemo lambdaDemo = new LambdaDemo(); MyThread01 myThread01 =lambdaDemo.new MyThread01(); new Thread(myThread01).start(); //静态内部类方式 MyThread02 myThread02 = new MyThread02(); new Thread(myThread02).start(); //匿名内部类的方式 new Thread(runnable).start(); } }

    可以看到上面创建方式,代码量都不少,使用Lambda表达式实现,如下所示:

    //Lambda方式 new Thread(() -> System.out.println("使用Lambda就对了")).start();

    可以看到代码明显简洁了许多。

    三、Lambda表达式语法

    Lambda表达式在Java语言中引入了一个操作符**“->”**,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两个部分:

    左侧:指定了Lambda表达式需要的所有参数右侧:制定了Lambda体,即Lambda表达式要执行的功能。

    像这样:

    (parameters) -> expression 或 (parameters) ->{ statements; }

    以下是lambda表达式的重要特征:

    可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。可选的大括号:如果主体包含了一个语句,就不需要使用大括号。可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

    下面对每个语法格式的特征进行举例说明:

    (1)语法格式一:无参,无返回值,Lambda体只需一条语句。如下:

    @Test public void test01(){ Runnable runnable=()-> System.out.println("Runnable 运行"); runnable.run();//结果:Runnable 运行 }

    (2)语法格式二:Lambda需要一个参数,无返回值。如下:

    @Test public void test02(){ Consumer<String> consumer=(x)-> System.out.println(x); consumer.accept("Hello Consumer");//结果:Hello Consumer }

    (3)语法格式三:Lambda只需要一个参数时,参数的小括号可以省略,如下:

    public void test02(){ Consumer<String> consumer=x-> System.out.println(x); consumer.accept("Hello Consumer");//结果:Hello Consumer }

    (4)语法格式四:Lambda需要两个参数,并且Lambda体中有多条语句。

    @Test public void test04(){ Comparator<Integer> com=(x, y)->{ System.out.println("函数式接口"); return Integer.compare(x,y); }; System.out.println(com.compare(2,4));//结果:-1 }

    (5)语法格式五:有两个以上参数,有返回值,若Lambda体中只有一条语句,return和大括号都可以省略不写

    @Test public void test05(){ Comparator<Integer> com=(x, y)-> Integer.compare(x,y); System.out.println(com.compare(4,2));//结果:1 }

    (6)Lambda表达式的参数列表的数据类型可以省略不写,因为JVM可以通过上下文推断出数据类型,即“类型推断”

    @Test public void test06(){ Comparator<Integer> com=(Integer x, Integer y)-> Integer.compare(x,y); System.out.println(com.compare(4,2));//结果:1 }

    类型推断:在执行javac编译程序时,JVM根据程序的上下文推断出了参数的类型。Lambda表达式依赖于上下文环境。

    语法背诵口诀:左右遇一括号省,左侧推断类型省,能省则省。

    四、函数式接口

    1.什么是函数式接口

    只包含一个抽象方法的接口,就称为函数式接口。我们可以通过Lambda表达式来创建该接口的实现对象。 我们可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以用于检测它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。

    2.自定义函数式接口

    按照函数式接口的定义,自定义一个函数式接口,如下:

    @FunctionalInterface public interface MyFuncInterf<T> { public T getValue(String origin); }

    定义一个方法将函数式接口作为方法参数。

    public String toLowerString(MyFuncInterf<String> mf,String origin){ return mf.getValue(origin); }

    将Lambda表达式实现的接口作为参数传递。

    public void test07(){ String value=toLowerString((str)->{ return str.toLowerCase(); },"ABC"); System.out.println(value);//结果ABC }

    3.Java内置函数式接口

    四大核心函数式接口的介绍,如图所示: 使用示例: 1.Consumer:消费型接口 void accept(T t)

    public void makeMoney(Integer money, Consumer<Integer> consumer){ consumer.accept(money); } @Test public void test01(){ makeMoney(100,t-> System.out.println("今天赚了"+t));//结果:今天赚了100 }

    2.Supplier:供给型接口 T get()

    /** * 产生指定的整数集合放到集合中 * Iterable接口的forEach方法的定义:方法中使用到了Consumer消费型接口, * default void forEach(Consumer<? super T> action) { * Objects.requireNonNull(action); * for (T t : this) { * action.accept(t); * } * } */ @Test public void test02(){ List list = addNumInList(10, () -> (int) (Math.random() * 100)); list.forEach(t-> System.out.println(t)); } public List addNumInList(int size, Supplier<Integer> supplier){ List<Integer> list=new ArrayList(); for (int i = 0; i < size; i++) { list.add(supplier.get()); } return list; }

    3.Function<T,R>:函数型接口 R apply(T t)

    /** * * 使用函数式接口处理字符串。 */ public String handleStr(String s,Function<String,String> f){ return f.apply(s); } @Test public void test03(){ System.out.println(handleStr("abc",(String s)->s.toUpperCase())); } //结果:ABC

    4.Predicate:断言型接口 boolean test(T t)

    /** * 自定义条件过滤字符串集合 */ @Test public void test04(){ List<String> strings = Arrays.asList("啊啊啊", "2333", "666", "?????????"); List<String> stringList = filterStr(strings, (s) -> s.length() > 3); for (String s : stringList) { System.out.println(s); } } public List<String> filterStr(List<String> list, Predicate<String> predicate){ ArrayList result = new ArrayList(); for (int i = 0; i < list.size(); i++) { if (predicate.test(list.get(i))){ result.add(list.get(i)); } } return result; }

    其他接口的定义,如图所示:

    五、方法引用

    当要传递给Lambda体的操作,已经有实现的方法了,就可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用的参数列表)。方法引用可以理解为方法引用是Lambda表达式的另外一种表现形式。 方法引用的语法:使用操作符“::”将对象或类和方法名分隔开。 方法引用的使用情况共分为以下三种:

    对象::实例方法名类::静态方法名类::实例方法名

    使用示例: 1.对象::实例方法名

    /** *PrintStream中的println方法定义 * public void println(String x) { * synchronized (this) { * print(x); * newLine(); * } * } */ //对象::实例方法名 @Test public void test1(){ PrintStream out = System.out; Consumer<String> consumer=out::println; consumer.accept("hello"); } 类::静态方法名 /** * Integer类中的静态方法compare的定义: * public static int compare(int x, int y) { * return (x < y) ? -1 : ((x == y) ? 0 : 1); * } */ @Test public void test2(){ Comparator<Integer> comparable=(x,y)->Integer.compare(x,y); //使用方法引用实现相同效果 Comparator<Integer> integerComparable=Integer::compare; System.out.println(integerComparable.compare(4,2));//结果:1 System.out.println(comparable.compare(4,2));//结果:1 }

    3.类::实例方法名

    @Test public void test3(){ BiPredicate<String,String> bp=(x,y)->x.equals(y); //使用方法引用实现相同效果 BiPredicate<String,String> bp2=String::equals; System.out.println(bp.test("1","2"));//结果:false System.out.println(bp.test("1","2"));//结果:false }

    六、构造器引用与数组引用

    1.构造器引用

    格式:类名::new 与函数式接口相结合,自动与函数式接口中方法兼容,可以把构造器引用赋值给定义的方法。需要注意构造器参数列表要与接口中抽象方法的参数列表一致。 使用示例: 创建一个实体类Employee:

    public class Employee { private Integer id; private String name; private Integer age; @Override public String toString() { return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } public Employee(){ } public Employee(Integer id) { this.id = id; } public Employee(Integer id, Integer age) { this.id = id; this.age = age; } public Employee(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } }

    使用构造器引用与函数式接口相结合

    @Test public void test01(){ //引用无参构造器 Supplier<Employee> supplier=Employee::new; System.out.println(supplier.get()); //引用有参构造器 Function<Integer,Employee> function=Employee::new; System.out.println(function.apply(21)); BiFunction<Integer,Integer,Employee> biFunction=Employee::new; System.out.println(biFunction.apply(8,24)); } 输出结果: Employee{id=null, name='null', age=null} Employee{id=21, name='null', age=null} Employee{id=8, name='null', age=24}

    2.数组引用

    数组引用的格式:type[]:new 使用示例:

    @Test public void test02(){ Function<Integer,String[]> function=String[]::new; String[] apply = function.apply(10); System.out.println(apply.length);//结果:10 }

    笔记总结自:尚硅谷的视频教程-【Java8新特性】 参考: 1.Java 8 Lambda 表达式

    Processed: 0.020, SQL: 9