函数式编程-Stream流
1 简介
1.1 什么是函数式编程?
函数式编程式JDK1.8的新特性,具体特性如下:
- 是一种编程范式,与OOP是同一层次的思想
- 注重描述过程而非具体执行步骤
- 更关心代数结构之间的关系
- 不可变,输入确定之后便就确定了函数式关系
1.2 函数式编程有什么优点?
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 易于"并发编程"
1.3 为什么要引入函数式编程?
为了解释这个问题,我参考了很多关于函数式编程的文章,并结合自己的理解进行归纳总结如下:
- 函数式编程源于数学函数,数学函数表示计算过程,这就类比我们java中的方法。这个时候你肯定又回问了,java不是已经有方法了嘛,还弄个函数式编程作甚?下面来看个例子就恍然大悟了…
- 在JS中,函数是可以像变量随时定义并使用的,如下所示。所以在JS中,函数
Function
是“一等公民”。
function add(a,b) {
return a+b
}
function subtract(a,b) {
return a-b
}
const a = 2
const b = 3
console.log(add(b,a))
console.log(subtract(b,a))
- 在Java中则不能像JS那样做,因为Java是OOP,无法直接实现。需要封装在对象中才能存在(这里不考虑POP),所以我称Java中的
方法
为“二等公民”,必须依附对象才能存在。
package com.cycyong;
public class test {
private int a;
private int b;
public test(int a, int b) {
this.a = a;
this.b = b;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
public int add(){
return this.a+this.b;
}
public int subtract(){
return this.a-this.b;
}
}
- 为了能在Java中随时使用函数的性质,JDK1.8引入了函数式编程,我们就可以随时使用函数的性质了!
2 Lambda函数式编程表达式
2.1 Lambda表达式介绍
Lambda属于函数式编程,是JDK1.8引入的新特性,也可以叫做语法糖。它可以对某些匿名内部类的写法进行简化,也是我之前提到的函数式编程思想的一个重要体现。让我们不用再关注什么是对象,而是更关注数据对我们进行了什么操作。
2.2 基本格式
(参数列表)->{代码}
2.3 简单例子
例1:我们在创建线程并启动时可以使用匿名内部类的写法,也可以使用Lambda的格式对其进行修改。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("cycyong");
}
}).start();
使用Lambda的写法改进之后,会简洁不少。
new Thread(()->{
System.out.println("cycyong");
}).start();
例2:现有方法定义如下,其中IntBinaryOperator是一个接口。先使用匿名内部类的写法调用该方法。
public static int calculateNum(IntBinaryOperator operator){
int a = 10;
int b = 20;
return operator.applyAsInt(a, b);
}
public static void main(String[] args) {
int i = calculateNum(new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return left + right;
}
});
System.out.println(i);
}
使用Lambda的写法
public static void main(String[] args) {
int i = calculateNum((int left, int right)->{
return left + right;
});
System.out.println(i);
}
例3:现有方法定义如下,其中IntPredicate是一个接口:
public static void printNum(IntPredicate predicate){
int[] arr = {1,2,3,4,5,6,7,8,9,10};
for (int i : arr) {
if(predicate.test(i)){
System.out.println(i);
}
}
}
public static void main(String[] args) {
printNum(new IntPredicate() {
@Override
public boolean test(int value) {
return value%2==0;
}
});
}
经过Lambda改进之后:
public static void main(String[] args) {
printNum((int value)-> {
return value%2==0;
});
}
2.4 省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号可以省略
- 以上这些规则都记不住也可以省略不记
3 Stream流
3.1 Stream流简要介绍
JDK1.8的Stream流使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组链状流式的操作,可以方便的让我们对集合或数组操作。
3.2 案例数据准备
导入依赖:
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
pojo实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Author {
//id
private Long id;
//姓名
private String name;
//年龄
private Integer age;
//简介
private String intro;
//作品
private List<Book> books;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Book {
//id
private Long id;
//书名
private String name;
//分类
private String category;
//评分
private Integer score;
//简介
private String intro;
}
实验假数据
private static List<Author> getAuthors() {
//数据初始化
Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
Author author2 = new Author(2L,"亚拉索",15,"狂风也追逐不上他的思考速度",null);
Author author3 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
Author author4 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
//书籍列表
List<Book> books1 = new ArrayList<>();
List<Book> books2 = new ArrayList<>();
List<Book> books3 = new ArrayList<>();
books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀划分了爱恨"));
books1.add(new Book(2L,"一个人不能死在同一把刀下","个人成长,爱情",99,"讲述如何从失败中明悟真理"));
books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
books2.add(new Book(4L,"吹或不吹","爱情,个人传记",56,"一个哲学家的恋爱观注定很难把他所在的时代理解"));
books3.add(new Book(5L,"你的剑就是我的剑","爱情",56,"无法想象一个武者能对他的伴侣这么的宽容"));
books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
author.setBooks(books1);
author2.setBooks(books2);
author3.setBooks(books3);
author4.setBooks(books3);
List<Author> authorList = new ArrayList<>(Arrays.asList(author,author2,author3,author4));
return authorList;
}
3.3 快速入门
3.1.2 需求
我们可以调用getAuthors方法获取到作家的集合。现在需要打印所有年龄小于18的作家的名字,并且要注意去重。
3.2.2 实现
没有使用Lambda表达式:
想要转成Lambda表达式则移动函数部分,ctrl+Enter
即可
//打印所有年龄小于18的作家的名字,并且要注意去重
getAuthors().stream()
.distinct()
.map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
})
.forEach(new Consumer<String>() {
@Override
public void accept(String name) {
System.out.println(name);
}
});
转换成Lambda表达式之后,感觉特别清爽!
//打印所有年龄小于18的作家的名字,并且要注意去重
getAuthors().stream()
.distinct()
.map(author -> author.getName())
.forEach(name -> System.out.println(name));
3.4 常用操作
3.4.1 创建Stream流
单列集合:集合对象.stream()
List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();
数组:Arrays.stream(数组)
或者使用Stream.of
来创建
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
双列集合:转换成单列集合后再创建
Tips:转成单列集合的意思是说将map集合键和值作为一个整体
Map<String,Integer> map = new HashMap<>();
map.put("蜡笔小新",19);
map.put("黑子",17);
map.put("日向翔阳",16);
//entrySet():将原有Map集合中的键值作为一个整体返回Set集合
Stream<Map.Entry<St@ring, Integer>> stream = map.entrySet().stream();
3.4.2 中间操作
filter
顾名思义就是对流中数据进行过滤的意思,符合过滤条件的才能留在流中。
简单例子:打印所有姓名长度大于1的作家的姓名
//打印所有姓名长度大于1的作家的姓名
getAuthors().stream()
.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getName().length()>1;
}
})
.forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
//打印所有姓名长度大于1的作家的姓名
getAuthors().stream()
.filter(author -> author.getName().length()>1)
.forEach(author -> System.out.println(author.getName()));
map
可以把对流中的元素进行计算或转换。
转换的简单例子:打印所有作家的姓名
注意:转换之后,流中的数据就从Author类型转换成String类型。另外参数的名字是可以随意指定的,但是最好见名知意。
getAuthors().stream()
.map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
})
.forEach(new Consumer<String>() {
@Override
public void accept(String name) {
System.out.println(name);
}
});
getAuthors().stream()
.map(author -> author.getName())
.forEach(name -> System.out.println(name));
计算的简单例子:将所有作家的年龄加上十岁并输出
注意:这里计算之后,原流中的数据已经改变,因为笔者这里使用了setAge
的方法,改变了原来作家的年龄。 另外Function
的第一个参数表示apply
函数的参数类型,第二个参数为返回类型,且第一个参数必须和Stream流中的参数相同,Author
就是Author
,String
就是String
,不能改变,否则报错。
getAuthors().stream()
.map(new Function<Author, Author>() {
@Override
public Author apply(Author author) {
author.setAge(author.getAge()+10);
return author;
}
})
.forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author.getAge());
}
});
getAuthors().stream()
.map(author -> {
author.setAge(author.getAge()+10);
return author;
})
.forEach(author -> System.out.println(author.getAge()));
distinct
和MySQL数据库一样,distinct可以去除流中的重复元素。
简单例子:打印所有作家的姓名,并且要求其中不能有重复元素。
//打印所有作家的姓名,并且要求其中不能有重复元素。
getAuthors().stream()
.distinct()
.forEach(author -> System.out.println(author.getName()));
注意:distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重写equals方法。新版本Lombok的@Data已包含。
sorted
可以对流中的元素进行排序。
简单例子:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
有参数调用:sorted(new Compartor<?>)
//对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
getAuthors().stream()
.distinct()
.sorted(new Comparator<Author>() {
@Override
public int compare(Author o1, Author o2) {
return o1.getAge()-o2.getAge();
}
})
.forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author.getAge());
}
});
//对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
getAuthors().stream()
.distinct()
.sorted((o1, o2) -> o1.getAge()-o2.getAge())
.forEach(author -> System.out.println(author.getAge()));
空参数调用:sorted()
getAuthors().stream()
.distinct()
.sorted()
.forEach(author -> System.out.println(author.getAge()));
注意:调用空参数sorted()
的话,必须在实体类pojo中实现compareTo
方法
package com.cycyong.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.Comparator;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Author implements Comparable<Author> {
private Long id;
private String name;
private Integer age;
private String intro;
private List<Book> books;
@Override
public int compareTo(Author o) {
return this.getAge()-o.getAge();
}
}
Tips:无论是有参构造还是无参后遭,都不需要去死记哪个在前哪个在后,没有必要,写完调试一番就知道答案,要学会思想和方法才是最重要的!
limit
可以设置流的最大长度,超出的部分将被抛弃。
简单例子:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名。
//对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名。
getAuthors().stream()
.distinct()
.sorted((o1, o2) -> o2.getAge()-o1.getAge())
.limit(2)
.forEach(author -> System.out.println(author.getName()));
skip
跳过流中的前n个元素,返回剩下的元素
简单例子:打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。
//打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。
getAuthors().stream()
.distinct()
.sorted(((o1, o2) -> o2.getAge()-o1.getAge()))
.skip(1)
.forEach(author -> System.out.println(author.getName()+" "+author.getAge()));
flatMap
map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
简单例子1:打印所有书籍的名字。要求对重复的元素进行去重。
错误使用map:会产生流中流的情况
//打印所有书籍的名字。要求对重复的元素进行去重。
getAuthors().stream()
.map(author -> author.getBooks())
.map(new Function<List<Book>, Object>() {
@Override
public Object apply(List<Book> books) {
return books.stream();
}
})
.forEach(new Consumer<Object>() {
@Override
public void accept(Object o) {
System.out.println(o);
}
});
正确的应该是使用flatMap
,产生Book
对象的流
注意:由于Function
函数的参数为stream<?>
,函数并不知道你要转成什么流,因此要改成Book
//打印所有书籍的名字。要求对重复的元素进行去重。
getAuthors().stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.forEach(book -> System.out.println(book.getName()));
//打印所有书籍的名字。要求对重复的元素进行去重。
getAuthors().stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.forEach(book -> System.out.println(book.getName()));
简单例子2:打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情
//打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情
List<Author> authors = getAuthors();
authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.flatMap(book -> Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(category-> System.out.println(category));
3.5 终结操作
没有终结操作的流是不会执行的
forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体操作。
简单例子:输出所有作家的名字
//输出所有作家的名字
getAuthors().stream()
.distinct()
.map(author -> author.getName())
.forEach(name-> System.out.println(name));
count
可以用来获取当前流中元素的个数。
简单例子:打印这些作家的所出书籍的数目,注意删除重复元素。
//打印这些作家的所出书籍的数目,注意删除重复元素。
long count = getAuthors().stream()
.distinct()
.flatMap(author -> author.getBooks().stream())
.distinct()
.count();
System.out.println(count);
max&min
可以用来或者流中的最值。
简单例子:分别获取这些作家的所出书籍的最高分和最低分并打印。
List<Author> authors = getAuthors();
Optional<Integer> max = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getScore())
.max((score1, score2) -> score1 - score2);
Optional<Integer> min = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getScore())
.min((score1, score2) -> score1 - score2);
System.out.println(max.get());
System.out.println(min.get());
collect
把当前流转换成一个集合。
简单例子1:获取一个存放所有作者名字的List集合。
List<String> name = getAuthors().stream()
.distinct()
.map(author -> author.getName())
.collect(Collectors.toList());
for (String s : name) {
System.out.println(s);
}
简单例子2:获取一个所有书名的Set集合。
注意:Set集合是自动去重的!
Set<String> books = getAuthors().stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getName())
.collect(Collectors.toSet());
for(String book:books){
System.out.println(book);
}
简单例子3:获取一个Map集合,map的key为作者名,value为Book集合List
List<Author> authors = getAuthors();
Map<String, List<Book>> map = authors.stream()
.distinct()
.collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
System.out.println(map);
查找与匹配
anyMatch
可以用来判断是否存在符合匹配条件的元素,结果为boolean类型。
简单例子:判断是否有年龄在29以上的作家
boolean match = getAuthors().stream()
.anyMatch(author -> author.getAge() > 29);
System.out.println(match);
allMatch
可以用来判断是否都符合匹配条件,结果为boolean类型。
简单例子:判断是否有年龄在29以上的作家
boolean match = getAuthors().stream()
.anyMatch(author -> author.getAge() > 29);
System.out.println(match);
anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型。
简单例子:判断是否所有的作家都是成年人
List<Author> authors = getAuthors();
boolean flag = authors.stream()
.allMatch(author -> author.getAge() >= 18);
System.out.println(flag);
noneMatch
可以判断流中的元素是否都不符合匹配条件,结果为boolean类型。
简单例子:判断作家是否都没有超过100岁的。
List<Author> authors = getAuthors();
boolean b = authors.stream()
.noneMatch(author -> author.getAge() > 100);
System.out.println(b);
findAny
获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素。
简单例子:获取任意一个年龄大于18的作家,如果存在就输出他的名字
Optional<Author> one = getAuthors().stream()
.filter(author -> author.getAge() > 18)
.findAny();
System.out.println(one.get().getName());
findFirst
获取流中的第一个元素。
简单例子:获取一个年龄最小的作家,并输出他的姓名。
Optional<String> first = getAuthors().stream()
.distinct()
.sorted((o1, o2) -> o1.getAge() - o2.getAge())
.map(author -> author.getName())
.findFirst();
System.out.println(first.get());
reduce归并
对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值进行计算,计算结果再和后面的元素计算。
reduce两个参数的重载形式内部的计算方式如下:
T result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
其中identity
就是我们可以通过方法参数传入的初始值,accumulator的apply
具体进行什么计算也是我们通过方法参数来确定的。
简单例子:使用reduce求所有作者年龄的和
Integer sum = getAuthors().stream()
.map(author -> author.getAge())
.reduce(0, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result = result + element;
}
});
System.out.println(sum);
List<Author> authors = getAuthors();
Integer sum = authors.stream()
.distinct()
.map(author -> author.getAge())
.reduce(0, (result, element) -> result + element);
System.out.println(sum);
注意:
result
为返回结果的累加器,element
为遍历对象result
在这里就是我们reduce的第一个参数,初始值
简单例子2:使用reduce求所有作者中年龄的最大值
Integer max = getAuthors().stream()
.map(author -> author.getAge())
.reduce(Integer.MIN_VALUE, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result < element ? element : result;
}
});
System.out.println(max);
Integer max = getAuthors().stream()
.map(author -> author.getAge())
.reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);
System.out.println(max);
简单例子3:使用reduce求所有作者中年龄的最小值
Integer min = getAuthors().stream()
.map(author -> author.getAge())
.reduce(Integer.MAX_VALUE, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result > element ? element : result;
}
});
System.out.println(min);
Integer min = getAuthors().stream()
.map(author -> author.getAge())
.reduce(Integer.MAX_VALUE, (result, element) -> result > element ? element : result);
System.out.println(min);
注意:
Integer.MAX_VALUE
是Integer的最大值Integer.MIN_VALUE
是Integer的最小值
3.6 注意事项
- 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
- 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
- 不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)
4 函数式接口
4.1 简介
只有一个抽象方法的接口我们称之为函数接口。
JDK1.8的函数式接口都加上了 @FunctionalInterface
注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
4.2 常见的函数式接口
Consumer 消费接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
Function 计算转换接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回。
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
Predicate 判断接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
Supplier 生产型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回。
/**
* Gets a result.
*
* @return a result
*/
T get();
4.3 常用的默认方法
and
我们在使用Predicate接口时候可能需要进行判断条件的拼接。而and方法相当于是使用&&来拼接两个判断条件。
简单例子:打印作家中年龄大于17并且姓名的长度大于1的作家。
getAuthors().stream()
.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge()>17;
}
}.and(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getName().length()>1;
}
}))
.forEach(author -> System.out.println(author.getName()));
getAuthors().stream()
.filter(((Predicate<Author>) author -> author.getAge() > 17).and(author -> author.getName().length()>1))
.forEach(author -> System.out.println(author.getName()));
or
我们在使用Predicate接口时候可能需要进行判断条件的拼接。而or方法相当于是使用||来拼接两个判断条件。
简单例子:打印作家中年龄大于17或者姓名的长度小于2的作家。
getAuthors().stream()
.filter(((Predicate<Author>) author -> author.getAge() > 17).or(author -> author.getName().length()>1))
.forEach(author -> System.out.println(author.getName()));
negate
Predicate接口中的方法。negate方法相当于是在判断添加前面加了个! 表示取反。
简单例子:打印作家中年龄不大于17的作家。
getAuthors().stream()
.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge()>17;
}
}.negate())
.forEach(author -> System.out.println(author.getName()));
getAuthors().stream()
.filter(((Predicate<Author>) author -> author.getAge() > 17).negate())
.forEach(author -> System.out.println(author.getName()));
5 方法引用(了解即可)
我们在使用lambda时,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。
5.1 如何使用
我们在使用lambda时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。
当我们方法引用使用的多了慢慢的也可以直接写出方法引用。
5.2 基本使用
类名::方法名
5.3 语法详情
5.3.1 引用类的静态方法
格式:
类名::方法名
如下代码就可以用方法引用进行简化
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getAge())
.map(age->String.valueOf(age));
注意,如果我们所重写的方法是没有参数的,调用的方法也是没有参数的也相当于符合以上规则。
优化后如下:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getAge())
.map(String::valueOf);
5.3.2 引用类的静态方法
格式:
对象名::方法名
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法。
简单例子:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName())
.forEach(name->sb.append(name));
优化后:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName())
.forEach(sb::append);
5.3.3 引用类的实例方法
格式:
类名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。
6 高级应用
6.1 基本数据类型优化
我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。但是你一定要知道装箱和拆箱肯定是要消耗时间的。虽然这个时间消耗很下。但是在大量的数据不断的重复装箱拆箱的时候,你就不能无视这个时间损耗了。
所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方法。
例如:mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble等。
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getAge())
.map(age -> age + 10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);
authors.stream()
.mapToInt(author -> author.getAge())
.map(age -> age + 10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);
6.2 并行流
当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。
parallel方法可以把串行流转换成并行流。
private static void test28() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = stream.parallel()
.peek(new Consumer<Integer>() {
@Override
public void accept(Integer num) {
System.out.println(num+Thread.currentThread().getName());
}
})
.filter(num -> num > 5)
.reduce((result, ele) -> result + ele)
.get();
System.out.println(sum);
}
也可以通过parallelStream直接获取并行流对象。
List<Author> authors = getAuthors();
authors.parallelStream()
.map(author -> author.getAge())
.map(age -> age + 10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);