集合优化了对象的存储,流和对象的处理有关
利用流,我们无需迭代集合中的元素,就可以提取和操作它们。这些管道通常被组合在一起,在流上形成一条操作管道。
举个例子,假如你要随机展示 5 至 20 之间不重复的整数并进行排序。实际上,你的关注点首先是创建一个有序集合。围绕这个集合进行后续的操作。但是使用流式编程,你就可以简单陈述你想做什么:
// streams/Randoms.java import java.util.*; public class Randoms { public static void main(String[] args) { new Random(47) .ints(5, 20) .distinct() .limit(7) .sorted() .forEach(System.out::println); } }
输出
6 10 13 16 17 18 19
分析一下这段代码
1.首先给Random一个seed,保证每次运行产生相同的结果。
2.ints()会返回一个流对象,这样我们就对流进行进一步处理了。
public IntStream ints(int randomNumberOrigin, int randomNumberBound) { if (randomNumberOrigin >= randomNumberBound) throw new IllegalArgumentException(BadRange); return StreamSupport.intStream (new RandomIntsSpliterator (this, 0L, Long.MAX_VALUE, randomNumberOrigin, randomNumberBound), false); }
3.实际上后面的distinct(),sorted(),limit()都是对流对象处理返回一个流对象,这点是必要的,我们不能处理流对象后返回一个其他的对象。
4.foreach()这边是InputStream接口下的一个方法,利用了前面的方法引用
void forEach(IntConsumer action);
在这一段代码中没有声明一个变量,这里全程表达的是怎么做,如果是传统的声明式编程就像下面这段代码一样。
// streams/ImperativeRandoms.java import java.util.*; public class ImperativeRandoms { public static void main(String[] args) { Random rand = new Random(47); SortedSet<Integer> rints = new TreeSet<>(); while(rints.size() < 7) { int r = rand.nextInt(20); if(r < 5) continue; rints.add(r); } System.out.println(rints); } }
实际上,它看起来反而更难理解了。
在 ImperativeRandoms.java
中显式地编写迭代机制称为外部迭代。而在 Randoms.java
中,流式编程采用内部迭代,这是流式编程的核心特性之一。这种机制使得编写的代码可读性更强,也更能利用多核处理器的优势。通过放弃对迭代过程的控制,我们把控制权交给并行化机制。我们将在并发编程一章中学习这部分内容。
另一个重要方面,流是懒加载的。这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟,流使我们能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。
流创建
// streams/StreamOf.java import java.util.stream.*; public class StreamOf { public static void main(String[] args) { Stream.of(new Bubble(1), new Bubble(2), new Bubble(3)) .forEach(System.out::println); Stream.of("It's ", "a ", "wonderful ", "day ", "for ", "pie!") .forEach(System.out::print); System.out.println(); Stream.of(3.14159, 2.718, 1.618) .forEach(System.out::println); } }
输出
Bubble(1) Bubble(2) Bubble(3) It's a wonderful day for pie! 3.14159 2.718 1.618
// streams/CollectionToStream.java import java.util.*; import java.util.stream.*; public class CollectionToStream { public static void main(String[] args) { List<Bubble> bubbles = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3)); System.out.println(bubbles.stream() .mapToInt(b -> b.i) .sum()); Set<String> w = new HashSet<>(Arrays.asList("It's a wonderful day for pie!".split(" "))); w.stream() .map(x -> x + " ") .forEach(System.out::print); System.out.println(); Map<String, Double> m = new HashMap<>(); m.put("pi", 3.14159); m.put("e", 2.718); m.put("phi", 1.618); m.entrySet().stream() .map(e -> e.getKey() + ": " + e.getValue()) .forEach(System.out::println); } }
输出
6 a pie! It's for wonderful day phi: 1.618 e: 2.718 pi: 3.14159
mapToInt()
方法将一个对象流(object stream)转换成为包含整型数字的 IntStream
// streams/RandomGenerators.java import java.util.*; import java.util.stream.*; public class RandomGenerators { public static <T> void show(Stream<T> stream) { stream .limit(4) .forEach(System.out::println); System.out.println("++++++++"); } public static void main(String[] args) { Random rand = new Random(47); show(rand.ints().boxed()); show(rand.longs().boxed()); show(rand.doubles().boxed()); // 控制上限和下限: show(rand.ints(10, 20).boxed()); show(rand.longs(50, 100).boxed()); show(rand.doubles(20, 30).boxed()); // 控制流大小: show(rand.ints(2).boxed()); show(rand.longs(2).boxed()); show(rand.doubles(2).boxed()); // 控制流的大小和界限 show(rand.ints(3, 3, 9).boxed()); show(rand.longs(3, 12, 22).boxed()); show(rand.doubles(3, 11.5, 12.3).boxed()); } }
输出
-1172028779 1717241110 -2014573909 229403722 ++++++++ 2955289354441303771 3476817843704654257 -8917117694134521474 4941259272818818752 ++++++++ 0.2613610344283964 0.0508673570556899 0.8037155449603999 0.7620665811558285 ++++++++ 16 10 11 12 ++++++++ 65 99 54 58 ++++++++ 29.86777681078574 24.83968447804611 20.09247112332014 24.046793846338723 ++++++++ 1169976606 1947946283 ++++++++ 2970202997824602425 -2325326920272830366 ++++++++ 0.7024254510631527 0.6648552384607359 ++++++++ 6 7 7 ++++++++ 17 12 20 ++++++++ 12.27872414236691 11.732085449736195 12.196509449817267 ++++++++
为了消除冗余代码,我创建了一个泛型方法 show(Stream<T> stream)
(在讲解泛型之前就使用这个特性,确实有点作弊,但是回报是值得的)。类型参数 T
可以是任何类型,所以这个方法对 Integer、Long 和 Double 类型都生效。但是 Random 类只能生成基本类型 int, long, double 的流。幸运的是, boxed()
流操作将会自动地把基本类型包装成为对应的装箱类型,从而使得 show()
能够接受流。
一段读文件内容的代码
// streams/RandomWords.java import java.util.*; import java.util.stream.*; import java.util.function.*; import java.io.*; import java.nio.file.*; public class RandomWords implements Supplier<String> { List<String> words = new ArrayList<>(); Random rand = new Random(47); RandomWords(String fname) throws IOException { List<String> lines = Files.readAllLines(Paths.get(fname)); // 略过第一行 for (String line : lines.subList(1, lines.size())) { for (String word : line.split("[ .?,]+")) words.add(word.toLowerCase()); } } public String get() { return words.get(rand.nextInt(words.size())); } @Override public String toString() { return words.stream() .collect(Collectors.joining(" ")); } public static void main(String[] args) throws Exception { System.out.println( Stream.generate(new RandomWords("Cheese.dat")) .limit(10) .collect(Collectors.joining(" "))); } }
当你使用 Collectors.joining()
,你将会得到一个 String
类型的结果,每个元素都根据 joining()
的参数来进行分割。还有许多不同的 Collectors
用于产生不同的结果。
在主方法中,我们提前看到了 Stream.generate()
的用法,它可以把任意 Supplier<T>
用于生成 T
类型的流。
IntStream的一些小tip
// streams/Ranges.java import static java.util.stream.IntStream.*; public class Ranges { public static void main(String[] args) { // 传统方法: int result = 0; for (int i = 10; i < 20; i++) result += i; System.out.println(result); // for-in 循环: result = 0; for (int i : range(10, 20).toArray()) result += i; System.out.println(result); // 使用流: System.out.println(range(10, 20).sum()); } }
输出
145 145 145
repeat代替for循环
// onjava/Repeat.java package onjava; import static java.util.stream.IntStream.*; public class Repeat { public static void repeat(int n, Runnable action) { range(0, n).forEach(i -> action.run()); } }
// streams/Looping.java import static onjava.Repeat.*; public class Looping { static void hi() { System.out.println("Hi!"); } public static void main(String[] args) { repeat(3, () -> System.out.println("Looping!")); repeat(2, Looping::hi); } }
输出
Looping! Looping! Looping! Hi! Hi!
@FunctionalInterface public interface Runnable { public abstract void run(); }
这里我们的传给Runnable的是什么呢?虽然我觉得这边有点像多态的感觉,但是我们传的并不是一个类啊,是一个方法体,这边我暂时的理解是一个实现接口,以后明白了再补充上。
generate()
// streams/Generator.java import java.util.*; import java.util.function.*; import java.util.stream.*; public class Generator implements Supplier<String> { Random rand = new Random(47); char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); public String get() { return "" + letters[rand.nextInt(letters.length)]; } public static void main(String[] args) { String word = Stream.generate(new Generator()) .limit(30) .collect(Collectors.joining()); System.out.println(word); } }
输出
YNZBRNYGCFOWZNTCQRGSEGZMMJMROE
如果要创建包含相同对象的流,只需要传递一个生成那些对象的 lambda
到 generate()
中:
// streams/Duplicator.java import java.util.stream.*; public class Duplicator { public static void main(String[] args) { Stream.generate(() -> "duplicate") .limit(3) .forEach(System.out::println); } }
输出
// streams/Duplicator.java import java.util.stream.*; public class Duplicator { public static void main(String[] args) { Stream.generate(() -> "duplicate") .limit(3) .forEach(System.out::println); } }
// streams/Bubble.java import java.util.function.*; public class Bubble { public final int i; public Bubble(int n) { i = n; } @Override public String toString() { return "Bubble(" + i + ")"; } private static int count = 0; public static Bubble bubbler() { return new Bubble(count++); } }
由于 bubbler()
与 Supplier<Bubble>
是接口兼容的,我们可以将其方法引用直接传递给 Stream.generate()
:
// streams/Bubbles.java import java.util.stream.*; public class Bubbles { public static void main(String[] args) { Stream.generate(Bubble::bubbler) .limit(5) .forEach(System.out::println); } }
输出
Bubble(0) Bubble(1) Bubble(2) Bubble(3) Bubble(4)
这个generate有点工厂类的味道,反正看起来很舒服,好理解,之前还对这类的感觉不好理解,现在想一下这样好像易读性更好。
Iterate()
生成一个斐波那契
// streams/Fibonacci.java import java.util.stream.*; public class Fibonacci { int x = 1; Stream<Integer> numbers() { return Stream.iterate(0, i -> { int result = x + i; x = i; return result; }); } public static void main(String[] args) { new Fibonacci().numbers() .skip(20) // 过滤前 20 个 .limit(10) // 然后取 10 个 .forEach(System.out::println); } }
输出
6765 10946 17711 28657 46368 75025 121393 196418 317811 514229
斐波那契数列将数列中最后两个元素进行求和以产生下一个元素。iterate()
只能记忆结果,因此我们需要利用一个变量 x
追踪另外一个元素。
在主方法中,我们使用了一个之前没有见过的 skip()
操作。它根据参数丢弃指定数量的流元素。在这里,我们丢弃了前 20 个元素。
这里的函数表达式改变的就是外部变量x,因为并不是一个参数传过来的所以没有问题。
流的建造者模式
// streams/FileToWordsBuilder.java import java.io.*; import java.nio.file.*; import java.util.stream.*; public class FileToWordsBuilder { Stream.Builder<String> builder = Stream.builder(); public FileToWordsBuilder(String filePath) throws Exception { Files.lines(Paths.get(filePath)) .skip(1) // 略过开头的注释行 .forEach(line -> { for (String w : line.split("[ .?,]+")) builder.add(w); }); } Stream<String> stream() { return builder.build(); } public static void main(String[] args) throws Exception { new FileToWordsBuilder("Cheese.dat") .stream() .limit(7) .map(w -> w + " ") .forEach(System.out::print); } }
这边需要对builder有了解,当然现在基本都用autoValue
先吃饭,回来再写