Java 8 作为一个里程碑式的版本,其中所做出的改变,在许多方面比Java历史上任何一次改变都深远。Java为什么会一直在改变,因为编程语言就像生态系统一样,更优秀语言的出现,落后的语言就会被取代,除非它们不断地演变和进步。Java 8 引入的核心新特性包括:Lambda表达式、函数式接口、Stream流API、方法引用/构造器引用等。
1. 什么是行为参数化
行为参数化是Java 8 增加的一个编程概念,即把不同行为的代码通过参数传递给方法。对于初学者来说这可能不太好理解,我们需要记住的是Java 8 增加了把方法(你的代码)作为参数传递给另一个方法的能力。Java 8中的Lambda表达式和方法引用的语法就是使用了行为参数化的体现。
package com.mj.lambda;
/**
* @author: gjm
* @description: 常见的Lambda使用
*/
public class UseLambda {
public static void main(String[] args) {
// Lambda表达式把一个匿名函数传递给方法
new Thread(()-> System.out.println("t1 running"),"t1").start();
}
}
package com.mj.method_ref;
import java.util.function.Function;
/**
* @author: gjm
* @description: 如何使用方法引用
*/
public class UseMethodRef {
public static void main(String[] args) {
// 方法引用语法: (类::方法名),传递的是方法的代码
getThreadInfo(new Thread(()->
System.out.println("t1 running"),"t1"),Thread::getId,Thread::getName);
}
// 使用函数式接口接收
public static void getThreadInfo(Thread t, Function f,
Function f1) {
// 调用函数,传递的方法不同,获取的结果也不同
Long threadId = f.apply(t);
String threadName = f1.apply(t);
System.out.println("threadId:"+threadId);
System.out.println("threadName:"+threadName);
}
}
2. 方法引用的语法
方法引用是Java 8 支持的新语法,使用 ": :" 的语法声明方法引用,可以引用静态方法、普通成员方法、构造器方法。
package com.mj.method_ref;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author: gjm
* @description: 用户类
*/
@Setter
@Getter
@AllArgsConstructor
@ToString
public class User {
private long id;
private String username;
private int age;
}
public static void main(String[] args) {
List users = new ArrayList<>();
users.add(new User(1L,"张三", 30));
users.add(new User(2L,"李四", 25));
users.add(new User(3L,"王五", 35));
// 可以按照语法理解,通过年龄从小到大排序,
users.sort(Comparator.comparing(User::getAge));
System.out.println(users.toString());
}
package com.mj.method_ref;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author: gjm
* @description: 用户类
*/
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private long id;
private String username;
private int age;
// 按用户年龄降序排序
public static int compareByAgeDesc(User u1, User u2){
return u2.getAge() - u1.getAge();
}
}
public static void main(String[] args) {
List users = new ArrayList<>();
users.add(new User(1L,"张三", 30));
users.add(new User(2L,"李四", 25));
users.add(new User(3L,"王五", 35));
// 使用工具类的排序方法,引用自定义排序规则的静态方法
Collections.sort(users, User::compareByAgeDesc);
System.out.println(users.toString());
}
public int compareByAgeAsc(User u1, User u2){
return u1.getAge() - u2.getAge();
}
public static void main(String[] args) {
List users = new ArrayList<>();
users.add(new User(1L,"张三", 30));
users.add(new User(2L,"李四", 25));
users.add(new User(3L,"王五", 35));
// 使用工具类的排序方法,引用自定义排序规则的静态方法
User user = new User();
Collections.sort(users, user::compareByAgeAsc);
System.out.println(users.toString());
}
package com.mj.method_ref;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* @author: gjm
* @description: 构造器引用
*/
public class ConstructorRef {
public static void main(String[] args) {
List names = new ArrayList<>();
Collections.addAll(names,"11223","122","1222","12","21122","12aa");
// 找到所有以12开头的字符串,并转换为数字
List integers = names.stream().filter(num -> {
// 字符串只能是数字
Pattern pattern = Pattern.compile("[0-9]+");
return (pattern.matcher(num).matches() && num.startsWith("12"));
} )
// 把过滤出的数字字符串转换成 Integer类型,
// Integer::new所对应构造器为 public Integer(String s)
.map(Integer::new).collect(Collectors.toList());
System.out.println(integers.toString());
}
}
3. Lambda表达式
Lambda表达式也是Java 8 支持的新语法,本质就是一个匿名函数,把函数当成参数传递给其它方法。前面的方法引用使用的是具体方法,也就是需要被提前声明出的静态方法或普通成员方法才能被引用。
1. 参数列表: 和函数一样支持无参(),一个参数(a),多个参数(a, b , ..),无需书写参数类型。
2. 箭头: 箭头 —>把参数列表与Lambda主体分隔开。
3. Lambda主体: 在大括号{ }中书写具体的执行代码,只有一行代码可以省略大括号。
// 无参无返回值,对应的是函数式接口Runnable
Runnable runnable = () -> { };
// 无参有返回值,对应的是函数式接口Supplier或者Callable
Supplier supplier = () -> "Gets a result";
// Callable一般用于多线程,可以拿到执行结果
Callable callable = () -> "Computes a result";
// 一个参数无返回值,对应的是函数式接口Consumer
Consumer consumer = (a) -> { };
// 一个参数有返回值,对应的是函数式接口Function
Function f = (a) -> a.toString();
// 两个参数无返回值,对应的是函数式接口BiConsumer
BiConsumer biConsumer = (a, b) -> { };
// 两个参数有返回值,返回类型为int,对应的是函数式接口Comparator
Comparator comparator = (u1, u2) -> u1.getAge() - u2.getAge();
4. 函数式接口
函数式接口也是Java 8 新增加的一种类型,函数式接口的定义是只有一个抽象方法的接口。函数式接口作为方法的参数时,可以使用Lambda表达式或者方法引用把代码作为参数值传递,这就是行为参数化。让方法通过接收多种不同的代码实现,来完成不同的行为,这也被称为函数式编程。
java.util.function.Predicate
package com.mj.lambda;
import com.mj.method_ref.User;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
/**
* @author: gjm
* @description: Predicate函数式接口使用
*/
public class UsePredicate {
public static void main(String[] args) {
List users = new ArrayList<>();
users.add(new User(1L,"张三", 30));
users.add(new User(2L,"李四", 25));
users.add(new User(3L,"王五", 35));
/**
* filterUsers()方法的第二个参数可以使用Lambda表达式
* Lambda表达式必须匹配函数描述符: T-> boolean
* 函数描述符: T-> boolean,表示接收一个T类型参数,返回boolean类型结果
*/
// 找出大于等于35岁的用户
System.out.println(filterUsers(users, user -> user.getAge() >=30 ));
// 找出姓张的用户
System.out.println(filterUsers(users, user -> user.getUsername().startsWith("张") ));
}
/**
* 过滤用户
* @param user
* @param p
* @return
*/
public static List filterUsers(List users, Predicate p) {
List result = new ArrayList<>();
for (User user: users){
// 执行方法,就是执行了lambda表达式中的代码
if (p.test(user)) {
result.add(user);
}
}
return result;
}
}
java.util.function.Consumer
package com.mj.lambda;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author: gjm
* @description: Consumer函数式接口
*/
public class UseConsumer {
public static void main(String[] args) {
List names = new ArrayList<>();
Collections.addAll(names,"刘备","关羽","张飞");
/**
* forEach()方法的参数就是Consumer函数式接口
* default void forEach(Consumer super T> action) {}
* forEach()方法也是jdk1.8新增加的接口中允许有实现的默认方法
*/
names.forEach(str -> System.out.println(str));
}
}
java.util.function.Supplier
package com.mj.lambda;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.util.function.Supplier;
/**
* @author: gjm
* @description: Supplier函数式接口
*/
public class UseSupplier {
public static void main(String[] args) {
// 获取当前时间的字符串
String currentDateTime = getDateTimeStr(LocalDateTime::now, "yyyy-MM-dd HH:mm:ss");
System.out.println(currentDateTime);
// 获取指定时间的字符串
String dateTime = getDateTimeStr(()->LocalDateTime.of(2022, Month.APRIL,1,12,30),
"yyyy-MM-dd HH:mm:ss");
System.out.println(dateTime);
}
/**
* 获取LocalDateTime时间的字符串格式
* @param supplier
* @param pattern
* @return
*/
public static String getDateTimeStr(Supplier supplier,String pattern){
LocalDateTime time = supplier.get();
return time.format(DateTimeFormatter.ofPattern(pattern));
}
}
java.util.function.Function
package com.mj.lambda;
import com.mj.method_ref.User;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* @author: gjm
* @description: Function函数式接口
*/
public class UseFunction {
public static void main(String[] args) {
List users = new ArrayList<>();
users.add(new User(1L,"张三", 30));
users.add(new User(2L,"李四", 25));
users.add(new User(3L,"王五", 35));
List
Java 8为我们前面所说的函数式接口带来了一个专门的版本,以便在输入和输出都是原始类型时避免自动装箱的操作。这些函数式接口对原始类型进行了扩展,比如我们使用Predicate时需要指定输入参数的泛型T,如果输入的参数类型是可以确定的,比如int、long、double,那么我们就可以使用IntPredicate、LongPredicate、DoublePredicate等接口,这样可以避免使用包装类型,产生自动装箱操作。
package com.mj.lambda;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
/**
* @author: gjm
* @description: IntPredicate
*/
public class UseIntPredicate {
public static void main(String[] args) {
IntPredicate intPredicate = (int a) -> a==0;
intPredicate.test(0); // 此时无自动装箱
Predicate predicate = (Integer a) -> a==0;
predicate.test(0); // 产生了自动装箱
}
}
某些时候我们在java.util.function包下没找到符合自己要求的函数式接口时,我们可以自定义满足自己要求的函数式接口。根据函数式接口只有一个抽象方法的定义,我们也可以实现自定义函数式接口,从而来满足自己的开发。注意函数式接口上的@FunctionalInterface注解是非必需的,只是用来标识这是一个函数式接口,我们建议加上让代码更容易理解。
package com.mj.lambda;
import java.lang.FunctionalInterface;
/**
* @author: gjm
* @description: 自定义函数式接口
*/
@FunctionalInterface
public interface CustomFunInterface {
R compute(T t, U u, X x);
}
package com.mj.lambda;
/**
* @author: gjm
* @description: 测试自定义函数式接口
*/
public class UseCustomFunInterface {
public static void main(String[] args) {
Integer a = 90, b = 20, c = 30;
// 计算出a,b,c的最大值,并返回String
CustomFunInterface myFunInterface = (x, y, z)-> {
Integer max = x;
max = max > y ? max : y;
max = max > z ? max : z;
return max.toString();
};
String compute = myFunInterface.compute(a, b, c);
System.out.println(compute);
}
}
4. 什么是Stream流
Stream流是Java 8 提供的新API,它主要是用来帮助我们简单和高效地处理集合数据的,Stream提供了集合处理的常用API。我们可以使用Stream的API配合前面学的函数式编程思想实现更简洁的声明式方法表达和几乎透明的并行处理。
Java8之前如果我们想在集合中过滤掉并返回我们想要的数据,我们要对集合进行遍历,并书写自定义过滤逻辑处理再返回。这在代码层面往往不能够清晰地表达你所做的事情,Java8为我们提供了专门做这个事情的filter方法。
List employees = new ArrayList<>();
employees.add(new Employee(1L,"张三", 30));
employees.add(new Employee(2L,"李四", 25));
employees.add(new Employee(3L,"王五", 35));
List old = new ArrayList<>();
// Java8以前的写法
for(Employee e: employees){
if(e.getAge() >= 35) old.add(e);
}
// 使用Stream API的写法
old = employeeList.stream()
.filter(employee -> employee.getAge() >=35).collect(toList());
List employees = new ArrayList<>();
employees.add(new Employee(1L,"张华", 35));
employees.add(new Employee(2L,"王五", 35));
employees.add(new Employee(3L,"张小樊", 21));
employees.add(new Employee(4L,"李东东", 25));
// 找到所有姓张的员工,并按年龄从小到大排序
List search = employeeList.stream()
.filter(employee -> employee.getName().startsWith("张"))
.sorted(Comparator.comparing(Employee::getAge))
.collect(toList());
List roleRefResources = new ArrayList<>();
roleRefResources.add(new RoleRefResource(1L,"/api/employee","roleA"));
roleRefResources.add(new RoleRefResource(2L,"/api/department","roleB"));
roleRefResources.add(new RoleRefResource(3L,"/api/user","roleC"));
roleRefResources.add(new RoleRefResource(4L,"/api/employee","roleD"));
String resPath = "/api/employee";
// 获取可访问该资源路径的所有角色列表
List roles = roleRefResources.stream()
// 过滤路径
.filter(rr -> resPath.equals(rr.getResPath()))
// 映射为角色名称
.map(rr -> rr.getRoleName() )
.collect(toList());
Integer[] a = new Integer[]{1,3,5,2};
Integer[] b = new Integer[]{1,4,5,9};
List list = Arrays.asList(a,b);
// 把a,b集合合并成一个新集合,并过滤重复元素
List uniqueInteger = list.stream()
.flatMap(x -> Arrays.stream(x)) // 把每个集合都转换成流,并且合并
.distinct() // 对合并的这个流过滤重复元素
.sorted()
.collect(Collectors.toList());
System.out.println(uniqueInteger);
long count = students.stream()
.filter(s -> s.getGrade() >= 60) // 中间操作,返回的是流
.distinct() // 中间操作,返回的是流
.limit(50) // 中间操作,返回的是流
.count(); // 终端操作,返回执行结果
System.out.println(count);
List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0) // 筛选偶数
.distinct() // 排除重复元素
.forEach(System.out::println); // 终端操作,打印元素
Set students = new HashSet<>();
students.add(new Student(10010, "李小军",77));
students.add(new Student(10013, "赵平",89));
students.add(new Student(10009, "刘小美",85));
// 输出结果是自己内部顺序,非元素放入顺序
students.stream().limit(5).forEach(System.out::println);
Set students = new HashSet<>();
students.add(new Student(10010, "李小军",77));
students.add(new Student(10013, "赵平",89));
students.add(new Student(10009, "刘小美",85));
// 判断是否有李小军这个学生
boolean b = students.stream().anyMatch(s -> "李小军".equals(s.getName()));
System.out.println(b);
Set students = new HashSet<>();
students.add(new Student(10010, "李小军",77));
students.add(new Student(10013, "赵平",89));
students.add(new Student(10009, "刘小美",85));
// 返回任意一个学生,并打印
Optional student = students.stream().findAny();
System.out.println(student.get());