十五、函数式编程

本文介绍Java函数式编程,涵盖Lambda表达式、函数式接口、Stream流和Optional类。Lambda表达式有多种特点和注意事项;函数式接口有特定定义和分类;Stream流可进行并行操作,有多种生成方式和操作类型;Optional类能避免空指针异常,有构建、获取、判断等方法。

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

十五、函数式编程

    1、Lambda表达式

        (1)、概述

            Lambda表达式由参数列表、箭头和Lambda主体组成。

            例:

            ①、(parameters) -> expression

            ②、 (parameters) -> { statements; }      

                a、参数 + 箭头符号 + 执行体

        (2)、特点

            ①、可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。

            ②、可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。

            ③、可选的大括号:如果主体包含了一个语句,就不需要使用大括号。

            ④、可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。

            ⑤、注意事项

                a、在Lambda表达式中this是指外围实例,而匿名类中的this是指匿名类实例。Java默认会把当前实例传入非静态方法,参数名为this,位置在第一个。

                b、如果想在Lambda表达式里面修改外部变量的值也是可以的,可以将变量定义为非局部变量,即为实例变量或者将变量定义为数组。

                c、Lambda表达式如果引用某局部变量,则直接将其视为final。

        (3)、示例

public static void main(String args[]){
    Test tester = new Test();
    // 类型声明
    MathOperation addition = (int a, int b) -> a + b;
    // 不用类型声明
    MathOperation subtraction = (a, b) -> a - b;
    // 大括号中的返回语句
    MathOperation multiplication = (int a, int b) -> { return a * b; };
    // 没有大括号及返回语句
    MathOperation division = (int a, int b) -> a / b;
    // 运算结果
    System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
    System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
    System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
    System.out.println("10 / 5 = " + tester.operate(10, 5, division));
    // 不用括号
    GreetingService greetService1 = message -> System.out.println("Hello " + message);
    // 用括号
    GreetingService greetService2 = (message) -> System.out.println("Hello " + message);
    greetService1.sayMessage("Runoob");
    greetService2.sayMessage("Google");
}

    2、函数式接口

        (1)、概述

            ①、《Java语言规范》中定义函数接口是一种只有一个抽象方法(除Object中的方法)的接口,因此代表一种单一函数契约。函数接口的抽象方法可以是从超级接口继承而来,但继承而来的方法应该是覆写等效的,这种情况,在逻辑上,代表一个方法。如果接口声明的抽象方法覆写Object类的公共方法,那这方法不算作接口的抽象方法。

            ②、创建函数接口实例,除了以声明和实例化类的形式这种常规过程之外,还可以使用方法引用表达式和lambda表达式创建函数接口的实例。

            ③、声明函数接口时,除了要声明一个抽象方法,还可以声明覆写Object类中的public方法以及default方法。

            ④、覆写等效释义

                C接口继承接口A,B,但继承而来的fun方法是覆写等效,因此C接口也属于函数接口

// 定义A接口
interface A {
    int fun(int x, int y);
}
// 定义B接口
interface B {
    int fun(int x, int y);
}
// C接口继承A,B,但继承而来的fun方法是覆写等效,因此C接口也属于函数接口
interface C extends A, B{
}

        (2)、@FunctionalInterface注解

            @FunctionalInterface注解标记的类型表明这是一个函数接口,该注解只能应用于接口类型,并且只允许有一个抽象方法,否则就会编译出错。主要是帮助程序员避免一些低级错误,例如在接口中再增加抽象方法。

        (3)、分类

            java.util.function它包含了很多类,用来支持Java的函数式编程。

            ①、消费型接口:Consumer<T>

                代表接受一个输入参数并且无返回的操作。此函数式接口在foreach循环中用到过。

            ②、供给型接口:Supplier<T>

                无参数,返回一个结果。

            ③、断言型接口:Predicate<T>

                接受一个输入参数,返回一个布尔值结果。

            ④、函数型接口:Function<T,R>

                接受一个输入参数,返回一个结果。

            ⑤、级联表达式和柯里化

                柯里化是把多个参数的函数转换为只有一个参数的函数,目的是函数标准化。

public class Test {
    public static void main(String args[]){
        // 函数型接口
        Function<String,String> function = (str)->{
            str += " function";
            return str;
        };
        Test.functionTest(function);
        // 消费型接口
        Consumer<String> consumer = (str)->{
            str += " consumer";
            System.out.println(str);
        };
        Test.consumerTest(consumer);
        // 供给型接口
        Supplier supplier = ()->{
            String str = "test supplier";
            return str;
        };
        Test.supplierTest(supplier);
        // 断言型接口
        Predicate<String> predicate = (str)->{
            boolean check = StringUtils.isBlank(str);
            return check;
        };
        Test.predicateTest(predicate);
        // 级联表达式实现
        Function<Integer, Function<Integer, Integer>> fun1 = a -> b -> a + b;
        System.out.println(fun1.apply(3).apply(5));
        Function<Integer, Function<Integer, Function<Integer, Integer>>> fun2 = a -> b -> c -> a + b + c;
        System.out.println(fun2.apply(2).apply(4).apply(3));
    }
    // 函数型接口测试打印:test function
    public static void functionTest(Function<String,String> function){
        System.out.println(function.apply("test"));
    }
    // 消费型接口测试打印:test consumer
    public static void consumerTest(Consumer<String> consumer){
        consumer.accept("test");
    }
    // 供给型接口测试打印:
    public static void supplierTest(Supplier supplier){
        String str = (String) supplier.get();
        System.out.println(str);
    }
    // 断言型接口测试打印:test predicate:false
    public static void predicateTest(Predicate<String> predicate){
        boolean check = predicate.test("predicate");
        System.out.println("test predicate:" + check);
    }
}

分类

接口

描述

Consumer

Consumer<T>

接收一个输入参数,无返回值

BiConsumer<T,U>

接收两个输入参数,无返回值

DoubleConsumer

接收一个double类型的输入参数,无返回值

IntConsumer

接收一个int类型的输入参数,无返回值 

LongConsumer

接收一个long类型的输入参数,无返回值

ObjDoubleConsumer<T>

接收一个object类型和一个double类型的输入参数,无返回值

ObjIntConsumer<T>

接收一个object类型和一个int类型的输入参数,无返回值

ObjLongConsumer<T>

接收一个object类型和一个long类型的输入参数,无返回值

Supplier

Supplier<T>

无参数,返回一个结果

BooleanSupplier

无参数,返回一个boolean类型结果

DoubleSupplier

无参数,返回一个double类型结果

IntSupplier

无参数,返回一个int类型结果

LongSupplier

无参数,返回一个结果long类型结果

Predicate

Predicate<T>

接收一个输入参数,返回一个boolean类型结果

BiPredicate<T,U>

接收两个输入参数,返回一个boolean类型结果

DoublePredicate

接收一个double类型的输入参数,返回一个boolean类型结果

IntPredicate

接收一个int类型的输入参数,返回一个boolean类型结果

LongPredicate

接收一个long类型的输入参数,返回一个boolean类型结果

Function

Function<T,R>

接收一个输入参数,返回一个结果

DoubleFunction<R>

接收一个double类型的输入参数,并且返回一个结果

IntFunction<R>

接收一个int类型的输入参数,返回一个结果

LongFunction<R>

接收一个long类型的输入参数,返回一个结果

ToDoubleFunction<T>

接收一个输入参数,返回一个double类型结果

ToIntFunction<T>

接收一个输入参数,返回一个int类型结果

ToLongFunction<T>

接收一个输入参数,返回一个long类型结果

DoubleToIntFunction

接收一个double类型的输入参数,返回一个int类型结果

DoubleToLongFunction

接收一个double类型的输入参数,返回一个long类型结果

IntToDoubleFunction

接收一个int类型的输入参数,返回一个double类型结果

IntToLongFunction

接收一个int类型的输入参数,返回一个long类型结果

LongToDoubleFunction

接收一个long类型的输入参数,返回一个double类型结果

LongToIntFunction

接收一个long类型的输入参数,返回一个int类型结果

ToDoubleBiFunction<T,U>

接收两个输入参数,返回一个double类型结果

BiFunction<T,U,R>

接收两个输入参数,并且返回一个结果

ToIntBiFunction<T,U>

接收两个输入参数,返回一个int类型结果

ToLongBiFunction<T,U>

接收两个输入参数,返回一个long类型结果

UnaryOperator

UnaryOperator<T>

接收一个同类型的输入参数,返回一个同类型结果

DoubleUnaryOperator

接收一个double类型的输入参数,返回一个double类型结果

IntUnaryOperator

接收一个int类型的输入参数,返回一个int类型结果

LongUnaryOperator

接收一个long类型的输入参数,返回一个long类型结果

BinaryOperator<T>

接收两个同类型的输入参数,返回一个同类型结果

DoubleBinaryOperator

接收两个double类型的输入参数,返回一个double类型结果

IntBinaryOperator

接收两个int类型的输入参数,返回一个int类型结果

LongBinaryOperator

接收两个long类型的输入参数,返回一个long类型结果

    3、Stream

        (1)、概述

            Stream流是从支持数据处理操作的源生成的元素序列,源可以是数组、文件、集合、函数。Stream不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。

            ①、Stream是java.util包下的与java.io包里的InputStream和OutputStream是完全不同的概念。

            ②、Stream就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

            ③、而和迭代器又不同的是,Stream可以并行化操作,迭代器只能命令式地、串行化操作。Stream的并行操作依赖于Java7中引入的Fork/Join框架(JSR166y)来拆分任务和加速处理过程。

                a、当使用串行方式去遍历时,每个item读完后再读下一个item。

                b、而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。

            ④、Stream 的另外一大特点是,数据源本身可以是无限的。

        (2)、流的构成

            当我们使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换 → 执行操作获取想要的结果。每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

        (3)、生成方式

            ①、利用Stream接口静态方法创建:Stream<Integer> stream1 = Stream.of(1,2,3,4,5);

            ②、从Collection和数组获得,集合接口有stream()和parallelStream()两个方法来生成流。

                a、集合创建串行流:Collection.stream()

                b、集合创建并行流:Collection.parallelStream()

                c、数组:Arrays.stream(T array) or Stream.of()

            ③、从BufferedReader获得:java.io.BufferedReader.lines()

            ④、静态工厂

                a、java.util.stream.IntStream.range()

                b、java.nio.file.Files.walk()

            ⑤、自定义:java.util.Spliterator

            ⑥、其他

                a、Random.ints()

                b、BitSet.stream()

                c、Pattern.splitAsStream(java.lang.CharSequence)

                d、JarFile.stream()

        (4)、操作类型

            ①、Intermediate(中间操作符)

                一个流可以后面跟随零个或多个intermediate操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。

                a、filter:通过设置的条件过滤出元素。

Integer[] sixNums = {1, 2, 3, 4, 5, 6};
Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
Arrays.stream(evens).forEach(System.out::println);
// 打印偶数evens结果:2 4 6

                b、map:map接收的参数类型为Function,这个函数会被应用到每个元素上,每个元素都按照这个规则转换成为另外一个元素。

List<String> strings = Arrays.asList("abc", "ssaad", "bc", "efg", "abcd", "jkl");
List<String> mapped = strings.stream().filter(str -> str.length() > 4).map(String::toUpperCase).collect(Collectors.toList());
// 打印mapped结果:SSAAD

                c、distinct:去重,筛选出不重复的单词。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> numList = numbers.stream().distinct().collect(Collectors.toList());
// 打印numList结果:3 2 7 5

                d、sorted:排序,它比数组排序的优势在于可以首先对Stream进行各类map、filter、limit、skip甚至distinct来减少元素数量后再排序,能明显缩短执行时间。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> numList = numbers.stream().distinct().sorted().collect(Collectors.toList());
// 打印numList结果:2 3 5 7

                e、peek:对元素进行遍历,并执行相应处理,可以对一个Stream进行多次Intermediate运算。forEach也是terminal操作,无法对一个Stream进行多次terminal运算。peek接收的参数类型为Consumer,Consumer接收单个输入参数T但不返回结果的操作。而map接收的参数类型为Function,Function接收一个参数并产生结果的函数,也就是有返回值的操作。

        List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
        List<String> peeked = strings.stream()
                .filter(str -> str.length() > 4)
                .peek(str -> System.out.println("666:" + str))
                .map(String::toUpperCase)
                .peek(str -> System.out.println("777:" + str))
                .collect(Collectors.toList());
// 打印peeked结果:
// 666:ssaad
// 777:SSAAD

                f、skip:返回一个扔掉了前n个元素的Stream。

List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
List<String> skiped = strings.stream().skip(3).collect(Collectors.toList());
// 打印skiped结果:jkl

                g、limit:返回Stream的前面n个元素。

List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
List<String> limited = strings.stream().limit(1).collect(Collectors.toList());
// 打印limited结果:abc

                h、flatMap:可以解决一对多映射关系,flatMap把Stream中的层级结构扁平化。map生成的是个1:1映射,每个输入元素都按照规则转换成为另外一个元素。

Stream<List<Integer>> inputStream = Stream.of(
        Arrays.asList(1,4),
        Arrays.asList(2, 3),
        Arrays.asList(0, 5)
);
Stream<Integer> outputStream = inputStream
        .flatMap((childList) -> childList.stream().filter( i -> i > 2))
        .sorted();
// 打印outputStream结果:3 4 5

            ②、Terminal(终端操作符)

                一个流只能有一个terminal操作,当这个操作执行后,流就被使用“光”了,无法再被操作。Terminal操作的执行,才会真正开始流的遍历,并且会生成一个结果。在对一个Stream进行多次转换操作Intermediate操作,每次都对Stream的每个元素进行转换,转换操作都是lazy的,多个转换操作只会在Terminal操作的时候融合起来,一次循环完成。

                a、collect:收集器,将流转换为其他形式,例如将流转换成集合和聚合元素。

Map<String, Student> studentMap = studentList.stream().collect(Collectors.toMap(Student::getId, Function.identity(), (k1, k2) -> k1));
// (k1, k2) -> k1 如果k1和k2的key值相同,选择k1作为那个key对应的value值。

                b、forEach:forEach不能修改自己包含的本地变量值,也不能用break/return之类的关键字提前结束循环。

List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
strings.stream().forEach(System.out::println);
// 循环打印

                c、count:返回流中元素总数。

List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
long n = strings.stream().count();
// 打印n结果:4

                d、sum:对指定元素求和。

int sum = studentList.stream().mapToInt(Student::getAge).sum();

                e、max:返回最大值。  

int max = userList.stream().max(Comparator.comparingInt(User::getId)).get().getId();

                f、min:返回最小值。

int min = userList.stream().min(Comparator.comparingInt(User::getId)).get().getId();

                g、anyMatch:Stream中只要有一个元素符合传入的条件,返回true。

List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
boolean match = strings.stream().anyMatch(s -> s == "abc”);
// 打印match结果:true

                h、allMatch:Stream中全部元素符合传入的条件,返回true。

List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
boolean match = strings.stream().anyMatch(s -> s == "abc”);
// 打印match结果:false

                i、noneMatch:Stream中没有一个元素符合传入的条件,返回true。

List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
boolean match = strings.stream().noneMatch(s -> s == "abc”);
// 打印match结果:false

                j、findFirst:返回Stream的第一个元素或者空,返回值类型Optional。

List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
Optional<String> str = strings.stream().findFirst();
// 打印str结果:Optional[abc]

                k、findAny:将返回当前流中的任意元素,返回值类型Optional。

List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
Optional<String> str = strings.stream().findAny();
// 打印str结果:Optional[jkl]

            ③、Short-circuiting 操作

                对于一个intermediate操作,如果它接受的是一个无限大(infinite/unbounded)的Stream,但返回一个有限的新Stream。对于一个terminal操作,如果它接受的是一个无限大的Stream,但能在有限的时间计算出结果。当操作一个无限大的Stream,而又希望在有限时间内完成操作,则在管道内拥有一个short-circuiting操作是必要非充分条件。

                a、anyMatch

                b、allMatch

                c、noneMatch

                d、findFirst

                e、findAny

                f、limit

    4、Optional

        (1)、概述

            Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,其值可能是null或者不是null。在Java 8之前一般某个函数表示一个值不存在可能返回了null,而在Java 8以后,不推荐返回null而是返回Optional。Optional可以更好的表达这个概念,并且可以避免空指针异常。

        (2)、方法

            ①、构建类方法

                a、Optional.of(T t)

                    创建一个 Optional 实例,t必须非空。

                b、Optional.empty()

                    创建一个空的 Optional 实例。

                c、Optional.ofNullable(T t)

                    创建一个 Optional 实例,t可以为null,为null时返回Optional.empty()。

            ②、获取类方法

                a、T get()

                    如果调用对象有值,返回该值,否则抛NPE异常。

                b、T orElse(T other)

                    如果有值则将其返回,否则返回指定的other对象。

                c、T orElseGet(Supplier<? extends T> other)

                    如果有值则将其返回,否则返回由Supplier接口实现提供的对象。

                d、T orElseThrow(Supplier<? extends X> exceptionSupplier)

                    如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。

            ③、判断类方法

                a、boolean isPresent()

                    判断值对象是否存在,存在返回true。

                b、void ifPresent(Consumer<? super T> consumer)

                    如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。

            ④、过滤类方法

                a、Optional<T> filter(Predicate<? super <T> predicate)

                    如果值存在,并且这个值匹配给定的predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。

            ⑤、映射类方法

                a、<U>Optional<U> map(Function<? super T,? extends U> mapper)

                    如果有值,则对其执行调用映射函数得到返回值。如果返回值不为null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。

                b、<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)

                    如果值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象。

        (3)、应用

            ①、场景一

public static void main(String[] args) {
    // 查询用户信息,不为空把pin赋值为priceInfo对象
    // 未使用Optional的if判断
    UserInfo userInfo = userService.getUserInfoById(user.getId());
    if (null != userInfo) {
        priceInfo.setPin(userInfo.getPin());
    }
    // 使用Optional
    Optional.ofNullable(userInfo).ifPresent(p -> priceInfo.setPin(p.getPin()));
}

            ②、场景二

public static void main(String[] args) {
    // 商品信息查询为空或者商品状态为空,抛出下发简易模板的异常
    // 未使用Optional
    Product product = productService.getProductInfoById(skuId);
    if (product == null || isEmpty(product.getWareStatus())) {
        throw new SimpleTemplateException();
    }
    // 使用Optional
    Optional.ofNullable(product).filter(s -> !isEmpty(product.getWareStatus())).orElseThrow(() -> new SimpleTemplateException());
}

public static boolean isEmpty(CharSequence str) {
    return str == null || str.length() == 0;
}

            ③、场景三

public static void main(String[] args) {
    // 判读传进来的多层对象是否为空
    // A对象设置B对象,B对象设置C对象
    // 未使用Optional
    if (a != null) {
        B b = a.getB();
        if (b != null) {
            C c = b.getC();
            if (c != null) {
                return c.getName();
            }
        }
    }
    throw new IllegalArgumentException("The value of param comp isn't available.");

    // 使用Optional
    Optional.ofNullable(a)
            .map(A::getB)  // 相当于p -> p.getB(),下同
            .map(B::getC)
            .map(C::getName)
            .orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
}

            ④、场景四

public static void main(String[] args){
    // 类型之间的转换,并且当没有值的时候返回一个默认值
    int timeout = Optional.ofNullable(redisProperties.getTimeout())
            .map(x -> Long.valueOf(x.toMillis()).intValue())
            .orElse(10000);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值