lambda stream 循环_函数式编程 : Lambda与Stream流

一. Lambda表达式

1. 函数式编程思想概述

在数学中函数就是有输入量 , 输出量的一套计算方案, 也就是拿什么东西做什么事情. 相对而言, 面向对象过分强调"必须通过对象的形式来做事情", 而函数式 思想则尽量忽略面向对象的复杂语法, 更加强调做什么,而不是以什么形式做

面向对象的思想 :

​ 做一件事情, 找一个能解决这个事情的对象, 调用对象的方法, 完成事情.

函数式编程思想 :

​ 只要能获取到结果, 谁去做的, 怎么做的都不重要. 重视的是结果, 不重视过程

2. 冗余的Runnable代码

当需要启动一个线程去完成任务时, 通常会通过java.lang.Runnable接口来定义任务内容, 并使用java.lang.Thread类来启动该线程. 代码如下 :

public static RunnableTest {

public static void main(String[] args) {

Runnable task = new Runnable() {

@Override

public void run() {//重写抽象方法 System.out.println("多线程任务执行!");

}

};

new Thread(task).start();//启动线程

//体验Lambda表达式 new Thread(() -> System.out.println(Thread.currentThread().getName()+"执行了")).start();

}

}

本着面向对象的思想, 先创建一个Runnable接口的匿名内部类对象, 来指定任务内容, 在将其交给一个线程来启动

代码分析

对于Runnable的匿名内部类用法, 可以分析出几点内容 :Thread类需要Runnbale接口作为参数, 其中抽象run方法是用来指定线程任务内容的核心.

为了指定run的方法体, 不得不需要Runnable接口的实现类

为了省去定义一个RunnableImpl实现类的麻烦, 不得不用匿名内部类

必须覆盖重写抽象run方法, 所以方法名称, 方法参数, 方法返回值不得不再重写一遍, 且不能写错.

而实际上, 似乎只有方法体才是关键所在

3. 编程思想转换

3.1 做什么, 而不是怎么做

我们真的希望创建一个匿名内部类对象吗 ? 不, 我们只是为了做这件事情 而不得不创建一个对象, 我们真正希望做的是 : 将run方法体内的代码传递给Thread类知晓.

传递一段代码---------这才是我们真正的目的, 而创建对象只是受限于面向对象语法而不得不采取的一种手段方式, 那有没有更简单的办法呢 ? 如果我们将关注点从 " 怎么做 " 回归到" 做什么 "的本质上, 就会发现只要能够更好地达到目的, 过程与形式其实并不重要.

JDK8以后, Java中加入了==Lambda表达式==的重量级新特性, 为我们打开了新世界的大门

3.2 体验Lambda的更优写法

借助Java 8 的全新语法, 上述Runnable接口的匿名内部类写法可以通过更简单的Lambda表达式来达到等效 :

public class LambdaRunnable {

public static void main(String[] args) {

new Thread(() -> System.out.println("多线程任务执行!")).start();//启动线程 }

}

这段代码和刚才执行的效果是完全一样的, 可以在JDK8以上的版本下编译通过, 从代码语义可以看出, 我们启动了一个新线程, 而线程任务的内容以一种更加简洁的形式被指定

这让我们不再有不得不创建接口对象的束缚, 不再有抽象方法覆盖重写的负担 !

3.3 Lambda表达式标准格式

由三部分组成:一些参数

一个箭头

一段代码

格式:

( 参数类型 参数名称 ) -> {

方法体;

return 返回值;

}

解释说明格式:小括号中的参数和之前方法的参数写法一样,可以写任意个参数,如果多个参数,要使用逗号隔开。

->是一个运算符,表示指向性动作。

大括号中的方法体以及return返回值的写法和之前方法的大括号中的写法一样。

Lambda表达式是函数式编程思想

函数式编程: 可推导, 就是可省略

因为在Thread构造方法中需要Runnable类型的参数, 所以可以省略new Runnable

因为Runnable中只有一个抽象方法run, 所以重写的必然是这个run方法, 所以可以省略run方法的声明部分.

匿名内部类与Lambda对比 :

//匿名内部类new Thread(new Runnable() {

@Override

public void run() {

System.out.println("多线程执行!");

}

}).start();

----------------------------------------------

//Lambda() -> System.out.println("多线程任务执行!")

仔细分析该代码中, Runnable 接口只有一个run 方法的定义:public abstract void run(); 即制定了一种做事情的方案(其实就是一个方法):无参数:不需要任何条件即可执行该方案。

无返回值:该方案不产生任何结果。

代码块(方法体):该方案的具体执行步骤。

同样的语义体现在Lambda 语法中,要更加简单

Lambda :前面的一对小括号即run 方法的参数(无),代表不需要任何条件;

中间的一个箭头代表将前面的参数传递给后面的代码;

后面的输出语句即业务逻辑代码。

3.4 参数和返回值

下面演示java.util.Comparator接口的使用场景代码, 其中抽象方法定义为 :public abstract int compare(T o1, T o2);

当需要对一个对象数组进行排序时, Arrays.sort方法需要一个Comparator接口实例来指定排序的规则, 假设有一个Person类, 含有String name和int age两个成员变量 :

public class Person {

private String name;

private int age;

//省略构造器, toString和get set方法}

以前学的写法 :

//使用传统代码对Person[] 数组进行排序

public class Demo05Comparator {

public static void main(String[] args) {

// 本来年龄乱序的对象数组 Person[] array = { new Person("古力娜扎", 19), new Person("迪丽热巴",

18), new Person("马尔扎哈", 20) };

// 匿名内部类 Comparator comp = new Comparator() {

@Override

public int compare(Person o1, Person o2) {

return o1.getAge() - o2.getAge();

}

};

Arrays.sort(array, comp); // 第二个参数为排序规则,即Comparator接口实例 for (Person person : array) {

System.out.println(person);

}

}

}

只有参数和方法体才是关键, 其他都是多余的

Lambda写法 :

public class Demo06ComparatorLambda {

public static void main(String[] args) {

Person[] array = {

new Person("古力娜扎", 19),

new Person("迪丽热巴", 18),

new Person("马尔扎哈", 20) };

Arrays.sort(array, (Person a, Person b) -> {

return a.getAge() - b.getAge();

});

for (Person person : array) {

System.out.println(person);

}

}

}

3. 5 Lambda表达式省略格式

省略规则 :

在Lambda标准格式的基础上, 使用省略写法的规则为 :小括号内参数的类型可以省略

如果小括号内有且仅有一个参, 则小括号可以省略

如果大括号内有且仅有一个语句, 则无论是否有返回值, 都可以省略大括号, return关键字及语句分号

原则就是 : 可推导出来的就可以省略

Lambda强调的是"做什么"而不是"怎么做", 所以凡是可以推导得出的信息都可以省略, 例如上例还可以使用Lambda的省略写法.

//Runnable接口简化:() -> System.out.println("多线程任务执行!");

//Comparator接口简化:Arrays.sort(array, (a, b)) -> a.getAge() - b.getAge());

4. Lambda的前提条件使用Lambda必须具有接口, 且要求接口中有且仅有一个抽象方法, 无论是JDK内置的Runnable, Comparator接口还是自定义的接口, 只有当接口中的抽象方法存在且唯一时, 才可以使用Lambda.

使用Lambda必须具有接口作为方法参数, 也就是方法的参数或局部变量必须为Lambda对应的接口类型, 才能使用Lambda作为该接口的实例.

必须支持上下文推导,要能够推导出来Lambda表达式表示的是哪个接口中的内容。 可以使用接口当做参数,然后传递Lambda表达式(常用) 也可以将Lambda表达式赋值给一个接口类型的变量。有且仅有一个抽象方法的接口, 称为函数式接口.

public class Demo05BeforeLambda {

//使用接口当做参数 public static void method(MyInterface m) {//m = s -> System.out.println(s) m.printStr("HELLO");

}

public static void main(String[] args) {

//使用接口当做参数,然后传递Lambda表达式。 //method(s -> System.out.println(s));

//使用匿名内部类方式创建对象 /*MyInterface m = new MyInterface() {@Overridepublic void printStr(String str) {System.out.println(str);}};*/

MyInterface m = str -> System.out.println(str);

m.printStr("Hello");

}

}

二. 函数式接口

1. 概述

函数式接口在Java中是指, 有且仅有一个抽象方法的接口.

函数式接口, 适用于函数式编程场景的接口, 而Java中的函数式编程体现就是Lambda, 所以函数式接口就是可以适用于Lambda使用的接口, 只有确保接口中有且仅有一个抽象方法, Java中的Lambda才能顺利的进行推导.

格式 :

只要确保接口中有且仅有一个抽象方法即可 :

修饰符 interface 接口名称 {

public abstract 返回值类型 方法名 (参数列表);

//其他非抽象方法的内容}

由于接口当中的抽象方法public abstract 是可以省略的, 所以定义一个函数式接口很简单,

public interface MyFunctionalInterface {

void myMethod();

}

2. 自定义函数式接口

对于刚刚定义好的MyFunctionalInterface函数式接口, 典型的引用长久就是作为方法的参数 :

public class FunctionalInterfaceTest {

//使用自定义的函数式接口作为方法参数 private static void doSomething (MyFunctionalInterface inter) {

inter.myMethod();//调用自定义的函数式接口方法 }

public static void main(String[] args) {

//调用使用函数式接口的方法 doSomething (() -> System.out.println("Lambda执行啦"));

}

}

3. 接口注解

与@Override注解的作用类似, Java8中专门为函数式接口引入了一个新的注解: @FunctionalInterface. 该注解可用于一个接口的定义上 :

@FunctionalInterface

public interface MyFunctionalInterface {

void myMethod();

}

一旦使用该注解来定义接口, 编译器会强制检查该接口是否确实有且仅有一个抽象方法, 否则会报错. 不过, 就算不使用它, 只要满足函数式接口的定义, 这仍然是一个函数式接口, 使用起来是一样的.

4. 常用的函数式接口

JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景, 他们主要在java.util.function包中被提供, 以下介绍两个常用的函数式接口.Consumer接口

Predicate接口

4.1 Consumer接口

在JDK8的时候,提供java.util.function包,这个包下有大量的函数式接口。

其中有一个接口叫做Consumer,这个接口可以看成一个消费者,可以去消费(使用)一个数据。

抽象方法:

void accept(T t): 对参数t进行使用

public class Demo01Consumer {

//定义方法,使用函数式接口Consumer当做方法参数 public static void method(Consumer c) {

//调用accept方法,消费使用一个数据。 c.accept("hello");

}

public static void main(String[] args) {

//调用method方法 method(new Consumer() {

@Override

public void accept(String s) {

System.out.println(s);

}

});

//调用method方法,传递Lambda表达式。 method(s -> System.out.println(s));

}

}

4.2 Predicate接口

有时候我们需要对某种类型的数据进行判断, 从而得到一个boolean值结果, 这时候可以使用java.util.function.Predicate接口.

这个函数式接口可以对一个数据进行判断,判断是否符合要求抽象方法:

| 修饰符 | 返回值 | 方法名 | 参数列表 | 作用 | | --------------- | ------- | ------ | -------- | ------------ | | public abstract | boolean | test | ( T t ) | 用于条件判断 |

判断参数t是否符合规则,如果符合规则返回true。

//1. 练习: 判断字符串长度是否大于5//2. 练习: 判断字符串是否包含"H"public class PredicateTest {

private static void method(Predicate predicate, String str) {

boolean veryLong = predicate.test(str);

System.out.println("字符串很长吗" + veryLong);

}

public static void main(String[] args) {

method(s -> s.length() > 5, "HelloWorld");

}

}

条件判断的标准是传入的Lambda表达式逻辑, 只要字符串长度大于5就认为很长.

三. Stream流

在Java8中, 得益于Lambda所带来的函数式编程, 引入了一个全新的Stream概念, 用于解决已有集合类库既有的弊端.

1. Stream流的初体验

package cn.itcast.demo03_stream;

import java.util.ArrayList;

import java.util.List;

/*要求:1. 首先筛选所有姓张的人;2. 然后筛选名字有三个字的人;3. 最后进行对结果进行打印输出。*/

@SuppressWarnings("all")

public class Demo01PrintList {

public static void main(String[] args) {

List list = new ArrayList<>();

list.add("张无忌");

list.add("周芷若");

list.add("赵敏");

list.add("张强");

list.add("张三丰");

//1. 首先筛选所有姓张的人; List zhangList = new ArrayList<>();

for (String s : list) {

if(s.startsWith("张")) {

zhangList.add(s);

}

}

//2. 然后筛选名字有三个字的人; List threeList = new ArrayList<>();

for (String s : zhangList) {

if (s.length() == 3) {

threeList.add(s);

}

}

//3. 最后进行对结果进行打印输出。 for (String s : threeList) {

System.out.println(s);

}

System.out.println("=====================================");

//Stream流的初体验 list.stream()

.filter(s -> s.startsWith("张"))

.filter(s -> s.length() == 3)

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

}

}

2. 获取流的方式

2.1 单列集合获取流的方式

在Java中,Stream表示流,Stream是一个接口类型,后面我们使用的流都是Stream这个接口的实现类。获取Stream流的两种方式:通过Collection中的方法获取流(单列集合获取流的方式)

通过数组获取流

如果要通过单列集合获取流,那么可以调用集合的stream方法。

Stream stream():获取集合对应的流

// Stream stream = list.stream();

public class Demo02CollectionGetStream {

public static void main(String[] args) {

//创建单列集合 List list = new ArrayList<>();

//向集合添加元素 list.add("aa");

list.add("bb");

list.add("cc");

//调用集合的stream方法,获取流 Stream stream = list.stream();

//将流调用toArray方法转成数组,再借助数组的工具类将数组的内容打印。 System.out.println(Arrays.toString(stream.toArray()));

}

}

2.2 通过数组获取流

如果要通过数组获取Stream流,有两种方式 :通过Stream中的静态方法of获取(记住)

static Stream of(T... values): 根据数组或多个元素获取流。

通过Arrays工具类中的静态方法stream获取

static Stream stream(T[] array): 根据数组获取流。

public class Demo03ArrayGetStream {

public static void main(String[] args) {

//1. 通过Stream中的静态方法of获取 String[] strArr = {"aa", "bb", "cc"};

//static Stream of(T... values): 根据数组或多个元素获取流。 //Stream stream = Stream.of(strArr); Stream stream = Stream.of("hello", "java", "world");

//将流进行输出 System.out.println(Arrays.toString(stream.toArray()));

//2. 通过Arrays工具类中的静态方法stream获取 Stream stream2 = Arrays.stream(strArr);

//将流输出 System.out.println(Arrays.toString(stream2.toArray()));//[aa, bb, cc] }

}

3. Stream中的方法

流模型的操作很丰富,这里介绍一些常用的API。

这些方法可以被分成两种:终结方法:返回值类型不再是Stream 接口自身类型的方法,因此不再支持类似StringBuilder 那样的链式调 用。本小节中,终结方法包括count 和forEach 方法。

非终结方法:返回值类型仍然是Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为非终结方法。)

以下方法, 方法种类为终结的方法不再支持链式调用.

| 修饰符 | 返回值 | 方法名 | 参数列表 | 作用 | 方法种类 | | ------------- | --------- | --------- | -------------------------------------------------- | ---------- | -------- | | public | void | forEach | ( Consumer 1 super T> action ) | 逐一处理 | 终结 | | public | Stream | filter | (Predicate super T> predicate ) | 过滤 | 终结 | | public | long | count | ( ) | 统计个数 | 函数拼接 | | public | Stream | limit | ( long maxSize ) | 取用前几个 | 函数拼接 | | public | Stream | skip | ( long n ) | 跳过前几个 | 函数拼接 | | public static | Stream | concat | ( Stream extends T> a, Stream extends T> b ) | 组合 | 函数拼接 |

3.1 forEach / forEachOrdered

forEach与增强 for不同, 该方法并不能保证元素的逐一消费动作在流中是被有效执行的

该方法接收一个Consumer接口函数, 会将每一个流元素交给该函数进行处理.

package drafts.drafts2;

import java.util.HashSet;

import java.util.Set;

public class Drafts2 {

public static void main(String[] args) {

Set set = new HashSet<>();

set.add("捣乱黄");

set.add("捣乱靛");

set.add("捣乱灰");

set.add("捣乱红");

set.add("捣乱王");

set.stream().forEach((str) -> System.out.println(str));

}

}

捣乱靛

捣乱王

捣乱黄

捣乱红

捣乱灰

3.2 filter 过滤

可以通过filter方法将一个流转换成另一个子集流.

该接口接收一个Predicate函数式接口, 作为筛选条件.

package drafts.drafts3;

import java.util.stream.Stream;

public class Draft3 {

public static void main(String[] args) {

Stream stream = Stream.of("孙悟空", "哪吒", "邋遢大王", "捣乱黄", "捣乱绿", "捣乱黑");

stream

.filter((str) -> str.length() == 3)

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

}

}

孙悟空

捣乱黄

捣乱绿

捣乱黑

3.3 统计个数

流中提供了count方法来数一数其中元素的个数, 该方法是终结方法.

import java.util.HashMap;

import java.util.Map;

import java.util.Set;

import java.util.stream.Stream;

public class Drafts4 {

public static void main(String[] args) {

Map map = new HashMap();

map.put("张三", 23);

map.put("李四", 25);

map.put("王五", 25);

map.put("赵六", 26);

Set> set = map.entrySet();

Stream> stream = set.stream();

/* Stream> result =stream //可以省略数据类型.filter((Map.Entry e) -> e.getValue() == 25);System.out.println(result.count());` */

System.out.println(stream.filter((Map.Entry e) -> e.getValue() == 25).count());

}

}

3.4 取用 / 跳过

limit方法可以对流进行截取, 得到只取用前n个元素的流

skip方法可以跳过前几个元素, 得到截取后的流

package drafts.drafts5;

import java.util.stream.Stream;

public class Drafts5 {

public static void main(String[] args) {

Stream stream = Stream.of("捣乱王", "捣乱黑", "捣乱绿", "捣乱红");

stream.skip(1).limit(2).forEach((s) -> System.out.println(s));

}

}

捣乱黑

捣乱绿

3.5 concat 组合

如果有两个流, 希望合为一个流, 那么可以使用Stream接口的==静态方法==concat

static Stream concat(Stream extends T> a, Stream extends T> b): 把参数列表中的 两个Stream流对象a和b,合并成一个新的Stream流对象

package drafts.drafts6;

import java.util.stream.Stream;

public class Drafts6 {

public static void main(String[] args) {

Stream stream = Stream.of("捣乱王");

Stream stream1 = Stream.of("捣乱绿");

Stream.concat(stream, stream1).forEach((s) -> System.out.println(s));

}

}

捣乱王

捣乱绿

Stream 综合案例

现在有两个ArrayList 集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下 若干操作步骤:第一个队伍只要名字为3个字的成员姓名;

第一个队伍筛选之后只要前3个人;

第二个队伍只要姓张的成员姓名;

第二个队伍筛选之后不要前2个人;

将两个队伍合并为一个队伍;

打印整个队伍的姓名信息。

两个队伍(集合)的代码如下:

public class Demo21ArrayListNames {

public static void main(String[] args) {

List one = new ArrayList<>();

one.add("迪丽热巴");

one.add("宋远桥");

one.add("苏星河");

one.add("老子");

one.add("庄子");

one.add("孙子");

one.add("洪七公");

List two = new ArrayList<>();

two.add("古力娜扎");

two.add("张无忌");

two.add("张三丰");

two.add("赵丽颖");

two.add("张二狗");

two.add("张天爱");

two.add("张三");

// ....

Stream streamOne =

one.stream().filter(s -> s.length() == 3).limit(3);

Stream streamTwo =

two.stream().filter(s ->s.startsWith("张")).skip(2);

Stream.concat(s1, s2).forEach((s) -> System.out.println(s));

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值