JAVA进阶-lambda及Stream使用

2024-01-08 00:00:04

7.lambda

7.1 lambda理解

Lambda表达式是JDK8的一个新特性,可以取代大部分的匿名内部类,写出更优雅的Java代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。

JDK也提供了大量的内置函数式接口供我们使用,使得Lambda表达式的运用更加方便、高效。

Lambda表达式,也称为闭包:java8的新特性,lambda运行将函数作为一个方法的参数,也就是将函数作为参数传递到方法中。使用lambda表达式可以让代码更加简洁。
Lambda表达式常用于简化接口实现,关于接口实现,可以有很多种方式。例如:创建接口的实现类;或者使用匿名内部类;

举例说明:

接口和继承实现

public interface TestInterface {
    public void test();
}

public class TestInterfaceImpl implements TestInterface {
    @Override
    public void test() {
        System.out.println("实现继承接口方法");
    }
}

使用

 @Test
    public void t7(){
        //继承实现接口
        System.out.println("继承实现接口==============");
        
        TestInterface service=new TestInterfaceImpl();
        service.test();
        
        System.out.println("匿名类实现接口==============");
        
        service=new TestInterface(){

            @Override
            public void test() {
                System.out.println("匿名类实现");
            }
        };
        service.test();

        System.out.println("匿名类调用==============");
        
        new TestInterface(){
            @Override
            public void test() {
                System.out.println("匿名类调用直接调用");
            }
        }.test();
    }

使用lambda表达式就非常简单:

 TestInterface ts=()->{
            System.out.println("我用使用lambda语法来实现接口");
        };
        ts.test();

7.2lambda语法

7.2.1 基本定义

基本语法: (parameters) ->{ statements; }

(parameters)就是省略了类名的构造函数
->是lambda标志,后面是方法体{}

new Class类名《省略类名》(构造参数) ->{
函数语句
}
Lambda表达式由三部分组成:

  • paramaters:
    类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。
  • ->:
    -可理解为“被用于”的意思,是lambda标志
  • 方法体:
    -可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不反回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回。
// 1. 不需要参数,返回值为 2
() -> 2
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的和
(x, y) -> x + y
// 4. 接收2个int型整数,返回他们的乘积
(int x, int y) -> x * y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

7.2.2 函数式接口

要了解Lambda表达式,首先需要了解什么是函数式接口,函数式接口定义:一个接口有且只有一个抽象方法

注意:

1.如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口

2.如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接口中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的。
定义方式:

@FunctionalInterface
public interface NoParameterNoReturn {
    void test();
}

7.2.3 Lambda表达式的基本使用

函数接口声明,声明无返回函数接口

//1.无参无返回函数接口
@FunctionalInterface
public interface NoParameterNoReturn {
    void test();
}
//2.有参无返回参数
public interface OneParameterNoReturn {
    public void test(int x);
}

//3.多参无返回函数接口
@FunctionalInterface
public interface NoParameterNoReturn {
    void test();
}

lambda和匿名实现类的对比

   @Test
    public  void t0() {

        NoParameterNoReturn n = () -> {
            System.out.println("无参无返回参数");
        };
        n.test();
       //lambda表达式等价于匿名类接口实现
        NoParameterNoReturn nn=new NoParameterNoReturn(){
            @Override
            public void test() {
                System.out.println("无参无返回参数");
            }
        };
        nn.test();
        System.out.println("==========================================");

        OneParameterNoReturn o=(x)->{
            System.out.println("x的值==="+x);
        };
        o.test(200);
        //lambda表达式等价于匿名类接口实现
        OneParameterNoReturn on=new OneParameterNoReturn(){

            @Override
            public void test(int x) {
                System.out.println("x的值==="+x);
            }
        };
        on.test(200);

    }

有返回值的接口函数

//1.无参有返回函数接口
public interface NoParamReturn {
    public int test();
}
//2.有参有返回函数接口
public interface OneParamReturn {
    public int test(int x);
}
//3.多参有返回函数接口
public interface MoreParamReturn {
    public int test(int x,int y);
}

对应的lambda函数

@Test
    public void t2(){
        NoParamReturn nr=()->{
            return 666;
        };
        System.out.println(" nr.test()="+ nr.test());;
        NoParamReturn nr2=()->2;
        System.out.println(" nr2.test()="+ nr2.test());;

        OneParamReturn or=(x)->{
            return 2*x;
        };
        System.out.println(" or.test(100)="+ or.test(100));

        MoreParamReturn mr=(x,y)->{
            return x+y;
        };
        System.out.println("mr.test(100, 300)="+mr.test(100, 300));
    }

7.2.4 lambda简写

1.参数类型可以省略,如果需要省略,每个参数的类型都要省略。
A a=(int x,int y)->{return 2;};
可以简写成
A a=(x,y)->{return 2;};

2.参数的小括号里面只有一个参数,那么小括号可以省略
A a=(int x)->{return 2;};
可以简写
A a=x->{return 2;}; //这种说实话,不好理解

3.如果方法体当中只有一句代码,那么大括号可以省略
A a=x->System.out.println(“ffff”);

4.如果方法体中只有一条语句,其是return语句,那么大括号可以省略,且去掉return关键字
()->2;
(x)-> 2x;
x-> 2;

再看示例:

	public static void main(String[] args) {
        MoreParameterNoReturn moreParameterNoReturn = (a, b)->{
            System.out.println("无返回值多个参数,省略参数类型:"+a+" "+b);
        };
        moreParameterNoReturn.test(20,30);
        OneParameterNoReturn oneParameterNoReturn = a ->{
            System.out.println("无参数一个返回值,小括号可以省略:"+ a);
        };
        oneParameterNoReturn.test(10);
        NoParameterNoReturn noParameterNoReturn = ()->System.out.println("无参数无返回值,方法体中只有 一行代码");
        noParameterNoReturn.test();
        //方法体中只有一条语句,且是return语句
        NoParameterReturn noParameterReturn = ()-> 40;
        int ret = noParameterReturn.test();
        System.out.println(ret);
    }


7.3 Lambda内置函数接口

今天我们还讲讲Consumer、Supplier、Predicate、Function这几个接口的用法,在 Java8 的用法当中,这几个接口虽然没有明目张胆的使用,但是,却是润物细无声的。为什么这么说呢?

这几个接口都在 java.util.function 包下的,分别是Consumer(消费型)、supplier(供给型)、predicate(谓词型)、function(功能性)

7.3.1 消费性Consumer/BiConsumer

从字面意思上我们就可以看得出啦,consumer接口就是一个消费型的接口,通过传入参数,然后输出值,就是这么简单,Java8 的一些方法看起来很抽象,其实,只要你理解了就觉得很好用,并且非常的简单。
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); };
    }
}

Consumer 顾名思义,就是消费者,它传入一个任意参数,通过accept()接口,交给实现者去处理, 其实Consumer就是一个内置lambda的函数接口,通过它,就可以使用lamdba语法了,用户不需要自己再写一个 函数接口了

实例:

 @Test
    public void t9(){
    /**
我们直接创建 Consumer 接口,并且实现了一个名为 accept 的方法,这个方法就是这个接口的关键了。
我们看一下 accept 方法;这个方法传入一个参数,不返回值。
当我们发现 forEach 需要一个 Consumer 类型的参数的时候,传入之后,就可以输出对应的值了
     **/
        Consumer<String> cl=new Consumer<String>(){

            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        cl.accept("who are you?");

        Consumer<String> cl1=(String str)->{
            System.out.println(str);
        };

        cl1.accept("who are you?");

    }

再看一个例子,增加理解,list.forEach()需要传入一个Consumer函数接口

  @Test
    public void t10(){
        List<String> list=Arrays.asList("1","2","3");
        Consumer<String> cl1=(String str)->{
            System.out.println(str);
        };
        list.forEach(cl1);
        //简写forEach
        System.out.println("简写forEach=====================");


        list.forEach(str-> System.out.println(str));

        System.out.println("更简写forEach,使用引用=====================");
        //采用引用
        Consumer<String> cl2= System.out::println;
        list.forEach(cl2);
        //不需要赋值,直接传递给forEach
        System.out.println("不需要赋值,直接传递给forEach");
        list.forEach(System.out::println);

    }

BiConsumer 就是Consumer是两个参数,方法和Consumer一样

@Test
    public void t13(){
        System.out.println("BiConsumer 匿名函数==========");
        BiConsumer<Integer,String> bc=new BiConsumer<>(){
            @Override
            public void accept(Integer id, String name) {
                System.out.println("id="+id+",name="+name);
            }
        };
        bc.accept(10, "jzk");
        System.out.println("BiConsumer lambda表达式==========");
        bc=(id,name)->System.out.println("id="+id+",name="+name);
        bc.accept(10, "jzk");
    }

7.3.2 供给型 supplier

Supplier 接口是一个供给型的接口,其实,说白了就是一个容器,可以用来存储数据,然后可以供其他方法使用的这么一个接口。

Supplier是Java8配合Lambda表达式和函数式接口编程组合使用的一个接口,对外表现为 ::

接口Supplier 最适合表示工厂。带有Supplier 的方法,通常应该限制输入工厂的类型参数使用有限制的通配符类型,以便客户端可以传入工厂,来创建制定类型的任意子类。

简而言之,Supplier就是来创建对象的。

@FunctionalInterface
public interface Supplier<T> {

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

看一个实际代码

	@Test
    public void t11(){
        System.out.println("======Supplier==匿名函数");
        /***
         我们通过创建一个 Supplier 匿名对象,实现了一个 get 方法,这个方法无参数,返回一个值;
         所以,每次使用这个接口的时候都会返回一个值,并且保存在这个接口中,所以说是一个容器。
         */
        Supplier<Integer> supplier1=new Supplier<Integer>(){
            @Override
            public Integer get() {
                //返回一个随机值
                return new Random().nextInt();
            }
        };
        System.out.println(supplier1.get());

        System.out.println("======Supplier==lambda");
        /***
         使用 lambda 表达式返回一个 Supplier类型的接口,
         然后,我们调用 get 方法就可以获取这个值了
         */
        Supplier<Integer> supplier2=()->new Random().nextInt();
        System.out.println(supplier2.get());

        System.out.println("======Supplier==lambda方法引用");
        //方法引用也是返回一个Supplier类型的接口。
        Supplier<Double> supplier3 = Math::random;
        System.out.println(supplier3.get());

    }

再看一个例子

 public int getVal(Supplier<Integer> supplier) {
        return supplier.get();
    }

    @Test
    public void testSupplier() {
        List<Integer> valList = Arrays.asList(1, 245, 6, 7, 8, 65, 432, 345);
        //getVal(Supplier<Integer> supplier)是一个函数式接口
        int maxVal = getVal(
                () -> {
                    int val = Integer.MIN_VALUE;
                    for (Integer v : valList) {
                        if (v > val) {
                            val = v;
                        }
                    }

                    return val;
                }
        );
        System.out.println("最大值:" + maxVal);
    }

7.3.4 List/Map的forEach方法

List.forEach函数源码:

default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

传入的是一个Consumer接口函数,看源码是循环获得
代码

@Test
    public void t8(){

        String[] array = {"aaaa", "bbbb", "cccc"};
        List<String> list = Arrays.asList(array);
        //引用静态方法,通过类名+::+方法名的方式
        //forEach传入的是Consumer接口
        list.forEach(
                (String s)->
                {
                    System.out.println(s);
                }
                );

        System.out.println("========简写");
        list.forEach(s-> System.out.println(s));
        System.out.println("=========简写==引用");
        list.forEach(System.out::println);

        System.out.println("复杂类型===============");
        List<Person> l=new ArrayList<>();
        l.add(new Person(1,"jzk"));
        l.add(new Person(2,"mike"));
        l.add(new Person(3,"smith"));
        l.forEach(person -> {
            if (person.getId()==1){
                System.out.println(person.getName());
            }
        });
        //引用函数
        System.out.println("复杂类型=========引用函数======");
        l.forEach(System.out::println);
    }

  @Test
    public void t88(){
        Map<Integer,Integer> map=new HashMap<>();
        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        map.put(4, 4);
        map.forEach((x,y)->{
            System.out.println("x="+x+",y="+y);
        });
    }

7.3.5 总结

这两个接口方法很少,但是实用性很强,并且可以看成是一对逆运算.一个作为生产工厂生产对象(Supplier),另一个作为消费者去消费对象(Consumer).

7.4 引用函数

7.5 Stream流

文章来源:https://blog.csdn.net/weixin_41158378/article/details/135419975
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。