欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java从零开始 第17讲 lambda表达式,Stream类

程序员文章站 2022-05-28 13:10:34
...

Java从零开始 第17讲 lambda表达式,Stream类隆


14年发布的Java 8 API目前仍是各大企业最为青睐的JDK版本,无论更新了新的长期稳定版本又或是发布了最新的版本,JDK8永远是不变的选择(当然也有很多公司会使用JDK7甚至6)。
使用JDK8的原因有很多,但是在本文中,我将会介绍并简单教学JDK8中两个十分重要的特性,lambda表达式和Stream类。

lambda表达式

首先让我们假设一个应用场景,有一个接口中的抽象方法需要被实现,那我们通常的方法是

// 原始的接口
interface MyIf{
    void myPrint();
}

// 实现接口,重写其中抽象方法
class MyClass implements MyIf{
    @Override
    public void myPrint(){
        System.out.println(2);
    }
}

// 调用该方法
// MyClass test = new MyClass();
// test.myPrint(); // 标准流程
new MyClass().myPrint(); // 简要版

如果我们只需要使用一次这个方法(或者次数比较少),继承接口和重写方法这些流程看下来实在是有些复杂,现在让我们用lambda的方式来实现这个功能

// 原始的接口
interface MyIf{
    void myPrint();
}

// 实现接口,重写其中抽象方法
MyIf test = () -> System.out.println(2);

// 调用该方法
test.myPrint();

使用了lambda之后整个代码量就下降了许多,而且实现和调用的代码可以放在一起,不需要翻看具体的实现语句了

现在让我们正式介绍一下lambda表达式,lambda表达式的目的是为了让代码更优美,其完整表达式如下

  • (parameters) ->{ statements; }

lambda使用也有一些规则:

  1. 如果statement只有一句,那么后面的大括号可以省略
  2. 必须实现接口(即含有抽象方法的抽象类不行),接口中有且只能有一个抽象的方法
  3. 参数列表可以不用写明参数类型

让我们再写两个简单的例子来加深一下印象

class Scratch {
    public static void main(String[] args) {
        InnerIf1 test1 = (a) -> System.out.println(a*a);
        InnerIf2 test2 = (a, b) -> {
            a=a*2;
            return a+b;
        };
        
        test1.lambdaMulti(2);
        System.out.println( test2.lambdaAdd(2,2) );
    }

    // 内部接口
    interface InnerIf1{
        void lambdaMulti(int i);
    }

    interface InnerIf2{
        int lambdaAdd(int a, int b);
    }
}



// 运行结果
4
6

Process finished with exit code 0

lambda表达式十分常用的一个场景就是实现多线程的Runnable接口,在这里我就不举例子了。

双冒号调用方法

在lambda表达式被提出的同时,也推出了一种新的方法调用方式,即双冒号 :: ,使用双冒号能够直接调用类中的方法,可以帮助lambda进一步简化代码
同样让我们用几个简单的例子实用一下

// 使用形式
类名::方法名 // 对于静态方法
对象名::方法名 // 对于非静态方法
new 类名::方法名 // 匿名调用
super::父类方法名 // super调用
类名::new // 构造方法调用

System.out::println // 调用println方法,注意此方法为静态的
new Object()::equals // 调用Object类中的equals方法

但是注意,这些方法并不能如此直接使用,而要和lambda表达式一同使用,或者配合其他方法使用,如下对于一个简单加的运算可以使用Integer中的sum方法替代

class Scratch {
    public static void main(String[] args) {
        
        // InnerIf ifImpl = (i, j) -> i+j; // 原语句
        InnerIf ifImpl = Integer::sum;
        System.out.println(ifImpl.add(2,2));
    }

    interface InnerIf{
        int add(int i, int j);
    }
}

另一个常用的场景是对Collection接口下的每一个元素进行操作,如下是输出每一个元素

        // 下两行不会输出结果,因为myList是空的
        List<Integer> myList = new ArrayList<>();
        myList.forEach(System.out::println); 
        

        // 这里可以正常输出
        List<Integer> myList = Arrays.asList(3, 2, 1);
        myList.forEach(System.out::println);

Stream类

在学完了lambda表达式和双冒号调用后,让我们来学习JDK8中另一个重要的新特性,那就是Stream/流。
之所以要在流之前学习lambda,是因为在流中将会大量使用lambda表达式来简化代码,如果看到这里但是还没有完全弄清楚lambda的使用,建议再多温习几遍上面的内容再继续看下去。

首先让我们定义一下流,流是一个来自数据源的元素队列,通常用于在类集中执行较为复杂的查找和过滤等操作。其中数据源通常是Collection接口下的类集(list和set),元素队列即是该类集中的元素。

        // 一个普通的链表
        List<Integer> myList = Arrays.asList(3, 2, 1);
        // 得到串行流
        myList.stream();
        // 得到并行流
        myList.parallelStream();

流不会存储数据,所以在获取到流之后,需要对最终的结果进行存储,其间可以对流中的元素进行任意的操作。同样使用例子来简要说明一下

List<Integer> myList = Arrays.asList(3, 2, 1, 2, 3);

// 不进行任何操作
List<Integer> myList2 = myList.stream().collect(Collectors.toList());

// 将每一个元素平方
List<Integer> myList3 = myList.stream().map(x -> x*x).collect(Collectors.toList());

// 仅保留小于2的元素
List<Integer> myList4 = myList.stream().filter(x -> x<2).collect(Collectors.toList());

System.out.println(myList2);
System.out.println(myList3);
System.out.println(myList4);


// 运行结果
[3, 2, 1, 2, 3]
[9, 4, 1, 4, 9]
[1]

Process finished with exit code 0

通过方法的叠加也可以定义复杂一些的方法,在stream中可以夹杂许多方法,它们的结构并不复杂,我们就不一一展示了,此处仅写一些基础的常用方法

        List<Integer> myList = Arrays.asList(7, 2, 1, 4, 3, 5, 2);
        List<Integer> myList2 = myList.stream()
                .map(x -> x*3) // 每一个元素*3
                .filter(x -> x<15) // 保留小于15的元素
                .sorted() // 从小到大排列
                .distinct() // 去除重复结果
                .limit(5) // 最多5个元素
                .collect(Collectors.toList());
        System.out.println(myList2);


// 运行结果
[3, 6, 9, 12]

Process finished with exit code 0

除此之外也可以使用forEach和count等方法来结尾,以应对多样的需求

        List<Integer> myList = Arrays.asList(7, 2, 1, 4, 3, 5, 2);

        long count = myList.stream().count(); // 计数
        String str = myList.stream()
                .map(x -> ""+x) // 需要将其先转为string类型
                .collect(Collectors.joining(", "));
        IntSummaryStatistics statistics = myList.stream()
                .mapToInt(x -> x) // 为了通过编译器审核
                .summaryStatistics(); // 得到统计数据
        // 统计数据主要用于int、double、long等基本类型上

        // 输出每一个元素
        myList.stream().forEach(System.out::print);
        System.out.println("\n" + count);
        System.out.println(str);
        System.out.println("max="+ statistics.getMax()+
                "\nmin="+ statistics.getMin()+
                "\navg="+ statistics.getAverage());



// 运行结果
7214352
7
7, 2, 1, 4, 3, 5, 2
max=7
min=1
avg=3.4285714285714284

Process finished with exit code 0

转载请注明出处