死磕Lambda表达式(四):常用的函数式接口

2022-04-22 12:12:31 浏览数 (1)

失去人性,失去很多;失去兽性,失去一切。——《三体》

在Java8支持Lambda表达式以后,为了满足Lambda表达式的一些典型使用场景,JDK为我们提供了大量常用的函数式接口。它们主要在 java.util.function 包中,下面简单介绍几个其中的接口及其使用示例。

Supplier接口

Supplier接口是对象实例的提供者,定义了一个名叫get的抽象方法,它没有任何入参,并返回一个泛型T对象,具体源码如下:

代码语言:javascript复制
package java.util.function;

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

源码比较简单,我们来个例子。这是一个之前提过的表示口罩的类:

代码语言:javascript复制
package one.more.study;

/**
 * 口罩
 */
public class Mask {
    public Mask(String brand, String type) {
        this.brand = brand;
        this.type = type;
    }
    /**
     * 品牌
     */
    private String brand;
    /**
     * 类型
     */
    private String type;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

下面我们使用Lambda表达式声明一个Supplier的实例:

代码语言:javascript复制
Supplier<Mask> supplier = () -> new Mask("3M", "N95");

用它来创建品牌为3M、类型为N95的Mask实例:

代码语言:javascript复制
Mask mask = supplier.get();
System.out.println("Brand: "   mask.getBrand()   ", Type: "   mask.getType());

运行结果如下:

代码语言:javascript复制
Brand: 3M, Type: N95

特别需要注意的是,本例中每一次调用get方法都会创建新的对象。

Consumer接口

Consumer接口是一个类似消费者的接口,定义了一个名叫accept的抽象方法,它的入参是一个泛型T对象,没有任何返回(void),主要源码如下:

代码语言:javascript复制
package java.util.function;

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

结合上面的Supplier接口,我们来个例子:

代码语言:javascript复制
Supplier<Mask> supplier = () -> new Mask("3M", "N95");
Consumer<Mask> consumer = (Mask mask) -> {
    System.out.println("Brand: "   mask.getBrand()   ", Type: "   mask.getType());
};
consumer.accept(supplier.get());

首先使用Lambda表达式声明一个Supplier的实例,它是用来创建品牌为3M、类型为N95的Mask实例;再使用Lambda表达式声明一个Consumer的实例,它是用于打印出Mask实例的相关信息;最后Consumer消费了Supplier生产的Mask。运行结果如下:

代码语言:javascript复制
Brand: 3M, Type: N95

Predicate接口

Predicate接口是判断是与否的接口,定义了一个名叫test的抽象方法,它的入参是一个泛型T对象,并返回一个boolean类型,主要源码如下:

代码语言:javascript复制
package java.util.function;

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

结合上面的Supplier接口,我们来个例子:

代码语言:javascript复制
Supplier<Mask> supplier = () -> new Mask("3M", "N95");
Predicate<Mask> n95 = (Mask mask) -> "N95".equals(mask.getType());
Predicate<Mask> kn95 = (Mask mask) -> "KN95".equals(mask.getType());
System.out.println("是否为N95口罩:"   n95.test(supplier.get()));
System.out.println("是否为KN95口罩:"   kn95.test(supplier.get()));

首先使用Lambda表达式声明一个Supplier的实例,它是用来创建品牌为3M、类型为N95的Mask实例;再使用Lambda表达式声明一个Predicate的实例n95,它是用于判断是否为N95口罩;再使用Lambda表达式声明一个Predicate的实例kn95,它是用于判断是否为KN95口罩;最后分别用两个Predicate判断Supplier生产的Mask。运行结果如下:

代码语言:javascript复制
是否为N95口罩:true
是否为KN95口罩:false

Function接口

Function接口是对实例进行处理转换的接口,定义了一个名叫apply的抽象方法,它的入参是一个泛型T对象,并返回一个泛型T对象,主要源码如下:

代码语言:javascript复制
package java.util.function;

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

结合上面的Supplier接口,我们来个例子:

代码语言:javascript复制
Supplier<Mask> supplier = () -> new Mask("3M", "N95");
Function<Mask, String> brand = (Mask mask) -> mask.getBrand();
Function<Mask, String> type = (Mask mask) -> mask.getType();
System.out.println("口罩品牌:"   brand.apply(supplier.get()));
System.out.println("口罩类型:"   type.apply(supplier.get()));

首先使用Lambda表达式声明一个Supplier的实例,它是用来创建品牌为3M、类型为N95的Mask实例;再使用Lambda表达式声明一个Function的实例brand,它是用于获取口罩的品牌;再使用Lambda表达式声明一个Function的实例type,它是用于获取口罩的类型;最后分别用两个Function分析Supplier生产的Mask。运行结果如下:

代码语言:javascript复制
口罩品牌:3M
口罩类型:N95

BiFunction接口

Function接口的入参只有一个泛型对象,JDK还为我们提供了两个泛型对象入参的接口:BiFunction接口,主要源码如下:

代码语言:javascript复制
package java.util.function;

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

我们可以用BiFunction接口传入两个String直接创建Mask实例:

代码语言:javascript复制
BiFunction<String,String,Mask> biFunction = (String brand, String type) -> new Mask(brand, type);
Mask mask = biFunction.apply("3M", "N95");
System.out.println("Brand: "   mask.getBrand()   ", Type: "   mask.getType());

运行结果如下:

代码语言:javascript复制
Brand: 3M, Type: N95

基本数据类型

以上介绍的几个常用的函数式接口入参和返回,都是泛型对象的,也就是必须为引用类型。当我们传入或获取的是基本数据类型时,将会发生自动装箱和自动拆箱,带来不必要的性能损耗,比如:

代码语言:javascript复制
Supplier<Long> supplier = () -> System.currentTimeMillis();
long timeMillis = supplier.get();

在上面例子里,发生了一次自动装箱(long被装箱为Long)和一次拆箱(Long被拆箱为long),如何避免这种不必要的性能损耗呢?JDK为我们提供相应的函数式接口,如LongSupplier接口,定义了一个名叫getAsLong的抽象方法,签名是() -> long。上面的例子可以优化为:

代码语言:javascript复制
LongSupplier supplier = () -> System.currentTimeMillis();
long timeMillis = supplier.getAsLong();

类似这样的接口还有很多,我为大家整理了一下:

Supplier相关的接口

接口名称

方法名称

方法签名

Supplier

get

() -> T

BooleanSupplier

getAsBoolean

() -> boolean

DoubleSupplier

getAsDouble

() -> double

IntSupplier

getAsInt

() -> int

LongSupplier

getAsLong

() -> long

Consumer相关的接口

接口名称

方法名称

方法签名

Consumer

accept

(T) -> void

DoubleConsumer

accept

(double) -> void

IntConsumer

accept

(int) -> void

LongConsumer

accept

(long) -> void

ObjDoubleConsumer

accept

(T, double) -> void

ObjIntConsumer

accept

(T, int) -> void

ObjLongConsumer

accept

(T, long) -> void

Predicate相关的接口

接口名称

方法名称

方法签名

Predicate

test

(T) -> boolean

BiPredicate

test

(T, U) -> boolean

DoublePredicate

test

(double) -> boolean

IntPredicate

test

(int) -> boolean

LongPredicate

test

(long) -> boolean

Function相关的接口

接口名称

方法名称

方法签名

Function

apply

(T) -> R

BiFunction

apply

(T, U) -> R

DoubleFunction

apply

(double) -> R

DoubleToIntFunction

applyAsInt

(double) -> int

DoubleToLongFunction

applyAsLong

(double) -> long

IntFunction

apply

(int) -> R

IntToDoubleFunction

applyAsDouble

(int) -> double

IntToLongFunction

applyAsLong

(int) -> long

LongFunction

apply

(long) -> R

LongToDoubleFunction

applyAsDouble

(long) -> double

LongToIntFunction

applyAsInt

(long) -> int

ToDoubleFunction

applyAsDouble

(T) -> double

ToDoubleBiFunction

applyAsDouble

(T, U) -> double

ToIntFunction

applyAsInt

(T) -> int

ToIntBiFunction

applyAsInt

(T, U) -> int

ToLongFunction

applyAsLong

(T) -> long

ToLongBiFunction

applyAsLong

(T, U) -> long

《死磕Lambda表达式》回顾

  • 死磕Lambda表达式(一):初识Lambda
  • 死磕Lambda表达式(二):Lambda的使用
  • 死磕Lambda表达式(三):更简洁的Lambda

0 人点赞