JavaSE-Lambda表达式-03-201804
Stream(流)
1.背景
自从lambda表达式成为Java语言的一部分之后,Java集合(Collections)API就面临着大幅变化。
尽管我们可以从头实现一个新的集合框架(比如“Collection II”),但取代现有的集合框架是一项非常艰难的工作,因为集合接口渗透了 Java 生态系统的每个角落,将它们一一换成新类库需要相当长的时间。因此,我们决定采取演化的策略(而非推倒重来)以改进集合 API:
- 为现有的接口(例如
Collection
,List
和Stream
)增加扩展方法; - 在类库中增加新的 流(stream,即
java.util.stream.Stream
)抽象以便进行聚集(aggregation)操作; - 改造现有的类型使之可以提供流视图(stream view);
- 改造现有的类型使之可以容易的使用新的编程模式,这样用户就不必抛弃使用以久的类库,例如
ArrayList
和HashMap
(当然这并不是说集合 API 会常驻永存,毕竟集合 API 在设计之初并没有考虑到 lambda 表达式。我们可能会在未来的 JDK 中添加一个更现代的集合类库)。 - 提供更加易用的并行(Parallelism)库。尽管 Java 平台已经对并行和并发提供了强有力的支持,然而开发者在实际工作(将串行代码并行化)中仍然会碰到很多问题。我们把编程的重点从具体执行细节(how computation should be formed)转移到抽象执行步骤(what computation should be perfomed)。除此之外,我们还需要在将并行变的 容易(easier)和将并行变的 不可见(invisible)之间做出抉择,我们选择了一个折中的路线:提供 显式(explicit)但 非侵入(unobstrusive)的并行。(如果把并行变的透明,那么很可能会引入不确定性(nondeterminism)以及各种数据竞争(data race)问题)。
2.内部迭代与外部迭代(internal or external iteration)
集合类库主要依赖于外部迭代,Collection
实现 Iterable
接口,从而使得用户可以依次遍历集合的元素。
List<School> list=new ArrayList<>();
list.add(new School("Havard",1));
list.add(new School("Cambridge",2));
list.add(new School("Oxford",3));
list.add(new School("Stanford",4));
list.add(new School("MIT",5));
for(School s:list) { //外部迭代
System.out.println(s);
}
list.forEach(s->System.out.println(s)); //内部迭代
for-each循环调用list的iterator()方法进行依次遍历。外部循环的代码非常直接,但它有如下问题:
- Java 的 for 循环是串行的,而且必须按照集合中元素的顺序进行依次处理;
- 集合框架无法对控制流进行优化,例如通过排序、并行、短路(short-circuiting)求值以及惰性求值改善性能。
内部迭代的forEach()方法实际也是采取for-each循环遍历,但我们无法控制迭代的具体流程。用户把对操作的控制权交还给类库,从而允许类库进行各种各样的优化(例如乱序执行、惰性求值和并行等等)。总的来说,内部迭代使得外部迭代中不可能实现的优化成为可能。
外部迭代同时承担了做什么(打印每个条目)和怎么做(得到 Iterator
实例然后依次遍历)两项职责,而内部迭代只负责做什么,而把怎么做留给类库。
3.Stream(流)
a.概念:A sequence of elements supporting sequential and parallel aggregate operations.
java.util.stream
(这个包里有若干流类型:Stream<T>
代表对象引用流,此外还有一系列特定(specialization)流,比如 IntStream
代表整形数字流)。流的操作可以被组合成 流水线(Pipeline)。
- 流不存储元素,这些元素可能存在于底层的集合中,或是按需生成;
- 流操作不会修改数据源,比如filter()不会从原始流中移除元素,而是生成一个不包含已过滤元素的新流;
- 流操作尽可能使惰性执行的(lazy evaluation)。即直至需要其结果时,才会被执行。(??)
f.流的创建步骤:
i.创建一个流
ii.指定将初始流转换成其他流的中间操作
iii.终止这个流,得到结果。此动作将强制流执行之前的惰性操作。终止之后该流将无法被调用。
4.流的创建
a.将集合转换成一个流:使用Collection接口的stream()方法
Stream<Employee> stream=list.stream();
b.将数组转换成一个流:主要有两种方法-Stream.of(T... values)和Arrays.stream(T[] arr,int start,int end)
//将数组转换成一个流使用public static<T> Stream<T> of(T... values)
String[] euCities={"Munchen","Paris","London","Oslo","Manchester"};
String[] asiaCities={"Tokyo","Seoul","HK","Mumbai"};
Stream.of(euCities).filter(s->s.length()>5).forEach(System.out::println);
Stream.of(euCities,asiaCities).forEach(s->System.out.println(Arrays.toString(s)));
System.out.println("-----------------------------");
//Arrays.stream(T[] array,int startInclusive,int endExclusive)--取下标为[0,3)的数组元素成流
Arrays.stream(euCities).filter(s->s.contains("o")).forEach(System.out::println);
Arrays.stream(asiaCities, 0, 3).forEach(System.out::println);
c.创建一个初始为空的流
Stream<String> s2=Stream.empty();
d.创建一个无限流(延迟计算/惰性)
i. public static<T> Stream<T> generate(Supplier<T> s)
返回无限顺序无序流,其中每个元素由提供的Supplier
生成。 这适合于产生恒定流,随机元素流等。
①生成一个无限fabonacci流,使用limit(n)取前n项
Stream.generate(new Fabonacii()).limit(10).forEach(System.out::println);
class Fabonacii implements Supplier<Long>{
long a=0;
long b=1;
@Override
public Long get() {
// TODO Auto-generated method stub
long sum=a+b;
a=b;
b=sum;
return a;
}
}
②利用级数展开 π/4=1-1/3+1/5-1/7... ,计算π(莱布尼茨级数)
Stream.generate(new piTest()).skip(100).limit(10).forEach(System.out::println);
class piTest implements Supplier<Double>{
double sgn=-1;
double deno=-1;
double sum=0;
@Override
public Double get() {
// TODO Auto-generated method stub
sgn*=-1;
deno+=2;
sum+=sgn/deno;
return sum*4;
}
}
③ 使用泰勒级数展开计算arctanx
/**
* 使用泰勒级数展开 计算arctanx
* 在0点泰勒展开 arctanx=∑(-1)^n*x^(2n+1)/(2n+1) ,n∈[0,+∞)
*/
// arctan1=π/4
Stream.generate(new Arctan(1.0)).skip(1000).limit(10).forEach(System.out::println);
class Arctan implements Supplier<Double>{
double sgn=-1;
double deli=-1;
double res=0;
Double x;
public Arctan(Double x) {
this.x=x;
}
@Override
public Double get() {
// TODO Auto-generated method stub
sgn*=-1;
deli+=2;
x*=x*x;
res+=sgn*x/deli;
return res;
}
}
④马青公式 π/4=4arctan1/5+arctan1/239快速逼近π;
/**
* 使用马青公式 π/4=4arctan1/5+arctan1/239快速逼近π; 将arctanx=∑(-1)^n*x^(2n+1)/(2n+1) ,n∈[0,+∞)代入
* 这里使用BigDecimal提高精度[double有效数字15位]
*/
Stream<BigDecimal> machin=Stream.generate(new Machin()).limit(10);
//由于lambda表达式中引入的外部变量是等效常量,无法表示索引,这里使用数组的方法折中处理
int[] index= {0};
machin.forEach(t->{
int idx=++index[0];
char[] s=t.toString().toCharArray();
char[] pi=String.valueOf(Math.PI).toCharArray();
for(int i=0;i<s.length;i++) {
if(s[i]!=pi[i]) {
System.out.println(t.toString()+" "+idx+".精确到小数点后第"+(i-2)+"位");
break;
}
}
});
class Machin implements Supplier<BigDecimal>{
final BigDecimal t1=BigDecimal.valueOf(25);
final BigDecimal t2=BigDecimal.valueOf(57121); //239*239
BigDecimal s1=BigDecimal.valueOf(5);
BigDecimal s2=BigDecimal.valueOf(239);
final BigDecimal li=BigDecimal.valueOf(4);
BigDecimal sgn=BigDecimal.valueOf(-1);
BigDecimal deli=BigDecimal.valueOf(-1);
BigDecimal res=BigDecimal.valueOf(0.0);
@Override
public BigDecimal get() {
// TODO Auto-generated method stub
sgn=sgn.multiply(BigDecimal.valueOf(-1));
deli=deli.add(BigDecimal.valueOf(2));
s1=s1.divide(t1,20,RoundingMode.HALF_EVEN); //保留小数点后20位
s2=s2.divide(t2,20,RoundingMode.HALF_EVEN);
BigDecimal temp=s1.multiply(li).subtract(s2);
res=res.add(sgn.divide(deli,20,RoundingMode.HALF_EVEN).multiply(temp).multiply(li));
return res; //两个小数点后保留20位的数相乘,最后保留40位
}
}
结果: 第10次就已精确到小数点后14位
3.1832635983263598326400000000000000000000 1.精确到小数点后第1位
3.1405970293260603143070933323566696618500 2.精确到小数点后第2位
3.1416210293250344250510933323566696618500 3.精确到小数点后第3位
3.1415917721821772950225224889280982330220 4.精确到小数点后第5位
3.1415926824043995172447447020480982330220 5.精确到小数点后第7位
3.1415926526153086081538356114368982330220 6.精确到小数点后第8位
3.1415926536235547619999894576310742330220 7.精确到小数点后第9位
3.1415926535886022286666561242959932730220 8.精确到小数点后第11位
3.1415926535898358474901855360606498106220 9.精确到小数点后第12位
3.1415926535897916969217644834290730707820 10.精确到小数点后第14位
ii. public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
返回有序无限连续Stream
由函数的迭代应用产生f
到初始元素seed
,产生Stream
由seed
, f(seed)
, f(f(seed))
等
①创建一个自然数流
//使用iterate生成一个带初始值的无限迭代流--自然数
Stream.iterate(0,i->i+1).limit(10).forEach(System.out::println);
②3n+1猜想:随意选一个整数,如果它是偶数,那么将它除以2;如果它是奇数,那么将它乘以3再加1。最终将得到1Stream.iterate(99,i->i%2==0?i/2:3*i+1).takeWhile(i->i!=1).forEach(System.out::println);
e.使用Pattern类的splitAsStream方法创建一个流
根据给定的输入序列创建一个流,该流与该模式匹配。
public Stream<String> splitAsStream(final CharSequence input)
String s="dfwefewfwefweffqwweffrgrgweweeqwweerrf";
Pattern p=Pattern.compile("e");
p.splitAsStream(s).filter(i->i!="").forEach(i->System.out.print(i+","));
f.使用Files类的静态方法
① public static Stream<String> lines(Path path, Charset cs)
从文件中读取所有行作为 Stream
。
② public static Stream<Path> walk(Path start, FileVisitOption... options)
返回一个Stream
,它通过走在一个给定的起始文件的根文件树上, Path
。 文件树被深度优先地遍历,流中的元素是Path
对象,如resolving
所示 ,相对路径为start
。
Path path=Paths.get("D:/java");
try(Stream<Path> paths=Files.walk(path, 1)){
paths.forEach(pa->System.out.println(pa.getFileName()));
}catch(IOException e) {
e.printStackTrace();
}
g.IntStream类的静态方法range
//返回有序IntStream:从[s1,s2]递增元素
IntStream.rangeClosed(0,6).forEach(System.out::println);
//返回有序IntStream:从[s1,s2)递增元素
IntStream.range(0,6).forEach(System.out::println);
h.使用StreamSupport类的静态方法与Spliterator的trySplit方法创建
Spliterator<School> sp=list.spliterator();
Spliterator<School> spl=sp.trySplit();
Spliterator<School> spl2=spl.trySplit();
StreamSupport.stream(sp, false).forEach(System.out::println);
System.out.println("-------------------------");
StreamSupport.stream(spl, false).forEach(System.out::println);
System.out.println("-------------------------");
StreamSupport.stream(spl2, false).forEach(System.out::println);
参考:http://zh.lucida.me/blog/java-8-lambdas-inside-out-library-features/
https://www.liaoxuefeng.com/article/001411309538536a1455df20d284b81a7bfa2f91db0f223000