Skip to main content

函数式编程

huhxAbout 5 minjavaJava

千呼万唤始出来,Java 8终于引入了对函数式编程的支持。函数式编程是一种编程范式,它将计算视为数学函数的计算,使代码更加简洁、易于理解和维护。

函数式编程

java.util.function中,包含了大量的函数式的接口。其中就有我们熟悉的FunctionSupplier,这些基本上满足了开发日常的需要。这些接口中的每一个都是通用且抽象的,使得它们很容易适应几乎任何lambda表达式。

什么是函数接口

上面提到的接口有一个共性,那就是加上了@FunctionalInterface且只有一个abstract方法。比如

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

查看jdk的文档可知,一个接口是函数式的需要满足特定的条件:该接口有且只有一个抽象方法。

关于函数接口需要注意以下几点

  • 函数式接口是允许有default method的,因为default method是有实现的不是abstract的
  • 如果接口满足函数式接口的规则,那么就算没有@FunctionalInterface,它也被编译器当成函数式接口

类似于@Override注解,只要你的函数符合重载的要求,无论你是否标注了@Override,编译器都会识别这个重载函数

那么作为一个函数接口,它跟其他普通接口有什么优势或者区别呢?

  • 函数接口只有一个抽象方法,如果接口标注了@FunctionalInterface,且有多个抽象方法,那编译报错
  • 函数接口的实例可以通过Lambda表达式、方法引用和构造函数引用来创建

好了,到了函数接口创建的环节了,正好也可以看看Lambda表达式方法引用构造函数引用这些具体是什么。

函数接口的创建

Lambda表达式

Lambda表达式(lambda expressions)是Java 8最显著的函数式编程特性之一。它们允许您以更简洁的方式传递匿名函数,从而减少了冗余的代码。Lambda表达式的语法如下

(parameter_list) -> { body }

例如,对一个整数列表进行排序可以使用Lambda表达式如下:

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3);
numbers.sort((a, b) -> a.compareTo(b));

方法引用

方法引用(method references)是Lambda表达式的一种简写形式,它允许您直接引用现有方法或构造函数作为Lambda表达式。Java 8提供了四种类型的方法引用

  • 静态方法引用: 使用类名::静态方法名的方式引用静态方法
  • 实例方法引用: 使用实例对象::实例方法名的方式引用实例方法。
  • 类的实例方法引用: 使用类名::实例方法名的方式引用类的任意对象的实例方法
  • 构造函数引用: 使用类名::new的方式引用类的构造方法

例如

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println); // 实例方法引用

构造函数引用

构造函数引用(constructor references)是一种简化构造函数调用的方法,它允许您将构造函数本身作为Lambda表达式传递。构造函数引用主要用于以下情况:

  • 创建新对象:您可以使用构造函数引用来创建新的对象,而无需显式地编写构造函数的调用。
  • 与函数式接口结合:构造函数引用可以与函数式接口结合使用,例如SupplierFunction等。

下面是构造函数引用的示例:

Person
public class Person {
    private String name;
    
    public Person() {
        this.name = "Unknown";
    }
    
    public Person(String name) {
        this.name = name;
    }
    // Getters and setters
}

以上就是关于函数接口实例的创建了,既然java 8自带了些开箱即用的函数式接口,那我们就去瞅瞅呗

java函数式接口

java.util.function包提供的函数式接口有很多,确实给开发带来了很大的便利。下面我们来讲下比较通用的几个,它们分别是FunctionConsumerPredicateSupplier

Function:接受参数,有返回参数

package com.linux.huhx.function;

import java.util.function.Function;

public class FunctionDemo {

  private static int operateValue(int value, Function<Integer, Integer> function) {
    return function.apply(value);
  }

  private static int operateValue(int value, Function<Integer, Integer> srcFunc, Function<Integer, Integer> destFunc) {
    return srcFunc.andThen(destFunc).apply(value);
  }

  public static void main(String[] args) {
    int value = 10;
    
    int lambdaResult = operateValue(value, t -> t + 20);
    System.out.println(lambdaResult); // 30

    int andThenResult = operateValue(value, val -> val + 20, val -> val + 30);
    System.out.println(andThenResult); // 60
  }
}

Consumer:接受参数,没有返回

package com.linux.huhx.function;

import java.util.function.Consumer;

public class ConsumerDemo {

  private static void printValue(String value, Consumer<String> consumer) {
    consumer.accept(value);
  }

  public static void main(String[] args) {
    printValue("huhx", s -> System.out.println(s.toUpperCase())); // HUHX
  }
}

Predicate:接受参数,返回boolean

package com.linux.huhx.function;

import java.util.function.Predicate;

public class PredicateDemo {

  private static boolean predicateValue(int value, Predicate<Integer> predicate) {
    return predicate.test(value);
  }

  public static void main(String[] args) {
    System.out.println(predicateValue(17, x -> x >= 18)); // false
  }
}

Supplier:不接受参数,有返回值

package com.linux.huhx.function;

import java.util.function.Supplier;
import org.springframework.util.StringUtils;

public class SupplierDemo {

  private static String getOrDefault(String value, Supplier<String> supplier) {
    return StringUtils.isEmpty(value) ? supplier.get() : value;
  }

  public static void main(String[] args) {
    String name = "";
    System.out.println(getOrDefault(name, () -> "huhx")); // huhx
  }
}

FAQ

Lambda为什么不能抛出异常?

Lambda表达式在Java 中不能抛出已检查异常(checked exception)的原因与Java 8引入Lambda表达式时的设计哲学有关,主要是为了简化函数式编程的语法和提高代码的可读性。

总结

参考