Java8 函数式编程

Java 函数式编程

什么是函数式编程

函数式编程是一种是一种编程范式,它将计算视为函数的运算,并避免变化状态和可变数据。它是一种声明式编程范式,也就是说,编程是用表达式或声明而不是语句来完成的,即强调做什么,而不是以什么形式去做。

Lamda 表达式:

(a,b)->a+b

比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

1. Lambda 表达式

在了解 Lambda 表达式之前,先看这样一个需求:

通过 Runnable 启动一个线程,并打印:线程已启动

对于这样一个需求,有以下三种方式:

方式一

实现 Runnable 接口,如下:

class MyRunnable implements Runnable{
   
    @Override
    public void run() {
   
        log.info("线程已启动");
    }
}

@Test
void test_runnable_01() {
   
    MyRunnable myRunnable = new MyRunnable();
    Thread thread = new Thread(myRunnable);
    thread.start();
}

方式二

匿名内部类,如下:

@Test
void test_runnable_02() {
   
    Thread thread = new Thread(new Runnable() {
   
        @Override
        public void run() {
   
            log.info("线程已启动");
        }
    });
    thread.start();
}

方式三

Lambda 表达式,如下:

@Test
void test_runnable_03() {
   
    Thread thread = new Thread(() -> {
   
        log.info("线程已启动");
    });
    thread.start();
}

可以看到使用 Lambda 表达式使得代码看起来更简单。

1.1 标准格式

匿名内部类中重写 run() 方法的代码分析:

  • 方法形式参数为空,说明调用方法时不需要传递参数
  • 方法返回值类型为 void,说明方法执行没有结果返回
  • 方法体中的内容,是我们具体要做的事情

Lambda 表达式的代码分析:

  • ():里面没有内容,可以看成是方法形式参数为空
  • ->:用箭头指向后面要做的事情
  • {}:包含一段代码,我们称之为代码块,可以看成是方法体中的内容

由此可得知,组成 Lambda 表达式的三要素:形式参数、箭头、代码块。

Lambda 表达式的格式

  • 格式:(形式参数)-> { 代码块 }
  • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
  • ->:由英文中画线和大于符号组成,固定写法,代表指向动作
  • 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容

1.2 使用前提

Lambda表达式的使用前提:

  • 有一个接口
  • 接口中有且仅有一个抽象方法

在 Runnable 的测试中,其 run()是一个无参数的方法,接下来我们按照上述前提来创建一个参数和多个参数的接口来实现 Lambda 表达式。

1.2.1 一个参数

定义一个名为 LambdaFunction 的接口,在定义其抽象方法为 func(String str),如下:

public interface LambdaFunction {
   

    public abstract void func(String str);

}

测试:

@Test
void test_Lambda_function() {
   
    Lambda_function((String str) -> {
   
        log.info(str);
    }, "一个形参");
}

void Lambda_function(LambdaFunction function, String str) {
   
    function.func(str);
}
1.2.2 多个参数

同上,定义包含 2 个参数的抽象方法。

public interface LambdaFunction2 {
   

    public abstract void func2(String str, int i);

}

测试:

@Test
void test_Lambda_function2() {
   
    Lambda_function2((String str, int i) -> {
   
        log.info(str + ": " + i);
    }, "多个参数", 2);
}

void Lambda_function2(LambdaFunction2 function, String str, int i) {
   
    function.func(str, i);
}
1.2.3 有返回值

除了多参数,还要存在返回值的情况,如下定义:

public interface LambdaFunction3 {
   

    public abstract int add(int i1, int i2);

}

测试:

@Test
void test_Lambda_function3() {
   
    int i = Lambda_function3((int i1, int i2) -> {
   
        return i1 + i2;
    }, 1, 2);
    log.info(i + "");
}

int Lambda_function3(LambdaFunction3 function, int i1, int i2) {
   
    return function.add(i1, i2);
}

1.3 省略简化

Lambda 表达式可以省略简化,它的规则如下:

  • 参数类型可以省略,如果有多个参数的情况下,同时省略
  • 如果参数有且仅有一个,那么小括号可以省略
  • 如果代码块的语句只有一条,可以省略大括号和分号,包括返回值 return

因此我们可以把上面的简化,如下:

@Test
void test_Lambda_function_simplify() {
   
    Lambda_function(str -> log.info(str), "一个参数");
}

@Test
void test_Lambda_function2_simplify() {
   
    Lambda_function2((str, i) -> log.info(str + ": " + i), "多个参数", 2);
}

@Test
void test_Lambda_function3_simplify() {
   
    int i = Lambda_function3((i1, i2) -> i1 + i2, 1, 2);
    log.info(i + "");
}

1.4 函数式接口

函数式接口:有且仅有一个抽象方法的接口

Java 中的函数式编程体现就是 Lambda 表达式,所以函数式接口就是可以使用于 Lambda 使用的接口只有确保接口中有且仅有一个抽象方法,Java 中的 Lambda 才能顺利地进行推导。

如何检测一个接口是不是函数式接口呢?

@FunctionalInterface 注解,放在接口定义的上方,如果接口是函数接口,编译通过;如果不是,编译失败。

注意:

我们自己定义函数式接口的时候,@FunctionalInterface 是可选的,就算我们不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。但是,建议加上注解。

Java 8 在 java.util.function 包下预定了大量的函数式接口供我们使用,常用如下:

  • Supplier 接口
  • Consumer 接口
  • Predicate 接口
  • Function 接口
1.4.1 Supplier
方法 说明
T get() 生产数据的接口

Supplier 接口被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的 get 方法就会生产什么类型的数据供我们使用。

@FunctionalInterface
public interface Supplier<T> {
   

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

该方法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据,如下:

@Test
void supplier_test() {
   
    String string = getString(() -> "String");
    log.info(string);
    Integer integer = getInteger(() -> 1);
    log.info(integer + "");
}

String getString(Supplier<String> supplier) {
   
    return supplier.get();
}

Integer getInteger(Supplier<Integer> supplier) {
   
    return supplier.get();
}
1.4.2 Consumer
方法 说明
void accept(T t) 消费一个指定泛型的数据
default Consumer andThen(Consumer<? super T> after) 消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合

Consumer 接口被称为消费型接口,与 Supplier 接口相反,它消费的数据类型由泛型指定。

① accept

@FunctionalInterface
public interface Consumer<T> {
   

    /**
     * 消费一个指定泛型的数据
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * 默认方法
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
   
        Objects.requireNonNull(after);
        return (T t) -> {
    accept(t); after.accept(t); };
    }
}

对于 accept() 方法,它是消费,如下:

@Test
void consumer_test() {
   
    consumer_accept(100, money -> log.info("本次消费: " + money));
}

/**
 * 定义一个方法
 *
 * @param money    传递一个int类型的 money
 * @param consumer
 */
void consumer_accept(Integer money, Consumer<Integer> consumer) {
   
    consumer.accept(money);
}

② andThen

如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。

要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是一步接一步操作。例如两个步骤组 合的情况:

@Test
void consumer_andThen_test() {
   
    consumer_andThen("abcdefg",
            // 先转为大写
            str -> log.info(str.toUpperCase()),
            // 在转为小写
            str -> log.info(str.toLowerCase()));
}

void consumer_andThen(String str, Consumer<String> consumer1, Consumer<String> consumer2) {
   
    consumer1.andThen(consumer2).accept(str);
}

例子

给出一个字符数组:

String[] array = {
    "张三,女", "李四,女", "王五,男" };

让它们按照以下格式打印:

姓名: XX 性别: XX

代码如下:

@Test
void printInfo() {
   
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汪了个王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值