java8-stream
Stream 是java8中处理集合的关键抽象概念,他可以制定你希望对集合进行的操作,但是将执行操作的时间交给具体实现来决定。
要点
Stream Example:
@Test//*不使用Stream的方法*
public void testCount(){
List<String> words = getWords();
int count = 0;
for (String w: words) {
if(w.length()>10)count++;
}
System.out.println(count);
}
@Test//*使用Stream*
public void testStream(){
List<String> words = getWords();
long count=0;
count =words.stream().filter(w->w.length()>10).count();
System.out.println(count);
}
public List<String> getWords(){
List<String> words = new ArrayList<String>();
for (int i = 0; i < 20; i++) {
words.add("aaabbbccc"+i);
}
return words;
}
可以看出来Stream给我们带来了很大的方便性和可读性
当使用Stream时,通过三个阶段来建立一个操作流水线
1. 创建一个Stream
2. 在一个或多个步骤中,制定将初始Stream转换为另一个Stream的中间操作。
3. 使用一个终止操作来产生一个结果。该操作会强制他之前的延迟操作立即执行。在这之后,该Stream就不会在被使用了。
java8在Collection接口中添加了stream方法,可以将任何一个集合转化为一个Stream。
Stream<String> words = Stream.of(contents.split(","));
//split方法返回一个String[]数组
//of方法接受可变长度的参数,因此可以构造一个含有任何个参数的Stream
Stream<String> words = Stream.of("hello","world","java8","lambda");
使用Arrays.stream(array,from,to)方法将数组的一部分转化为Stream
如果要创建一个不含任何元素的Stream,可以使用静态的Stream.empty方法:
Stream<String> words = Stream.empty();
Stream接口有两个用来创建无限Stream的静态方法。generate方法接受一个无参函数,
@Test
public void testInit(){
Stream<String> echos = Stream.generate(()->"Echo");
Stream<Double> randoms= Stream.generate(Math::random);
}
Java8中添加了许多能够产生Stream的方法。比如 Pattern类添加了一个splitAsStream方法。
Stream<String> words = Pattern.complie(",").splitAsStream(contents);
Stream<String> words = getWords().stream().filter(w->w.length()>10);
//将单词转换为大写
Stream<String> words = getWords().stream().map(String::toUpperCase);
//获得单词的第一个字母
Stream<Character> wordsFirstChar = getWords().stream().map(s->s.charAt(0));
@Test
public void testMapAndFlatMap(){
Stream<Stream<Character>> chars = getWords().stream().map(w->getCharacter(w));
//这里可以用flatMap
//flatMap 会对每个对象调用getCharacter()方法,并展开结果
Stream<Character> charss=getWords().stream().flatMap(w->getCharacter(w));
}
public Stream<Character> getCharacter(String s){
List<Character> chars = new ArrayList<Character>();
for (char c: s.toCharArray()) {
chars.add(c);
}
return chars.stream();
}
//返回1个具有100个随机数的流
Stream<Double> randoms100 = Stream.generate(Math::random).limit(100);
//跳过第一个单词组成流
Stream<String> wordsWithoutFirst = getWords().stream().skip(1);
System.out.println(wordsWithoutFirst.count());//打印为 19 略过了第一个元素
Stream<String> total = Stream.concat(getWords().stream().skip(10), getWords().stream());
System.out.println(total.count());
上面的流转换都是无状态的。也就是获得的结果不依赖前面的元素。java8也提供了有状态的转换。例如distinct()
distinct()方法需要记录已存储的单词。然后判断重复及是否添加
Stream<String> unique = Arrays.asList("todo","todo","todo","today").stream().distinct();
System.out.println(unique.count());//2
sorted方法排序必须遍历整个流。并在产生任何元素之前对他进行排序。
Stream<String> sortWords = getWords().stream().sorted(Comparator.comparing(String::length).reversed());
Collections.sort方法会对原有集合进行排序,Stream.sorted方法会返回一个新的已排序的流;
聚合方法都是终止操作。当一个流应用了终止操作以后,他就不能在进行其他操作了。
聚合方法主要有:count(),max(),min(),findFirst,findAny,allMatch,noneMatch等
//找到排序最大的单词 min同理
Optional<String> max = getWords().stream().max(String::compareToIgnoreCase);
if(max.isPresent()){
System.out.println(max.get());
}
//找到所有的以a开头的单词
Optional<String> words = getWords().stream().filter(s->s.startsWith("a")).findAny();
//找到所有的以a开头的第一个单词
Optional<String> wordFirst = getWords().stream().filter(s->s.startsWith("a")).findFirst();
allMatch,noneMatch分别表示所有元素和没有元素匹配predicate返回true。
Optional<String> max = getWords().stream().max(String::compareToIgnoreCase);
if(max.isPresent()){
//do something
}
String result = max.orElse("");
String[] result = words.toArray(String[]::new);
HashSet<String> result = stream.collect(HashSet::new,HashSet::add,HashSet::addAll);
List<String> result = getWords().stream().collect(Collectors.toList());
List<String> result = getWords().stream().collect(Collectors.toSet());
TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet::new));
主要有IntStream、DoubleStream、LongStream等。
IntStream:short、char、byte、boolean
DoubleStream:float
LongStream:long
IntStream intStream = IntStream.of(1,2,3,4);
intStream = Arrays.stream(value,from,to); //value 为一个int[]数组
当有一个对象流的时候,可以通过mapToInt、mapToLong或者mapToDouble方法将他们转换为一个原始流。
Stream<String> words = getWords();
IntStream length = words.mapToInt(String::length);
将一个原始流转换为对象流时,可以使用boxed方法。
Stream<Integer> integers = IntStream.range(0,100).boxed();
默认情况下,流操作会创建一个串行流,方法Collection.parallelStream()除外。parallel方法可以将任意一个串行流变为一个并行流。
Stream<String> words = getWords().stream().parallel();
只要在终止方法执行时,流处于并行模式,那么所有的延迟执行的流操作就都会被并行执行。
当并行运行流时,他应返回与串行运行时相同的结果。这些操作都是无状态的,因此可以以任意顺序被执行
下面是一个反例,用来提示不应该这么做,假设你需要绩一个字符串流中所有短单词的总数:
int[] shortWords = new int[11];
Stream<String> words = Arrays.asList("java","c++","javac","eclipse","tomcat","helloworld").stream();
words.parallel().forEach(s->{if(s.length()<5)shortWords[s.length()]++;});
System.out.println(Arrays.toString(shortWords));
传递给foreach的函数会在多个线程中并发运行,来更新一个共享数组,这是一个经典的条件竞争。如果多运行几次,很有可能会得到不同的总数,并且没有一个是对的。
使用并行流操作的函数都应该是线程安全的。
当执行一个流(串行和并行)操作时,并不会修改流底层的集合,流不会收集他自己的数据–这些数据总是存在另一个集合中。如果想要修改原有的集合,那么就无法定义流操作的输出。
下面这么做是可以的。
List<String> words = ...;
Stream<String> wordStream=words.stream();
words.add("end");
long n = wordStream.distinct().count();
下面是不可以的,因为干扰了原集合
Stream<String> wordStream = words.stream();
wordStream.forEach(s->if(s.length<12)words.remove(s));