Java 8 stream练习
目录
构造流的方法
// 1. Arrays
String [] strArray = new String[] {"a", "b", "c"};
Stream stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 2. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();
流 转化为其他数据结构
// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();
流的操作
1) 流的操作类型分为两种:
- Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
- Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
- 还有一种操作被称为 short-circuiting。用以指:
对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。
2) 接下来,当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。
- Intermediate:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
- Terminal:
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
- Short-circuiting:
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
Map
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
flatMap
flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
Stream<Integer> outputStream = inputStream.
flatMap((childList) -> childList.stream());
filter
filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();
Collectors
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
Optional
在更复杂的 if (xx != null) 的情况中,使用 Optional 代码的可读性更好,而且它提供的是编译时检查,能极大的降低 NPE 这种 Runtime Exception 对程序的影响,或者迫使程序员更早的在编码阶段处理空值问题,而不是留到运行时再发现和调试。
String strA = " abcd ", strB = null;
print(strA);
print("");
print(strB);
getLength(strA);
getLength("");
getLength(strB);
public static void print(String text) {
// Java 8
Optional.ofNullable(text).ifPresent(System.out::println);
// Pre-Java 8
if (text != null) {
System.out.println(text);
}
}
public static int getLength(String text) {
// Java 8
return Optional.ofNullable(text).map(String::length).orElse(-1);
// Pre-Java 8
// return if (text != null) ? text.length() : -1;
};
用 Collectors 来进行 reduction 操作
java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。
groupingBy/partitioningBy
按照年龄归组
Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()). limit(100). collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
}
上面的 code,首先生成 100 人的信息,然后按照年龄归组,相同年龄的人放到同一个 list 中,可以看到如下的输出:
Age 0 = 2
Age 1 = 2
Age 5 = 2
Age 8 = 1
Age 9 = 1
Age 11 = 2
……
按照未成年人和成年人归组
Map<Boolean, List<Person>> children = Stream.generate(new PersonSupplier()).
limit(100).
collect(Collectors.partitioningBy(p -> p.getAge() < 18));
System.out.println("Children number: " + children.get(true).size());
System.out.println("Adult number: " + children.get(false).size());
输出结果:
Children number: 23
Adult number: 77
在使用条件“年龄小于 18”进行分组后可以看到,不到 18 岁的未成年人是一组,成年人是另外一组。partitioningBy 其实是一种特殊的 groupingBy,它依照条件测试的是否两种结果来构造返回的数据结构,get(true) 和 get(false) 能即为全部的元素对象。
统计
另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
System.out.println("列表个数:" + stats.getCount());
小练习
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
class Trade {
private String name;
private String city;
public Trade(String name, String city) {
this.name = name;
this.city = city;
}
@Override
public String toString() {
return "Trade{" +
"name='" + name + '\'' +
", city='" + city + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
class Transaction {
private Trade trade;
private int year;
private int value;
public Transaction() {
}
public Transaction(Trade trade, int year, int value) {
this.trade = trade;
this.year = year;
this.value = value;
}
public Trade getTrade() {
return trade;
}
public void setTrade(Trade trade) {
this.trade = trade;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
@Override
public String toString() {
return "Transaction{" +
"trade=" + trade +
", year=" + year +
", value=" + value +
'}';
}
}
public class Demo {
List<Transaction> transactions = null;
@Before
public void before() {
Trade raoul = new Trade("Raoul", "Cambridge");
Trade mario = new Trade("Mario", "Milan");
Trade alan = new Trade("Alan", "Cambridge");
Trade brian = new Trade("Brian", "Cambridge");
transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
}
// 找出2011年发生的所有交易,并按交易额排序(从低到高)
@Test
public void test1() {
List<Transaction> res = transactions.stream().
filter(e -> e.getYear() == 2011).
sorted((e1,e2) -> Integer.compare(e1.getValue(), e2.getValue())).
collect(Collectors.toList());
System.out.println(res);
}
// 找出交易员都在哪些不同的城市工作过?
@Test
public void test2() {
List<String> list = transactions.stream().
map(e -> e.getTrade().getCity()).distinct().
collect(Collectors.toList());
System.out.println(list);
}
// 查找出所有来自剑桥的交易员,并按姓名排序
@Test
public void test3() {
List<Trade> list = transactions.stream().filter(e -> e.getTrade().getCity().equals("Cambridge")).
map(e -> e.getTrade()).sorted((e1, e2) -> e1.getName().compareTo(e2.getName())).
collect(Collectors.toList());
System.out.println(list);
}
// 返回所有交易员的姓名字符串,按字母顺序排序
@Test
public void test4() {
//String result = transactions.stream().map(e -> e.getTrade().getName()).
// flatMap(Demo::filterCharacter).
// sorted((s1, s2) -> s1.compareToIgnoreCase(s2)).
// collect(Collectors.joining());
//System.out.println(result);
// 流的扁平化
String result = transactions.stream().map(e -> e.getTrade().getName().split("")).
flatMap(Arrays::stream). // 将各个生成流扁平化为单个流
sorted((s1, s2) -> s1.compareToIgnoreCase(s2)).
collect(Collectors.joining());
System.out.println(result);
}
public static Stream<String> filterCharacter(String str) {
ArrayList<String> list = new ArrayList<>();
for (Character c: str.toCharArray()) {
list.add(c.toString());
}
return list.stream();
}
// 有没有交易员是在米兰工作的?
@Test
public void test5() {
boolean result = transactions.stream().anyMatch(e -> e.getTrade().getCity().equals("Milan"));
System.out.println(result);
}
// 打印生活在剑桥的交易员的所有交易额
@Test
public void test6() {
Optional<Integer> op = transactions.stream().filter(e -> e.getTrade().getCity().equals("Cambridge")).
map(Transaction::getValue).
reduce(Integer::sum);
System.out.println(op.get());
}
// 所有交易中,最高的交易额是多少
@Test
public void test7() {
Optional<Integer> op = transactions.stream().map(Transaction::getValue).
reduce(Integer::max);
System.out.println(op.get());
}
// 找到交易额最小的交易
@Test
public void test8() {
Optional<Transaction> op = transactions.stream().min(Comparator.comparing(Transaction::getValue));
System.out.println(op.get());
}
// 统计每年的交易额
@Test
public void test9() {
Map<String, Integer> result = transactions.stream().collect(Collectors.toMap(transaction -> String.valueOf(transaction.getYear()), transaction -> transaction.getValue(), (value1, value2) -> value1 + value2));
System.out.println(result);
}
}
指定字段去重
class StreamUtil {
/**
* 指定字段去重
* @param keyExtractor
* @return
*/
static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object,Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
}
@Test
public void test10() {
List<Transaction> list = transactions.stream().filter(StreamUtil.distinctByKey(e -> e.getYear())).collect(Collectors.toList());
System.out.println(list);
}