一.Lambda表达式入门
Lambda表达式支持将代码块作为方法参数,允许使用更简洁的代码来创建只有一个抽象方法的接口(函数式接口)的实例。
Lambda表达式的语法分为三部分:
· 形参列表:形参列表允许省略形参类型,如果形参列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
· 箭头(->):左侧是形参列表,右侧花括号内的内容是代码块。
· 代码块:重写的方法的代码。如果代码块中只有一条语句,就可以省略花括号;如果代码块中只有一条return语句,那么连return也可以省去,Lambda语句会自动将结果返回。
/*
* 看一段代码,体会一下Lambda表达式的用法
* /
//定义三个接口
interface Eatting { void food(String foods); }
interface Sleeping { void sleeptight(); }
interface Addable { int adding(int a, int b); }
public class LambdaTest {
//调用接口
public void eat(Eatting e) {
e.food("steak");
}
public void sleep(Sleeping s) {
s.sleeptight();
}
public void add(Addable add) {
System.out.println("2+3 = " + add.adding(2,3));
}
public static void main(String[] args) {
LambdaTest lt = new LambdaTest();
//通过实例的方法,使用Lambda表达式创建方法中需要的接口类型实例
//省略形参列表的圆括号和代码块的花括号
lt.eat(foods -> System.out.println(foods + " is tasty!") );
//形参列表中没有参数时,不能省略圆括号;省略代码块的花括号
lt.sleep(() -> System.out.println("sleeptight is pretty good!") );
//返回值为a、b之和,省略了return
lt.add((a,b) -> a+b );
}
}
二.Lambda表达式与函数式接口
Lambda表达式的类型是目标类型(target type),目标类型必须为函数式接口。函数式接口是有且只有一个抽象方法,无论有几个变量、默认方法或类方法,但抽象方法只能有一个的一种接口。查看Java8的API文档中会发现有很多函数式接口,比如我们多线程常用的Runnable接口就是一个函数式接口
/*
* Runnable只包含一个无参数的抽象函数run();
* Lambda表达式的返回值赋给了Runnable的一个对象
*/
Runnable r = () -> {
for(int i = 0;i < 100;i++)
System.out.println(i + " ");
};
从上面代码可以看到Lambda表达式的结果(即目标类型)被当做对象,可以将其直接进行赋值操作
/*
* 因为赋给了Object类型,不是函数式接口,所以这段代码不能够编译运行
*/
Objective obj = () -> {
for(int i = 0;i < 100;i++)
System.out.println(i + " ");
};
/*
* 但将其强制类型转换之后就可以赋给Object类型
*/
Objective obj = (Runnable)() -> {
for(int i = 0;i < 100;i++)
System.out.println(i + " ");
};
通过以上代码我们发现,Lambda表达式类型必须是明确的函数式接口,并且可以为函数式接口创建对象。为保证目标类型是一个明确的目标类型可以通过三种方式:
· 将目标类型直接赋值给函数式接口的对象
· 将目标类型作为某个函数的参数
· 将目标类型进行强制类型转换
Java8在java.util.function包下预定义了大量函数式接口,想深入了解的同学可以查看API文档学习
三.方法引用于构造器引用
方法构造和构造器引用都需要使用两个英文冒号,所谓引用是在代码块即在花括号中的代码引用了方法或构造器,分为四种引用类型:
1. 引用类方法:
- 示例:类名:类方法
- 说明:函数式接口中被实现的方法的全部参数传给该方法作为参数
- 对应的Lambda表达式:(a,b,…)->类名.类方法(a,b,…)
e.g.:
/*
* 定义函数式接口,@FunctionalInterface注解对程序没有任何功能上的作用,只是在编译上严格保证这是一个函数式接口,是Java8新增的注解。
@FunctionalInterface
public interface Converter {
Integer converter(String from);
}
//使用Lambda表达式创建函数式接口对象
//普通方式
Converter converter1 = from -> Integer.valueOf(from);
//因为valueOf是Integer类的静态方法,可以直接使用类名::类方法的方式创建函数式接口对象
Converter converter2 = Integer::valueOf;
Integer value1 = converter1.converter("99");
Integer value2 = converter2.converter("100");
System.out.println(value1 + " --- " + value2);
输出结果:99 --- 100
2. 引用特定对象的实例方法
- 示例:特定对象::实例方法
- 说明:函数式接口中被实现的方法的全部参数传给该方法作为参数
- 对应的Lambda表达式:(a,b,…)->特定对象.实例方法(a,b,…)
e.g.:
//接口使用上面定义的Converter接口
//使用Lambda表达式创建函数式接口对象
//普通方式
Converter converter3 = from -> "ILoveLambda".indexOf(from);
//因为indexOf是String类的实例方法,可以直接使用特定对象::实例方法的方式创建函数式接口对象
Converter converter4 = "ILoveLambda"::indexOf;
Integer value3 = converter3.converter("Lambda");
Integer value4 = converter4.converter("Lambda");
System.out.println(value3 + " --- " + value4);
输出结果:5 --- 5
3. 引用某类对象的实例方法
- 示例:类名::实例方法
- 说明:函数式接口中被实现的方法的第一个参数作为调用者,后面参数全部传给该方法作为参数
- 对应的Lambda表达式:(a,b,…)->a.实例方法(b,…)
e.g.:
//定义接口,sub方法含有三个参数
@FunctionalInterface
public interface Sub {
String sub(String a, int b, int c);
}
//普通方式创建函数式接口对象
Sub sub1 = (a,b,c) -> a.substring(b, c);
//使用类名::实例方法的方式创建函数式接口对象
Sub sub2 = String::substring;
String value5 = sub1.sub("ILoveLambda", 1, 5);
String value6 = sub2.sub("ILoveLambda", 5, 11);
System.out.println(value5 + " " + value6);
运行结果:Love Lambda
4. 引用构造器
- 示例:类名::实例方法
- 说明:函数式接口中被实现的方法的第一个参数作为调用者,后面参数全部传给该方法作为参数
- 对应的Lambda表达式:(a,b,…)->a.实例方法(b,…)
e.g.:
//定义接口
@FunctionalInterface
public interface gouZao {
String str(String title);
}
//普通方式创建函数式接口对象
gouZao gz1 = (String a) -> new String(a);
//引用构造器方式创建函数式接口对象
gouZao gz2 = String::new;
String string1 = gz1.str("this is a test of Lambda?");
String string2 = gz2.str("this is a test of Lambda!");
System.out.println(string1 + " - " + string2);
运行结果:this is a test of Lambda? - this is a test of Lambda!
四.Lambda表达式与匿名内部类
可以看出,Lambda表达式的功能与匿名内部类很相似,Lambda表达式就是为了使匿名内部类的代码更简洁,但这种方式只适合于函数式接口才可以使用。
Lambda表达式与匿名内部类的区别如下:
- 匿名内部类可以为任何接口创建实例,也可以为抽象类甚至普通类创建实例;而Lambda表达式只能为函数式接口创建实例
- 匿名内部类实现的抽象方法允许调用接口中定义的默认方法;但Lambd表达式不允许调用接口中的默认方法