引言:
Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。
一、Lambda表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
public class Lambda {
@Test
public void test1() {
//匿名内部类
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("哈哈哈哈哈");
}
};
r1.run();
//Lambda表达式
Runnable r2 = () -> System.out.println("嘻嘻嘻嘻嘻");
r2.run();
}
@Test
public void test2() {
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
int compara1 = com1.compare(12, 21);
System.out.println(compara1);
//Lambda表达式
Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
int compara2 = com2.compare(32, 21);
System.out.println(compara2);
//方法引用
Comparator<Integer> com3 = Integer::compare;
int compara3 = com3.compare(32, 21);
System.out.println(compara3);
}
}
Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
- 左侧:指定了 Lambda 表达式需要的参数列表(其实就是接口中的抽象方法的形参列表,如果能自动进行类型推断,则形参类型可以省略)
- 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。
Lambda表达式的本质:作为接口的实例
Lambda表达式语法:
- 语法格式一:无参,无返回值
Runnable r1 = () -> {System.out.println(“Hello Lambda!”);};
- 语法格式二:Lambda需要一个参数,但是没有返回值
Consumer<String> con = (String str) -> {System.out.println(str);};
- 语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
Consumer<String> con = (str) -> {System.out.println(str);};
- 语法格式四:Lambda若只有一个参数是,参数的小括号可以省略
Consumer<String> con = str -> {System.out.println(str);};
- 语法格式五:Lambda需要两个或以上的参数,多条执行语句,并且可有返回值
Comparator<Integer> com = (x, y) -> {System.out.println(“实现函数式接口方法!”);return Integer.compare(x,y);};
- 语法格式六:当Lambda体只有一条语句时,return与大括号若有,都可以省略
Comparator<Integer> com = (x, y) -> Integer.compare(x,y);
类型推断
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
二、函数式(Functional)接口
- 只包含一个抽象方法的接口,称为函数式接口。
- 你可以通过 Lambda 表达式来创建该函数式接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
- 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
- 在java.util.function包下定义了Java 8 的丰富的函数式接口
- Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)
- 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
- 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
- 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
Java内置四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer<T> 消费型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t) |
Supplier<T> 供给型接口 | 无 | T | 返回类型为T的对象,包含方法:T get() |
Function<T,R> 函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t) |
Predicate<T> 断定型接口 | T | boolean | 确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t) |
/**
* 消费型接口 Consumer<T> void accept(T t)
* 供给型接口 Supplier<T> T get()
* 函数型接口 Function<T,R> R apply(T t)
* 断定型接口 Predicate<T> boolean test(T t)
*/
public class LambdaTest {
/* 消费型接口使用测试 */
@Test
public void test1() {
happyTime(500, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("买水,花费:" + aDouble);
}
});
happyTime(250, money -> System.out.println("买水,花费:" + money));
}
public void happyTime(double money, Consumer<Double> con) {
con.accept(money);
}
/* 断定型接口使用测试 */
@Test
public void test2() {
List<String> list = Arrays.asList("北京", "南京", "天津", "东京", "西京", "普京");
List<String> filterStrs1 = filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
System.out.println(filterStrs1);
List<String> filterStrs2 = filterString(list, s -> s.contains("京"));
System.out.println(filterStrs2);
}
//根据给定的规则,过滤集合中的字符串。此规则是由 Predicate 的方法决定
public List<String> filterString(List<String> list, Predicate<String> pre) {
ArrayList<String> filterList = new ArrayList<>();
for (String s : list) {
if (pre.test(s)) {
filterList.add(s);
}
}
return filterList;
}
}
作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
其他函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction<T,U,R> | T, U | R | 对类型为 T, U 参数应用操作,返回 R 类型的结果。包含方法为:R apply(T t, U u) |
UnaryOperator<T> (Function子接口) | T | T | 对类型为T的对象进行一元运算,并返回T类型的结果。包含方法为:T apply(T t) |
BinaryOperator<T> (BiFunction 子接口) | T, T | T | 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为:T apply(T t1, T t2) |
BiConsumer<T,U> | T, U | void | 对类型为T, U 参数应用操作。包含方法为:void accept(T t, U u) |
BiPredicate<T,U> | T,U | boolean | 包含方法为:boolean test(T t, U u) |
ToIntFunction<T> ToLongFunction<T> ToDoubleFunction<T> |
T | int long double |
分别计算int、long、double值的函数 |
IntFunction<R> LongFunction<R> DoubleFunction<R> |
int long double |
R | 参数分别为int、long、double 类型的函数 |
三、方法引用(Method References)
- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
- 方法引用可以看做是Lambda表达式深层次的表达。方法引用本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以方法引用,也是函数式接口的实例。
- 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
- 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
public class Employee {
private int id;
private String name;
private int age;
private double salary;
public String getName() {
return name;
}
public Employee() {
System.out.println("Employee().....");
}
public Employee(int id) {
this.id = id;
System.out.println("Employee(int id).....");
}
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public Employee(int id, String name, int age, double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
}
}
public class MethodReferencesTest {
/** 情况一: 对象::实例方法
* Consumer 中的 void accept(T t)
* PrintStream 中的 void println(T t)
*/
@Test
public void test1() {
//Lambda表达式
Consumer<String> con1 = str -> System.out.println(str);
con1.accept("北京");
//方法引用
PrintStream ps = System.out;
Consumer<String> con2 = ps::println;
con2.accept("成都");
}
/** 情况一: 对象::实例方法
* Supplier 中的 T get()
* Employee 中的 String getName()
*/
@Test
public void test2() {
//Lambda表达式
Employee emp = new Employee(1001, "Tom", 23, 5600);
Supplier<String> sup1 = () -> emp.getName();
System.out.println(sup1.get());
//方法引用
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}
/** 情况二: 类::静态方法
* Comparator 中的 int compare(T t1, T t2)
* Integer 中的 int compare(T t1, T t2)
*/
@Test
public void test3() {
//Lambda表达式
Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
System.out.println(com1.compare(12, 21));
//方法引用
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(31, 13));
}
/** 情况二: 类::静态方法
* Function 中的 R apply(T t)
* Math 中的 Long round(Double d)
*/
@Test
public void test4() {
//lambda表达式
Function<Double, Long> func1 = d -> Math.round(d);
System.out.println(func1.apply(123.32));
//方法引用
Function<Double, Long> func2 = Math::round;
System.out.println(func2.apply(123.32));
}
/** 情况三: 类::非静态方法
* Comparator 中的 int compare(T t1, T t2)
* String 中的 int t1.compareTo(t2)
*/
@Test
public void test5() {
//Lambda表达式
Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
System.out.println(com1.compare("ab", "abc"));
//方法引用
//当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName
Comparator<String> com2 = String::compareTo;
System.out.println(com2.compare("abc", "ab"));
}
/** 情况三: 类::非静态方法
* BiPredicate 中的 boolean test(T t1, T t2)
* String 中的 boolean t1.equals(t2)
*/
@Test
public void test6(){
//Lambda表达式
BiPredicate<String, String> pre1 = (s1, s2) -> s1.equals(s2);
System.out.println(pre1.test("ab", "abc"));
//方法引用
BiPredicate<String, String> pre2 = String::equals;
System.out.println(pre2.test("abc", "abc"));
}
/** 情况三: 类::非静态方法
* Function 中的 R apply(T t)
* Employee 中的 String toString()
*/
@Test
public void test7() {
//Lambda表达式
Employee emp = new Employee(1002, "Jerry", 23, 6500);
Function<Employee, String> func1 = e -> e.toString();
System.out.println(func1.apply(emp));
//方法引用
Function<Employee, String> func2 = Employee::toString;
System.out.println(func2.apply(emp));
}
}
四、构造器引用
- 格式: ClassName::new
- 与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
public class ConstructorReferencesTest { /** * Supplier 中的 T get() * Employee 中的空参构造器 Employee() */ @Test public void test1() { Supplier<Employee> sup = new Supplier<Employee>() { @Override public Employee get() { return new Employee(); } }; Supplier<Employee> sup1 = () -> new Employee(); System.out.println(sup1.get()); Supplier<Employee> sup2 = Employee::new; System.out.println(sup2.get()); } /** * Function 中的 R apply(T t) */ @Test public void test2() { Function<Integer, Employee> func1 = id -> new Employee(id); System.out.println(func1.apply(1002)); Function<Integer, Employee> func2 = Employee::new; System.out.println(func2.apply(1003)); } /** * BiFunction 中的 R apply(T t, U u) */ @Test public void test3() { BiFunction<Integer, String, Employee> bfunc1 = (id, name) -> new Employee(id, name); System.out.println(bfunc1.apply(1002, "Tom")); BiFunction<Integer, String, Employee> bfun2 = Employee::new; System.out.println(bfun2.apply(1003, "Jerry")); } }
五、数组引用
- 格式:type[]::new
- 可以把数组看作是一个特殊的类,则写法与构造器引用一致。
public class ArrayReferencesTest { /** 数组引用 * Function 中的 R apply(T t) */ @Test public void test1() { Function<Integer, String[]> func1 = Length -> new String[Length]; String[] arr1 = func1.apply(5); System.out.println(Arrays.toString(arr1)); Function<Integer, String[]> func2 = String[]::new; String[] arr2 = func2.apply(10); System.out.println(Arrays.toString(arr2)); } }