Java基础知识08--lambda表达式与函数式接口详解
1.接口及函数式接口
1.1 接口
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。
接口特性
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。其中public abstract 可以省略
- 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
- 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
public interface Myinterface { //以下都可以 public abstract void method(); public void method1(); abstract void method2(); void method3(); }
1.2 什么是函数式接口?
函数式接口定义:一个接口有且只有一个抽象方法 。
接口中只有一个抽象方法的接口,称为函数式接口,可以用@FunctionalInterface修饰一下,这里需要注意的是:未使用 @FunctionalInterfaces注解的接口未必就不是函数式接口,一个接口是不是函数式接口的条件只有一条,即接口中只有一个抽象方法的接口(Object类中的方法不算)。而使用@FunctionalInterface注解修饰了的接口就一定是函数式接口,添加@FunctionalInterface注解可以帮助我们检查是否是函数式接口。
函数式接口(Functional Interface)是java8新增的特性,它是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为lambda表达式。
JDK中常见的函数式接口有:
package java.lang; @FunctionalInterface public interface Runnable { void run(); }
package java.util.concurrent; @FunctionalInterface public interface Callable<V> { V call() throws Exception; }
2.lambda表达式基础语法
2.1.什么是Lambda表达式?
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
lambda表达式,其实都依赖于接口的支持,lambda表达式的本质是对接口的一种实现。java 8 内部Lambda 表达式的实现方式在本质是以匿名内部类的形式的实现的。
Lambda表达式是接口的实现类的对象,因此Lambda需要依赖一个接口。Lambda的本质是java中接口的一个实例(接口的实现类的具体对象)。可通过下图形象理解。
2.2 Java8的Lambda语法及特性
2.2.1 lambda语法
java中,引入了一个新的操作符“->”,该操作符在很多资料中,称为箭头操作符,或者lambda操作符;箭头操作符将lambda分成了两个部分:
// 参数列表 箭头 方法体
( ParameterType1 param1,ParameterType2 param2... ) -> { ... }
左侧:lambda表达式的参数列表。这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明 也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。
右侧:lambda表达式中所需要执行的功能,即lambda方法体。可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不返回,这里的代码块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不返回。
2.2.2 lambda特性
以下是lambda表达式的重要特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值
1、Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
2、Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
3、使用 Lambda 表达式可以使代码变的更加简洁紧凑。
典型案例:
(1) 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
这里e的类型就是由编译器推理得到的,不需要声明类型,当然也可以声明类型,比如:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
(2) 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
Arrays.asList( "a", "b", "d" ).forEach( e -> {
System.out.print( e );
System.out.print( e );
} );
(3) 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指明表达式返回了一个数值。并且如果Lambda表达式中的语句块只有一行,则可以不用使用return语句。
如下两个是一样的效果:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> { int result = e1.compareTo( e2 ); return result; } );
2.3使用Lambda的限制条件
Lambda并不是任何地方都可以使用,Lambda表达式需要“函数式接口”的支持。
3.lambda表达式基本使用
@FunctionalInterface interface NoParameterNoReturn { //注意:只能有一个抽象方法 void test(); } //无返回值一个参数 @FunctionalInterface interface OneParameterNoReturn { void test(int a); } //无返回值多个参数 @FunctionalInterface interface MoreParameterNoReturn { void test(int a, int b); } //有返回值无参数 @FunctionalInterface interface NoParameterReturn { int test(); } //有返回值一个参数 @FunctionalInterface interface OneParameterReturn { int test(int a); } //有返回值多参数 @FunctionalInterface interface MoreParameterReturn { int test(int a, int b); } public class TestDemo2 { public static void main(String[] args) { NoParameterNoReturn noParameterNoReturn = () -> { System.out.println("无参数无返回值"); }; //test方法的主体内容在上述括号内 noParameterNoReturn.test(); OneParameterNoReturn oneParameterNoReturn = (int a) -> { System.out.println("无参数一个返回值:" + a); }; oneParameterNoReturn.test(10); MoreParameterNoReturn moreParameterNoReturn = (int a, int b) -> { System.out.println("无返回值多个参数:" + a + " " + b); }; moreParameterNoReturn.test(20, 30); NoParameterReturn noParameterReturn = () -> { System.out.println("有返回值无参数!"); return 40; }; //接收函数的返回值 int ret = noParameterReturn.test(); System.out.println(ret); OneParameterReturn oneParameterReturn = (int a) -> { System.out.println("有返回值有参数!"); return a; }; ret = oneParameterReturn.test(50); System.out.println(ret); MoreParameterReturn moreParameterReturn = (int a, int b) -> { System.out.println("有返回值多个参数!"); return a + b; }; ret = moreParameterReturn.test(60, 70); System.out.println(ret); } }
lambda表达式语法可以进一步精简;
- 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
- 参数的小括号里面只有一个参数,那么小括号可以省略
- 如果方法体当中只有一句代码,那么大括号可以省略
- 如果方法体中只有一条语句,其是return语句,那么大括号可以省略,且去掉return关键字。
@FunctionalInterface interface NoParameterNoReturn { //注意:只能有一个抽象方法 void test(); } //无返回值一个参数 @FunctionalInterface interface OneParameterNoReturn { void test(int a); } //无返回值多个参数 @FunctionalInterface interface MoreParameterNoReturn { void test(int a, int b); } //有返回值无参数 @FunctionalInterface interface NoParameterReturn { int test(); } //有返回值一个参数 @FunctionalInterface interface OneParameterReturn { int test(int a); } //有返回值多参数 @FunctionalInterface interface MoreParameterReturn { int test(int a, int b); } public class TestDemo2 { public static void main(String[] args) { //方法参数有多个且方法体中无返回值,则可以省略参数类型 MoreParameterNoReturn moreParameterNoReturn = (a, b) -> { System.out.println("无返回值多个参数,省略参数类型:" + a + " " + b); }; moreParameterNoReturn.test(20, 30); //方法中只有一个参数,那么小括号可以省略 OneParameterNoReturn oneParameterNoReturn = a -> { System.out.println("方法中只有一个参数,那么小括号可以省略:" + a); }; oneParameterNoReturn.test(10); //无参数无返回值,方法体中只有 一行代码的时候,可以去掉方法体的大括号 NoParameterNoReturn noParameterNoReturn = () -> System.out.println("无参数无返回值,方法体中只有 一行代码"); noParameterNoReturn.test(); //方法体中只有一条语句,且是return语句,且无参数 NoParameterReturn noParameterReturn = () -> 40; int ret = noParameterReturn.test(); System.out.println(ret); } }
4.1 无参数,无返回值
() -> System.out.println("hello lambda");
Runnable r = new Runnable() { @Override public void run() { System.out.println("hello runnable"); } }; r.run(); Runnable r1 = () -> System.out.println("hello lambda"); r1.run();
此时,如果右边的代码简单,只有一行代码时,{}可以省略。
4.2 有一个参数,无返回值
public class CalculatorTest { public void print(Consumer<String> msg) { System.out.println(msg); } public void doPrint(String msg) { print((str) -> System.out.println(msg)); print(str -> System.out.println(msg)); // 简写 } }
此时,左边的()可以省略。
4.3 有两个参数,Lambda体内只有一条语句,且有返回值,return可省略
public void test3() { BinaryOperator<Integer> binary = (x, y) -> x + y; System.out.println(binary.apply(1, 2));// 3 }
4.4 多个参数,多行语句
public void test4() { // 无返回值lambda函数体中用法 Runnable r1 = () -> { System.out.println("hello lambda1"); System.out.println("hello lambda2"); System.out.println("hello lambda3"); }; r1.run(); // 有返回值lambda函数体中用法 BinaryOperator<Integer> binary = (x, y) -> { int a = x * 2; int b = y + 2; return a + b; }; System.out.println(binary.apply(1, 2));// 6 }
5.典型函数式接口的使用
Consumer<T> : 消费型接口(无返回值,有去无回) void accept(T t); Supplier<T> : 供给型接口 T get(); Function<T,R> : 函数型接口 R apply(T t); Predicate<T> : 断言型接口 boolean test(T t); 四大核心接口的-->扩展子接口
Predicate接口适合用于过滤,测试对象是否符合某个条件,Predicate接口源码如下:
@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); } }
可以看到,Predicate接口待实现的唯一抽象方法是 boolean test(T t) 方法。我们用Predicate接口实现从整数型数组中过滤正数:
public static void main(String[] args) { List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5); filter(numbers, n -> n > 0); } public static void filter(List<Integer> numbers, Predicate<Integer> condition) { for (Integer number : numbers) { if (condition.test(number)) { System.out.println("Eligible number: " + number); } } }
运行结果如下:
Eligible number: 4
Eligible number: 5
对数组的迭代,还可以使用Stream API的方式:
public static void main(String[] args) { List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5); numbers.stream().filter(n -> n > 0).forEach(n -> System.out.println("Eligible number: " + n)); }
参考文献:https://www.jianshu.com/p/8d7f98116693
https://www.cnblogs.com/wuhenzhidu/p/lambda.html
https://blog.csdn.net/qq_28410283/article/details/80961022
https://www.runoob.com/java/java8-lambda-expressions.html
https://blog.csdn.net/Mynewclass/article/details/80169476--推荐
https://www.jb51.net/article/236453.htm---五星推荐
https://blog.csdn.net/weixin_40391011/article/details/104031152
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)