Java8
之前传递一个代码段并不容易,不能直接传递代码段。Java
是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法能包含所需的代码。Lamabd
是数学中的一个函数,Java
中使用方法来代替函数,方法总是作为类或对象的一部分存在的。可以把Lamabda
看做是一个匿名方法,拥有更简洁的语法。
1. 基本语法
语法:(参数列表) -> {语句;}
Lamabda
表达式由参数列表和一个Lamabda
体组成,通过箭头连接。
(1)完整lambda
表达式。参数有括号、类型。表达式有大括号,有返回值的语句需要加return
关键字。
java
(String first, String second) -> {return first.length() - second.length();}
(2)即使lambda
表达式没有参数,也需要提供一个空括号。表达式没有返回值不写return
。
java
() -> { System.out.println("Hello"); }
(3)如果可以推导出参数的类型,可以不写类型。不写类型的话所有参数都不写类型。写类型的话所有参数都要写类型。
java
(first, second) -> {return first.length() - second.length();}
(4)方法只有一个参数,而且这个参数类型可以推导,可以省略小括号和参数类型。
java
x -> {System.out.println(x);}
(5)如果Lamabda
体只有一条语句,大括号可以省略,return
关键字也可以省略。没有大括号不能添加return
关键字。有大括号不能省略return
关键字(有返回值的情况下)。
java
x -> x+1; 或者 x -> {return x+1;}
2. 函数式接口
只定义了一个抽象方法的接口,称为函数式接口。
Java
有很多封装代码块的接口,如Comparator
接口,Runnable
接口,都属于函数式接口 ,在需要这种接口的对象时,就可以提供一个lambda表达式
。实际上在Java
中,lambda表达式
必须实现一个函数式接口。函数式接口就是为Lamabda
表达式准备的。
2.1. 自定义函数式接口
java
@FunctionalInterface//注解,声明接口为函数式接口。
public interface Adder{
int add(int x, int y);
}
2.2. 基本函数式接口
java.util.function
包中定义了一些基本的函数式接口,如Predicate
,Consumer
,Function
,Supplier
。
2.2.1. Predicate
java
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Predicate<T>
接口定义了一个抽象方法test(T t)
,接收一个T
类型的参数,返回一个布尔值。当需要一个涉及类型T
的布尔表达式时,可以使用这个接口。示例代码:
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* @date 2019/10/29 23:15
* @auther wangbo
*/
public class TestPredicate {
public static void main(String[] args) {
List<String> list = Arrays.asList("123", "1234", "12345");
//传递 predicate 条件
List<String> result = filter(list, x -> x.length() > 4);
System.out.println(result);
}
public static <T>List<T> filter(List<T> list, Predicate<T> predicate){
List<T> result = new ArrayList<>();
//遍历list参数列表,把符合 predicate 条件的元素存储到result中
for (T t : list) {
if (predicate.test(t)) {
result.add(t);
}
}
return result;
}
}
2.2.2. Consumer
java
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Consumer<T>
接口定义了一个抽象方法accept(T t)
,接收一个T
类型的参数,没有返回值。当需要访问类型T
的对象,对该对象做一些操作,就可以使用这个接口。在Collection
集合和Map
集合中都有forEach(Consumer)
方法。示例代码:
java
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @date 2019/10/29 23:33
* @auther wangbo
*/
public class TestConsumer {
public static void main(String[] args) {
List<String> list = Arrays.asList("123", "1234", "12345");
list.forEach(s -> System.out.println(s));
Map<String, Integer> map = new HashMap<>();
map.put("张三", 10);
map.put("李四", 20);
map.put("王五", 30);
map.forEach((k, v) -> System.out.println(k + "->" + v));
}
}
2.2.3. Function
java
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Function<T,R>
接口定义了apply(T t)
方法,接收一个T
类型的参数对象,返回一个R
类型的数据,如果需要定义一个Lambda
,将一个输入对象T
的信息加工后映射为输出对象R
,就可以使用这个接口。示例代码:
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
/**
* @date 2019/10/29 23:45
* @auther wangbo
*/
public class TestFunction {
public static void main(String[] args) {
List<String> list = Arrays.asList("123", "1234", "12345");
System.out.println(list);
//将元素长度存储到result
List<Integer> result = map(list, x -> x.length());
System.out.println(result);
}
//把一个list中的元素映射到另一个list中
public static <T,R>List<R> map(List<T> list, Function<T,R> function){
List<R> result = new ArrayList<>();
for (T t : list){
result.add(function.apply(t));
}
return result;
}
}
2.3. 基本数据类型函数式接口
泛型只能绑定引用数据类型,不能使用基本数据类型。Java8
中函数式接口为基本数据类型提供了对应的接口,可以避免在进行输入输出基本数据类型时频繁进行装箱、拆箱操作。
一般来说,在针对专门的基本类型数据的函数式接口名称前面加上对应的基本类型前缀,如IntPredicate
,IntConsumer
,IntFunction
等。示例代码:
java
public static void main(String[] args) {
IntPredicate evenNumbers = (int x) -> x%2 == 0;
System.out.println(evenNumbers.test(10));//true
System.out.println(evenNumbers.test(11));//false
}
3. 捕获 Lambda
Lambda
表达式可以使用外层作用域中定义的变量,比如成员变量,局部变量,称为捕获Lambda
。
- 在
Lambda
表达式中可以使用成员变量,静态方法中只能使用静态成员变量。 - 在
Lambda
表达式中可以使用局部变量,局部变量必须是final
修饰,或者是事实上的final
变量。
java
import java.util.function.IntUnaryOperator;
/**
* @date 2019/10/30 0:19
* @auther wangbo
*/
public class Test {
int xx = 123;//实例变量
static int yy = 456;//静态变量
public static void main(String[] args) {
//在Lambda表达式中可以使用成员变量,当前静态方法中只能使用静态变量。
IntUnaryOperator operator = i -> i + yy;
System.out.println(operator.applyAsInt(100));
//在Lambda表达式中使用局部变量,局部变量必须是final修饰,或者是事实上的final。
int zz = 789;//局部变量
IntUnaryOperator operator1 = i -> i + zz;//虽然zz没有用final修饰,但是它是事实上的final,后面无法修改zz的值。
System.out.println(operator1.applyAsInt(100));
//zz = 11;//对zz进行修改,则上面的lambda表达式报语法错误。
final int ff = 111;//final修饰的局部变量
IntUnaryOperator operator2 = i -> i + ff;
System.out.println(operator2.applyAsInt(100));
}
}
4. 方法引用
方法引用可以让你重复使用现有的方法定义,并像Lambda
一样传递它们。方法引用可以看做是仅仅调用特定方法的Lambda
表达式的一种便捷写法。类似于Lambda
表达式,方法引用不能独立存在,总是会转换为函数式接口的实例。
4.1. 语法
需要使用方法引用时,目标引用放在分隔符::
前面,方法名放在::
后面。注意,只需要方法名,不需要小括号。
用::
操作符分隔对象(或类名)和方法名,主要有三种情况:
- 对象::实例方法
- Class::静态方法
- Class::实例方法
4.1.1. 总述
前两种情况,方法引用等价于提供方法参数的lambda表达式
,例如:
java
System.out::prinlth 等价于 x -> System.out.println(x)
Math::pow 等价于 (x, y) -> Math.pow(x, y)
第三种情况,第一个参数为方法的调用者,第二个参数为方法的参数。例如:
java
String::compareToIgnoreCase 等价于 (x, y) -> x.compareToIgnoreCase(y)
方法引用中可以使用this
和super
,属于上面的第一种情况,例如:
java
this::equals 等价于 x -> this.equals(x)
super::equals 等价于 x -> super.equals(x)
4.1.2. 示例
java
List list = new ArrayList();
list.forEach(x -> System.out.println(x));
list.forEach(System.out::println);
List<Student> list1 = new ArrayList<>();
list1.forEach((Student stu) -> stu.getScore());
list1.forEach(Student::getScore);
java
() -> Thread.currentThread().dumpStack()
Thread.currentThread()::dumpStack
(Str, i) -> Str.substring(i)
String::substring
4.2. 常见方法引用
方法引用主要有三类:
4.2.1. 指向静态方法的方法引用
Lambda 表达式:(args) -> ClassName.staticMethod(args)
方法引用:ClassName::staticMethod
java
Integer[] data = {21,90,34,76};
Arrays.sort(data, Integer::compare);//引用静态方法
System.out.println(Arrays.toString(data));
4.2.2. 指向任意类型的实例方法的引用
Lambda 表达式:(args0, args1) -> args0.instanceMethod(args1)
args0
是ClassName
类型的一个对象。
方法引用:ClassName::instanceMethod
java
List<String> list = Arrays.asList("ccc", "bbb", "aaa");
list.sort(String::compareTo);//引用实例方法
System.out.println(list);
4.2.3. 指向现有对象的实例方法的引用
Lambda表达式:(args) -> obj.instanceMethod(args)
方法引用:obj::instanceMethod
java
list.forEach(System.out::println);
4.3. 构造方法引用
对于一个现有的构造方法,可以使用类名和关键字new
来创建一个构造方法的引用:ClassName::new
java
package com.wangbo.cto.lambda;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @date 2019/11/14 23:08
* @auther wangbo
*/
public class Test1 {
public static void main(String[] args) {
//引用无参数构造方法
Supplier<Person> supplier = Person::new;
Person p1 = supplier.get();
System.out.println(p1);
//引用有一个参数构造方法
Function<String, Person> function = Person::new;
Person p2 = function.apply("wangbo");
System.out.println(p2);
//应用有两个参数的构造方法
BiFunction<String, Integer, Person> biFunction = Person::new;
Person p3 = biFunction.apply("wangbo", 28);
System.out.println(p3);
//如果引用有三个参数及三个以上参数的构造方法,需要自定义匹配的函数式接口
TriFunction<String, Integer, String, Person> triFunction = Person::new;
Person p4 = triFunction.myMethod("zhangsan", 90, "男");
System.out.println(p4);
}
}
java
/**
* 自定义函数式接口
*/
@FunctionalInterface
public interface TriFunction<T, U, V, R>{
R myMethod(T t, U u, V v);
}
java
public class Person{
String name;
Integer age;
String gender;
public Person(){}
public Person(String name) {
this.name = name;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public Person(String name, Integer age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
@Override
public String toString() {
return "Person{" +
"name=" + name +
", age=" + age +
", gender="+ gender +
"}";
}
}
运行结果:
java
Person{name=null, age=null, gender=null}
Person{name=wangbo, age=null, gender=null}
Person{name=wangbo, age=28, gender=null}
Person{name=zhangsan, age=90, gender=男}
5. 综合示例
java
package com.wangbo.cto.lambda;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* @date 2019/11/15 0:24
* @auther wangbo
*/
public class LambdaTest {
public static void main(String[] args) {
//定义List存储Student
List<Student> list = new ArrayList<>();
list.add(new Student("zhangsan", 10));
list.add(new Student("lisi", 9));
list.add(new Student("wangwu", 13));
//使用匿名内部类排序(按名字升序)
list.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
});
System.out.println(list);
//使用lambda表达式(按名字降序)
list.sort((s1, s2) -> s2.name.compareTo(s1.name));
System.out.println(list);
//使用Comparator接口中的comparing静态方法,可以返回一个Comparator比较器(按名字升序)
list.sort(Comparator.comparing(student -> student.name));
System.out.println(list);
//方法引用(按分数升序)
list.sort(Comparator.comparing(Student::getAge));
System.out.println(list);
}
}
java
public class Student{
String name;
Integer age;
public Integer getAge() {
return age;
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
运行结果:
java
[Student{name='lisi', age=9}, Student{name='wangwu', age=13}, Student{name='zhangsan', age=10}]
[Student{name='zhangsan', age=10}, Student{name='wangwu', age=13}, Student{name='lisi', age=9}]
[Student{name='lisi', age=9}, Student{name='wangwu', age=13}, Student{name='zhangsan', age=10}]
[Student{name='lisi', age=9}, Student{name='zhangsan', age=10}, Student{name='wangwu', age=13}]