JAVA8 Lambda表达式权威教程
目录
1、[Lambda表达式的概念](#lambda表达式的概念)
2、[Lambda表达式的语法](#lambda表达式的语法)
3、[函数式接口](#函数式接口)
4、[Lambda表达式的使用场景](#lambda表达式的使用场景)
5、[方法引用](#方法引用)
6、[Lambda表达式与匿名类的区别](#lambda表达式与匿名类的区别)
7、[常见问题与解答](#常见问题与解答)
Lambda表达式的概念
Lambda表达式是Java 8引入的一项重要功能,它允许我们以更简洁和灵活的方式编写代码,可以把Lambda表达式看作是一种更方便的匿名函数,可以像数据一样传递和使用,使用Lambda表达式可以让我们写出更短、更易读的代码,它可以替代传统的匿名类,使代码更加简洁,Lambda表达式还支持函数式编程,这意味着我们可以将函数作为参数传递给其他方法,使得代码更加灵活和可扩展。
Lambda表达式的语法
基本语法
(parameters) > expression 或 (parameters) > { statements; }
Lambda表达式由三部分组成:
parameters:类似方法中的形参列表,这里的参数是函数式接口里的参数,这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断,另外当只有一个推断类型时可以省略掉圆括号。
>
:可理解为“被用于”的意思
方法体:可以是表达式也可以代码块,是函数式接口里方法的实现,代码块可返回一个值或者什么都不反回,这里的代码块块等同于方法的方法体,如果是表达式,也可以返回一个值或者什么都不反回。
示例
对于只有单个表达式的Lambda表达式:
import java.util.Arrays; import java.util.List; public class LambdaExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // Lambda表达式作为参数传递给forEach方法 numbers.forEach(number > System.out.print(number + " ")); } }
运行截图如下:
这个示例首先创建了一个整数列表 numbers,通过调用 forEach 方法并传递一个 Lambda 表达式作为参数,对列表中的每个元素执行操作。
对于包含多个语句的Lambda表达式:
import java.util.Arrays; import java.util.List; public class LambdaExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // Lambda表达式使用多个语句块 numbers.forEach(number > { int doubled = number * 2; System.out.println(number + " doubled: " + doubled); }); } }
Lambda 表达式使用了一个语句块,首先计算每个数字的两倍值,并打印原始数字和计算结果。
函数式接口
要了解 Lambda 表达式 , 首先需要了解什么是函数式接口,函数式接口定义:一个接口有且只有一个抽象方法。
注意:如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口,如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的,从某种意义上来说,只要你保证你的接口中只有一个抽象方法,你可以不加这个注解,加上就会自动进行检测的。
举个简单的例子:假设我是一位厨师,需要有一位助手来帮我,你给助手提供了一个简单的任务:切洋葱,你告诉助手只需要进行切洋葱的操作,其他的工作你会负责,在这个例子中,我们可以将这个任务看作是一个接口,而助手则是接口的实现者,这个接口定义了一个方法,即切洋葱的操作。
代码案例:
// 定义一个函数式接口 @FunctionalInterface interface Task { void perform(); } public class LambdaExample { public static void main(String[] args) { // 创建一个助手对象,使用Lambda表达式实现任务 Task assistant = () > System.out.println("助手正在切洋葱..."); // 调用厨师的方法,传递助手对象执行任务 cookMeal(assistant); } public static void cookMeal(Task task) { // 准备食材 System.out.println("准备食材..."); // 执行任务 task.perform(); // 煮菜 System.out.println("开始烹饪..."); } }
运行截图:如果我在接口再定义一个方法,则会报错,但是有另外一种情况可以:在Java 8之前,接口中只能包含抽象方法,也就是没有具体的实现,Java 8引入了默认方法的概念,允许在接口中定义具有默认实现的方法,默认方法使用default关键字进行修饰,由于接口中的默认方法拥有具体的实现,所以你可以直接在接口中调用它们,在实现该接口的类中,可以选择是否覆盖默认方法,如果没有覆盖,默认方法会被继承并直接使用,现在我在接口定义一个washVegetables()的默认方法,package demo1;// 定义一个函数式接口@FunctionalInterfaceinterface Task {void perform();default void washVegetables() {System.out.println("助理2,帮我洗菜即可");}}public class Chef {public static void main(String[] args) {// 创建一个助手对象,使用Lambda表达式实现任务Task assistant = () > System.out.println("助手正在切洋葱…");// 调用厨师的方法,传递助手对象执行任务cookMeal(assistant);}public static void cookMeal(Task task) {// 准备食材System.out.println("准备食材…");// 执行任务task.perform();// 煮菜System.out.println("开始烹饪…");}}
Lambda表达式的使用场景
无参函数的简写
无参函数就是没有参数的函数,Runnable 接口的 run() 方法,其定义如下:@FunctionalInterfacepublic interface Runnable {abstract void run();}在 Java 7及之前版本,我们一般可以这样使用:new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Hello");System.out.println("Jimmy");}}).start();从 Java 8开始,无参函数的匿名内部类可以简写成如下方式:() > {@执行语句}这样接口名和函数名就可以省掉了,上面的示例可以简写成:new Thread(() > {System.out.println("Hello");System.out.println("Jimmy");}).start();当只有一条语句时,我们还可以对代码块进行简写,格式如下:() > 表达式注意这里使用的是表达式,并不是语句,也就是说不需要在末尾加分号,当上面的例子中执行的语句只有一条时,可以简写成这样:new Thread(() > System.out.println("Hello")).start();
单参函数的简写
单参函数是指只有一个参数的函数,View内部的接口OnClickListener的方法onClick(View v),其定义如下:public interface OnClickListener {/Called when a view has been clicked. * @param v The view that was clicked. */void onClick(View v);}在 Java 7及之前的版本,我们通常可能会这么使用view.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {v.setVisibility(View.GONE);}});从 Java 8开始,单参函数的匿名内部类可以简写成如下方式:([类名 ]变量名) > {@执行语句}其中类名是可以省略的,因为Lambda表达式可以自己推断出来,那么上面的例子可以简写成如下两种方式:view.setOnClickListener((View v) > {v.setVisibility(View.GONE);});view.setOnClickListener((v) > {v.setVisibility(View.GONE);});单参函数甚至可以把括号去掉,官方也更建议使用这种方式:变量名 > {@执行语句}上面的示例可以进一步简写成:view.setOnClickListener(v > {v.setVisibility(View.GONE);});当只有一条语句时,依然可以对代码块进行简写,格式如下:([类名 ]变量名) > 表达式类名和括号依然可以省略,如下:变量名 > 表达式那么,上面的示例可以进一步简写成:view.setOnClickListener(v > v.setVisibility(View.GONE));
多参函数的简写
多参函数是指具有两个及以上参数的函数,Comparator接口的compare(T o1, T o2)方法就具有两个参数,其定义如下:@FunctionalInterfacepublic interface Comparator {int compare(T o1, T o2);}在 Java 7及之前的版本,当我们对一个集合进行排序时,通常可以这么写:List list = Arrays.asList(1, 2, 3);Collections.sort(list, new Comparator() {@Overridepublic int compare(Integer o1, Integer o2) {return o1.compareTo(o2);}});从 Java 8开始,多参函数的匿名内部类可以简写成如下方式:([类名1 ]变量名1, [类名2 ]变量名2[, …]) > {@执行语句}同样类名可以省略,那么上面的例子可以简写成如下两种形式:Collections.sort(list, (Integer o1, Integer o2) > {return o1.compareTo(o2);});Collections.sort(list, (o1, o2) > {return o1.compareTo(o2);});当只有一条语句时,依然可以对代码块进行简写,格式如下:([类名1 ]变量名1, [类名2 ]变量名2[, …]) > 表达式此时类名也是可以省略的,但括号不能省略,如果这条语句需要返回值,return关键字是不需要写的,上面的示例可以进一步简写成:Collections.sort(list, (o1, o2) > o1.compareTo(o2));最后呢,这个示例还可以简写成这样:Collections.sort(list, Integer::compareTo);这就是我们下面要讲的内容:方法引用。
方法引用
方法引用也是一个语法糖,可以用来简化开发,在我们使用Lambda表达式的时候,>”的右边要执行的表达式只是调用一个类已有的方法,那么我们可以用「方法引用」来替代Lambda表达式,方法引用可以分为4类:引用静态方法;引用对象的方法;引用类的方法;引用构造方法,下面按照这4类分别进行阐述。
引用静态方法
当我们要执行的表达式是调用某个类的静态方法,并且这个静态方法的参数列表和接口里抽象函数的参数列表一一对应时,我们可以采用引用静态方法的格式,假如Lambda表达式符合如下格式:([变量1, 变量2, …]) > 类名.静态方法名([变量1, 变量2, …])那么可以使用静态方法引用来代替Lambda表达式,Collections.sort(list, (o1, o2) > o1.compareTo(o2));可以替换为:Collections.sort(list, Integer::compareTo);
引用对象的方法
当我们要执行的表达式是调用某个对象的方法,并且这个方法的参数列表和接口里抽象函数的参数列表一一对应时,我们可以采用引用对象的方法的格式,假如Lambda表达式符合如下格式:([变量1, 变量2, …]) > 对象名.方法名([变量1, 变量2, …])那么可以使用对象的方法引用来代替Lambda表达式,view.setOnClickListener((View v) > {v.setVisibility(View.GONE);});可以替换为:view.setOnClickListener(View::setVisibility);
引用类的方法
当我们要执行的表达式是调用某个类的方法,并且这个方法的参数列表和接口里抽象函数的参数列表一一对应时,我们可以采用引用类的方法的格式,假如Lambda表达式符合如下格式:([变量1, 变量2, …]) > 类名.方法名([变量1, 变量2] …])那么可以使用类的实例方法引用来代替Lambda表达式,Collections.sort(list, (o1, o2) > o1.compareTo(o2));可以替换为:Collections.sort(list, Comparator::compare);
引用构造方法
当我们要执行的表达式是调用某个类的构造方法时,我们可以采用引用构造方法的格式,假如Lambda表达式符合如下格式:new 类名([变量1, 变量2] …])那么可以使用构造方法引用来代替Lambda表达式,Supplier supplier = () > new String("hello world");可以替换为:Supplier supplier = String::new;
Lambda表达式与匿名类的区别
Lambda表达式与匿名类的主要区别在于语法简洁性和可读性,Lambda表达式用箭头符号(>)取代了匿名类中的implements关键字和分号(;),使得代码更加简洁明了,Lambda表达式还可以访问外部类的成员变量和方法(前提是这些成员变量和方法在外部类中是final或者effectively final),相比之下,匿名类则不能直接访问外部类的局部变量和方法。
常见问题与解答
问题1: Lambda表达式是否可以访问外部类的成员变量和方法?答案: 是的,Lambda表达式可以访问外部类的成员变量和方法(前提是这些成员变量和方法在外部类中是final或者effectively final),这是因为Lambda表达式实际上是一个匿名函数式接口的实现,而函数式接口只能有一个抽象方法,为了确保Lambda表达式能够正确地访问外部类的成员变量和方法,我们需要将这些成员变量和方法声明为final或者effectively final,这样可以防止我们在Lambda表达式中意外地修改这些成员变量和方法的值。
各位小伙伴们,我刚刚为大家分享了有关“JAVA8 lambda表达式权威教程”的知识,希望对你们有所帮助。如果您还有其他相关问题需要解决,欢迎随时提出哦!