Java8新特性Optional,Function,Supplier,Consumer

Java8新特性

1.Optional

首先,Optional 它不是一个函数式接口,设计它的目的是为了防止空指针异常(NullPointerException),要知道在 Java 编程中, 空指针异常可是臭名昭著的。

让我们来快速了解一下 Optional 要如何使用!你可以将 Optional 看做是包装对象(可能是 null, 也有可能非 null)的容器。当你定义了 一个方法,这个方法返回的对象可能是空,也有可能非空的时候,你就可以考虑用 Optional 来包装它,这也是在 Java 8 被推荐使用的做法。

java 复制代码
Optional<String> optional = Optional.of("bam");

optional.isPresent();           // true
optional.get();                 // "bam"
optional.orElse("fallback");    // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

Optional 经典实践

java 复制代码
Optional 经典实践
上面对 Optional 类中的函数都作了简要说明,但是在实践中不是每一个方法都会经常被使用到。接下来提供一些经典场景的演示代码,如果在实践中还有其他常见的情况,欢迎大家进行补充。

获取指定字段,返回非null结果
// 返回个人简介内容,没有则返回空字符串
String intro = Optional.ofNullable(person.getIntro()).orElse("");

// 返回学生列表,没有则返回空列表

// 3-优雅版
List<Student> students3 = Optional.ofNullable(data)
    .map(Data::getStudents)
    .orElse(Collections.emptyList());
    

#获取指定字段,不存在时通过工具方法生成赋值或抛出异常
// 补充缺失的uid字段
String uid = Optional.ofNullable(data.getUid()).orElseGet(() -> UUID.randomUUID().toString());
// uuid不能存在则抛出异常
String uuid = Optional.ofNullable(data.getUid()).orElseThrow(Exception::new);

#进行条件筛选
// 列表大小小于5时返回空列表
List<Student> list = Optional.ofNullable(data)
						.map(Data::getStudents)
    					.filter(s -> s.size() > 5).
    					orElse(Collections.emptyList());

#map常用用法
      static class Outer {
        Nested nested = new Nested();

        public Nested getNested() {
            return nested;
        }

        public String getString(){
            return "12345";
        }

    }

    static class Nested {
        Inner inner = new Inner();

        public Inner getInner() {
            return inner;
        }
    }

    static class Inner {
        String foo = "boo";

        public String getFoo() {
            return foo;
        }
    }
    private static void test2() {
        Optional.of(new Outer())
                .map(Outer::getNested)
                .map(Nested::getInner)
                .map(Inner::getFoo)
                .ifPresent(System.out::println);
    }

2.Function

Function 函数式接口的作用是,我们可以为其提供一个原料,他给生产一个最终的产品。通过它提供的默认方法,组合,链行处理(compose, andThen):

核心代码

java 复制代码
// Java Function 接口的定义
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    Objects.requireNonNull(before);
    return (V v) -> apply(before.apply(v));
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}

static <T> Function<T, T> identity() {
    return t -> t;
}

apply 方法的使用方式如下:

java 复制代码
Function<Integer, Integer> add = p -> p + 10;
Integer result = add.apply(10);
// 这里会输出 20,因为这个函数定义的操作时把参数加上 10 后返回
System.out.println(result);

compose 方法的参数也是一个 Function 对象。在 A 这个 Function 上调用 compose 方法时传入 B 这个 Function 对象,然后得到一个新的 Function 对象 C。C 这个 Function 对象的实现逻辑是先带调用 B 的 apply 方法对参数进行操作,将得到的结果 再作为参数 传递给 A 这个 Function 对象的 apply 方法,然后返回执行后的结果。

addThen 方法也是类似的原理,只不过内部执行方法的顺序不一样而已。

java 复制代码
Function<Integer, Integer> multiplyTen = a -> a * 10;
Function<Integer, Integer> addTen = a -> a + 10;
// 先增加 10,然后再乘 10,输出结果 110
Function<Integer, Integer> addTenThenMultiplyTen = multiplyTen.compose(addTen);
System.out.println(addTenThenMultiplyTen.apply(1));

// 先乘 10,然后再加 10,输出结果 20
Function<Integer, Integer> multiplyTenAddTenThen = multiplyTen.andThen(addTen);
System.out.println(multiplyTenAddTenThen.apply(1));

Function 接口的实例

java 复制代码
public class CheckUtils {
    private static final Function<String, String> lengthCheck = params -> {
        if (params.length() > 100) {
            throw new RuntimeException("Length exceed max limit.");
        }
        return params;
    };

    private static final Function<String, String> invalidCharacterCheck = str -> {
        if (!str.matches("^[a-f,A-F]$")) {
            throw new RuntimeException("Contains invalid character.");
        }
        return str;
    };
    
    /**
     * 这里的公共方法组合了该类中的基本校验逻辑构成一个复合的逻辑
     */
    public static void checkStringLengthAndPhoneNumber(String string) {
        invalidCharacterCheck.compose(lengthCheck).apply(string);
    }
}

3.Supplier 生产者

SupplierFunction 不同,它不接受入参,直接为我们生产一个指定的结果,有点像生产者模式:

class Person {
    String firstName;
    String lastName;

    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person

使用场景:

场景一:随机数生成

假设我们需要生成一个指定范围的随机数,可以使用Supplier来封装随机数生成的逻辑。

java 复制代码
java 代码解读复制代码import java.util.Random;  
import java.util.function.Supplier;  
  
public class RandomNumberSupplier {  
    public static void main(String[] args) {  
        Supplier<Integer> randomNumberSupplier = () -> new Random().nextInt(100);  
        System.out.println(randomNumberSupplier.get()); // 输出一个0到99之间的随机数  
    }  
}

场景二:创建对象

在创建对象时,我们可以使用Supplier来封装对象的创建逻辑,这样可以使代码更加清晰和易于维护。

java 复制代码
import java.util.function.Supplier;  
  
public class User {  
    private String name;  
    private int age;  
  
    public User(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
  
    @Override  
    public String toString() {  
        return "User{" + "name='" + name + '\'' + ", age=" + age + '}';  
    }  
  
    public static void main(String[] args) {  
        Supplier<User> userSupplier = () -> new User("张三", 25);  
        User user = userSupplier.get();  
        System.out.println(user); // 输出:User{name='张三', age=25}  
    }  
}

场景三:结合Stream API使用

Supplier可以与Java 8的Stream API结合使用,用于生成Stream的数据源。

java 复制代码
import java.util.function.Supplier;  
import java.util.stream.Stream;  
  
public class StreamWithSupplier {  
    public static void main(String[] args) {  
        Supplier<Integer> numberSupplier = () -> (int) (Math.random() * 100);  
        Stream<Integer> numberStream = Stream.generate(numberSupplier);  
        numberStream.limit(5).forEach(System.out::println); // 输出5个随机数  
    }  
}

4.Consumer 消费者

Consumer接口也是一个函数式接口,它表示一个接受单一输入参数并且不返回任何结果的操作。你可以把它想象成一个消费者,你给它一个东西,它消费掉这个东西,但不给你任何回报。

consumer.get();//获取消费主题

consumer.accept("")//设置传参内容

场景一:打印数据

当我们需要打印某个数据时,可以使用Consumer来封装打印的逻辑。

java 复制代码
import java.util.function.Consumer;  
  
public class PrintConsumer {  
    public static void main(String[] args) {  
        Consumer<String> printConsumer = System.out::println;  
        printConsumer.accept("Hello, World!"); // 输出:Hello, World!  
    }  
}

场景二:数据验证

在处理数据时,我们可能需要验证数据的合法性。使用Consumer可以方便地封装验证逻辑。

java 复制代码
import java.util.function.Consumer;  
  
public class ValidationConsumer {  
    public static void main(String[] args) {  
        String data = "12345";  
        Consumer<String> validationConsumer = s -> {  
            if (s.length() < 5) {  
                throw new IllegalArgumentException("数据长度不足");  
            }  
            // 其他验证逻辑...  
        };  
        try {  
            validationConsumer.accept(data);  
            System.out.println("数据验证通过");  
        } catch (IllegalArgumentException e) {  
            System.out.println("数据验证失败:" + e.getMessage());  
        }  
    }  
}

**场景三:**修改集合元素

对于集合中的每个元素,我们可能需要执行一些修改操作。使用Consumer可以方便地对集合中的元素进行处理。

java 复制代码
import java.util.ArrayList;  
import java.util.List;  
import java.util.function.Consumer;  
  
public class ListModificationWithConsumer {  
    public static void main(String[] args) {  
        List<Integer> numbers = new ArrayList<>();  
        numbers.add(1);  
        numbers.add(2);  
        numbers.add(3);  
          
        Consumer<Integer> multiplyByTwo = n -> n *= 2;  
          
        numbers.forEach(multiplyByTwo);  
          
        numbers.forEach(System.out::println); // 输出:2, 4, 6  
    }  
}

在这个例子中,我们定义了一个Consumer,它接受一个整数并将其乘以2。然后,我们使用forEach方法将这个操作应用于列表中的每个元素。

电商系统的应用举例

接下来,我通过电商系统中应用SupplierConsumer接口的实际案例,旨在帮助您更好地理解和记忆这两个接口在业务场景中的应用。

案例一:用户积分更新(Consumer应用)

在电商系统中,Consumer接口在这里使得积分更新逻辑变得更加清晰和模块化,易于维护和测试。它允许开发者将用户积分更新的具体操作与用户购买行为的其他处理逻辑分离,提高代码的扩展性和可重用性。

java 复制代码
// Consumer接口实现更新用户积分逻辑
Consumer<User> updateUserPoints = user -> {
    // 假设calculatePoints是一个方法,根据用户购买行为计算积分
    int pointsToAdd = PointsCalculator.calculatePoints(user.getPurchaseHistory());
    user.addPoints(pointsToAdd);
    // 更新用户积分到数据库
    userService.updateUserPoints(user);
};

// 在用户完成购买后调用
usersWhoPurchased.forEach(updateUserPoints);
案例二:商品库存扣减(Consumer应用)

使用Consumer接口可以将库存扣减的逻辑封装起来,便于在不同的订单处理流程中重用。这种方式简化了代码结构,使得库存管理更加集中和一致。

java 复制代码
 //Consumer接口实现库存扣减逻辑
Consumer<Product> deductInventory = product -> {
    // 检查库存
    if (inventoryService.checkInventory(product) > 0) {
        // 扣减库存
        inventoryService.deductInventory(product);
    }
};

// 在订单处理流程中调用,对订单中的每个商品扣减库存
order.getProducts().forEach(deductInventory);

我觉得这个本质就是给业务提供不同的扩展机制实现

案例三:动态获取商品价格(Supplier应用)

商品的价格可能会从不同的对象数据中获取,明显的动态变化。我们可以使用Supplier接口来定义获取商品价格的逻辑。有点时候可能我们的商城系统有不同的价格对象,那么此时可以通过该接口返回不同对象的结果,也是OK的.

java 复制代码
import java.util.function.Supplier;  
  
public class ProductPriceProvider {  
    private double price;  
  
    public ProductPriceProvider(double initialPrice) {  
        this.price = initialPrice;  
    }  
  
    public double calcPrice(Supplier<Double> priceSupplier) {  
		// 使用Supplier获取价格,某些不同的逻辑  ,然后统一返回结果
        return priceSupplier.get();  
    }  
  
    public void setPrice(double newPrice) {  
        this.price = newPrice;  
    }  
  
    public static void main(String[] args) {  
        ProductPriceProvider provider = new ProductPriceProvider(100.0);  
        provider.calcPrice(() -> {             // 可以返回NormalUser对象的价格
        });
        provider.calcPrice(() -> {             // 可以返回EmployeeUser对象的价格
        });
          
    }  
}
相关推荐
星迹日9 分钟前
数据结构:排序—计数,桶,基数排序(五)
java·数据结构·算法·排序算法·计数排序·桶排序·基数排序
向哆哆2 小时前
基于Java的分布式系统架构设计与实现
java·开发语言
付宇轩2 小时前
leetcode 3271.哈希表分割字符串
java·开发语言·算法
Zaralike2 小时前
SpringBoot项目练习
java·开发语言
Clank的游戏栈2 小时前
Unity进阶教程AOI算法原理详解
java·unity·游戏引擎
元亓亓亓2 小时前
java后端开发day14--之前练习的总结和思考
java·开发语言
iamphp2 小时前
数据库-嵌入SQL访问接口
java·数据库·sql
Abelard_3 小时前
Maven在idea中的使用
java·maven
北执南念3 小时前
Spring Boot 中的日志配置
java·spring boot·mybatis