java8系列01——函数式编程思想与Lambda表达式

1.1为什么要学函数式编程

java8引入了函数式编程,在工作中应用得特别广泛,如果不学习可能会看不懂公司中同事的代码。

函数式编程对于海量数据的处理特别有帮助,提供了并行流,可以让程序员不用自己进行并发编程(这无疑是有难度的)。

代码可读性其实会更高,避免嵌套地狱,可以看看下面这个例子。

image-20220218204642572

image-20220218204704806

1.2 函数式编程思想

面向对象编程关注什么对象做什么事情,但是函数事编程把关注点转移到了数据:对数据做哪些处理。

优点有:

  • 代码简洁
  • 接近于自然语言,易于理解(看名字就大致知道在做什么操作)
  • 易于"并发编程"(使用并行流处理数据,比单线程处理更具有效率,不必自己并发编程,去解决头疼的数据安全问题)

2.Lambda表达式

2.1 思想及原则

jdk8的语法糖,主要是优化部分内部类的操作方法。它是函数式编程的一个重要体现——让我们不用关注是什么类,什么方法,而只需要关注对数据进行什么操作。

核心原则是可推导、可省略

2.2 示例

例1:

匿名内部类创建线程。

 new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("我是一只小小鸟...");
            }
        }).start();

使用Lambda简化下。

new Thread(() -> System.out.println("想要飞却怎么也飞不高...")).start();;

什么时候可以使用Lambda表达式呢?答案是匿名内部类的参数是一个接口,并且只有一个抽象方法需要被实现(可以有默认方法)。这就是所谓的函数式接口。参考Runnable接口。@FunctionalInterface可以用来标注一个接口是函数式接口。

@FunctionalInterface
public interface Runnable {
    
    
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

我们到这里就可以领会开始说的函数式编程思想了:Lambda表达式不关注类名,不关注方法名,因此他们都可以省略。只关注是什么数据(参数),对数据做什么操作(方法体)。

下面来做一个练习。

例2:调用下面方法实现对a,b进行自定义运算。

  public static int calculateNum(IntBinaryOperator operator) {
    
    
        int a  = 10;
        int b = 20;
        return  operator.applyAsInt(a, b);
    }

参数IntBinaryOperatorjava的util包中自带的工具类,我们虽然不认识,但可以点进去看看,是个函数式接口。

@FunctionalInterface
public interface IntBinaryOperator {
    
    

    /**
     * Applies this operator to the given operands.
     *
     * @param left the first operand
     * @param right the second operand
     * @return the operator result
     */
    int applyAsInt(int left, int right);
}

实现就变得很简单了。

先做一个内部类的调用实现。

 int cal1  = calculateNum(
                new IntBinaryOperator() {
    
    
                    @Override
                    public int applyAsInt(int left, int right) {
    
    
                        return left + right;
                    }
                }
        );

再写一个Lambda实现。

   int cal2 = calculateNum((left,right)->{
    
    
            return left* right;
        });

再来体会下Lambda表达式所展示的函数式编程思想:我并不关心calculateNum中是哪个类作为参数,也不关心这个类中是哪个方法来帮我做操作,我只像关心,我要传几个参数,我对我的数据进行什么操作。这样我们就可以把那些无关必要的东西省略,让我们代码变得更加简洁。

而且,写一个Lambda表达式在idea中特别简单,只要一个匿名内部类可以被简化未Lambda表达式,我们都可以先把鼠标光标移动到方法参数上,使用Alt+enter将它简化成Lambda表达式。而且一个Lambda表达式看不懂,也可以使用同样的方式转换为匿名内部类。

例3:输入过滤条件,打印符合条件的参数。

 public static void printNum(IntPredicate predicate) {
    
    
        int[] arr = {
    
    1,2,3,4,5,6,7,8,9};
        for(int num : arr) {
    
    
            if(predicate.test(num)) {
    
    
                System.out.println(num);
            }
        }

    }

IntPredicate是函数式接口。虽然有两个默认方法,但是只有一个抽象方法需要被实现。

@FunctionalInterface
public interface IntPredicate {
    
    

    boolean test(int value);

    default IntPredicate and(IntPredicate other) {
    
    
        Objects.requireNonNull(other);
        return (value) -> test(value) && other.test(value);
    }

    default IntPredicate negate() {
    
    
        return (value) -> !test(value);
    }

 
    default IntPredicate or(IntPredicate other) {
    
    
        Objects.requireNonNull(other);
        return (value) -> test(value) || other.test(value);
    }
}

实现如下。

	 printNum(new IntPredicate() {
            @Override
            public boolean test(int num) {
               return num > 4;
            }
        });

        printNum((int num)->{
            return num < 4;
        });

例4:对字符串转成数组。

public static <R> R typeConver(Function<String, R> function) {
    
    
        String str = "12345";
        return function.apply(str);
    }

实现如下。

  int c = typeConver((String str) -> {
    
    
            return Integer.parseInt(str);
   });

例5:输出数组中的元素。

 public static void forEachArr(IntConsumer consumer) {
    
    
        int[] arr = {
    
    1,2,3,4,5};
        for(int num : arr)  {
    
    
            consumer.accept(num);
        }
    }

操作如下。

 forEachArr((int num) -> {
    
    
            System.out.println(num);
 });

上面的例子其实都是很常用的,我们可以熟练应用到项目中。

2.3 省略规则

上面的Lambda表达式甚至可以进一步省略。

(1)参数列表可以省略。

如,forEachArr省略int

 forEachArr((num) -> {
    
    
            System.out.println(num);
 });

原因是IntConsumer只有如下一个抽象方法,编译器可以推导出参数类型为int

void accept(int value);

(2)方法体只有一个语句时,{};可以省略

(3)参数只有一个时,()可以省略。

 forEachArr(num ->  System.out.println(num));

(4)记不住可以不记,使用Idea工具转换。

image-20220222213402901

猜你喜欢

转载自blog.csdn.net/qq_41708993/article/details/123078119