一.集合处理数据的弊端
当我们需要对集合中的元素进行操作的时候,除了必要的添加,删除,获取外,最典型的操作就是集合遍历。
public class StreamTest01 {
public static void main(String[] args) {
List<String> list = Arrays.asList("张三","张三丰","成龙","李连杰");
//获取张开头的名字
List<String> list1 = new ArrayList<>();
for (String s : list) {
if(s.startsWith("张")){
list1.add(s);
}
}
//获取名字长度为2的名字
List<String> list2 = new ArrayList<>();
for (String s : list1) {
if(s.length() == 3){
list2.add(s);
}
}
for (String s : list2) {
System.out.println(s);
}
}
}
上述代码针对不同的逻辑需要重复的遍历集合,我们希望有更加高效的处理方式,这是我们就可以通过JDK8中提供的Stream API 解决这个问题。
stream更加优雅的解决方案:
public class StreamTest02 {
public static void main(String[] args) {
List<String> list = Arrays.asList("张三","张三丰","成龙","李连杰");
list.stream().filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));
System.out.println("---------------------------------");
list.stream().filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out :: println);
}
}
二.Stream流式思想概述
Stream流式思想类似于工厂车间的生产流水线,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
Stream API能让他们快速完成许多复杂的操作,如筛选,切片,映射,查找,去除重复,统计,匹配和规约。
三.Stream流的获取方式
3.1根据Collection获取
首先,根据java.util.Collection接口中加入了default 方法stream,也就是说Collection接口下的所有实现类都可以通过stream方法来获取Stream流。
public class StreamTest03 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.stream();
Set<String> set = new HashSet<>();
set.stream();
Vector<String> vector = new Vector<>();
vector.stream();
}
}
然而,map接口并没有实现Collection接口,那怎么办呢?这是我们可以根据map获取对应的key或value的集合,再根据获得的集合去获取stream
public class StreamTest04 {
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>();
Stream<String> stream = map.keySet().stream();//key
Stream<Object> stream1 = map.values().stream();//value
Stream<Map.Entry<String, Object>> stream2 = map.entrySet().stream();//entry
}
}
3.2根据Stream的of方法
在实际开发过程中 我们不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,
但是stream接口中提供了静态方法of
public class StreamTest05 {
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("11", "22", "33");
String[] arr1 = {"a1", "a2", "a3"};
Stream<String> arr11 = Stream.of(arr1);
Integer[] arr2 = {11,22,33};
Stream<Integer> arr21 = Stream.of(arr2);
arr21.forEach(System.out::println);
//注意,基本数据类型的数组是不行的
int[] arr3 = {1,2,3};
Stream<int[]> arr31 = Stream.of(arr3);
arr31.forEach(System.out::println);
}
}
输出:
11
22
33
[I@23fc625e
四.Stream常用方法介绍
Stream常用方法
Stream流模型的操作很丰富,这里介绍一些常用的API
这些方法可以被分成两种,
终结方法:返回值类型不再是Stream方法,不再支持链式调用,
非终结方法:返回值类型仍然是stream类型,支持链式调用。
Stream注意事项(重要):
1.stream只能操作一次
2.Stream方法返回的是新的流
3.Stream不调用终结方法,中间的操作不会执行4
4.1forEach
forEach用来遍历流中的数据
void forEach(Consumer<? super T> action);
该方法接收一个Consumer接口,会将每一个流元素交给函数处理。
public class StreamTest06 {
public static void main(String[] args) {
Stream.of("11","22","33").forEach(System.out::println);
}
}
4.2 count
统计元素的个数
long count();
该方法返回一个long值,代表元素的个数
public class StreamTest07 {
public static void main(String[] args) {
long count = Stream.of("11", "22", "33").count();
System.out.println(count);
}
}
4.3 filter
将一个流转换为另一个子集流
Stream<T> filter(Predicate<? super T> predicate);
该接口接收一个Predicate函数式接口参数作为过滤条件
public class StreamTest08 {
public static void main(String[] args) {
Stream.of("11", "22", "33").filter(s -> s.contains("2"))
.forEach(System.out::println);
}
}
4.4 limit
该方法可以对流进行截取操作,只取前n个数据
Stream<T> limit(long maxSize);
参数是一个long 类型的数值,如果集合长度大于该参数则进行截取,否则不操作
public class StreamTestLimit {
public static void main(String[] args) {
Stream.of("11", "22", "33").limit(2).forEach(System.out::println);
}
}
输出:
11
22
4.5 skip
该方法获取一个截取后的新流
Stream<T> skip(long n);
public class StreamTestSkip {
public static void main(String[] args) {
Stream.of("11", "22", "33").skip(2).forEach(System.out::println);
}
}
输出:33
4.6 map
将流中的元素映射到另一个流中
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
public class StreamTestMap {
public static void main(String[] args) {
//将流中的元素都加1
Stream.of("11", "22", "33")
.map(s -> Integer.parseInt(s)+1)
.forEach(System.out::println);
}
}
4.7 sorted
对数据进行排序
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
public class StreamTestSorted {
public static void main(String[] args) {
Stream.of("4", "8", "2","34","24","66")
.map(Integer::parseInt)
//.sorted() //根据自然顺序排序
.sorted((o1,o2) -> o2-o1) //根据比较器指定规则
.forEach(System.out::println);
}
}
4.8distinct
去重
Stream<T> distinct();
public class StreamTestDistinct {
public static void main(String[] args) {
Stream.of("4", "4", "34","34","66")
.distinct()
.forEach(System.out::println);
System.out.println("---------------------");
Stream.of(
new Person("张三",18),
new Person("李四",25),
new Person("张三",18))
.distinct()
.forEach(System.out::println);
}
}
输出:
4
34
66
---------------------
Person{name='张三', age=18}
Person{name='李四', age=25}
注意:该方法可以对基本数据类型直接去重,但对于自定义类型,需要重写hashCode和equals方法
4.9 reduce
将所有数据归纳为一个数据
T reduce(T identity, BinaryOperator<T> accumulator);
public class StreamTestReduce {
public static void main(String[] args) {
Integer reduce = Stream.of(4, 8, 2)
.reduce(0, (x, y) -> {
System.out.println("x="+x + "," +"y="+y);
return x + y;
});
System.out.println(reduce);
//获取最大值
Integer max = Stream.of(4, 8, 2)
.reduce(0, (x, y) -> {
System.out.println("x="+x + "," +"y="+y);
return x > y ? x:y;
});
System.out.println(max);
}
}
4.10 map和reduce的组合使用
public class StreamTestMapReduce {
public static void main(String[] args) {
//获取年龄的总和
Integer reduce = Stream.of(
new Person("张三", 2),
new Person("李四", 4),
new Person("王五", 5))
.map(Person::getAge)
.reduce(0,Integer::sum);
System.out.println(reduce);
//获取年龄最大值
Integer max = Stream.of(
new Person("张三", 2),
new Person("李四", 4),
new Person("王五", 5))
.map(Person::getAge)
.reduce(0,Math::max);
System.out.println(max);
//获取字符”a"出现的次数
Integer count = Stream.of("a","b","a")
.map(ch -> "a".equals(ch)?1:0)
.reduce(0,Integer::sum);
System.out.println(count);
}
}
输出:
11
5
2
4.11 mapToInt
Integer占用内存比int高太多,为了提高代码效率,可以流中Integer数据先转换成int
public class StreamTestMapToInt {
public static void main(String[] args) {
Integer[] arr = {4, 8, 2};
Stream.of(arr)
.mapToInt(Integer::intValue)
.filter(i -> i>3)
.forEach(System.out::println);
}
}
4.12 concat
将两个流合并成一个流
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
使用:
public class StreamTestConcat {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("aa","bb");
Stream<String> stream2 = Stream.of("x","y");
Stream.concat(stream1, stream2)
.forEach(System.out::println);
}
}