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

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

转载请注明出处

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值