Java 8 函数式编程:Lambda、方法引用与Stream流完全指南
前言
Java 8 引入的函数式编程特性是Java语言的一次重大革新,它不仅让代码更加简洁,还带来了全新的编程思维。本文将系统地介绍Lambda表达式、函数式接口、方法引用以及Stream流的核心概念和使用方法,帮助读者快速掌握这些重要特性。
一、Lambda表达式
1.1 什么是Lambda
Lambda表达式是Java 8中引入的一种新语法,它允许我们将函数作为方法参数,或者将代码作为数据来处理。Lambda表达式可以简化匿名内部类的调用,让代码更加简洁。
1.2 初识Lambda
传统方式 vs Lambda方式
java
public interface OrderService {
void addOrder();
}
public class TestOrderService {
public static void main(String[] args) {
// 1. 使用实现类调用
OrderService orderService1 = new OrderServiceImpl();
orderService1.addOrder();
// 2. 使用匿名内部类调用
new OrderService() {
@Override
public void addOrder() {
System.out.println("添加订单");
}
}.addOrder();
// 3. 使用Lambda表达式
OrderService orderService = () -> System.out.println("添加订单");
orderService.addOrder();
}
}
1.3 Lambda语法规则
Lambda表达式的语法格式如下:
(参数列表) -> { 方法体 }
简化规则:
- 当方法体中只有一条语句时,可以省略花括号
{} - 当方法体只有一条
return语句时,可以省略return和花括号 - 参数类型可以省略,由编译器自动推导
1.4 函数式接口定义
Lambda表达式依赖函数式接口,函数式接口需要满足以下条件:
- 接口中只能有一个抽象方法
- 可以使用
@FunctionalInterface注解标记(编译期检查) - 可以通过
default关键字定义普通方法 - 可以定义
Object类中的方法
java
@FunctionalInterface
public interface MyFunctionalInterface {
void execute(); // 唯一的抽象方法
default void print() { // 默认方法
System.out.println("default method");
}
// Object类中的方法不算抽象方法
boolean equals(Object obj);
}
二、四大核心函数式接口
Java 8在 java.util.function 包中提供了四大核心函数式接口,几乎满足了所有的使用场景。
2.1 消费型接口 Consumer<T>
特点: 接收一个参数,没有返回值
java
public class TestConsumer {
public static void main(String[] args) {
List<String> list = Arrays.asList("java", "c", "python", "c++", "VB", "C#");
// 使用Lambda表达式遍历集合
list.forEach(s -> System.out.println(s));
// 使用方法引用更简洁
list.forEach(System.out::println);
}
}
2.2 供给型接口 Supplier<T>
特点: 没有参数,返回一个结果
java
public class TestSupplier {
public static void main(String[] args) {
Supplier<String> supplier = () -> "hello";
System.out.println(supplier.get()); // 输出: hello
}
}
2.3 判断型接口 Predicate<T>
特点: 接收一个参数,返回一个boolean值
java
public class TestPredicate {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("ok");
list.add("yes");
System.out.println("删除之前:");
list.forEach(t -> System.out.println(t));
// 删除包含字母o的元素
list.removeIf(s -> s.contains("o"));
System.out.println("删除包含o字母的元素之后:");
list.forEach(t -> System.out.println(t));
}
}
2.4 功能型接口 Function<T,R>
特点: 接收一个参数,返回一个结果(类型可能不同)
java
public class TestFunction {
public static void main(String[] args) {
// 实现将字符串首字母转为大写的功能
Function<String, String> fun = s ->
s.substring(0, 1).toUpperCase() + s.substring(1);
System.out.println(fun.apply("hello")); // 输出: Hello
}
}
三、方法引用
方法引用是Lambda表达式的简化写法,使用双冒号 :: 来表示。当Lambda体已经有现成的方法实现时,可以直接引用该方法。
3.1 方法引用的类型
3.1.1 对象名引用成员方法
java
public class MethodRefObject {
public void printUpperCase(String str) {
System.out.println(str.toUpperCase());
}
}
public class DemoMethodRef {
private static void printString(Printable lambda) {
lambda.print("Hello");
}
public static void main(String[] args) {
MethodRefObject obj = new MethodRefObject();
// 使用方法引用
printString(obj::printUpperCase);
}
}
3.1.2 类名引用静态方法
java
public class DemoMethodRef {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
// Lambda表达式写法
method(-10, n -> Math.abs(n));
// 方法引用写法(更简洁)
method(-10, Math::abs);
}
}
3.1.3 super引用成员方法
java
public class Human {
public void sayHello() {
System.out.println("Hello!");
}
}
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
public void method(Greetable g) {
g.greet();
}
public void show() {
// 使用super引用父类方法
method(super::sayHello);
}
}
3.1.4 this引用成员方法
java
public class Husband {
private void buyHouse() {
System.out.println("买套房子");
}
private void marry(Richable lambda) {
lambda.buy();
}
public void beHappy() {
// 使用this引用当前类方法
marry(this::buyHouse);
}
}
3.2 构造器引用
类构造器引用
java
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
// getter/setter...
}
// 函数式接口
@FunctionalInterface
public interface PersonBuilder {
Person buildPerson(String name);
}
public class DemoConstructorRef {
private static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name).getName());
}
public static void main(String[] args) {
// 使用构造器引用
printName("赵丽颖", Person::new);
}
}
数组构造器引用
java
@FunctionalInterface
public interface ArrayBuilder {
int[] buildArray(int length);
}
public class DemoArrayRef {
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
// 使用数组构造器引用
int[] array = initArray(10, int[]::new);
}
}
四、Stream流
Stream是Java 8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,并且支持链式调用。
4.1 什么是Stream
Stream是一个来自数据源的元素队列,支持聚合操作。它有几个关键特点:
- 元素队列:元素是特定类型的对象,形成一个队列
- 数据源:流的来源可以是集合、数组等
- 内部迭代:Stream提供了内部迭代方式,无需显式编写循环
- 延迟执行:中间操作不会立即执行,只有遇到终结操作才会执行
4.2 获取Stream的三种方式
4.2.1 从Collection获取
java
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Vector<String> vector = new Vector<>();
Stream<String> stream3 = vector.stream();
4.2.2 从Map获取
java
Map<String, String> map = new HashMap<>();
// 获取key的Stream
Stream<String> keyStream = map.keySet().stream();
// 获取value的Stream
Stream<String> valueStream = map.values().stream();
// 获取Entry的Stream
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
4.2.3 从数组获取
java
String[] array = {"张无忌", "张翠山", "张三丰", "张一元"};
Stream<String> stream = Stream.of(array);
4.3 Stream常用方法
Stream的方法可以分为两类:
- 中间操作:返回值类型仍然是Stream,支持链式调用
- 终结操作:返回值不再是Stream,执行后流结束
4.3.1 forEach:遍历元素
java
Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
stream.forEach(name -> System.out.println(name));
4.3.2 filter:过滤元素
java
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s -> s.startsWith("张"));
result.forEach(System.out::println); // 输出: 张无忌 张三丰
4.3.3 map:元素映射
java
Stream<String> original = Stream.of("10", "12", "18");
Stream<Integer> result = original.map(str -> Integer.parseInt(str));
result.forEach(System.out::println); // 输出: 10 12 18
4.3.4 count:统计个数
java
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s -> s.startsWith("张"));
System.out.println(result.count()); // 输出: 2
4.3.5 limit:取前n个元素
java
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.limit(2);
System.out.println(result.count()); // 输出: 2
4.3.6 skip:跳过前n个元素
java
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.skip(2);
System.out.println(result.count()); // 输出: 1
4.3.7 concat:合并两个流
java
Stream<String> streamA = Stream.of("张无忌");
Stream<String> streamB = Stream.of("张翠山");
Stream<String> result = Stream.concat(streamA, streamB);
result.forEach(System.out::println); // 输出: 张无忌 张翠山
4.4 Stream实战:优雅的集合操作
传统方式 vs Stream方式
需求:从姓名列表中筛选出姓张且名字长度为3的人
传统方式:
java
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
List<String> zhangList = new ArrayList<>();
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
List<String> shortList = new ArrayList<>();
for (String name : zhangList) {
if (name.length() == 3) {
shortList.add(name);
}
}
for (String name : shortList) {
System.out.println(name);
}
Stream方式:
java
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
// 链式调用,代码简洁明了
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
五、总结
本文详细介绍了Java 8函数式编程的核心内容:
- Lambda表达式:简化匿名内部类的编写,让代码更简洁
- 函数式接口:四大核心接口(Consumer、Supplier、Predicate、Function)满足不同场景
- 方法引用:进一步简化Lambda表达式,复用已有方法实现
- Stream流:提供函数式编程风格的集合操作,支持链式调用和延迟执行