Java8-Stream
- Java8-Stream
- 1. Stream
- 1.1 Stream介绍
- 1.1.1 介绍
- 1.1.2 Stream特点
- 1.1.3 直接对集合遍历操作与使用Stream操作集合的对比
- 1.2 Stream使用
- 1.2.1 使用流程
- 1.2.2 获取Stream接口对象
- 1.2.3 延迟方法
- 1.2.4 终结方法
- 1.1 Stream介绍
- 1. Stream
1. Stream
1.1 Stream介绍
1.1.1 介绍
-
Java 8 API添加了一个新的抽象称为流 Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
-
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
-
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果
1.1.2 Stream特点
(1)数据源
- 数据源可以是集合,数组,I/O channel,产生器generator等(Stream本身并不保存数据)
(2)支持聚合操作
- 聚合操作:类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等
(3)Pipelining
- 中间操作都会返回流对象stream本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)
(4)内部迭代
- 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现
(5)延迟执行
- 中间的操作只是提供了一个操作逻辑,并记录,只有最终求值时才会执行传入的操作
1.1.3 直接对集合遍历操作与使用Stream操作集合的对比
(1)使用循环遍历
-
如果是处理简单数据,进行简单操作,使用循环遍历集合,并没有什么不妥,但是如果是复杂的大型的数据使用循环不仅会使处理效率变得底下,而且会降低代码的可读性
-
例如下面代码:查找符合条件的学生
- 以下是通过遍历集合,通过条件判断来对符合条件的数据进行操作
class Student implements Comparable
{ int id; String name; String className; int grade; public Student(int id, String name, String className, int grade) {...} @Override public String toString() {...} //使用成绩决定排名 @Override public int compareTo(Student o) { return this.grade-o.grade; } } public class StreamTest { public static void main(String[] args) { LinkedList studentList = new LinkedList (); studentList.add(new Student(1, "Alice", "1班", 100)); studentList.add(new Student(2, "Bob", "2班", 85)); studentList.add(new Student(3, "Coco", "2班", 46)); studentList.add(new Student(4, "Dav", "3班", 95)); studentList.add(new Student(5, "Elizabeth", "4班", 81)); studentList.add(new Student(6, "Fermat", "4班", 69)); studentList.add(new Student(7, "grace", "4班", 59)); //选取4班,成绩大于60的人,和其他班成绩大于50的人 //由于是链表,为了性能,这里使用iterator进行迭代 Iterator iter = studentList.listIterator(); while(iter.hasNext()){ Student cur = iter.next(); if("4班".equals(cur.className)){ if(cur.grade>60) { System.out.println(cur); } } else if(cur.grade>50){ System.out.println(cur); } } } } - 这只是一个很简单的例子,如果说数据量很大或者涉及几种数据,而且条件设置地比较复杂,代码量以及执行效率会变得非常差
-
(2)使用Stream操作
-
以下是通过Stream处理集合实例
studentList.stream().filter(s->{ if("4班".equals(s.className)){ return s.grade > 60; }else { return s.grade > 50; } }).forEach(s-> System.out.println(s));
1.2 Stream使用
1.2.1 使用流程
(1)流程
-
当使用一个流时,通常包括三个基本步骤
graph LR 获取数据源-->数据转换 数据转换-->传入需要执行的操作 传入需要执行的操作-->操作集合获取想要的结果 -
当一个Stream流执行完终结方法时,就不能再使用该对象了,再次使用会抛出(
IllegalStateException
)
1.2.2 获取Stream接口对象
(1)Stream接口
-
Stream接口是定义在
java.util.stream.Stream
-
public interface Stream
extends BaseStream > { ... }
(2)Stream接口实现对象的获取方式
-
Collection
实现的stream()
与parallelStream()
接口方法,创建一个顺序流:-
java8中将Collection接口中加入了
stream()
,parallelStream()
默认方法,用于获取Stream对象public interface Collection
extends Iterable { ... default Stream stream() { return StreamSupport.stream(spliterator(), false); } ... default Stream parallelStream() { return StreamSupport.stream(spliterator(), true); } ... } List
list = new ArrayList<>(); Stream stream = list.stream(); //获取一个顺序流 Stream parallelStream = list.parallelStream(); //获取一个并行流 - 对于Map类型集合可以先获取keyset,再获取键的Stream对象;或者先获取values(),再获取值的Stream对象;也可以获取entrySet(),再获取键值对Stream对象
-
-
Arrays.stream(T[] array)
静态方法:-
public static
Stream stream(T[] array) { return stream(array, 0, array.length); } public static Stream stream(T[] array, int startInclusive, int endExclusive{ return StreamSupport.stream(spliterator(array,startInclusive,endExclusive), false); } String[] nums = new String[10]; Stream
stream = Arrays.stream(nums);
-
-
Stream
中的静态方法:-
public static
Stream of(T... values) { return Arrays.stream(values); } - 此方法将通过数组对象获取Stream对象
-
public static
Stream generate(Supplier s) { Objects.requireNonNull(s); return StreamSupport.stream( new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false); } -
public static
Stream iterate(final T seed, final UnaryOperator f) {...}
-
-
BufferedReader对象的lines()
方法-
public Stream
lines(){...} BufferedReader reader = new BufferedReader(new FileReader("/user/sd/readme.md")); Stream
lineStream = reader.lines();
-
-
使用
Pattern.splitAsStream()
方法,将字符串分隔成流-
Pattern pattern = Pattern.compile(","); Stream
stringStream = pattern.splitAsStream("a,b,c,d"); stringStream.forEach(System.out::println);
-
参考
1.2.3 延迟方法
- 延迟方法:一般为传入某种函数式接口来表明需要进行的操作,其返回类型依旧为Stream类型,所以可以链式调用(该系列方法并没有执行操作,只是传入函数式接口表明需要进行的操作)
(1)Stream filter(Predicate super T> predicate)
-
该方法通过传入
predicate
接口对象,作为筛选条件List
personList = new ArrayList(); personList.add("Alice"); personList.add("Bob"); personList.add("Coco"); Stream stream = personList.stream() stream.filter(s->s.startsWith("A")).forEach(System.out::println);//Alice
(2)Stream map(Functionsuper
-
该接口需要一个
Function
函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流Stream
stream = Stream.of("1", "2", "3"); Stream result = stream.map(Integer::parseInt);
(3)Stream limit(long maxSize)
-
该方法可以对流进行截取,只取用前n个
-
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作
stream.limit(2).forEach(System.out::println);//Alice Bob
(4)Stream skip(long n)
-
可以使用 skip 方法获取一个跳过n个数据后的新流
-
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流
stream.skip(1).forEach(System.out::println);//Bob Coco
(5)Stream
-
该方法会将重复元素给去除掉
-
该方法使用
hashCode()
和equals(Object o)
方法来比较元素,只有两者都相同才认为是相同的,只会保留第一个所遇到的元素public class StreamTest { public static void main(String[] args){ List
list = new ArrayList(); StreamTest s = new StreamTest(); list.add("1"); list.add("2"); list.add("1"); list.add(new String("1")); list.stream().distinct().forEach(System.out::println);//1 2 } } -
当我们需要去重时也可以使用
Set
去重:public static void main(String[] args) { users.parallelStream().filter(distinctByKey(User::getId)) .forEach(System.out::println); } public static
Predicate distinctByKey(Function super T, ?> keyExtractor) { Set - 通过
Function
接口处理数据后,将处理后的数据添加尝试添加进Set,如果添加不成功就代表已经有改元素了,返回false,此时过滤器函数会将改元素丢弃,实现去重
- 通过
(6)
-
该方法通过传入Function接口实例将数据元素进行元素转换,构造新的Stream对象
-
此外还有方法
XXStream mapToXX(ToXXFunction super T> mapper)
等一系列方法,来将数据转换为特定类型List
list = new ArrayList(); StreamTest s = new StreamTest(); list.add("String"); list.add("哈哈"); list.add(new String("hello")); list.stream().map(String::toUpperCase).forEach(System.out::println);//STRING 哈哈 HELLO
(7)Stream
-
该方法使用传入的
Comparator
进行比较,然后排序,构建排序后的新Stream对象 -
还包含一个无参的构造方法
list.add("String"); list.add("哈哈"); list.add(new String("hello")); list.stream().sorted(String::compareTo).forEach(System.out::println);//String hello 哈哈
(8)Stream
-
该方法通过传入Consumer接口对元素进行一些操作,并返回新的Stream对象
class A{ int var = 0; void display(){ System.out.println(var); } } List list = new ArrayList(); StreamTest s = new StreamTest(); list.add(new A()); list.add(new A()); list.add(new A()); list.stream().peek(o->o.var = 1).forEach(A::display);
(7)static
-
该静态方法可以将两个流合并成一个新Stream对象
-
该方法传入的流的泛型类型要一致
stream = Stream.concat(stream,Stream.of("1","2")); stream.forEach(System.out::println);//Alice Bob Coco 1 2
1.2.4 终结方法
- 终止方法:表明已经要求通过之前传入的操作进行数据处理,并给出结果
(1)void forEach(Consumersuper
-
该方法接收一个
Consumer
接口对象,会将每一个流元素交给该函数进行处理List
personList = new ArrayList(); personList.add("Alice"); personList.add("Bob"); personList.add("Coco"); personList.stream().forEach(s->{ if(s.startsWith("A")){ System.out.println(s); } });//打印以A开头的字符串
(2)long count()
-
该方法返回操作后的元素个数+
System.out.println(stream.filter(s -> s.startsWith("A")).count());//1
(3)Object[] toArray()
-
该方法将数据转入到数组中,并返回
List
list = new ArrayList(); StreamTest s = new StreamTest(); list.add("String"); list.add("哈哈"); list.add(new String("hello")); System.out.println(Arrays.toString(list.stream().sorted(String::compareTo).toArray()));//[String, hello, 哈哈]