JDK8新特性之Lambda表达式

Lambda表达式

1、为什么要有lambda表达式

面向对象中不好的地方:

1、不得不去创建一个类,来对接口来进行实现;
2、如果不想创建具体的类,那么需要使用匿名内部类;
3、使用内部类不得不去实现其中的抽象方法;
4、重写方法,不得不去书写跟父类一样返回值类型、方法名称和对应的参数列表;    

面向对象侧重点:怎么做?

函数式编程侧重点:强调的是做什么?

2、lambda表达式格式

函数式编程在java中又叫lambda表达式

格式:

()->{}

注意:lambda表达式其实是对匿名内部类的一种改写,如果用不了匿名内部类,那么一定用不了lambda表达式;

其中:

():匿名内部类中接口的参数列表;

->:将参数列表交给{}中的java表达式来进行操作;

{}:匿名内部类中重写方法之后要做的操作;

使用lambda表达式的前提:

1、一定是接口;
2、接口中只有一个抽象方法;并不是说接口中只能够有一个方法;
3、函数式接口要作为参数或者是返回值;(99%情况下都是作为参数来进行传递的)    

注意:并不是所有的匿名内部类使用的地方都可以改造成lambda表达式来进行操作;

/**
 * @Description 数组和集合来进行排序操作
 * @Author liguang
 * @Date 2022/04/13/23:32
 */
public class ListComparator {
    public static void main(String[] args) {
        // 来对list集合来进行进行排序
        List<Integer> integers = Arrays.asList(12, 3, 6, 11, 999, 666);
        // 升序操作:前--->后,升序操作;后--前;降序操作;
        Collections.sort(integers,((o1, o2) -> o1-o2));
        for (Integer integer : integers) {
            System.out.println(integer);
        }
        System.out.println("使用匿名内部类来进行操作的方式");
        Collections.sort(integers, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        for (Integer integer : integers) {
            System.out.println(integer);
        }

        System.out.println("来对数组进行排序操作");
        // 这里只能够是引用数据类型
        Integer[] intsArr = {12,85,66,77,11,55,66};
        Arrays.sort(intsArr,new Comparator<Integer>(){
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1-02;
            }
        });
        for (Integer integer : integers) {
            System.out.println(integer);
        }
        // 使用lambda表达式来进行操作,但是通过实践来进行操作之后,发现这里的匿名内部类无法更改成lambda表达式来进行操作
        // Arrays.sort(integers,(o1,o2)->o2-o1);
    }
}

3、lambda表达式

案例一

来举一个例子,计算接口Compute:

public interface Compute {
    int sum(int i,int j);
}

写一个需要使用到Compute的类:

public class MyMath {

    /**
     * 计算的一个方法
     * @param compute 接口对象
     * @param i 需要来进行求和的变量
     * @param j 需要来进行求和的变量
     */
    public void sum(Compute compute, int i, int j) {
        // 这里可以有通用操作,但是目标就是这种的对象来调用这里的方法
        int sum = compute.sum(i, j);
        // do other something
        // 但是关键的一个步骤就是这里的操作
        System.out.println(i + "+" +j+ "=" + sum);
    }
}

再来一个测试类来进行测试:

public class Test {
    public static void main(String[] args) {
        MyMath myMath = new MyMath();
        // 按照传统的方式的话,那么这里应该是创建实现类来进行实现
        myMath.sum(new Compute() {
            @Override
            public int sum(int i, int j) {
                return i+j;
            }
        }, 250, 520);
    }
}

可以看到这里的使用方式,书写了匿名内部类,就是为了做到返回i+j这个效果而已。

下面看下使用lambda表达式的效果:

public class Test {
    public static void main(String[] args) {
        // 其实可以不要这么进行操作
        MyMath myMath1 = new MyMath();
        myMath1.sum((i,j)->i+j,200,300);
    }
}

这里可以看到只需要实现我们需在在Compute接口中的方法需要做的事情而已,不用去管那些怎么如何去实现的内容而已。

所以这种使用方式对于开发人员来说,更加符合我们的开发习惯。所以在这种使用方式比较多的情况之下,引入出来了对应的lambda表达式。

案例二

那么再来一个案例,如下所示:

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("test functional interface");
            }
        }).start();

通过匿名内部类的方式来进行书写代码,可以看到上面的效果。

那么再看一下,如果通过实现类的方式:

先创建一个实现类:

public class ThreadDemoOne extends Thread{
    @Override
    public void run() {
        System.out.println("test functional interface");
    }
}


public class ThreadDemo {
    public static void main(String[] args) {
        ThreadDemoOne threadDemoOne= new ThreadDemoOne();
        new Thread(threadDemoOne).start();
    }
}

可以看到上面的两种方式的书写风格。

但是回想一下,我们写这段代码的目的是什么?我们只是想用接口中的一个方法,去做我们想做的事情。但是往往我们想做的事情事与愿违,我们想要一个功能,不得不去做这些事情:实现、继承、创建对象...

这种方式非常繁琐,但是为了去实现,我们又不得不去做这个事情。

但是在JDK8中,官方出版了函数式接口,那么函数式接口大大简化了我们的开发,让我们开发人员再面对这种模式的情况,让我们只关注我们的目标功能,而不是过程。只注重结果,而并不注重过程。那么问题来了,如果接口中有多个方法,开发人员怎么能够知道该去实现哪个方法?所以java规定,接口中只能有一个抽象方法。

注意:只能有一个抽象方法。这段话的描述是接口中可以有其他的非抽象方法。

为了方便这种方法接口,不需要使用匿名内部类,而直接使用lambda来进行实现。

比如说Runnable接口,这就是一个标准的函数式接口:

@FunctionalInterface // 函数式接口
public interface Runnable {
    // 里面可以写抽象方法,也可以是Object类中的方法或者是其他的默认方法、静态方法等等。
    public abstract void run();
}

紧接着开始写对应的功能代码:

Runnable r = ()->{
    System.out.println("hello");
};
new Thread(r).start();

或者说再次简化一下对应的书写方式:

new Thread(()->{
    System.out.println("hello");
};).start();

这种书写方式乍一看很不合理,但是写习惯了就好了(苦笑)。

上面使用尖括号将参数和函数体分开,由于Runnable接口中的run方法没有参数,所以括号中什么都没有写;

又因为run方法没有返回值,所以上面的方法体中println方法也是没有返回值的。

所以lambda表达式的参数和返回值和函数式接口中的方法签名是匹配的,这样就硬性规定了labmda表达式使用的时候必须匹配函数式接口中的参数和返回值,这也被称之为和方法签名兼容。因此lambda表达式属于接口方法的实现并将其值赋值给接口类型的引用。

java中并不存在着Lambda的类,lambda表达式只能够作为函数式接口的引用。

通过lambda表达式和匿名内部类的实现可以看到,lambda表达式就相当于是整个匿名内部类,而且在匿名内部类中对于函数式接口中的方法是并不在意的,在意的是传入的参数和经过处理后的结果。也就是输入和输出。也就是说使用lambda表达式的目的是注重功能实现的过程,而不是去做一些与功能实现无关的事情

如果没有输入,那么lambda表达式的参数括号中,即()不需要传入参数;如果没有输出,那么方法体中不需要进行返回,即没有return。

3、自定义函数式接口

首先一定要明确的是:使用lambda表达式的本质就是接口,接口中要做的事情就是lambda表达式要来进行操作的。

所以这里更加应该明确在什么情况下来使用函数式接口来进行编程!!

接口是一个高度抽象的操作!所以会让子类来对其进行实现对应的相对应的操作。

将lambda表达式所有的类型列举出来,然后举例子来进行说明。函数式接口一共有以下几种使用方式:

无参无返回值;
有参无返回值;
无参有返回值;
有参有返回值;

下面分别用这几种方式来实现一下

3.1、无参无返回值

上面的demo引入的就是一个典型的例子:

public class DemoOne {
    public static void main(String[] args) {
        // 一定要注意:这里是start方法调用了run方法。注意这里的执行顺序
        new Thread(new Runnable1()).start(); // 这行代码就相当于是下面的代码,但是这么写的话,还需要写一个类来进行实现,然后new一个对象传入进来。使用下面方式简化

        // 首先分析一波:这里是接口,那么利用的肯定是多态机制,传入的最终是对象形式。但是只是使用一次,所以这里利用的是匿名对象
        // 对象得去实现接口中的抽象方法,方便进行调用。这里匿名内部类直接进行实现,new了一个匿名对象
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("开启了一条新线程1");
            }
        }).start();

        // 上面的代码在jdk8中又进行优化,可以写成下面这种方式来进行简化。
        // 函数式编程: 强调: 做什么
        // 只用了一行代码写了上面五行代码做的事情。
        // 实行start方法,自动调用了里面的run方法
        new Thread(() ->  System.out.println("开启了一条新线程2") ).start();
        new Thread(() -> System.out.println("开启了一条新线程3")).start();
        new Thread(() -> System.out.println("开启了一条新线程4")).start();
        new Thread(() -> System.out.println("开启了一条新线程5")).start();
        new Thread(() -> System.out.println("开启了一条新线程6")).start();
        new Thread(() -> System.out.println("开启了一条新线程7")).start();

    }
  
   static class Runnable1 implements Runnable{
       // 需要重写的方法是无参无返回值的。所以直接在lambda表达式中写方法体即可
        @Override
        public void run() {
            System.out.println("开启了一条新线程0");
        }
    }
}

3.2、有参无返回值

// 在这个类中来测试无参无返回值
public class Demo1 {
    public static void main(String[] args) {
        // 调用invoke方法,最终是要调用里面的show方法。看下show方法是一个有参无返回值的
        // invoke方法调用了里面的show方法。show方法需要一个参数,这里传入33。lamba表达式表示了show方法的具体执行方式
        // 这里的lambda表达式不是代表MyInterface myInterface,而是里面的方法的具体执行方式。【重点】
         invoke(33,(n)->System.out.println("有参无返回值"));
    }

    // 要进行调用,因为这里接口中的方法需要进行调用,所以需要一个参数,在这里进行调用
    public static void invoke(Integer age,MyInterface myInterface) {
        myInterface.show(age);
    }
}

 // 这里加不加@FunctionalInterface都可以
 interface MyInterface {
    // 有参无返回值。所以在实现的时候,只需要对参数来进行消费即可。
    void show(Integer integer);
}

以函数式接口作为方法的参数来进行使用,这种才是最常用的。

至于使用者如何来对方法进行实现,那么属于使用者来进行自定义的方式来进行操作的了。使用如上所示,我这里的实现仅仅是将传递过来的数据进行输出到控制台而已。

那么也就是说这里的使用,1、首先需要定义一个函数式接口;2、然后将其作为方法参数传递;3、在进行调用的时候传递参数和对函数式接口抽象方法的实现即可。

3.3、无参有返回值

public class Demo2 {
    public static void main(String[] args) {
        // print方法来进行调用,一看参数是一个接口,应该是匿名对象调用了fun方法,print方法在进行执行的时候,传入了匿名对象,然后匿名对象调用了fun方法
        // fun方法如何进行实现?lambda来进行实现的。将其进行还原
        /**
         * new MyInterface(){
         *     @Override
         *     public String fun() {
         *         System.out.println("开启了一条新线程0");
         *      }
         * }
         */
        // 有一个返回值,需要来进行接收。
        print(()->"hello,无参有返回值");
    }

    public static void print(MyInterface lambda) {
        // lambda来实现里面具体的逻辑,节省代码
        String s = lambda.fun();
    }

    static interface MyInterface {
        // 无参有返回值,那么说明了需要有返回值需要接收。在lambda表达式中可以直接进行返回接收即可。
        String fun();
    }
}

3.4、有参有有返回值

public class Demo3 {
    public static void main(String[] args) {
        // 调用的时候,传递参数,然后匿名对象调用show方法,lambda中有具体的实现方式。
        fun("hello,有参有返回值",(n)->n);
    }

    public static String fun(String str,MyInterface myInterface){
        String show = myInterface.show(str);
        System.out.println(show);
        return show;
    }


    static interface MyInterface{
        // 有参有返回值,那么()需要写,{}中也需要写
        String show(String s);
    }
}

4、看一下JDK中的使用方式

最常见的在这里举例子说明,比如说Optional类:

    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

看一下这里的方法操作,传入进来的consumer就是一个匿名内部类,在接口Consumer中的accept中的方法就是我们需要来进行的操作

那么看一下接口中的方法是否符合我们的操作方法,可以看到接口中只有一个抽象方法,没有返回值

void accept(T t);

所以我们可以模仿这种使用方式来进行操作。也就是上面的有参无返回值的情况。(可以看到至少需要三个类来进行操作)

但是这种使用方式已经将接口和实际调用者进行分离开来。具体的实现只需要根据自己的需要来进行实现即可

过滤方式:

    public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            // 如果符合过滤条件,那么就返回本身
            return predicate.test(value) ? this : empty();
    }

转换方式:

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

使用Function函数将T转换成U,转换方式,也就是对应转换功能函数,可以由自己来进行定义。

5、使用方法引用

方法引用的前提:

方法引用是对lambda表达式更深层的一种抽象使用,对于方法引用来说,lambda表达式所表达的操作也是有点冗余的。

所以方法引用是对lambda的进一步简化!所以要想写出来对应的方法引用,那么首先确定的是当前的操作中可以写出来对应的方法引用

同样的道理:

不是所有的lambda表达式都可以改造成方法引用

那么如何确定什么时候可以使用方法引用?

在lambda表达式中的操作是已经定义好的

在之前的操作中:

stream.forEach(s->System.out.println(s))

这里指定的是在lambda中,对应的println方法已经定义好了。那么这个时候就可以来对其进行操作:

stream.forEach(System.out::println);

方法引用格式:

A::B

:: 引用运算符

B:是一个功能,不能肯定的说是一个方法;比如说:new操作

那么A::B的语义就是使用A中的B功能;

那么再看一下:

stream.forEach(System.out::println);

使用System.out::println 功能

再次来总结一下:这里的方法一定是要定义好的,能够使用的,才可以这么来进行使用。

使用方式通常有:

1、对象::成员方法;

2、类名::静态方法;

3、类名::成员方法 (这种应该是最常用的一种方式)

6、总结

0、jdk8中的接口

1、lambda表达式产生原因

2、当一个方法的参数是函数式接口的时候,我们更加在意的是函数式接口中的入参和返回值类型,而不是过多的在于方法名字;

3、使用函数式接口的时候,除了上面的入参和返回值之外,更加注重的是方法体的操作;

posted @ 2021-05-11 00:36  雩娄的木子  阅读(159)  评论(0编辑  收藏  举报