2w字合集 | 函数式编程—Stream流
Posted 结构化思维wz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2w字合集 | 函数式编程—Stream流相关的知识,希望对你有一定的参考价值。
文章目录
函数式编程思想:
面向对象思想需要关注用什么对象完成什么事情,儿函数式编程思想类似于我们数学中的函数。它关注的是对数据进行了什么操作。
优点:
- 代码简介,开发快速
- 接近自然语言,易于理解
- 易于"并发编程"
Stream流前置知识
一、Lambda表达式
概述
Lambda是JDK8中的一个语法糖。他可以对某些匿名内部类的写法进行简化,他是函数式编程思想的一个重要体现。让我们不用关注是什么对象,而是更关注我们对数据进行了什么操作。
核心原则:可推倒可省略
不关注类名,方法名。只关注方法体!
基本格式
(参数类型 参数名称) ->
方法体;
例一:多线程中创建线程的写法
new Thread(new Runnable()
@Override
public void run()
System.out.println("我是新建线程1的输出");
).start();
Lambda写法:
new Thread(()->
System.out.println("我是新建线程2的输出")
).start();
例二:
现有方法定义如下,其中IntBinaryOperation
是一个接口,先试用匿名内部类的写法调用该方法,再用Lambda
写法。
public class Lambda01
public static void main(String[] args)
System.out.println(calculateNum(new IntBinaryOperator()
@Override
public int applyAsInt(int left, int right)
return left + right;
));
/**
* 操作两个数
* @param binaryOperator
* @return applyAsInt
*/
public static int calculateNum(IntBinaryOperator binaryOperator)
int a = 10;
int b = 20;
return binaryOperator.applyAsInt(a,b);
Lambda写法:
public class Lambda01
public static void main(String[] args)
System.out.println(calculateNum((int left,int right)->
return left+right;
));
/**
* 操作两个数
* @param binaryOperator
* @return applyAsInt
*/
public static int calculateNum(IntBinaryOperator binaryOperator)
int a = 10;
int b = 20;
return binaryOperator.applyAsInt(a,b);
Lambda省略规则
- 参数类型可以省略
- 方法体中如果只有一句代码,
,
return
和唯一一句代码的;
可以省略 - 方法只有一个参数时
()
可以省略
Lambda总结
1.使用前提:
Lambda表达式语法是非常简洁的,但是Lambda
表达式不是随便使用的,使用时有几个条件要特别注意:
- 方法的参数或局部变量类型必须是接口才能使用Lambda
- 接口中有且仅有一个抽象方法(@FunctionalIneterface)
2.与匿名内部类的对比
-
所需类型不同
匿名内部类的类型可以是:类,抽象类,接口。
Lambda表达式需要的类型必须是
接口
。 -
抽象发发数量不同
匿名内部类所需的接口中的抽象方法的数量是随意的。
Lambda表达式所需的接口中只能有
一个抽象方法
-
实现原理不同
匿名内部类是在编译后形成.class
Lambda表达式是在程序运行的时候
动态生成.class
二、方法引用 | ::
在使用Lambda时,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用发发引用进一步简化代码
类名或者对象名 :: 方法名
推荐用法:
如果lambda方法体中只有一行代码,并且是方法的调用。
java8方法引用有四种形式:
- 静态方法引用 : ClassName :: staticMethodName
- 构造器引用 : ClassName :: new
- 类的任意对象的实例方法引用: ClassName :: instanceMethodName
- 特定对象的实例方法引用 : object :: instanceMethodName
三、Optional
概述
本质上,Optional
是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。我们要知道,Optional
是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现。但是 Optional
的意义显然不止于此。我们知道,任何访问对象方法或属性的调用都可能导致 NullPointerException
,在这里,我举个简单的例子来说明一下:
我们在编写代码的时候经常会出现空指针异常,为了避免空指针异常我们需要做各种非空的判断:
Author author = getAuthor();
if(author != null)
System.out.println(author.getName());
尤其是对象中的属性还是一个对象的情况下。这种判断会更多。而过多的判断会让我们的代码显得很臃肿。
Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
使用
Optional的构造函数
Optional 的三种构造方式:Optional.of(obj)
, Optional.ofNullable(obj)
和明确的 Optional.empty()
Optional.of(obj):它要求传入的 obj
不能是 null
值的, 否则直接报NullPointerException
异常。
Optional.ofNullable(obj):它以一种智能的
,宽容的方式来构造一个 Optional
实例。来者不拒,传 null 进到就得到 Optional.empty()
,非 null 就调用 Optional.of(obj).
Optional.empty():返回一个空的 Optional 对象。
Optional的常用函数
isPresent
:如果值存在返回true,否则返回false。
ifPresent
:如果Optional实例有值则为其调用consumer,否则不做处理
get
:如果Optional有值则将其返回,否则抛出NoSuchElementException。因此也不经常用。
orElse
:如果有值则将其返回,否则返回指定的其它值。
orElseGet
:orElseGet与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值
orElseThrow
:如果有值则将其返回,否则抛出supplier接口创建的异常。
filter
:如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional。
map
:如果有值,则对其执行调用mapping函数得到返回值。如果返回值不为null,则创建包含mapping返回值的Optional作为map方法返回值,否则返回空Optional。
flatMap
:如果有值,为其执行mapping函数返回Optional类型返回值,否则返回空Optional。
Optional 应该怎样用
在使用 Optional
的时候需要考虑一些事情,以决定什么时候怎样使用它。重要的一点是 Optional
不是 Serializable
。因此,它不应该用作类的字段。如果你需要序列化的对象包含 Optional 值,Jackson 库支持把 Optional 当作普通对象。也就是说,Jackson 会把空对象看作 null,而有值的对象则把其值看作对应域的值。这个功能在 jackson-modules-java8
项目中。Optional 主要用作返回类型
,在获取到这个类型的实例后,如果它有值,你可以取得这个值,否则可以进行一些替代行为。Optional 类可以将其与流或其它返回 Optional 的方法结合,以构建流畅的API
。我们来看一个示例,我们不使用Optional写代码是这样的
public String getName(User user)
if(user == null)
return "Unknown";
else return user.name();
接着我们来改造一下上面的代码,使用Optional来改造,我们先来举一个Optional滥用,没有达到流畅的链式API,反而复杂的例子,如下
public String getName(User user)
Optional<User> u = Optional.ofNullable(user);
if(!u.isPresent())
return "Unknown";
else return u.get().name();
这样改写非但不简洁,而且其操作还是和第一段代码一样。无非就是用isPresent
方法来替代原先user==null。这样的改写并不是Optional正确的用法,我们再来改写一次。
public String getName(User user)
return Optional.ofNullable(user)
.map(u -> u.name)
.orElse("Unknown");
这样才是正确使用Optional的姿势。那么按照这种思路,我们可以安心的进行链式调用,而不是一层层判断了。当然,我们还可以通过getter方式,对代码进行进一步缩减(前提是User要有getter方法哦),如下
String result = Optional.ofNullable(user)
.flatMap(User::getAddress)
.flatMap(Address::getCountry)
.map(Country::getIsocode)
.orElse("default");
四、常用的函数式接口
JDK8 之前:
interface
静态常量;
抽象方法;
JDK8 之后:
interface
静态常量;
抽象方法;
默认方法;
静态方法;
默认方法通过实例调用,静态方法可以通过接口名调用;
默认方法可以被继承,实现类可以直接调用接口的默认方法,也可以重写接口默认方法;
静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名调用。
函数式接口
有且仅有一个
抽象方法
,就是函数式接口。在java.util.function包中。一般函数式接口中还包含默认方法,通常用语配合函数式接口使用。
@FunctionalIneterface
所修饰的接口只能定义一个抽象方法。—>函数式接口。
Supplier
有参无返回值的接口,用来生产数据的。
这是一个函数式接口,其函数式方法是get() 。
自:
1.8
类型形参:
<T> – 此供应商提供的结果类型
@FunctionalInterface
public interface Supplier<T>
/**
* Gets a result.
*
* @return a result
*/
T get();
对应的Lambda表达式:
public class SupplierTest
/**
* 输出 max:66
* @param args
*/
public static void main(String[] args)
fun1(() ->
int arr[] = 22,33,11,44,55,66,12;
//计算出数组中的最大值
Arrays.sort(arr);
return arr[arr.length-1];
);
/**
* 完成数据的处理,例如找最大值
*/
private static void fun1(Supplier<Integer> supplier)
Integer max = supplier.get();
System.out.println("max"+max);
Consumer
有参无返回值的接口,用来消费数据,使用的时候需要指定一个泛型来定义参数类型
@FunctionalInterface
public interface Consumer<T>
void accept(T t);
//如果一个方法的参数和返回值全部是Consumer类型就可以实现连续操作:
//消费数据之前 先做一个操作,再做一个操作,在消费: consumer1.andThen(consumer2).accept(参数);
default Consumer<T> andThen(Consumer<? super T> after)
Objects.requireNonNull(after);
return (T t) -> accept(t); after.accept(t); ;
Lambda使用:
public class ConsumerTest
public static void main(String[] args)
test(msg->
//转小写
msg = msg.toLowerCase();
System.out.println(msg);
);
public static void test(Consumer<String> consumer)
consumer.accept("HELLO,WORLD!");
Function
有参有返回值的接口,根据一个类型的数据得到另一个类型的数据,前者成为前置条件,后者成为后置条件。
首先我们已经知道了Function是一个泛型类,其中定义了两个泛型参数T和R,在Function中,T代表输入参数,R代表返回的结果。
Function 就是一个函数,其作用类似于数学中函数的定义 ,(x,y)跟<T,R>的作用几乎一致。
R=function(T)
所以Function中没有具体的操作,具体的操作需要我们去为它指定,因此apply具体返回的结果取决于传入的lambda表达式。
R apply(T t);
/**表示接受一个参数并产生结果的函数。
这是一个功能接口,其功能方法是apply(Object) 。
自:
1.8
类型形参:
<T> – 函数输入的类型
<R> - 函数结果的类型*/
@FunctionalInterface
public interface Function<T, R>
/**返回一个组合函数,该函数首先将before函数应用于其输入,然后将此函数应用于结果。 如果对任一函数的评估引发异常,则将其转发给组合函数的调用者。
形参:
before – 在应用此函数之前要应用的函数
类型形参:
<V> – before函数和组合函数的输入类型
返回值:
首先应用before函数然后应用此函数的组合函数
*/
R apply(T t);
lambda用法:
public class FunctionTest
public static void main(String[] args)
// 6
fun(arr->
return arr+1;
);
//数组转字符串
public static void fun(Function<Integer, Integer> fun1)
System.out.println(fun1.apply(5));
Predicate
Predicate是个断言式接口其参数是<T,boolean>,也就是给一个参数T,返回boolean类型的结果。跟Function一样,Predicate的具体实现也是根据传入的lambda表达式来决定的。
@FunctionalInterface
public interface Predicate<T>
boolean test(T t);
基本使用:
import java.util.function.Predicate;
//字符串很长吗:true
public class Demo01Predicate
public static void main(String[] args)
method(s -> s.length() > 5);
private static void method(Predicate<String> predicate)
boolean veryLong = predicate.test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
Stream流
为什么学习Stream流?
- 工作需要
- 大数量下处理集合效率更高
- 代码可读性高
- 消灭嵌套地狱
概述
Java8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式操作。(类似于工厂中传送带)。可以更方便的让我们对集合或数组操作。
Stream产生的背景:
随着项目复杂度越来越高,数据源越来越多,有些数据无法通过SQL语句来查询,需要后期的组合筛选等,这就需要更高效的操作手段,如果使用遍历不但写法复杂,程序也会变得很复杂,Stream的出现大大增加了工作效率和程序的美观和效率。
2.使用案例
package stream.apple;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* @ClassName: Stream01
* @Description:
* @author: 结构化思维wz
* @date: 2021/12/7 21:34
*/
public class Stream01
private static List<Apple> appleStore = new ArrayList<>();
static
appleStore.add(new Apple(1,Color.RED,400,"重庆"));
appleStore.add(new Apple(2,Color.GREEN,300,"河北"));
appleStore.add(new Apple(3,Color.RED,500,"湖南"));
appleStore.add(new Apple(4,Color.YELLOW,400,"天津"));
appleStore.add(new Apple(5,Color.RED,500,"北京"));
/**
* 统计指定颜色的苹果
*/
public static void main(String[] args)
List<Apple> redApple = new ArrayList<>();
for (Apple apple : appleStore)
if (apple.getColor().equals(Color.RED))
redApple.add(apple);
redApple.forEach(System.out::println);
List<Apple> list = appleStore.stream()
.filter(apple -> apple.getColor().equals(Color.RED) && apple.getWeight()>400)
.collect(Collectors.toList());
list.forEach(System.out::println);
//控制台输出
Apple(id=1, color=RED, weight=400, origin=重庆)
Apple(id=3, color=RED, weight=500, origin=湖南)
Apple(id=5, color=RED, weight=500, origin=北京)
Apple(id=3, color=RED, weight=500, origin=湖南)
Apple(id=5, color=RED, weight=500, origin=北京)
通过上面的例子:可以看见流的方便之处,不仅仅是代码简洁,如果过滤的信息更多if就会更多,代码很不美观。使用Stream流只需要:filter(apple ->apple.getColor.equals("RED")&&apple.getWeight()>300)
。还可以进行 统计,分组等等…操作!
Stream流的两个基础特征:
Pipelining:
中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格。这样做可以对操作进行优化,比如延迟执行和短路。内部迭代:
以前集合遍历都是通过显示的在集合外部进行迭代,Stream提供了内部迭代的方式,流可以直接调用遍历方法。
当时用一个流的时候,通常包括三个基本步骤:
- 获取一个数据源
- 数据转换
- 执行操作获取想要的结果
每次转换原有的Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
Stream流的创建
顺序流和并行流:
java.util.stream.Stream<T>
以上是关于2w字合集 | 函数式编程—Stream流的主要内容,如果未能解决你的问题,请参考以下文章
Java高阶进阶之Java函数式编程-Stream流-Lambda表达式