Lambda表达式入门
Java 8之前,大家应该有创建匿名内部类的体验,代码有点繁琐。
Lambda表达式支持将代码块,作为方法参数,允许使用更简洁的代码,来创建只有一个抽象方法的接口的实例。Java 8里面,只有一个抽象方法的接口,叫做函数式接口,由@FunctionalInterface
修饰,例如线程常用到的Runnable接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Lambda表达式构成
三部分构成
形参列表。如果只有一个参数,连括号都可以省
箭头
->
代码块。如果只有一条语句,可省略花括号。Lambda表达式需要返回值,如果只有一条return语句,则可以省略
interface Eatable {
void taste();
}
interface Flyable {
void fly(String weather);
}
interface Addable {
int add(int a, int b);
}
//
public class LambdaTest {
public void eat(Eatable e) {
System.out.println(e);
e.taste();
}
public void drive(Flyable f) {
System.out.println("Driving....");
f.fly("===Sunny Day===");
}
public void sum(Addable add) {
System.out.println("5 plus 3 is " + add.add(5, 3));
}
public static void main(String[] args) {
LambdaTest lt = new LambdaTest();
//需要Eatable接口的实例,代码块只有一条,省略{}
lt.eat(() -> System.out.println("The cake is delicious"));
//需要Flyable接口实例,只有一个形参,省略()
lt.drive(weather -> {
System.out.println("Today's weather is " + weather);
System.out.println("Steady!");
});
//需要Addable接口实例,接口只有一条语句,省略{},省略return
lt.sum((a, b) -> a + b);
}
}
运行结果
com.LambdaTest$$Lambda$1/834600351@548c4f57
The cake is delicious
Driving....
Today's weather is ===Sunny Day===
Steady!
5 plus 3 is 8
Lambda表达式的类型
从上面的代码可以看到,传入的参数是各种类型的接口的实例,而实际传入的是lambda表达式,还编译通过,运行正常。
说明lambda表达式会被当成”任意类型”,准确来说,是目标类型(target type),就是表达式需要创建的那个接口实例的类型。
lambda表达式的目标类型必须是函数式接口。Java 8里,函数式接口可以包含多个default方法,static方法,但只能有一个abstract方法
Lambda表达式,实现的,是匿名方法,因此,只能实现特定函数式接口中的唯一方法。意味着,有2点限制
目标类型必须函数式接口
只能为函数式接口创建对象
保证目标类型是函数式接口的方法:
lambda表达式赋值给函数式接口类型的变量
lambda表达式作为函数式接口类型的参数传给某个方法
强制类型转换
//这代码不会编译通过,Object不是函数式接口
Object object = () -> {
for(int i=0;i<10;i++){
System.out.println(i);
}
};
//强制类型转换为函数式接口
Object object = (Runnable)() -> {
for(int i=0;i<10;i++){
System.out.println(i);
}
};
java.util.function
Java 8 里,预定义了大量函数式接口,典型含如下5种类型。用过Scala或Spark的人,应该会觉得似曾相识。个人感觉,Java 8里的这些操作跟Scala是很像的
XXXFunction
- 这类接口通常含有
R apply(T t);
抽象方法方法 - 方法对参数T处理,转换,最后返回一个新的值R
- 具体的处理,转换逻辑,由lambda表达式的代码块决定
- 通常用于对指定数据的转换处理
- XXXOperator继承Function
XXXConsumer
- 通常有
void accept(T t);
抽象方法 - 与XXXFunction接口类似,只是没有返回值
- 负责对参数处理
- Unlike most other functional interfaces, {@code Consumer} is expected to operate via side-effects
XXPredicate
- 通常有
boolean test(T t);
抽象方法 - 用来对参数做某种判断,具体逻辑由lambda代码块决定,最终返回一个boolean值
- 通常用于判断参数是否满足条件,用作筛选
Predicate verb(formal):to state that sth is true
XXXSupplier
- 通常含有
XXX getAsXXX();
方法,Supplier则含有T get();
方法 - 该方法不需要参数,返回一个数据
- 每次返回的结果,可以一样
方法引用和构造器引用
用实例来说明。首先,定义一个Converter函数式接口,目标是把字符串类型转换为Integer类型,至于怎么转,由lambda表达式具体实现
@FunctionalInterface
interface Converter {
Integer convert(String from);
}
引用静态方法/类方法
-
格式
-
类名::方法名
. 对应的的lambda,(a,b...) -> 类名.静态方法(a,b...)
public static void main(String[] args) {
//创建Converter实例,转换方法的具体实现,是透过Integer的静态方法实现的
Converter converter = from -> Integer.valueOf(from);
Integer integer = converter.convert("100");
System.out.println(integer);
//静态方法引用
Converter converter2 = Integer::valueOf;
Integer val2 = converter2.convert("1000");
System.out.println(val2);
}
引用特定对象的实例方法
-
格式
-
特定对象::实例方法名
. 对应的lambda,(a,b...) -> 特定对象.实例方法(a,b...)
//找出输入字符串在实例对象中的位置
Converter instance = from -> "Specific Intance Method Reference".indexOf(from);
Integer val3 = instance.convert("I");
System.out.println(val3);
Converter instance2 = "Specific Intance Method Reference"::indexOf;
Integer val4 = instance2.convert("I");
System.out.println(val4);
引用某类的对象的实例方法
-
格式
-
类名::实例方法名
. 对应的的lambda,(a,b...) -> a.实例方法(b...)
. 表达式中的第一个参数为方法调用者
@FunctionalInterface
interface StringTest {
String test(String str, int begin, int end);
}
...
//接口里传3个参数,最后返回String类型
//使用了String类的对象的substring方法
StringTest st = (str,beginIndex,endIndex) -> str.substring(beginIndex, endIndex);
String result = st.test("Specific Instance of a Class Method Reference", 7, 15);
System.out.println(result);
StringTest st2 = String::substring;
String result2 = st.test("Specific Instance of a Class Method Reference", 7, 15);
System.out.println(result2);
引用构造器
-
格式
-
类名::new
. 对应的的lambda,(a,b...) -> new (a,b...)
.
@FunctionalInterface
interface NewTest {
JFrame frame(String title);
}
//表达式代码块只有一条语句
NewTest nt = title -> new JFrame(title);
JFrame jFrame = nt.frame("MY WINDOW");
System.out.println(jFrame);
NewTest nt2 = JFrame::new;
JFrame jFrame2 = nt.frame("MY WINDOW");
System.out.println(jFrame2);
Lambda表达式与匿名内部类
相同
直接访问”effective final”的局部变量。即,访问过的局部变量(包括实例变量和类变量/静态变量),相当于局部变量被final修饰
都可以直接调用接口的默认方法
不同
匿名内部类可以创建任意接口的实例,不管接口有多少个抽象方法。lambda表达式只能创建函数式接口的实例
匿名内部类可以创建抽象类的实例。lambda不可以。
匿名内部类所实现的抽象方法的方法体,允许调用接口定义的默认方法。lambda的代码块不可以
在数组上的测试
String[] arr1 = new String[] {"Java","fkava","fkit","ios","android"};
//字符串越长,越大
Arrays.parallelSort(arr1,(o1,o2) -> o1.length() - o2.length());
System.out.println(Arrays.toString(arr1));
System.out.println(Arrays.deepToString(arr1));
int[] arr2 = new int[]{3,-4,25,16,30,18};
//两两计算
Arrays.parallelPrefix(arr2, (left, right)->left * right);
System.out.println(Arrays.toString(arr2));
long[] arr3 = new long[5];
//利用索引来计算
Arrays.parallelSetAll(arr3, generator -> generator - 5);
System.out.println(Arrays.toString(arr3));
//结果
[ios, Java, fkit, fkava, android]
[ios, Java, fkit, fkava, android]
[3, -12, -300, -4800, -144000, -2592000]
[-5, -4, -3, -2, -1]