初识Lambda表达式
初识Lambda表达式
Lambda表达式是Java8的新特性之一,Lambda表达式支持将代码块作为方法参数,Lambda表达式允许使用更简洁的代码来创建一个抽象方法的接口(函数式接口)的实例。
Lambda表达式入门
interface Eatable {
void eat();
}
interface Flyable {
void fly(String s);
}
interface Addable {
int add(int a, int b);
}
public class LambdaDemo {
//调用该方法需要Eatable对象
public void eat(Eatable e) {
System.out.println(e);
e.eat();
}
//调用该方法需要Flyable对象
public void drive(Flyable f) {
System.out.println("我正在驾驶:" + f);
f.fly("大晴天");
}
//调用该方法需要Addble对象
public void test(Addable a) {
System.out.println("1加1等于:" + a.add(1, 1));
}
public static void main(String[] args) {
LambdaDemo ld = new LambdaDemo();
ld.eat(() -> System.out.println("吃苹果"));
ld.drive(s -> {
System.out.println("今天天气是:" + s);
System.out.println("开车很爽");
});
ld.test((a, b) -> a + b);
}
}
从上面语法格式中可以看出,Lambda表达式就相当于一个匿名方法。它由三部分组成:
- 形参列表。
- 箭头(->)。必须由英文中画线和大于符号组成。
- 代码块
省略写法:
- Lambda表达式的形参列表只有一个形参,可以省略圆括号
- Lambda表达式的代码块只有一条语句时,可以省略花括号
- 代码块中只有一条语句,即使该表达式需要返回值,也可以省略return关键字
从代码中还可以看出,Lambda表达式实际上将会被当成一个“任意类型”的对象,需要当成何种类型,取决于运行环境的需要。
Lambda表达式与函数式接口
Lambda表达式的类型也被称为“目标类型”,Lambda表达式的目标类型必须是“函数式接口”。函数式接口代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但只能声明一个抽象方法。
Lambda表达式限制:
- Lambda表达式的目标类型必须是明确的函数式接口
- Lambda表达式只能为函数式接口创建对象。Lambda表达式只能实现一个方法,因此它只能为只有一个抽象方法的接口(函数式接口)创建对象。
其实,Lambda表达式的结果就是被当成对象,所以在程序中完全可以使用Lambda表达式进行赋值。
方法引用与构造器引用
方法引用和构造器引用都需要使用两个英文冒号。如表:
种类 | 示例 | 说明 | 对应的Lambda表达式 |
---|---|---|---|
引用类方法 | 类名::类方 法 | 函数式接口中被实现方法的全部参数传给该类方法作为参数 | (a,b,...)->类名.类方法(a,b,...) |
引用特定对象的实例方法 | 特定对象::实例对象 | 函数式接口中被实现方法的全部参数传给该类方法作为参数 | (a,b,...)->特定对象.实例方法(a,b,...) |
引用某类对象的实例方法 | 类名::实例方法 | 函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数 | (a,b,...)->a.实例方法(a,b,...) |
引用构造器 | 类名::new | 函数式接口中被实现方法的全部参数传给该构造器作为参数 | (a,b,...)->new 类名(a,b,...) |
举个栗子:
-
引用类方法
定义一个函数接口:
@FunctionalInterface interface Converter{ Integer convert(String s); }
创建一个Converter对象:
Converter converter = s -> Integer.valueOf(s);
替换如下:
Converter converter = Integer::valueOf;
-
引用特定对象的实例方法
接上面接口创建一个Converter对象:
Converter converter2 = s -> "hello lambda".indexOf(s);
替换如下:
Converter converter2 = "hello lambda"::indexOf;
-
引用某类对象的实例方法
定义一个函数式接口
@FunctionalInterface interface MyTest1{ String test(String a , int b , int c); }
创建一个MyTest对象
MyTest mt = (a, b, c) -> a.substring(b, c);
替换如下:
MyTest mt = String::substring;
-
引用构造器
定义一个函数式接口
@FunctionalInterface interface MyTest2{ JFrame win(String title); }
创建MyTest2对象
MyTest2 myTest2 = (String a) -> new JFrame(a);
替换如下:
MyTest2 myTest2 = JFrame::new;
Lambda表达式与匿名内部类的联系与区别
相同点:
- Lambda表达式与匿名内部类一样,都可以直接访问“effectively final”的局部变量,以及外部类的成员变量(包括实例变量和类变量) 。
- Lambda表达式创建的对象和匿名内部类生成的对象一样,都可以直接调用从接口中继承的默认方法。
effectively final 的意思是 在Java8中,对于被匿名内部类访问的局部变量,可以用final修饰,也可以不用final修饰,但必须按照有final修饰的方式来使用。
区别:
- 匿名内部类可以为任何接口创建实例---不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法即可;但Lambda表达式只能为函数式接口创建实例。
- 匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法;但Lambda表达式的代码块不允许调用接口中定义的默认方法。
使用Lambda表达式调用Arrays的类方法
public class LambdaArrays {
public static void main(String[] args) {
String[] arr1 = new String[]{"java", "python", "C++"};
Arrays.parallelSort(arr1, Comparator.comparingInt(String::length));
System.out.println(Arrays.toString(arr1));
int[] arr2 = new int[]{3, -4, 25, 16, 30, 18};
// left代表数组中前一个索引处的元素,计算第一个元素时,left为1
// right代表数组中当前索引处的元素
Arrays.parallelPrefix(arr2, (left, right) -> left * right);
System.out.println(Arrays.toString(arr2));
long[] arr3 = new long[5];
// operand代表正在计算的元素索引
Arrays.parallelSetAll(arr3, operand -> operand * 5);
System.out.println(Arrays.toString(arr3));
}
}
Java8在java.util.function包中定义的函数式接口
核心接口大致分为以下四类:
-
功能性接口(XxxFunction):
public interface Function<T,R> {public R apply(T t);}
此接口需要接受一个参数,并且返回一个处理结果。通常用于对指定数据进行转换处理。
-
消费型接口(XxxConsumer):
public interface Consumer<T> {public void accept(T t);}
此接口只是负责接受数据,并且不返回处理结果。与Function接口类似,只是不返回处理结果。
-
供给型接口(XxxSupplier):
public interface Supplier<T>{public T get();}
此接口不接受参数,但是可以返回结果。通常会按某种逻辑算法(由Lambda表达式实现)返回一个数据。
-
断言型接口(XxxPredicate):
public interface Predicate<T>{public boolean test(T t);}
进行判断操作使用。该接口通常用于判断参数是否满足特定条件,经常用于进行过滤数据。