JAVA函数式编程

252 阅读22分钟

Java函数式编程包含以下几个主要内容:

  1. Lambda 表达式:Lambda 表达式是一种轻量级的匿名函数,它可以传递到方法中作为参数或存储在变量中。Lambda 表达式提供了一种更简洁、灵活的方式来编写函数式代码。
  2. 函数接口(Functional Interface):函数接口是指只有一个抽象方法的接口。Java 8 引入了 java.util.function 包,其中包含了很多常用的函数接口,例如 ConsumerSupplierPredicate 等。函数接口提供了一种方便的方式来定义和使用函数式接口,并与 Lambda 表达式配合使用。
  3. 方法引用(Method Reference):方法引用是一种简化 Lambda 表达式的语法形式,它允许直接引用已经存在的方法,而不必像 Lambda 表达式那样编写新的方法体。
  4. Stream API:Stream API 是 Java 8 提供的一种处理集合数据的高级工具,它使用函数式编程的思想来对集合进行过滤、映射、聚合等操作。Stream API 可以帮助我们编写更简洁、可读性更强的代码,并且支持并行处理以提高性能。
  5. Optional 类型:Optional 是一种容器类型,用于表示一个值存在或不存在的情况。Optional 提供了一些方法来处理可能为空的值,避免了空指针异常的问题。
  6. 默认方法(Default Method):Java 8 引入了接口的默认方法,这使得我们可以在接口中提供具体的方法实现。默认方法使得接口的修改更加灵活,可以向已有的接口添加新的方法而不会破坏现有的实现。

函数式编程为 Java 增加了更强大和灵活的编程能力,并且提供了更简洁、可读性更高的代码风格。这些特性使得 Java 在处理集合、并发、事件驱动等场景时变得更加便捷和高效。

Lambda 表达式

Lambda 表达式是一种轻量级的匿名函数,它可以传递到方法中作为参数或存储在变量中。Lambda 表达式提供了一种更简洁、灵活的方式来编写函数式代码。

Lambda表达式是JAVA8中提供的一种新的特性,是一个匿名函数方法。可以把Lambda表达式理解为一段可以传递的代码,可以写出更简洁、更灵活的代码。

Lambda表达式使用条件

Lambda表达式可以认为是对匿名内部类的一种简化,只有函数式接口的匿名内部类才可以使用Lambda表达式来进行简化。

  1. @FunctionalInterface标记的接口是函数式接口,只能包含一个抽象方法的接口。如果包含多个抽象方法,编译会报错
  2. @FunctionalInterface该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
  3. 函数式接口只有一个抽象方法是需要我们去实现的,Lambda表达式正好是针对这个唯一的抽象方法使用。
/**
*正确的函数式接口
* 1. JDK8接口中的[静态方法和默认方法],都不算是抽象方法。
* 2. 接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
*/
@FunctionalInterface
public interface TestInterface {
 
    
    // 抽象方法
    public void sub();
 
    // java.lang.Object中的方法不是抽象方法
    public boolean equals(Object var1);
 
    // default不是抽象方法
    public default void defaultMethod(){
 
    }
 
    // static不是抽象方法
    public static void staticMethod(){
 
    }
}

Lambda表达式使用入门案例

我们常用的一些接口Callable、Runnable、Comparator等在JDK8中都添加了@FunctionalInterface注解。

线程创建的两种方式:继承Thread方式、实现Runnable方式。 Runable是一个函数式接口,里面只有一个抽象方法是需要我们强制实现的

匿名内部方式创建线程

public class carDemo {
    public static void main(String[] args) {
        Thread t1=new Thread(new Runnable() {
            @Override//这里重写Runnable接口里面的run()方法
            public void run() {
                System.out.println("匿名内部类方式-启动线程");
            }
        });
        t1.start();
    }
}

Lambda表达式创建线程

public static void main(String[] args) {
        Thread t1=new Thread(() -> System.out.println("匿名内部类方式-启动线程"));//看,多么简便、多么流畅,就问你,是不是已经爱上她了?
        t1.start();
}

Lambda语法格式:(参数列表)->{方法体}

  1. 参数列表:指定函数接受的参数,可以是零个或多个参数。参数列表位于括号内,并使用逗号分隔。
  2. 箭头符号->):标识着 Lambda 表达式的开始。箭头符号将参数列表与 Lambda 表达式体分开。
  3. Lambda 表达式体(方法体):定义了 Lambda 表达式的执行逻辑,可以是一个表达式或一段代码块。如果是表达式,它会被求值并返回结果;如果是代码块,需要使用花括号括起来,并且需要显式地指定返回值。

下面是一个例子,展示了 Lambda 表达式的基本结构:

(parameters) -> expression

or

(parameters) -> {
    // code block
    return value;
}

Lambda 表达式提供了一种简洁而灵活的方式来定义匿名函数,并在需要时进行传递和调用。

Lambda省略模式代码实现

在Lambda标准格式的基础上,使用省略写法的规则为:

  • 小括号内参数的参数类型可以省略。
  • 小括号有且只有一个参数,则小括号可以直接省略。
  • 如果大括号有且只有一个语句,无论是否有返回值,大括号、return关键字、分号可以省略。
  • 小括号内参数的参数类型可以省略:
// 完整写法
(String str) -> System.out.println(str);

// 省略参数类型
str -> System.out.println(str);
//定义一个计算器接口
public interface Calculator {
    int cal(int a);
}

public static void main(String[] args) {
       method(5, n -> {return n++; });
       /**
        * int cal(int a);抽象方法当中,小括号当中只有一个参数,这里省略5的小括号,用n指代前面的5,进行接下来的操作
        */
   }
   public static void method(int num,Calculator calculator){
       //method方法当中传入一个int类型数字,和一个计算器接口
       int result=calculator.cal(num);//接口类调用抽象方法
       System.out.println("reuslt="+result);
   }

  • 小括号有且只有一个参数,则小括号可以直接省略:
// 完整写法
(str) -> System.out.println(str);

// 省略小括号
str -> System.out.println(str);

  • 如果大括号有且只有一个语句,无论是否有返回值,大括号、return关键字、分号可以省略:
// 完整写法
(str) -> {
    return str.toUpperCase();
}

// 省略大括号、return关键字、分号
(str) -> str.toUpperCase();

类型检查

  1. Lambda的类型由上下文推断而来
  2. 同样的lambda表达式,不同的函数式接口,只要方法的签名一致,同样的表达式可以用于不同的函数是接口。
  3. 只有函数式接口的实现,能承载lambda表达式
  4. Objecto=()-{System.out.print("HellowWorld")} 这是不合法的 因为Object不是一个函数式接口

类型推断

  1. Lambda表达式可以省略参数的类型,java编译器能自动推断
  2. 当lambda只有一个参数需要推断类型时,参数两边的括号可以省略

函数式接口(Functional Interface)

函数接口是指只有一个抽象方法的接口。Java 8 引入了 java.util.function 包,其中包含了很多常用的函数接口,例如 ConsumerSupplierPredicate 等。函数接口提供了一种方便的方式来定义和使用函数式接口,并与 Lambda 表达式配合使用。

  1. @FunctionalInterface标记的接口是函数式接口,只能包含一个抽象方法的接口。如果包含多个抽象方法,编译会报错
  2. @FunctionalInterface该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
  3. 函数式接口只有一个抽象方法是需要我们去实现的,Lambda表达式正好是针对这个唯一的抽象方法使用。
  1. JDK8接口中的[静态方法和默认方法],都不算是抽象方法。
  2. 接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。

不同接口的默认方法冲突问题 如果实现的接口已有一个默认方法,但是另一个父类或者接口也有同样的默认方法。

  1. 如果是父类和接口默认方法一致,那么直接使用父类的方法实现,忽略接口中的默认方法(类优先规则,如果尝试重写默认方法toString 那么永远都不会优于Object的toString)
  2. 如果一个父接口提供了一个默认方法,另一个接口也提供了同名称和参数的方法(不论是否默认方法)那么都必须覆盖改方法。

 常见的函数式接口

介绍:函数式接口的抽象方法的签名,基本就是lambda表达式的签名,这种抽象方法称为 函数描述符 函数式接口的抽象方法签名是指该接口中定义的唯一一个抽象方法的方法签名。在Java中,函数式接口必须只有一个抽象方法,这是使用Lambda表达式的前提。

函数式接口的抽象方法签名由以下几个部分组成:

  1. 方法名称:指定了抽象方法的名称。
  2. 参数列表:定义了抽象方法接受的参数,可以是零个或多个参数。参数列表位于括号内,并使用逗号分隔。
  3. 返回类型:定义了抽象方法的返回类型。

下面是一个示例函数式接口及其抽象方法签名的定义:

@FunctionalInterface
public interface MyFunctionalInterface {
    int calculate(int a, int b);
}

在上述示例中,calculate 方法具有两个 int 类型的参数,并返回一个 int 类型的结果。这就是函数式接口的抽象方法签名。

值得注意的是,@FunctionalInterface 注解用于标识该接口是函数式接口,确保该接口仅包含一个抽象方法。虽然可以在函数式接口中包含默认方法和静态方法,但它们并不会影响函数式接口的抽象方法签名。

Predicate接口

方法签名为,输入某个对象 返回布尔结果

用于判断,返回布尔类型的数据。

	/**
     * java.util.Predicate 是一个只有test方法,返回布尔值的一个函数式接口,
     * 与其类似的还有用于比较,排序的Comparator接口,其只有一个返回整数的比较接口
     * @param list
     * @param p
     * @param <T>
     * @return
     */
    public static <T> List<T> filter(List<T> list, Predicate<T> p){
        List<T> result=new ArrayList<>();
        for (T t : list) {
            if (p.test(t))
                result.add(t);
        }
        return result;
    }

该方法有两个参数:list 是需要过滤的列表,p 是一个 Predicate 函数式接口实例,用于确定哪些元素应该被保留下来。<T> 表示泛型类型。

方法内部,它创建了一个空列表 result,然后遍历传入的 list 中的每个元素 t。对于每个元素,会调用 p.test(t) 来判断是否满足过滤条件,如果满足,则将该元素添加到 result 列表中。最后,返回过滤后的结果列表 result

接下来,在 main 方法中,使用了 filter 方法来过滤一个 Apple 对象的列表:

public static void main(String[] args) {
        //Predicate函数式接口示例
        List<Apple> appleList=new ArrayList<>();
        List<Apple> resulAppleList=filter(appleList,(Apple a)->a.getColor().equals("red"));
    }

Counsumer接口

Accept ()方法签名为,输入某个对象 返回void

Consumer 接口是Java函数式编程中的一个函数式接口,它代表了一个接受单个输入参数并且没有返回值的操作。它提供了一个 void accept(T t) 方法来定义要执行的操作。

Consumer 接口通常用于需要对某个对象进行一系列操作,而不需要返回结果的情况下。例如,可以使用 Consumer 来遍历列表,并对每个元素执行特定的操作。

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        // 使用 Consumer 接口打印每个名字
        Consumer<String> printName = name -> System.out.println(name);
        names.forEach(printName);

        // 使用方法引用简化 Lambda 表达式
        names.forEach(System.out::println);
    }
}

在上述示例中,首先创建了一个包含字符串元素的列表 names。然后,定义了一个 Consumer 接口实例 printName,其中使用 Lambda 表达式实现了 accept 方法,以便打印每个名字。

通过调用 names.forEach(printName),我们遍历列表中的每个元素,并将其传递给 printNameaccept 方法进行处理。这样,每个名字都会被打印出来。

另外,在示例中也展示了使用方法引用的方式来简化 Lambda 表达式:names.forEach(System.out::println)。这里直接使用了 System.out.println 方法作为 Consumer 接口的实现,达到相同的打印效果。

总结起来,Consumer 接口可以用于执行一些针对某个对象的操作,如打印、更新等,而不需要返回结果。它提供了一种灵活且简洁的方式来处理这类场景。

Function接口

Apply() 方法签名:输入某个对象、返回某个对象

Function 接口是Java函数式编程中的一个函数式接口,它代表了一个接受一个参数并产生结果的操作。它提供了一个 R apply(T t) 方法来定义输入和输出的转换关系。

Function 接口通常用于需要对某个对象进行转换或映射操作的情况下。可以通过实现 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));
}

以下是一个示例代码,展示了 Function 接口的用途:

import java.util.function.Function;

public class FunctionExample {
    public static void main(String[] args) {
        // 定义一个Function接口实例,将字符串转换为整数
        Function<String, Integer> strToInt = s -> Integer.parseInt(s);

        // 转换字符串 "123" 为整数
        int result = strToInt.apply("123");
        System.out.println(result); // 输出: 123

        // 使用方法引用简化Lambda表达式
        Function<String, Integer> strToIntRef = Integer::parseInt;
        int result2 = strToIntRef.apply("456");
        System.out.println(result2); // 输出: 456
    }
}

在上述示例中,首先定义了一个 Function 接口实例 strToInt,其中使用 Lambda 表达式实现了 apply 方法,将输入的字符串转换为整数。

通过调用 strToInt.apply("123"),我们将字符串 "123" 传递给 apply 方法进行转换,得到整数结果 123,并输出到控制台。

另外,在示例中也展示了使用方法引用的方式来简化 Lambda 表达式:Function<String, Integer> strToIntRef = Integer::parseInt。这里直接使用了 Integer.parseInt 方法作为 Function 接口的实现,达到相同的转换效果。

总结起来,Function 接口可以用于定义输入和输出之间的转换关系,常用于数据映射、类型转换等场景。它提供了一种方便和灵活的方式来处理这类操作。

常见的Lambda和已有的实现

image.png

复合Lambda函数

	List<Apple>apples=newArrayList<>();
	apples.add(newApple("red",11));
	apples.add(newApple("red",12));
	apples.add(newApple("green",13));
	
	/**
	*对排序lanmbda进行复复合-比较器链
	*1、默认逆序方法:reversed()
	*2、多级比较:thenComparing()
	*example:对apples按照颜色排序后,进行逆序,如果颜色一样再按照重量递增
	*/
	Comparator<Apple>comparator=Comparator.comparing(Apple::getColor).reversed().thenComparing(Apple::getWeight);
	apples.sort(comparator);
	
	
	/**
	*谓词复合且、或、非
	*1、negate否定
	*2、and且
	*3、or或
	*example:对不是红色的苹果进行过滤,且收集重量大于100的苹果
	*/
	Predicate<Apple>redApplePredicate=a->a.getColor().equals("red");
	Predicate<Apple>notRedApple=redApplePredicate.negate();
	List<Apple>notRedAppleList=apples.stream().filter(notRedApple.and(apple->apple.getWeight()>100)).collect(Collectors.toList());
	
	
	/**
	*函数复合
	*1、andThen将前一lambda执行结果,作为后一表达式的参数
	*2、compose将后一表达式的结果作为前一表达式的参数
	*example;complexReult=g(f(x))例如g(f(1))step1:1+1=2step2:(1+1)*2+""
	*/
	Function<Integer,Integer>f=x->x+1;
	Function<Integer,String>g=x->x*2+"";
	Function<Integer,String>complexResult1=f.andThen(g);

自定义函数式接口

@FunctionalInterface
public interface Supplier<T> throws Throwable {
    T get();
}

LockUtil方法:
public <T> T execute(String key,  Supplier<T> supplier) throws Throwable  {
    RLock lock = RedissonUtils.getLock(key);
    boolean lockSuccess = lock.tryLock();
    if (!lockSuccess) {
        throw new BusinessException("请求太频繁了,请稍后再试哦~~");
    }
    try {
        return supplier.get();
    } finally {
        lock.unlock();
    }
}

return LockUtil.execute(SAVE_LOCK, () -> {
    return add(param);
});

方法引用(Method Reference)

法引用是一种可以简化Lambda表达式的语法形式,它提供了一种直接引用已经存在的方法的方式。通过方法引用,我们可以将已有方法作为Lambda表达式的替代,从而更加简洁地表示特定的操作。

下面是几种使用方法引用的示例代码:

  1. 静态方法引用:

类::静态方法:可以直接引用某个类的静态方法。 例如:Math::pow 相当于 lambda 表达式 (x, y) -> Math.pow(x, y)

// 定义一个静态方法
public static void print(String message) {
    System.out.println(message);
}

// 使用方法引用调用静态方法
Consumer<String> consumer = MyClass::print;
consumer.accept("Hello World"); // 输出: Hello World
  1. 实例方法引用:

类::实例方法:可以直接引用某个对象的实例方法(作为函数接口的第一个参数)。 例如:String::compareToIgnoreCase 相当于 lambda 表达式 (x, y) -> x.compareToIgnoreCase(y)

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 使用方法引用调用实例方法
names.forEach(System.out::println); // 输出列表中的每个元素
  1. 对象方法引用:

对象::实例方法:可以直接引用某个对象的实例方法。 例如:System.out::println 相当于 lambda 表达式 (x) -> System.out.println(x)

// 定义一个自定义类
class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    // 实例方法
    public void sayHello() {
        System.out.println("Hello, I'm " + name);
    }
}

// 创建Person对象
Person person = new Person("Alice");

// 使用方法引用调用对象方法
Runnable runnable = person::sayHello;
runnable.run(); // 输出: Hello, I'm Alice

方法引用的语法形式可以根据所引用的方法进行不同的变体,包括静态方法引用、实例方法引用和对象方法引用。它可以使代码更加简练、易读,并提高代码的可维护性。

方法引用是一种简化Lambda表达式的语法形式,它允许我们直接引用已经存在的方法而不需要编写新的方法体。通过方法引用,我们可以使用现有方法来代替Lambda表达式,从而使代码更加简洁和易读。

Stream API

Stream API 是 Java 8 提供的一种处理集合数据的高级工具,它使用函数式编程的思想来对集合进行过滤、映射、聚合等操作。Stream API 可以帮助我们编写更简洁、可读性更强的代码,并且支持并行处理以提高性能。

Stream API 包含以下内容:

  1. 流的创建:可以通过集合、数组、I/O 资源等多种方式来创建流。 示例代码:

    List<String> list = Arrays.asList("apple", "banana", "orange");
    Stream<String> stream = list.stream();
    
  2. 中间操作:用于对流进行转换或过滤,产生一个新的流。 示例代码:

    Stream<String> filteredStream = stream.filter(s -> s.startsWith("a"));
    
  3. 终端操作:用于对流进行最终的计算或结果收集。 示例代码:

    long count = filteredStream.count();
    System.out.println(count); // 输出: 1
    

    在优化后的代码中,我们使用 toMap 方法的三个参数版本,并提供了一个合并函数 (existingValue, newValue) -> existingValue。这个合并函数简单地保留已经存在的值,以解决重复键的问题。

    这样做可以确保在构建 skuMap 时,如果存在重复的 skuCode,将保留先出现的 SkusVo 对象,并避免 value 为 null 的情况。 请根据实际需求根据合并函数的逻辑进行调整。

      // 构建 skuCode 到 SkusVo 的映射关系
      Map<String, SkusVo> skuMap = skusList.stream()
      .collect(Collectors.toMap(SkusVo::getSkuCode, Function.identity(),
                             (existingValue, newValue) -> existingValue));
    
  4. 并行流处理:Stream API 提供了并行流的支持,可以在多核处理器上并行执行操作以提高性能。 示例代码:

    long count = list.parallelStream().filter(s -> s.startsWith("a")).count();
    System.out.println(count); // 输出: 1
    
  5. 流的串行/并行切换:可以使用 sequential()parallel() 方法在流之间进行切换。 示例代码:

    Stream<String> sequentialStream = stream.parallel().sequential();
    
  6. 流的排序:可以使用 sorted() 方法对流进行排序。 示例代码:

    Stream<String> sortedStream = stream.sorted();
    
  7. 流的聚合操作:可以使用 reduce()collect() 等方法对流中的元素进行聚合操作。 示例代码:

    String concatenatedString = stream.reduce("", (s1, s2) -> s1 + s2);
    List<String> collectedList = stream.collect(Collectors.toList());
    
    private BigDecimal calculateTotalFee(List<DetailModel> detailModels, FeeTypeEnum feeType) {
        return detailModels.stream()
                .filter(n -> feeType.getCode().equals(n.getFeeType()))
                .map(OwnerFeeDetailModel::getTotalFee)
                .reduce(BigDecimal.ZERO, BigDecimal::add)
                .setScale(2, BigDecimal.ROUND_HALF_UP);
    }
    
  8. 其他操作:Stream API 还提供了许多其他有用的操作,如映射、筛选、去重等。

总体而言,Stream API 提供了一种功能丰富且优雅的方式来处理集合数据。通过链式调用各种中间操作和终端操作,可以快速、简洁地实现对集合的处理需求。

Optional 类型

Optional 类型是 Java 8 引入的一个容器类型,用于表示一个值可能存在或不存在的情况。它提供了一些方法来安全地处理可能为空的值,避免空指针异常。

Optional 类型包含以下内容:

  1. 创建 Optional 对象:
    • Optional.of(value):创建一个包含非空值的 Optional 对象。
    • Optional.empty():创建一个空的 Optional 对象。
    • Optional.ofNullable(value):根据给定的值创建 Optional 对象,如果该值为 null,则创建一个空的 Optional 对象。

示例代码:

String name = "Alice";
Optional<String> optional1 = Optional.of(name);
Optional<String> optional2 = Optional.empty();
Optional<String> optional3 = Optional.ofNullable(name);
  1. 检查值是否存在:
    • isPresent():判断 Optional 对象中是否存在值,并返回一个 boolean 值。

示例代码:

Optional<String> optional = Optional.of("Hello");
boolean isPresent = optional.isPresent(); // true
  1. 获取值:
    • get():获取 Optional 对象中的值,如果值不存在会抛出 NoSuchElementException 异常(应谨慎使用,尽量避免)。

示例代码:

Optional<String> optional = Optional.of("Hello");
String value = optional.get(); // "Hello"
  1. 判断值是否为空:
    • isEmpty():判断 Optional 对象中的值是否为空,并返回一个 boolean 值(Java 11+)。

示例代码:

java复制代码
Optional<String> optional = Optional.empty();
boolean isEmpty = optional.isEmpty(); // true
  1. 安全地处理值:

    • ifPresent(Consumer<? super T> consumer):如果 Optional 对象中存在值,则执行给定的操作。
    • orElse(T other):如果 Optional 对象中存在值,则返回该值,否则返回指定的默认值。
    • orElseGet(Supplier<? extends T> supplier):如果 Optional 对象中存在值,则返回该值,否则使用指定的供应商生成一个默认值。
    • orElseThrow(Supplier<? extends X> exceptionSupplier):如果 Optional 对象中存在值,则返回该值,否则根据给定的异常供应商抛出异常。

orElse() 方法无论 Optional 对象是否为空都会执行,因此它总是会创建一个新的对象。orElseGet() 方法只有在 Optional 对象为空时才会执行,因此它可以用来延迟创建新的对象。个人也是推荐使用orElseGet()。

示例代码:

Optional<String> optional = Optional.of("Hello");
optional.ifPresent(value -> System.out.println(value)); // 输出: Hello

String result1 = optional.orElse("Default"); // "Hello"
String result2 = optional.orElseGet(() -> "Default"); // "Hello"
String result3 = optional.orElseThrow(() -> new IllegalArgumentException("Value is missing"));

通过使用 Optional 类型,我们可以更加安全和优雅地处理可能为空的值,避免了空指针异常,并提供了一些便捷的方法来处理这类情况。

OptionalStream 结合使用:

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class OptionalStreamExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        // 使用 Optional 和 Stream 查找名字中包含指定字符的第一个名字
        Optional<String> result = names.stream()
                .filter(name -> name.contains("li"))
                .findFirst();

        // 如果值存在,则打印结果;否则打印默认消息
        result.ifPresentOrElse(
                value -> System.out.println("Found: " + value),
                () -> System.out.println("Not found"));

        // 将 Optional 转换为 Stream,用于进一步操作
        names.stream()
                .filter(name -> name.length() > 4)
                .flatMap(name -> Optional.ofNullable(name.toUpperCase()).stream())
                .forEach(System.out::println);
    }
}

在上述示例中,首先创建了一个字符串列表 names。然后,通过使用 stream() 方法将列表转换为流。

接下来,利用 filter() 方法筛选出名字中包含指定字符的第一个名字,并使用 findFirst() 方法获取结果。这里返回的结果是一个 Optional 对象。

通过使用 ifPresentOrElse() 方法,我们可以根据 Optional 对象的存在与否来执行相应的操作。如果值存在,则打印结果;否则打印默认的消息。

另外,我们还展示了如何将 Optional 对象转换为 Stream,以便进行进一步的操作。在这个示例中,我们使用 flatMap() 方法将名字转换为大写,并使用 forEach() 方法打印每个结果。

通过结合使用 OptionalStream,我们可以更加灵活地处理可能存在或不存在的值的情况,并进行链式的操作和转换。

默认方法(Default Method)

Java 8 引入了接口的默认方法(Default Method),也称为扩展方法(Extension Method)。默认方法是指在接口中提供具体的方法实现,而不再要求所有实现该接口的类都必须自己实现这些方法。

默认方法使得接口的修改更加灵活,可以向已有的接口添加新的方法而不会破坏现有的实现。这样可以方便地给接口添加新功能,同时保持对已有代码的向后兼容性。

以下是一个简单的示例,说明接口的默认方法:

public interface MyInterface {
    default void sayHello() {
        System.out.println("Hello from MyInterface!");
    }
}

public class MyClass implements MyInterface {
    // 可选:如果需要,可以覆盖默认方法
    @Override
    public void sayHello() {
        System.out.println("Hello from MyClass!");
    }
}

public class Main {
    public static void main(String[] args) {
        MyInterface myObject1 = new MyClass();
        myObject1.sayHello(); // 输出: Hello from MyClass!

        MyInterface myObject2 = new MyInterface() {
            // 匿名内部类中可以选择是否覆盖默认方法
        };
        myObject2.sayHello(); // 输出: Hello from MyInterface!
    }
}

在上述示例中,定义了一个接口 MyInterface,其中包含一个默认方法 sayHello()。接着,创建了一个实现了该接口的类 MyClass,该类可选择性地覆盖默认方法。

Main 类的 main 方法中,首先创建了一个 MyClass 对象,并调用其 sayHello() 方法。由于 MyClass 实现了该方法并提供了自己的实现,因此输出的是 "Hello from MyClass!"

接下来,通过匿名内部类创建了一个实现 MyInterface 的对象 myObject2。由于没有覆盖默认方法,因此仍然使用了默认方法的实现,输出的是 "Hello from MyInterface!"

通过默认方法,我们可以为接口提供具体的方法实现,从而更加灵活地扩展接口功能。这对于向已有的接口添加新方法或者给接口提供默认行为非常有用。