Java lambda表达式
一、简介
1. lambda表达式格式
(参数类型 参数名称) -> { 代码语句 }
说明:
(1) 小括号内:没有参数就留空(); 多个参数就用逗号分隔。
(2) -> 是新引入的语法格式,代表指向动作。
(3) 大括号内的语法与传统方法体要求基本一致。
(4) 参数类型可以省略,当编译器无法自动推导可以加上。
可省略部分:
凡是可以根据上下文推导得知的信息,都可以省略:
(1) 小括号内参数的类型可以省略;
(2) 如果小括号内只有一个参数,则小括号可以省略;
(3) 如果大括号内只有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
2. 使用要求
(1) 使用Lambda表达式必须具有接口,且要求接口中有且仅有一个抽象方法。
(2) 使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
3. 将Lambda匹配到接口规则
有且仅有一个抽象方法的接口称为“函数式接口”。将Java lambda表达式与函数式接口进行匹配需要步骤:
(1) 接口是否只有一个抽象方法? //必须 注:其它非抽象方法还是可以有的(但是实测是不能有的)
(2) lambda表达式的参数是否与抽象方法的参数匹配? //必须
(3) lambda表达式的返回类型是否与抽象方法的返回类型匹配? //必须
在lambda表达式中,通常可以推断参数类型,比如参数传lambda表达式,参数类型做为推断依据。lambda表达式内参数类型、个数也必须要完全匹配。
二、基础版本
1. 例子
(1) 普通实现——非lambda表达式
interface Learn { void study(); } class StudyDemo implements Learn { @Override public void study() { System.out.println("Hello World"); } } public class Test { public static void main(String args[]) { Learn s = new StudyDemo(); s.study(); } }
(2) 匿名接口类实现——非lambda表达式
interface Learn { void study(); } public class Test { public static void main(String args[]) { Learn s = new Learn() { @Override public void study() { //复写不能导致acces权限变弱,必须加public System.out.println("Hello World"); } }; s.study(); } }
(3) lambda表达式实现
interface Learn { void study(); } public class Test { public static void main(String args[]) { /* 只有一行,等效:Learn s = () -> System.out.println("Hello World"); */ Learn s = () -> { System.out.println("Hello World"); }; s.study(); } }
(4) 匿名接口类实现——有参数和返回值
interface Learn { void study(int a, int b); } public class Test { public static void main(String args[]) { Learn s = new Learn() { @Override public void study(int a, int b) { System.out.println("Hello World" + (a + b)); } }; s.study(1, 2); } }
(5) lambda表达式实现——有参数和返回值
interface Learn { void study(int a, int b); } public class Test { public static void main(String args[]) { Learn s = (a, b) -> { System.out.println("Hello World x " + (a + b)); }; s.study(1, 2); } } //----------------------- interface Learn { void study(int a); } public class Test { public static void main(String args[]) { //只有一个参数省略小括号,只有一行代码省略大括号 Learn s = a -> System.out.println("Hello World x " + a); s.study(1); } }
(6) lambda表达式实现——有返回值
interface Learn { int study(int a, int b); } public class Test { public static void main(String args[]) { Learn s = (a, b) -> { int c = a + b; System.out.println("Hello World x " + c); return c; }; int ret = s.study(1, 2); System.out.println("ret= " + ret); } }
4. 实现匿名接口可以增加一个成员属性,但是lambda不可以,lambda是无状态的。
interface Learn { int study(int a, int b); } public class Test { public static void main(String args[]) { Learn s = new Learn() { private int value = 0; @Override public int study(int a, int b) { int c = a + b; System.out.println("Hello World x " + c); //lambda表达式不行,因此lambda是无状态的 value_inc(); //不能在外通过对象调用,但是在函数内部调用,维护内部状态 return c; } public void value_inc() { System.out.println("value=" + ++value); } }; s.study(1, 2); s.study(1, 2); s.study(1, 2); } }
从 Java 8 开始,lambda 是迄今为止表示小函数对象的最佳方式。 除非必须创建非函数式接口类型的实例,否则不要使用匿名类作为函数对象。
三、变量捕获
在某些情况下,Java lambda表达式能够访问在lambda表达式主体外部声明的变量。可以捕获以下变量类型:
(1) 局部变量
(2) 实例变量
(3) 静态变量
这些变量捕获的每一个将在以下各节中进行描述。
1. 局部变量捕获
//lambda表达式访问局部变量 interface Factory { public String create(char[] chars); } public class Test { public static void main(String args[]) { String str = "Hello World"; Factory f = (chars) -> { return str + ":" + new String(chars); //访问局部变量str }; char[] name={'N', 'B'}; System.out.println(f.create(name)); //Hello World:NB } }
实例变量和静态变量应用方法也类似。
四、方法引用
如果你的lambda表达式所做的只是用传递给lambda的参数调用另一个方法,则Java lambda实现提供了更简洁的方式表示该方法调用。
interface Learn { void study(String s); } public class Test { public static void main(String args[]) { Learn s = System.out::println; //注意是::,即只是想实现study()为System.out.println(s) s.study("Hello"); //Hello } }
注意双冒号:: 它会向Java编译器发出信号,这是方法引用。引用的方法是双冒号之后的内容。拥有被引用方法的任何类或对象都在双冒号之前。
1. 静态方法引用
interface Finder { String find(String s1, String s2); } public class Test { public static String doFind(String s1, String s2) { return s1 + s2; } public static void main(String args[]) { Finder finder = Test::doFind; System.out.println(finder.find("Hello ", "World")); //Hello World } }
由于 Finder.find() 和 Test.doFind() 方法的参数匹配,因此可以创建实现 Finder.find() 并引用 Test.doFind() 方法的 lambda 表达式。
2. 参数方法引用
也可以将其中一个参数的方法引用到lambda。注意简洁方式版本是如何引用单个方法的。Java编译器尝试将引用的方法与第一个参数类型相匹配,使用第二个参数类型作为被引用方法的参数。
interface Finder { int find(String s1, String s2); } public class Test { public static String doFind(String s1, String s2) { return s1 + s2; } public static void main(String args[]) { Finder finder = String::indexOf; //等价于 Finder finder = (s1, s2) -> s1.indexOf(s2); System.out.println(finder.find("Hello World", "World")); //6 } }
3. 实例方法引用
还可以从lambda表达式中引用实例方法。首先,让我们来看一个函数式接口定义:
interface Deserializer { int deserialize(String v1); } class StringConverter { public int convertToInt(String v1){ return Integer.valueOf(v1); } } public class Test { public static void main(String args[]) { StringConverter stringConverter = new StringConverter(); Deserializer des = stringConverter::convertToInt; System.out.println(des.deserialize("3")); //打印3 } }
Deserializer 接口表示一个组件,该组件能够将字符串"反序列化"为int。StringConverter 类的 convertToInt()方法与 Deserializer deserialize()方法的 deserialize()方法具有相同的签名。因此,我们可以创建 StringConverter 的实例,并从Java lambda表达式引用其 convertToInt()方法。
3. 构造方法引用
可以引用一个类的构造方法。你可以通过在类名后加上::new来完成此操作。在lambda表达式中引用构造方法:
interface Factory { String create(char[] val); } public class Test { public static void main(String args[]) { Factory factory = String::new; //等价于 Factory factory = chars -> new String(chars); char[] c = {'3','4','5'}; System.out.println(factory.create(c)); //打印345 } }
此接口的 create()方法与String类中某个构造函数的签名匹配。因此,此构造函数可以被lambda表达式用到。
四、总结
1. Java中使用lambda表达式的前提条件是必须有一个函数式接口,也即是一个接口里面只有一个抽象方法。
2. 需要具有类型推导,参数个数类型需要匹配。
3. 当lambda表达式作为函数参数时,形参是一个对象类型,而不是lambda表达式的返回值。
参考:
Java Lambda表达式:https://www.cnblogs.com/three-fighter/p/13326627.html
一文看懂java中的Lambda表达式:https://zhuanlan.zhihu.com/p/112771403
posted on 2023-08-16 10:13 Hello-World3 阅读(96) 评论(0) 编辑 收藏 举报