函数式接口 && lambda && Stream

本文详细介绍了Java中的函数式接口,包括Lambda表达式、FunctionalInterface注解以及常见的函数式接口如Function、Consumer、Predicate和Supplier。此外,还讲解了Stream API的基本用法和并行流的概念,以及如何使用::符号简化lambda表达式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数式接口

内置常用的函数式接口位于java.util.function.*包中。
常用函数式接口Function、consumer、predicate、supplier等。

Lambda表达式 && FunctionalInterface

接口由@FunctionalInterface注解标注,该注解仅仅为定义约束,不注解也不会影响功能(从code规范上应该要注解),只要满足函数式接口定义都属于函数式接口(接口有且只能有个一个抽象方法)。

函数式接口为普通接口增加了更严格的约定;
lambda表达式是匿名类的简化书写形式,但lambda只能用于函数式接口;
即lambda表达式只能基于函数式接口而存在,因此是类接口实例化的简化书写形式,更简洁、方便和灵活。

@FunctionalInterface
public interface PrintFunction<T> {
    void print(T t);
}

上面定义了一个函数式接口,有且只有一个抽象方法。接口使用泛型T,便于实现不同类型的参数打印。

        PrintFunction<String> pf = new PrintFunction<String>() {
            @Override
            public void print(String s) {
                System.out.println(s);
            }
        };
        pf.print("a");  //a

上面通过匿名类方式直接实现了接口并实例化,并使用方法打印“a”。

        PrintFunction<String> pf = (s) -> {
          System.out.println(s);
        };
        pf.print("b");

上面通过lambda方式实现了接口并实例化,并调用方法print打印“b”,(s) 为入参,大括号内是方法的实现。大括号结束有分号。由于只有一个语句,可以省略{},只有一个参数,可以省略()。即:s -> System.out.println(s) 。

对比看lambda也就是书写形式上简化了。也因为lambda的这种书写形式,所以要求接口有且仅有一个抽象方法,便于直接用 “->” 实现。

这个lambda表达式是基于PrintFunction接口的定义来写的,编译器会按照接口定义检查表达式书写是否正确。

        PrintFunction<String> pf = System.out::println;
        pf.print("b");  //b

上面代码是对lambda表达式更简化的书写,System.out是指定的类,println是指定的方法,该方法只有一个入参,正好接口方法只会传入一个入参,因此可以正好匹配。所以只需要写明具体类的具体方法即可,编译器会自动识别及解析出来。

lambda表达式一定是基于某个接口定义而书写的,其他书写格式可参考lambda语法。

Function

接收T对象,返回R对象。
先看源码(去掉了注释)

@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;
    }
}

Function接口包含两个default方法及一个static方法。
default方法compose:先执行before入参的函数,再执行自身函数;
default方法andThen:先执行自身函数,再执行after入参的函数。

        Function<Integer, Integer> f = s -> s++;
        Function<Integer, Integer> g = s -> s * 2;

        /**
         * f.apply( g.apply(1) )
         */
        System.out.println(f.compose(g).apply(1)); // 2

使用该函数式接口时,先定义函数体,再调用该函数体方法执行(与匿名类只是书写形式不一样),lambda简洁的形式,让程序逻辑更灵活。

如上代码,f.compose(g),将g函数作为入参,与当前函数的方法一起构成了复杂的方法,该复杂的方法:入参v,方法体apply(before.apply(v))。即为:

        Function<Integer,Integer> ff = (v) -> {
            Integer s = v * 2; // g函数体
            return s++; // f函数体
        };

如上代码用匿名类实现如下,代码量明显较多,复杂的逻辑中不太灵活,如有3、5个以上的函数连贯使用,代码量就更多了。

        Function<Integer ,Integer> fxx1 = new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer s) {
                return s * 2;
            }
        };
        Function<Integer ,Integer> fxx2 = new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer s) {
                return s++;
            }
        };

        System.out.println( fxx1.compose(fxx2).apply(1));  // 2

default的andThen方法:

        /**
         *  g.apply( f.apply(1) )
         */
        System.out.println(f.andThen(g).apply(1)); // 2

static的identify方法:
该方法返回一个Function接口的实例对象(Function f2 = t -> t;),该lambda表达式是一个Function接口的对象。

直接将入参返回,用处如下:
1、使用该静态方法,可以直接创建一个function对象;
2、函数式编程的很多方法定义都是需要传递Function接口的参数,使用该static方法能快速构建对象。
如下代码toMap方法,需要Function类型的入参,t -> t 可直接使用静态方法取代。

        Stream<String> stream = Stream.of("a", "bbb", "cccc");
        Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length));
//        map = stream.collect(Collectors.toMap(t -> t, String::length));

Consumer

消费型接口,即传入的参数,在函数体方法中消费,不会有返回值。
用于一些将信息传递到方法中,方法直接处理掉,无需返回的场景。

源码如下:

@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); };
    }
}

default方法中andThen: 先执行自身的函数,再执行入参中的函数,可以用于链式处理。
default方法中andThen: 先执行自身的函数,再执行入参中的函数,可以用于链式处理。

        Consumer<Integer> ccc = System.out::print;
        ccc.andThen(t -> System.out.print("---c1:" + t))
                .andThen(t -> System.out.print("---c2:" + t))
                .andThen(t -> System.out.print("---c3:" + t))
                .accept(1);
        // 1---c1:1---c2:1---c3:12        

Predicate

测试型函数,用于判断或计算等逻辑,最后返回ture 或 false 的场景。

源码如下:


@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    // 两个逻辑型方法的 && 
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    // 当前逻辑方法的 !
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    // 两个逻辑型方法的 ||
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    // 静态方法,用于判断对象是否相等
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
        Predicate<String> pre = Predicate.isEqual("abc");
        Boolean bo = pre.test("abc");  // true

Supplier

供应型函数,无需入参,每次调用即可返回信息T。

源码如下:

@FunctionalInterface
public interface Supplier<T> {

    /**
     * @return a result
     */
    T get();
}

如下函数体S2,每次调用产生3位随机数。

        Supplier<Integer> s2 = () -> {
            return (int) (Math.random() * 1000);
        };
        System.out.println(s2.get());

BiFunction

接收两个入参且返回结果的函数,与Function对比,也就是多了一个入参,很多场景我们需要两个入参的情况。"Bi"是一个英语前缀,表示两个的意思(比如biennial,bi两,enn年,ial形容词后缀,两年生的)。


@FunctionalInterface
public interface BiFunction<T, U, R> {

    R apply(T t, U u);

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

BiConsumer

与Consumer功能一样,区别是可以传递两个入参


@FunctionalInterface
public interface BiConsumer<T, U> {

    void accept(T t, U u);

    default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
        Objects.requireNonNull(after);

        return (l, r) -> {
            accept(l, r);
            after.accept(l, r);
        };
    }
}

BiPredicate

与Predicate功能一样,区别是可以传递两个入参


@FunctionalInterface
public interface BiPredicate<T, U> {

    boolean test(T t, U u);

    default BiPredicate<T, U> and(BiPredicate<? super T, ? super U> other) {
        Objects.requireNonNull(other);
        return (T t, U u) -> test(t, u) && other.test(t, u);
    }

    default BiPredicate<T, U> negate() {
        return (T t, U u) -> !test(t, u);
    }

    default BiPredicate<T, U> or(BiPredicate<? super T, ? super U> other) {
        Objects.requireNonNull(other);
        return (T t, U u) -> test(t, u) || other.test(t, u);
    }
}

UnaryOperator

入参出参类型相同,Unary:一元的(一元结构)。

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

BinaryOperator

二元操作函数,minBy静态方法接受Comparator接口的函数作为入参,最终函数为:传递两个参数进行二元比较,返回最小的一个参数,比较的方法体写在Comparator的函数体中。

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {

    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }

    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}

int类型

IntBinaryOperator、
IntConsumer、
IntFunction、
IntPredicate、
IntSupplier、
IntUnaryOperator

@FunctionalInterface
public interface IntFunction<R> {

    R apply(int value);
}

double类型

DoubleBinaryOperator、
DoubleConsumer、
DoubleFunction、
DoublePredicate、
DoubleSupplier、
DoubleUnaryOperator

@FunctionalInterface
public interface DoubleFunction<R> {

    R apply(double value);
}

long类型

LongBinaryOperator、
LongConsumer、
LongFunction、
LongPredicate、
LongSupplier、
LongUnaryOperator

@FunctionalInterface
public interface LongSupplier {

    long getAsLong();
}

泛型对象与double、int、long混用类型

ObjDoubleConsumer、
ObjIntConsumer、
ObjLongConsumer

@FunctionalInterface
public interface ObjIntConsumer<T> {

    void accept(T t, int value);
}

boolean类型参数

BooleanSupplier

@FunctionalInterface
public interface BooleanSupplier {

    boolean getAsBoolean();
}

入参与出参类型转换

DoubleToIntFunction、
DoubleToLongFunction、
IntToDoubleFunction、
IntToLongFunction、
LongToDoubleFunction、
LongToIntFunction

@FunctionalInterface
public interface LongToIntFunction {

    int applyAsInt(long value);
}

出参类型限定的函数

ToDoubleBiFunction、
ToDoubleFunction、
ToIntBiFunction、
ToIntFunction、
ToLongBiFunction、
ToLongFunction

@FunctionalInterface
public interface ToIntFunction<T> {

    int applyAsInt(T value);
}

Stream

Stream是java 8 新特性,流式处理,将元素转换为流,流经过管道流出,在管道各节点可以进行筛选、排序等处理,在管道末端再进行聚合转换处理。
元素转换为流对象后,后续处理都可以返回流对象本身,多个操作即可串行执行,犹如数据在管道中流过。

        //1、使用Stream.of方法构造流,再对流转换为List对象
        List<String> strList = Stream.of("1", "5", "9", "2").collect(Collectors.toList());
        //2、将list中数据过滤并转换为Integer类型
        List<Integer> intList = strList.stream().map(Integer::parseInt).filter(i -> i > 3).collect(Collectors.toList());
        //3、list中元素迭代打印
        intList.stream().forEach(System.out::println);  // 5  9
        //4、list中元素求和计算
        int sum = intList.stream().mapToInt(Integer::intValue).sum();
        System.out.println(sum); // 14
  1. Stream中静态of方法构造流,返回Stream对象(该对象为实现了Stream接口的类的对象ReferencePipeline.Head,可通过源码找到该类)
    public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }

Steam的collect方法需要传递一个Collector类型参数,将流转换到Collector类型指定的ArrayList对象并返回List对象。

<R, A> R collect(Collector<? super T, A, R> collector);
//语句的前面<R,A>为声明该方法用到了两个泛型类型,只是声明,泛型方法的语法约定先声明后使用。

Collectors.toList(),该方法返回一个CollectorImpl对象(该类父类为Collector接口)。

    public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                   (left, right) -> { left.addAll(right); return left; },
                                   CH_ID);
    }
  1. strList.stream().map(Integer::parseInt)中的map要求传递的是Function类型参数,因此可以使用lambda构造;filter方法要求Predicate类型参数。
        <R> Stream<R> map(Function<? super T, ? extends R> mapper);
        
        Stream<T> filter(Predicate<? super T> predicate);
  1. Stream中forEach迭代方法,可以迭代每个元素,如何处理,需要传递Consumer消费型函数处理。
    void forEach(Consumer<? super T> action);
  1. 将流中元素转换为intStream,便于使用IntStream中相关运算的方法 count()、average()、 max()、min()、sum()等。
    转换为int用到了ToIntFunction函数接口。
    IntStream mapToInt(ToIntFunction<? super T> mapper);
并行和串行流

并行流即流中的元素可以并行处理,没有顺序。串行流则会按照顺序处理。

        List<String> list = Stream.of("1","2","3").collect(Collectors.toList());
        list.stream().forEach(System.out::println); // 1 2 3
        list.parallelStream().forEach(System.out::println); // 2 3 1

:: 符号

前面已经多次出现了::号,用于简化lambda表达式,

        Consumer<String> f0 = t -> System.out.println(t);
        
        Consumer<String> f1 = System.out::println;
  1. ::符号前面为具体的对象或类,
  2. ::符号后面为该对象的方法或该类的静态方法;
  3. ::符号后面的方法的入参和出参必须与函数接口要求的出入参一致

满足上面3点即可使用::符号简化lambda表达式。

public class FunctionTest {
    public static void main(String[] args) {
        Consumer<String> f1 = System.out::println;

        BiFunction<String, String, String> f2 = FunctionTest::bb;

        Function<String, String> f3 = new FunctionTest()::aa;
    }

    public static String bb(String s, String v) {
        System.out.println(s);
        return s;
    }

    public String aa(String s) {
        System.out.println(s);
        return s;
    }
}
  • 如上f1函数,println没有返回值,正好与Consumer也没有返回值匹配。因此不能使用Function接口。
  • f2中直接用了FunctionTest的类名,后面的方法必须是静态方法。
  • 如果不是静态方法,则必须传入实例化对象,即f3函数格式。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值