第五章 JDK 8内置常用函数式接口

目录

一、内置函数式接口由来

二、JDK 8 常用内置函数式接口介绍

​三、Supplier接口

 3.2. 使用方法

3.2. 使用场景

四、Consumer接口

4.1. 使用方法

4.2. 使用场景

五、Function接口  

5.1. 使用方法

5.2. 使用场景

六、Predicate接口

6.1. 使用方法

6.2. 使用场景


一、内置函数式接口由来

我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。

二、JDK 8 常用内置函数式接口介绍

它们主要在 java.util.function 包中,下面是最常用的几个接口: 

1. Supplier接口

2. Consumer接口

3. Function接口

4. Predicate接口

三、Supplier接口

Supplier接口是一个函数式接口,用于表示一个不接受参数但可以提供一个结果的生产者。

 3.2. 使用方法

java.util.function.Supplier<T> 接口,它意味着"供给" , 对应的Lambda表达式需要对外提供一个符合泛型类型的对象数据。

供给型接口,通过Supplier接口中的get方法可以得到一个值,无参有返回的接口。

使用Lambda表达式返回数组元素最大值

使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用 java.lang.Integer 类。

3.2. 使用场景

1. 无参回执/懒加载利器/惰性初始化

在某些情况下,你可能希望只有在真正需要结果时才进行计算或生成值。使用Supplier接口,你可以将计算或生成值的逻辑封装在get()方法中,并在需要时调用它‌。

这种延迟加载机制避免了不必要的开销,提升了性能。

‌2. 提供默认值‌

当某个方法需要返回一个值,但此时没有合适的值可返回时,可以使用Supplier接口来提供一个默认值‌。也可以使用Supplier接口来处理异常情况,通过Supplier抛出异常‌。

‌3.与Stream API结合生成无限数据流

在Java 8的Stream API中,Supplier接口可以与Stream.generate()方法一起使用,以生成一个无限流,其中每个元素都是通过调用Supplier的get()方法获得的‌。

4. 用于随机数、时间戳等动态数据的生成 

5. 优化 if else

Supplier与Optional和工厂模式结合,优化业务代码中的if else语句

6. 更高级用法

除了上述基本用法外,Supplier还可以与其他Java函数式接口结合使用,如Function和Predicate等,以实现更复杂的逻辑。例如,我们可以使用Supplier和Predicate来创建一个可重试的方法调用机制:

7. 自定义 Supplier,简化业务逻辑

比如不同级别或单位的用户,我们发送不同的祝福短信。根据不同需求,指定不同的业务处理逻辑。

四、Consumer接口

 java.util.function.Consumer<T> 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。 

4.1. 使用方法

使用Lambda表达式将一个字符串转成大写和小写的字符串

Consumer消费型接口,可以拿到accept方法参数传递过来的数据进行处理, 有参无返回的接口。基本使用如:

默认方法:andThen

如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。下面是JDK的源代码:

备注: java.util.Objects requireNonNull 静态方法将会在参数为null时主动抛出 NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。

要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是一步接一步操作。例如两个步骤组合的情况,如下代码将字符串先转小写,再转大写,相当于单独对字符串做了两次处理,一次从"Hello World"转"hello world",另一次从"Hello World"转"HELLO WORLD":

4.2. 使用场景

1‌. 数据处理和转换

在数据处理过程中,Consumer接口常用于对数据进行操作,例如修改对象的状态、输出信息或传递给其他方法进行进一步处理。例如,在处理集合时,可以使用Consumer接口对集合中的每个元素执行特定操作‌。

修改对象的状态

package org.example;

import java.util.function.Consumer;

class Person {
    // 姓名
    private String name;
    // 年龄
    private int age;
    // 座右铭
    private String motto;
    public Person() {

    }
    public Person(String name, int age, String motto) {
        this.name = name;
        this.age = age;
        this.motto = motto;
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setMotto(String motto) {
        this.motto = motto;
    }
}

public class Test {
    public static void main(String[] args) {
        Person person = new Person("王哲晓", 30, "坚持专注某领域,是一件很酷的事");
        Consumer<Person> updateName = p -> p.setName("wangzhexiao");
        updateName.accept(person);
    }
}

在集合中的使用

Consumer 常用于集合的元素遍历中,如使用 forEach 方法。

package org.example;

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class Test {
    public static void main(String[] args) {
        List<String> favourites = Arrays.asList("Coding", "骑行", "国漫", "音乐", "收纳", "烹饪");
        Consumer<String> printConsumer = System.out::println;
        favourites.forEach(printConsumer);
    }
}

2. 链式操作

Consumer接口支持链式操作,通过andThen方法可以将多个Consumer组合在一起,顺序执行。这种特性使得在处理复杂的数据处理流程时更加灵活和高效‌。

package org.example;

import java.util.function.Consumer;

class Person {
    // 姓名
    private String name;
    // 年龄
    private int age;
    // 座右铭
    private String motto;
    public Person() {}
    public Person(String name, int age, String motto) {
        this.name = name;
        this.age = age;
        this.motto = motto;
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setMotto(String motto) {
        this.motto = motto;
    }
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "', motto=" + motto + '}';
    }
}

public class Test {
    public static void main(String[] args) {
        Person person = new Person("王哲晓", 30, "坚持专注某领域,是一件很酷的事");
        Consumer<Person> updateName = p -> p.setName("wangzhexiao");
        Consumer<Person> updateMotto = p -> p.setMotto("来人世一遭,努力实现自我的价值");
        updateName.accept(person);
        updateMotto.accept(person);
        // Person{name='wangzhexiao', age=30', motto=来人世一遭,努力实现自我的价值}
        System.out.println(person);
    }
}

‌3. 日志记录

在需要记录日志的场景中,可以使用Consumer接口来处理日志信息。例如,在某个操作完成后,使用Consumer接口将日志信息输出到控制台或日志文件中‌。

package org.example;

import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;

@FunctionalInterface
interface LogConsumer extends Consumer<String> {
    static LogConsumer forLevel(Level level) {
        return message -> getLogger().log(level, message);
    }

    static Logger getLogger() {
        return Logger.getAnonymousLogger();
    }
}
public class Test {
    public static void main(String[] args) {
        LogConsumer infoConsumer = LogConsumer.forLevel(Level.INFO);
        infoConsumer.accept("这是一条信息级别的日志");

        LogConsumer errorConsumer = LogConsumer.forLevel(Level.SEVERE);
        errorConsumer.accept("这是一条错误级别的日志");
    }
}

‌4. 条件判断

虽然Consumer接口本身不直接用于条件判断,但在某些情况下,可以结合其他函数式接口(如Predicate)来实现条件判断后的数据处理。例如,先使用Predicate接口判断数据是否满足特定条件,然后使用Consumer接口对满足条件的数据进行处理‌。

5. 自定义业务复杂操作

对集合计算求和

package org.example;

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class Test {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(10, 123, 3432, 42, 11);
        // 定义一个内部类
        class Sum {
            private int total = 0;
            public void add(int number) {
                total += number;
            }
            public int getTotal() {
                return total;
            }
        }
        Sum sum = new Sum();
        Consumer<Integer> sumConsumer = sum::add;
        // 循环遍历求和
        numbers.forEach(sumConsumer);
        // 输出: Sum: 3618
        System.out.println("Sum: " + sum.getTotal());
    }
}

一万条业务数据批量入库(每10条)并统计入库数量

package org.example;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class Test {
    public static void main(String[] args) throws Exception {
        test();
    }
    public static void test() throws Exception {
        // 初始化一万条模拟数据
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            User user = new User();
            user.setName("我叫" + i);
            userList.add(user);
        }
        if (userList.isEmpty()) throw new Exception();
        // 构建一个数据入库并统计入库数据量的Consumer函数式接口
        Consumer<List<User>> consumer = Test::importData;
        // 构建消费者类,并传入函数式接口及数据列表
        new DataConsumer(consumer).handlerData(userList);
    }

    public static void importData(List<User> list) {
        // 假设这块先处理了数据入库操作
        System.out.println("输出入库数据记录总数:" + list.size());
    }
}

class DataConsumer<T> {
    public Consumer<List<T>> importService;

    public DataConsumer(Consumer<List<T>> importService) throws Exception {
        this.importService = importService;
    }
    public void handlerData(List<T> list) {
        // 模拟解析,每达到10条数据就进行回调入库/统计操作
        List<T> dataList = new ArrayList<>();
        for (T t : list) {
            dataList.add(t);
            if (dataList.size() == 1000) {
                // 回调保存数据
                saveData(dataList);
            }
        }
        // 回调保存数据
        saveData(dataList);
    }
    private void saveData(List<T> dataList) {
        if (!dataList.isEmpty()) {
            importService.accept(dataList);
            dataList.clear();
        }
    }
}

class User {
    private String name;
    public User() {}
    public User(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

五、Function接口  

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有参数有返回值。

5.1. 使用方法

使用Lambda表达式将字符串转成数字

Function转换型接口,对apply方法传入的T类型数据进行处理,返回R类型的结果,有参有返回的接口。例如:将 String 类型转换为 Integer 类型。

默认方法:andThen

Function 接口中有一个默认的 andThen 方法,用来进行组合操作。JDK源代码如:

该方法同样用于先做什么,再做什么的场景,和 Consumer 中的 andThen 差不多:

第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一起。

注意,Function的前置条件泛型和后置条件泛型可以相同。

5.2. 使用场景

1. 个性化逻辑数据处理

我们可以按照自身的多种业务需求,对数据进行不同的加工处理,比如可以对相同的业务数据进行加减乘除操作:

@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
}
 
public static void useCalculator(Calculator calculator, int a, int b) {
    int result = calculator.calculate(a, b);
    System.out.println("结果是:" + result);
}
 
public static void main(String[] args) {
    useCalculator((a, b) -> a + b, 5, 3);
    useCalculator((a, b) -> a * b, 5, 3);
}

2. 并发编程示例 

package org.example;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;

public class Test {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(4);
        // 这里可以替换成实际项目中的业务逻辑
        Function<String, Integer> stringToLengthFunction = String::length;

        for(int i = 0; i< 100; i++) {
            Random random = new Random();
            int num = random.nextInt(100000);
            String input = "Task-" + num;
            executor.submit(() -> System.out.println(stringToLengthFunction.apply(input)));
        }
        executor.shutdown();
    }

}

 3. 对集合的操作

import java.util.Arrays;
import java.util.List;
 
public class Main {
    public static void main(String[] args) {
        // 定义一个Function函数式接口集合,针对字符串进行各种不同的处理逻辑
        List<Function<String, String>> functions = Arrays.asList(
            s -> s.toUpperCase(),
            s -> s.toLowerCase(),
            s -> s.substring(0, 1)
        );
 
        String input = "Hello World!";
        // 循环遍历集合,不同函数式接口对字符串进行处理后输出结果
        functions.forEach(f -> System.out.println(f.apply(input)));
    }
}

六、Predicate接口

有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果,这时可以使用

java.util.function.Predicate<T> 接口

6.1. 使用方法

使用Lambda判断一个人名如果超过3个字就认为是很长的名字 

test方法的参数T进行判断,返回boolean类型的结果。用于条件判断的场景:

条件判断的标准是传入的Lambda表达式逻辑,只要名称长度大于3则认为很长。

默认方法:and

既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用逻辑连接起来实现“并且的效果时,可以使用default方法 and 。其JDK源码为:

使用Lambda表达式判断一个字符串中即包含W,也包含H

使用Lambda表达式判断一个字符串中包含W或者包含H

使用Lambda表达式判断一个字符串中即不包含W

如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:

默认方法:or

使用Lambda表达式判断一个字符串中包含W或者包含H与 and 类似,默认方法 or 实现逻辑关系中的JDK源码为:

如果希望实现逻辑字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可,其他都不变:

默认方法:negate

使用Lambda表达式判断一个字符串中即不包含W

已经了解了,剩下的(取反)也会简单。默认方法 negate JDK源代码为:

从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前调用 negate 方法,正如 and or 方法一样:

6.2. 使用场景

1. 作为过滤条件

package org.example;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class Test {
    public static void main(String[] args) {
        List<String> mottos = Arrays.asList(
                "每天要足够努力,方能肩负起更多责任",
                "每天要足够努力,方能不负此生",
                "只要学不死,就往死里学",
                "专注于一件事,持续地去努力,你会看到自身的价值和潜能的!");

        // 创建一个Predicate来检查座右铭长度
        Predicate<String> isLongContent = motto -> motto.length() > 16;

        // 使用Predicate过滤名字列表
        List<String> list = mottos.stream()
                .filter(isLongContent)
                .collect(Collectors.toList());

        // 打印座右铭
        list.forEach(System.out::println);
    }
}

2. 与Supplier结合实现重试机制

除了上述基本用法外,Predicate还可以与其他Java函数式接口结合使用,如Supplier,以实现更复杂的逻辑。例如,我们可以使用Supplier和Predicate来创建一个可重试的方法调用机制:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值