Jdk1.8新特性————stream流操作,让代码更优雅

首先附上官方文档https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#NonInterference。英文水平还不错的看官方文档那肯定比往下看好很多。

stream咋一看和io流中的InputStream和OutputStream很像,但是它们之间并没有任何关系,被称作流操作是因为很像是流水线操作。stream分为中间操作和终止操作,怎么理解?类似做披萨,我们可以加很多东西,比如加菠萝、加榴莲、加BBQ等等,这些就是中间操作,而放进烤箱出炉,这就是终止操作。

Stream stream = Arrays.asList("a1", "a2", "b1", "c2", "c1").stream();

讲了半天,stream流怎么来,怎么创建?通常和Collection 挂钩,如list、set,调用stream()方法变可以获取流。stream有很多方法,这些方法能让我们往披萨加自己喜欢的东西。Jdk1.8新特性————stream流操作,让代码更优雅_第1张图片

foreach

这些是常见的一些方法,像foreach,看这个词就知道是遍历用,并且是用void修饰,返回的不是流,这无疑是一个终止操作。

Stream stream = Arrays.asList("a1", "a2", "b1", "c2", "c1").stream();
stream.forEach(s -> System.out.println(s));

这就是一个简单的遍历输出,而且这代码还可以更加简洁

Arrays.asList("a1", "a2", "b1", "c2", "c1")
                .stream()
                .forEach(System.out::println);

这里涉及的Lmabda表达式我不讲,怕误人子弟,其实用多了也就好了。讲到循环,这里提一下IntStream。

List list = new ArrayList<>();
IntStream
     .range(0,4)
     .forEach(i->list.add("Test"+i));
System.out.println(list);

// [Test0, Test1, Test2, Test3]

是不是比写for循环更加简洁方便,人总是懒的,少些一点是一点。这就是stream的终止操作之一foreach

filter

Stream filter(Predicate predicate);

过滤,也大概能理解这个方法的作用。返回值是Stream,就说明这个是中间操作,对流中数据的选取过滤。

Arrays.asList("a1", "a2", "b1", "c2", "c1")
                .stream()
                .filter(s -> s.startsWith("c"))
                .forEach(System.out::println);

//c2
//c1

选取以c字母开头的数据,不用写for循环遍历的感觉是真的爽。

map

/**
     * Returns a stream consisting of the results of applying the given
     * function to the elements of this stream.
     * 返回由将给定函数应用于此流元素的结果组成的流。
*/
 Stream map(Function mapper);

上面是百度给出的翻译,有点难懂,什么叫返回由将给定函数应用于此流元素的结果组成的流。听起来和filter过滤不是很像么?

Arrays.asList("a1", "a2", "b1", "c2", "c1")
                .stream()
                .filter(s -> s.startsWith("c"))
                .map(s -> s.replaceAll("c","a"))
                .map(s -> s.toUpperCase())
                .forEach(System.out::println);

//A2
//A1

filter是对流进行过滤、筛检,而map则是对流中数据处理后返回新的流。中间操作可以接多个,第一个map将字母c换成a,第二个map将字母大写,输出的是A2  A1。简单的例子往往使方法的作用一目了然。

流处理顺序

java代码一般执行顺序都是由上往下,遇到方法进入执行方法还是这个从上往下的顺序。那么流呢?

Arrays.asList("a1", "a2", "b1", "c2", "c1")
                .stream()
                .filter(s -> {
                    System.out.println("filter:"+s);
                    return true;
                });

//无输出

这代码为什么没有输出?流操作只有在有终止操作的时候,中间操作才会执行

 Arrays.asList("a1", "a2", "b1", "c2", "c1")
                .stream()
                .filter(s -> {
                    System.out.println("filter:"+s);
                    return true;
                })
                .forEach(s -> {
                    System.out.println("foreach:"+s);
                });

//filter:a1
//foreach:a1
//filter:a2
//foreach:a2
//filter:b1
//foreach:b1
//filter:c2
//foreach:c2
//filter:c1
//foreach:c1

输出的顺序可以知道,并不是filter操作执行完成后,再执行foreach的终端操作。设计者是真的厉害,这个是为了性能的考虑。foreach有点难看出来效果,但是像anyMacth()和noneMatch()就很明显

/**
     * 分别是流中数据一次匹配、全部匹配、一次都不匹配返回true
     *
*/
boolean anyMatch(Predicate predicate);
boolean allMatch(Predicate predicate);
boolean noneMatch(Predicate predicate);
Arrays.asList("a1", "a2", "b1", "c2", "c1")
                .stream()
                .filter(s -> {
                    System.out.println("filter:"+s);
                    return true;
                })
                .anyMatch(s -> s.startsWith("a"));

//filter:a1

这个就很明显,filter就只执行了一次。性能上很定是有提升的,获取我这例子没啥差别,可是当中间操作和流数据增加很多时,性能提升还是很可观的。中间操作执行顺序明白之后,filter自然而然应该放到最先执行,过滤掉了的数据没必要执行后面流操作

collect

 R collect(Supplier supplier,
                  BiConsumer accumulator,
                  BiConsumer combiner);
 R collect(Collector collector);

上面全是foreach输出,不可能咱们拿到数据就只有输出对不对。collect就是将流转为其他的对象,常见的有list、set、map集合等。上面第一个看起来参数就很复杂的暂且不管,第二参数是Collector,jdk1.8提供了Collectors,里面定义了很多Collector,经常性用到的就是toList、toSet、toMap、joining。当然Collector也可以自定义,这个稍后再说。

List list = Arrays.asList("a1", "a2", "b1", "c2", "c1")
                                    .stream()
                                    .filter(s -> s.startsWith("a"))
                                    .collect(Collectors.toList());
list.forEach(System.out::println);

//a1
//a2

set集合一样,Collectors.toSet()。map稍有不同,需要指定key和value,key必须唯一,不唯一会报错。

@Data
@AllArgsConstructor
static class Hero {
        String name;
        int price;
    }

static List list = Arrays.asList(
      new Hero("魂锁典狱长", 6300),
      new Hero("派克", 6300),
      new Hero("布里茨", 3150),
      new Hero("莫甘娜", 1350),
      new Hero("风女", 1350)
);



public static void main(String[] args) {


    Map map = list.stream()
        .collect(Collectors.toMap(
            b -> b.price,//key
            b -> b.name,//value
            (name1, name2) -> name1 + "----" + name2 //key相同时 value拼接方式
        ));
    System.out.println(map);
}

//{1350=莫甘娜----风女, 6300=魂锁典狱长----派克, 3150=布里茨}

FlatMap

 Stream flatMap(Function> mapper);

还是返回一个流,flatMap可以通过映射关系将流换成其他的流

@Data                                     
@AllArgsConstructor                       
static class LOl {                        
    String type;                          
    List heroes = new ArrayList<>();
                                          
}                                         
                                          
@Data                                     
@AllArgsConstructor                       
static class Hero{                        
    String name;                          
    int price;                            
}                

public static void main(String[] args) {                                                      
    List list = Arrays.asList(                                                      
            new LOl("sup",Arrays.asList(new Hero("魂锁典狱长", 6300))),                       
            new LOl("top",Arrays.asList(new Hero("炼金术士", 6300))),                        
            new LOl("mid",Arrays.asList(new Hero("刀锋之影", 6300))),                        
            new LOl("adc",Arrays.asList(new Hero("瘟疫之源", 6300))),                        
            new LOl("jug",Arrays.asList(new Hero("傲之追猎者", 6300)))                        
    );                                                                                   
    list.stream()                                                                        
            .flatMap(l->l.heroes.stream())                                               
            .forEach(System.out::println);                                               
} 

//Test1.Hero(name=魂锁典狱长, price=6300)
//Test1.Hero(name=炼金术士, price=6300)
//Test1.Hero(name=刀锋之影, price=6300)
//Test1.Hero(name=瘟疫之源, price=6300)
//Test1.Hero(name=傲之追猎者, price=6300)                                                                                                     

flatMap将Lol流换成了Hero流,最终输出Hero对象。

Optional

Optional这也是jdk1.8引入的一个专门来处理空值问题的类。不作细讲,但是Optional中也有flatMap方法,特别适合多层对象空值判断问题。

static class A {
    B b;        
}               
                
static class B {
    C c;        
}               
                
static class C {
    String test;
}               


public static void main(String[] args) {               
                                                       
    Optional.of(new A())                                
            .flatMap(a->Optional.ofNullable(a.b))       
            .flatMap(b->Optional.ofNullable(b.c))       
            .flatMap(c -> Optional.ofNullable(c.test))  
            .ifPresent(System.out::println);           
}                                                      

最后还剩一个自定义收集器Collector没说,这个东西我自己也才会用,怕误人子弟就不说了。有很多大佬专门讲过这个,Collectors里面提供的收集器已经很全面了,一般简单的业务逻辑都可以满足。Stream、Optional、Collectors这三个类还有其他的方法我并没有提到,有兴趣自己了解是最好的,反正现在翻译软件也挺好用,基本能自己慢慢读懂。最后,多用必定不难,多总结必定能进步

你可能感兴趣的:(Java)