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表达式的类型是目标类型(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表达式只能为函数式接口创建实例 - 匿名内部类实现的抽象方法允许调用接口中定义的默认方法;但Lambd表达式不允许调用接口中的默认方法