函数式编程Stream学习

JAVA函数式编程学习

函数式接口

java8中有四大函数式接口Function<T, R>(方法),Consumer(消费者), Supplier(提供者), Predicate(谓词)。函数式接口有一个共同特点,即只有一个抽象方法。这为我们可以使用lambda表达式创造了条件。


Function<T, R>

T:入参,R:出参

抽象方法:R apply(T t);

Consumer

T:入参,void:出参

抽象方法:void accept(T t)

Supplier

T: 出参 ,void:入参

抽象方法:T get();

Predicate

T: 入参,boolean:出参

抽象方法:boolean test(T t);


在java函数式编程中我们会经常使用到以上接口作为参数,我们要做的是使用lambda表达式对以上接口进行实现。


lambda表达式

在javaScript中我们经常会使用到这种方式传入回调函数,java从jdk8开始也引入了lambda表达式,这种表达式相对于匿名内部类有两个个特点

  • 不改变this指向

  • 省略参数类型的定义,他们将会被自动推导

但由于JAVA是面向对象的,所以即使我们传入的是方法,但实际上会在编译时使用该方法帮我们实现一个匿名类。

lambda表达式:()->{} 若参数有且仅有一个时,则可以省略(),若有返回值,且方法体有且仅有一条语句,则可以不加rerurn以及{}

示例:

  • 无参/多参,多条语句:(a, b) -> {a += 2; b += 3; return a + b;}
  • 一参,一条语句:item -> item+1

双引用

在使用lambda的时候,我们传入的实际是一个方法,但有时我们可能已经有了一个现有的方法,仅需对方法进行调用即可。这时候使用lambda表达式就显得有些冗余了,双引用就是为了解决这中情况。我们看一个例子。

public interface Printable{
    public void print(String args);
}
public void test(){
    // lambda表达式
    printHello((param)->{System.out.println(param)});
    // 方法引用
    printHello(System.out::println);
}
public void printHello(Printable print){
    print.print("hello world");
}

可以看到使用::的方式省略了 参数的定义和引用,写法虽然十分简洁,但要注意入参的类型和调用方法的入参类型是否匹配。

关于双引用的详细用法在这里就不详细展开了。


Stream

Stream(流)出现的目的是为了解决数据来源不一致,但要对数据进行处理,聚合等操作。按照以前java的处理逻辑必然要对每个集合进行循环遍历,然后将结果保存在一个集合中。而JAVA 8为我们提供了一种新的解决思路——流处理。

流处理中将要处理的元素看作是一个流,流在管道中进行传输,而我们则可以在管道的节点上进行处理,比如过滤,映射,排序等,在管道的末尾对流进行聚合或消费,从而得到最终的结果。

对流的操作分为三类

流操作

  • 源:源是流的数来源,支持由Collection,数组等数据类型生成流。

  • 中间操作:在流上进行操作,所有中间操作的返回结果都是一个新的流。

  • 最终操作:流的最终处理结果,流将会被消费,不再返回新的流,且无法再对流进行操作。


特性

流具有一些特性:不可重用性,惰性,短路。

  • 不可重用性:所有流都只能使用一次,经过一次流操作后将无法再使用该流。

  • 惰性:流的中间操作必须和最终操作一起使用,若仅仅只是用中间操作,则不会执行。

  • 短路:这是优化流的一种方式,一旦条件满足,将直接终止当前操作,比如limit


JAVA 8支持从不同的源中获取流,也有多种获取的方法。

  • Collection接口提供了stream方法:Stream<String> stream = new ArrayList<String>().stream();

  • Arrays类中提供了asStream方法,支持从数组中获取流:Stream<String> stream = Arrays.stream(T[]);

  • Stream接口的of方法:Stream.of(...T)

通过以上方式创建的流为串行流,JAVA 8中还支持并行流,提高运算的效率(需要注意并发问题,并且某些情况下并行流的效率不一定优于串行流)。关于并行流的创建其实也很类似,就不进行展开。


中间操作

中间操作有一个共同特点,返回结果为Stream类型。中间操作具有惰性,只有在遇到最终操作时才会对中间操作进行执行。下面举出几种常用中间操作。

  • map:映射,通过Function接口把元素从一个类型T转化为另一个类型R。

  • flatmap:通过Function接口把一个类型为T的元素中的每个元素转化为一个类型为R的流,再把这些流进行合并。(这里可能不太好理解,举个例子:[["hello", "world"], ["你好","世界"]] -> ["hello", "world", "你好", "世界"]

  • distinct:去重,值得一提的是实现方法其实是通过Set实现的,如果执行最终操作获得的是一个Set对象的话,则不会执行distinct。所以如果是自定义的类型,要记得实现hashCode和equals方法

  • limit:返回执行数量的元素,超出部分的流将被截断,是典型的短路操作。

  • filter:过滤,通过Pridicate接口,过滤掉不符合条件的元素


最终操作

最终操作将不再返回Stream,而是返回最终结果。根据具体情况返回其他类型或者空,这个操作也被称为消费。

常见最终操作

  • forEach:该方法接受一个Consumer,返回空类型,按照方法内容对元素进行消费。

  • collect:对流中的元素进行收集,并返回指定类型的集合,如List,Set,Map。

  • count:对元素个数进行统计,返回long类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值