《Java核心技术 卷II》笔记——(6)Lambda表达式&Stream流库

第 1 章

关于lambda表达式?

1. 接口的默认方法(Default Methods for Interfaces),即以前的接口只能有抽象方法,现在可以有“定义好的方法-有方法体”,但是用default修饰;

2. 从匿名内部类-Lambda表达式,以前的方法参数传入一个接口的对象,可以使用内部匿名类,现在可以将“函数”(抽象方法的实现内容()->{})作为参数传入;但是,仅仅是作为方法参数,不能把Lambda表达式当做一个对象来当做句柄调用default方法,但是可以先把他转换成一个接口对象,才可以调用;

注意:在初学Lambda表达式的时候,可以先将()->{}转换成一个接口对象,再去作为方法参数传入,便于理解;

3. Java语言设计者们投入了大量精力来思考如何使现有的函数友好地支持Lambda。最终采取的方法是:增加函数式接口的概念。“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的default方法)的接口。像这样的接口,可以被隐式转换为lambda表达式。java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的两个例子。

Java8增加了一种特殊的注解@FunctionalInterface,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用@FunctionalInterface注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。

注意大部分函数式接口都不用我们自己写,Java8都给我们实现好了,这些接口都在java.util.function包里;

4. 方法和构造函数引用(Method and Constructor References),即当需要实现接口的抽象方法时,我们使用了()->{}的方式,当这个抽象方法的“传入参数和返回类型”与已有的某个类的方法“一致”,且我们也刚好需要这个已有方法类似的实现过程,则可以使用 Class::StaticMethod/Class::InstanceMethod的形式;

eg1:

@FunctionalInterface
public interface Converter<F, T> {
   T convert(F from);
 }

class Something { //已有的类及其实例方法
  String startsWith(String s){ //传入一个String,返回一个String
    return String.valueOf(s.charAt(0));
  }
}

//test

 Something something = new Something();
 Converter<String, String> converter = something::startsWith; //定义converter的具体实现,即把lambda(方法引用)赋值给接口对象;
 String converted = converter.convert("Java");
 System.out.println(converted);    // "J"

eg2:

 class Person { //Person类
    String firstName;
    String lastName;
    Person() { 
    }
    Person(String firstName, String lastName) {
         this.firstName = firstName;
         this.lastName = lastName;
     }
 }

 interface PersonFactory<P extends Person> { //创建Person对象的对象工厂接口
     P create(String firstName, String lastName);
 }

 // test
 PersonFactory<Person> personFactory = Person::new; // 构造函数相当于**静态方法**,即等价于(a,b)->new Person(a,b);
 Person person = personFactory.create("Peter", "Parker");

在学JDK8 流库 概念前,先总结几个Lambda表达式的JDK8四大内置核心函数式接口:

**(1)Predicate接口 断言——传入参数t,判断条件,返回布尔型 Predicate<t_TYPE>

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);

    // 返回值为已实现Predicate接口抽象方法的类
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
// 以上为源码
eg1:
public class Main {
    @Test
    public void test(){
        List<String> list = Arrays.asList("hEOOL","AJS","ashdkjas","ww");
        List<String> strlist = filterStr(list,(s)->s.length()>3);
    }

    // 需求:将满足条件的字符串放到集合中
    public List<String> filterStr(List<String> list, Predicate<String> pre) {
        List<String> strlist = new ArrayList<>();
        for(String str:list){
            if(pre.test(str)){
                strlist.add(str);
            }
        }
        return strlist;
    }
}

eg2:
public class Main {
    @Test
    public static void main(String[] args){
        Predicate<Integer> p1 = age -> age>18;
        Predicate<Integer> p2 = age -> age<30;
        //其实就是表达式(age -> age>18)&&(age -> age<30)赋值给and
        Predicate<Integer> P_and = p1.**and**(p2);  
        boolean test1 = P_and.test(20);  //返回p1和p2的与结果
        out.println(test1);    //输出true
    }
} 

(2)Function接口 函数——传入参数A,操作,返回某种类型B,在Function<A_TYPE, B_TYPE>
Function接口为函数型接口,该接口的抽象方法接收一个参数并且做一些处理然后返回,Function接口还附带了一些可以和其他函数组合的默认方法(compose,andThen);

    public String steHandler(String str, Function<String,String> fun){
        return fun.apply(str);
    }

    @Test
    public void test_03() {
        String newStr0 = " 博哥牛B ";
        System.out.println(newStr0);

        String newStr = steHandler(newStr0,(str)-> str.trim());
        System.out.println(newStr);

        String newStr1 = steHandler(newStr0,(str)-> str.substring(0,3));
        System.out.println(newStr1);
    }

    @Test
    public void test_04() {
        //f1参数为String,结果为Boolean
        Function<String,Boolean> f1 = s1 -> s1!=null;  
        Function<Boolean,Integer> f2 = b -> b?1:0;

        // 将f1的布尔值结果作为f2函数式接口的参数来传递
        // 所以stringIntegerFunction接收值为f1的参数类型String,返回值类型为f2的返回类型Integer,
        // 即先接收一个String作为f1的参数,f1操作后返回一个boolean,作为f2的传入参数,f2操作后返回一个Integer作为本函数的输出;
        Function<String,Integer> stringIntegerFunction = f1.andThen(f2);

        Integer apply = stringIntegerFunction.apply("123");
        System.out.println(apply);  //输出1
    }

    @Test
    //Function的默认方法compose(协作)大同小异,用法差不多
    public void test_05() {
        Function<String,Boolean>  f1 = s1 -> s1!=null;   // f1参数为String,结果为Boolean
        Function<Boolean,Integer>  f2 = b -> b?1:0;

        // 将f1的布尔值结果作为f2函数式接口的参数来传递
        // 所以compose接收值为f1的参数类型String,返回值类型为f2的返回类型Integer
        Function<String,Integer> compose= f2.compose(f1); //与andThen相比,反过来了

        Integer apply = compose.apply("123");
        System.out.println(apply);  //输出1
    } 

(3)Consumer接口——传入参数,操作,无返回值
Consumer是消费型接口。Consumer表示执行在单个参数上面的操作,但没有返回值的(正如消费有去无回)

@FunctionalInterface
public interface Consumer<T> {
    // 该函数式接口的唯一的抽象方法,接收一个参数,没有返回值
    void accept(T t);

   // 在执行完调用者方法后再执行传入参数的方法
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
eg:
 @Test
 public void test_06() {
  Consumer<Integer> consumer = (x) -> {
   int num = x * 2;
   System.out.println(num);
  };
  Consumer<Integer> consumer1 = (x) -> {
   int num = x * 3;
   System.out.println(num);
  };
  consumer.andThen(consumer1).accept(10);
 }

(4)Supplier接口 提供者——不传入参数,返回某种类型T Supplier<T>
Supplier接口为供给型接口;该接口不接受任何参数,返回一个任意泛型的值;

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
eg:
 @Test
 public void test_07() {
  List<Integer> list = getNumList(10,()->(int)Math.random()*100);
  System.out.println(list.toString());
 }

 public List<Integer> getNumList(int num, Supplier<Integer> sup){
  List<Integer> list = new ArrayList<>();

  for(int i=0;i<num;i++){
   Integer e = sup.get();//无参数传入,只返回一个Integer随机数
   list.add(e);
  }
  return list;
 }

*总结:
Function<T, R>——将T作为输入,返回R作为输出
Predicate<T>——将T作为输入,返回一个布尔值作为输出
Consumer<T>——将T作为输入,不返回任何内容
Supplier<T>——没有输入,返回T
BinaryOperator<T>——将两个T作为输入,返回一个T作为输出

可以使用“连接符,类似 .and() .andThen()”连接多个接口“实例”,使用“accept() apply() test()”传入参数

*JDK流库?

Stream是Java8中新加入的api,更准确的说:Java8中的Stream是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作;

StreamAPI借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性;

以前我们处理复杂的数据只能通过各种for循环,不仅不美观,而且时间长了以后可能自己都看不太明白以前的代码了,但有Stream以后,通过filter,map,limit等等方法就可以使代码更加简洁并且更加语义化。

什么是流库?小例子?为什么用流库?

集合API是Java API中最重要的部分。基本上每一个java程序都离不开集合。尽管很重要,但是现有的集合处理在很多方面都无法满足需要。

  1. 一个原因是,许多其他的语言或者类库以声明的方式来处理特定的数据模型,比如SQL语言,你可以从表中查询,按条件过滤数据,并且以某种形式将数据分组,而不必需要了解查询是如何实现的——数据库帮你做所有的脏活。这样做的好处是你的代码很简洁。很遗憾,Java没有这种好东西,你需要用控制流程自己实现所有数据查询的底层的细节。
  2. 其次,是你如何有效地处理包含大量数据的集合。理想情况下,为了加快处理过程,你会利用多核架构。但是并发程序不太好写,而且很容易出错。

StreamAPI很好的解决了这两个问题。它抽象出一种叫做流的东西让你以声明的方式处理数据,更重要的是,它还实现了多线程:帮你处理底层诸如线程、锁、条件变量、易变变量等等。

例如,假定你需要过滤出一沓发票找出哪些跟特定消费者相关的,以金额大小排列,再取出这些发票的ID。如果用StreamAPI,你很容易写出下面这种优雅的查询:

Listids = invoices.stream()
        .filter(inv ->inv.getCustomer() == Customer.ORACLE)
        .sorted(comparingDouble(Invoice::getAmount))
        .map(Invoice::getId)
        .collect(Collectors.toList());

说了这么多,到底什么是流

通俗地讲,你可以认为是支持类似数据库操作的“花哨的迭代器”。技术上讲,它是从某个数据源获得的支持聚合操作的元素序列。

下面着重介绍一下正式的定义:

元素序列:针对特定元素类型的有序集合流提供了一个接口。但是流不会存储元素,只会根据要求对其做计算
数据源:流所用到的数据源来自集合、数组或者I/O。
聚合操作:流支持类似数据库的操作以及函数式语言的基本操作,比如: filter,map,reduce,findFirst,allMatch,sorted等待。

此外,流操作还有两种额外的基础属性根据不同的集合区分:

管道连接:许多流操作返回流本身,这种操作可以串联成很长的管道,这种方式更加有利于像延迟加载,短路,循环合并等操作。
内部迭代器:不像集合依赖外部迭代器,流操作在内部帮你实现了迭代器。

流库与集合的区别?

  1. 流并不存储其元素——这些元素存在底层集合中,或按需生成;
  2. 流的操作不会修改其数据源——如使用流中的filter方法,会生成一个包含过滤元素的新的流,原来的流不变;(不修改,不修改,****不修改!****)
  3. 流的操作是尽可能惰性执行——需要其结果才会执行;

流的性能问题?

  1. 对于简单操作,比如最简单的遍历,Stream串行API性能明显差于显示迭代,但并行的StreamAPI能够发挥多核特性
  2. 对于复杂操作,Stream串行API性能可以和手动实现的效果匹敌,在并行执行时StreamAPI效果远超手动实现。

所以,如果出于性能考虑,建议:
1. 对于简单操作推荐使用外部迭代手动实现,
2. 对于复杂操作,推荐使用Stream API,
3. 在多核情况下,推荐使用并行Stream API来发挥多核优势,
4. 单核情况下不建议使用并行Stream API。

如果出于代码简洁性考虑,使用StreamAPI能够写出更短的代码。即使是从性能方面说,尽可能的使用StreamAPI也另外一个优势,那就是只要JavaStream类库做了升级优化,代码不用做任何修改就能享受到升级带来的好处。

流的使用/操作?

流机器(动画来自Tagir Valeev https://segmentfault.com/img/remote/1460000018919149)(图)

image.png

fuse.svg

Stream的效果就像上图展示的它可以先把数据变成符合要求的样子(map),吃掉不需要的东西(filter)然后得到需要的东西(collect)。

使用流类库基本上可以分为以下几步:

  1. 把集合转换为流;
  2. 对流进行操作;
  3. 将流转换为相应的数据结构。

中间操作和终端操作?

流接口在java.util.stream.Stream定义了许多操作,这些可以分为以下两类

  • filtersorted 和 map 一样的可以被连接起来形成一个管道的操作。
  • collectfindFirst和 allMatch一样的终止管道并返回数据的操作。

可以被连接起来的操作被称为中间操作,它们能被连接起来是因为都返回流。中间操作都“很懒”并且可以被优化。

终止一个流管道的操作被叫做结束操作,它们从流管道返回像List,Integer或者甚至是void等非流类型的数据,操作完流会关闭。

常用的几种操作?

1.获取流?
Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。

eg:

    @Test
    public void fuc_01(){//测试流的获取
        List<String> myList = new ArrayList<>();
        myList = Arrays.asList("a","c","b","d","f","e","g");
        Stream<String> myStream = **myList.stream()**; //第1种方式
        myStream.forEach(System.out::print);
        System.out.println();
        myStream = **Stream.of**("a","c","b","d","f","e","g"); //第2种方式
        myStream.forEach(System.out::print);
    }

*2.Filter-过滤

有好几个方法可以用来从流里面过滤出元素:

filter:通过传递一个预期匹配的对象作为参数并返回一个包含所有匹配到的对象的流。
distinct:返回包含唯一元素的流(唯一性取决于元素相等的实现方式)。
limit:返回一个特定上限的流。
skip:返回一个丢弃前n个元素的流。

过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。

    @Test
    public void fuc_02() {//过滤
        List<String> myList = Arrays.asList("aa", "bc", "ab", "bd", "af", "be", "ag", "aa", "bc", "ab", "bd", "af", "be", "ag");
        myList.stream()
                  .filter((s) -> s.startsWith("a")) //过滤了,仅包含s.startsWith("a")为true的字符串;
                  .forEach(System.out::println); //aa ab af ag
        System.out.println("-----取前5个元素:");
        myList.stream()**.limit(5**).forEach(System.out::println); //取前5个元素
        System.out.println("-----跳过前5个元素:");
        myList.stream()**.skip(5)**.forEach(System.out::println); //跳过前5个元素
        System.out.println("-----去相同元素:");
        myList.stream()**.distinct()**.forEach(System.out::println); //去相同元素
    }

3.Finding-查找

流接口还提供了像findFirst和findAny等从流中取出任意的元素。它们能与像filter方法相连接。findFirst和findAny都返回一个可选对象;

    @Test
    public void fuc_03() {
        List<String> myList = Arrays.asList("aa", "bc", "ab", "bd", "af", "be", "ag");
        String res = myList.stream().filter((s) -> s.startsWith("a")).findAny().get(); //找一个满足filter的元素
        System.out.println(res);
    }

4.Sorted-排序

    @Test
    public void fuc_03() {//Sorted-排序
        System.out.println("-----排序前:");
        List<String> myList = Arrays.asList("a", "c", "b", "d", "f", "e", "g");
        myList.stream().forEach(System.out::println);//去相同元素
        System.out.println("-----排序后:");
        myList.stream()**.sorted()**.forEach(System.out::println);//排序后
        myList.stream().sorted(String::compareTo).forEach(System.out::println);//排序后
    }

*5.Map-映射

中间操作map会将每个****元素根据指定的Function接口来依次将元素转成另外的对象。类似于SQL中的select,可以将一组元素转换成另一种元素

下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的;

    public void fuc_05() { //Map
        List<String> myList = Arrays.asList("a", "c", "b", "d", "f", "e", "g");
        myList.stream()
                .map(String::toUpperCase) // 映射:对于**每个元素**,**Function<String,String>**:转换成大写-对于流中**每一个元素**——将字符串转换为大写字符串;
                .sorted((a, b) -> b.compareTo(a)) // 重新排序-按字母从大到小;
                .forEach(System.out::println); // "A", "C", "B", "D", "F", "E", "G"
    }

6.Match-匹配(终止操作

Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个****Stream;所有的匹配操作都是“最终操作”,并返回一个boolean类型的值;

    @Test
    public void fuc_06() { //Match
        List<String> myList = Arrays.asList("a", "c", "b", "d", "f", "e", "g");
        boolean anyStartsWithA = myList.stream().anyMatch((s) -> s.startsWith("a"));
        System.out.println("存在任意以a为开头?"+anyStartsWithA);      // true
        boolean allStartsWithA = myList.stream().allMatch((s) -> s.startsWith("a"));
        System.out.println("全部字符串以a为开头?"+allStartsWithA);      // false
        boolean noneStartsWithZ = myList.stream().noneMatch((s) -> s.startsWith("z"));
        System.out.println("没有字符串以z为开头?"+noneStartsWithZ);      // true
    }

7.Count-计数

计数是一个“最终操作”,b遍历整个Stream,通过filter + Predicate判断条件,返回Stream中满足条件的元素的个数,返回值类型是long;

    @Test
    public void fuc_07() { //count()计数
        List<String> myList = Arrays.asList("aa", "bc", "ab", "bd", "af", "be", "ag");
        Stream<String> stream = myList.stream();
        long startsWithB = stream.filter((s) -> s.startsWith("b"))**.count()**;
        System.out.println(startsWithB);    // 3
        System.out.println(stream.count());//由于是终止操作,流已经关闭,java.lang.IllegalStateException: stream has already been operated upon or closed;
    }

8.Reduce-规约

这是一个“最终操作”,允许通过指定的函数BinaryOperator<T>(操作两个T类元素,返回一个T)来将stream中的多个元素规约为一个元素(有点像一个迭代/循环),规约后的结果是通过Optional接口表示的;例如,“计算最高金额的发票” 或者 “计算所有发票的总额”。 这可以应用流中的reduce方法反复应用于每个元素直到返回最后数据。

下面是reduce模式的例子,能帮你了解如何用for循环来计算一列数据的和:

int sum = 0; for (int x : numbers) {sum += x;}

对一列数据的每一个元素的值反复应用加法运算符获得结果,最终将一列值减少到一个值。

这段代码用到两个参数:初始化总和变量,这里是0;用来结合所有列表里面元素的操作方法,这里是加法操作。在流上应用reduce方法,可以把流里面的所有元素相加,如下:

int sum = numbers.stream().reduce(0, (a, b) -> a + b);

reduce方法需要两个参数:

  • 初始值,这里是0;

  • 一个BinaryOperator方法连接两个元素产生一个新元素。reduce方法本质上是抽象了重复方法模式; “计算最大值” 都是reduce方法的特殊用例,比如:

int max = numbers.stream().reduce(Integer.MIN_VALUE, Integer::max);
eg: 

    @Test
    public void fuc_08() {//reduce()规约

        // 字符串连接,concat = "ABCD"

        String concat = Stream.of("A", "B", "C", "D")**.reduce("", String::concat)**;

        System.out.println(concat);

        // 求最小值,minValue = -3.0

        double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce**(Double.MAX_VALUE, Double::min)**;

        System.out.println(minValue);

        // 求和,sumValue = 10, 有起始值

        int sumValue = Stream.of(1, 2, 3, 4)**.reduce(0, Integer::sum)**;

        System.out.println(sumValue);

        // 求和,sumValue = 10, **无起始值**

        sumValue = Stream.of(1, 2, 3, 4).reduce(**Integer::sum**).get();

        System.out.println(sumValue);

        // 过滤,字符串连接,concat = "ace"

        concat = Stream.of("a", "B", "c", "D", "e", "F").filter(x -> x.compareTo("Z") > 0).reduce("", String::concat);

        System.out.println(concat);

        //实际上,**reduce返回的是Optional<T>类型**

        List<String> myList = Arrays.asList("aa", "bc", "ab", "bd", "af", "be", "ag");

        Optional<String> reduced = myList.stream().sorted()**.reduce((s1, s2) -> s1 + "#" + s2)**;

        reduced.ifPresent(System.out::println);//aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2

    }

Optionals<T>?

Optionals不是函数式接口,而是用于防止NullPointerException的漂亮工具。Optional是一个简单的容器,其值可能是null或者不是null。在Java8之前一般某个函数应该返回非空对象但是有时却什么也没有返回,而在Java 8中,你应该返回Optional而不是null

@Test

public void fuc_09() {//Optional<T>不是函数式接口,而是用于防止NullPointerException的漂亮工具;

    //of():为非null的值创建一个Optional

    Optional<String> optional = Optional.of("haha");

    //Optional<String> optional = Optional.empty();

    // **isPresent**(): 如果值存在返回true,否则返回false

    System.out.println(optional.isPresent());// true

    //**get**():如果Optional有值则将其返回,否则抛出**NoSuchElementException**

    System.out.println(optional.get());                 // "haha"

    //**orElse**():如果有值则将其返回,否则返回指定的其它值

    System.out.println(optional.orElse("fallback"));    // "haha"

    //**ifPresent**():如果Optional实例有值则为其调用**consumer**,否则不做处理

    optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "h"

}

*9.Collectors-收集器——流转为集合List/Set

目前为止的方法都是返回另一个流或者一个像boolean, int类型的值(get()),或者返回一个可选对象(Optionals)。相比之下,collect方法是一个"结束操作",它可以使流里面的所有元素聚集到汇总结果。

collect(Collector<? super T, A, R> collector)

传递给collect方法参数是一个java.util.stream.Collector类型的对象Collector对象实际上定义了一个如何把流中的元素聚集到最终结果的方法。最开始,工厂方法Collectors.toList()被用来返回一个描述了如何把流转变成一个List的Collector对象。后来Collectors类又内建了很多相似的collectors变量。例如,你可以用Collectors.groupingBy方法按消费者把发票分组,如下:

Map<Customer, List> customerToInvoices = invoices.stream().collect(Collectors.groupingBy(Invoice::getCustomer));
eg:

    @Test
    public void fuc_10() {//Collectors-收集器

        List<Integer> integers = Stream.of(1, 2, 3, 4).collect(Collectors.toList());

        Set<Integer> set = Stream.of(1, 2, 3).collect(Collectors.toSet());

        // 使用自己希望的集合

        ArrayList<Integer> integers2 = Stream.of(1, 2, 3).collect(Collectors.toCollection(ArrayList::new));

    }

数据分组?——转成Map(使用groupingBy-组合收集器)

使用方法: .collect(Collectors.groupingBy(...)), eg:

(I) 只传入Function ,即分类规则,计算的结果作为key,得到的key相等的流元素组成一个List,作为Map的value;这里其实与下面的(II)是相同的,只不过隐式的第二个参数是Collectors.toList()而已;

.groupingBy(Function classifier)

Map<Integer,List<Integer>> = Stream.of(1, 11, 22, 2, 3, 33).collect(Collectors.groupingBy(i->i%10));
输出:{1=[1, 11], 2=[22, 2], 3=[3, 33]}

解释:按照"i → i%10"的规则对"1, 11, 22, 2, 3, 33"分组,运算结果值相等的为一组(放入同一个**List**)作为**value值**,"i → i%10"则作为Map的key;

如果问题比较复杂,还可以将多个收集器组合起来使用,一些收集器有重载的版本,支持第二个收集器,可以用来实现这个功能;

(II) 传入Function和Collector,与(1)类似,第一个参数是分类器,分类规则计算的结果作为key,得到的key相等的流元素还要进一步操作(第二个参数:Collector对象);

与上面的(I)是一样的,第二个参数除了Collectors.toList(),还可以对List元素进一步处理再放入List,如Collectors.mapping...对原List元素操作(类似上面的map()依次对每个元素操作,mapping的第二个参数是Collectors.toList()),其操作后的结果组成一个List,作为value;

其他的还有Collectors.counting()计数,Map的value为Long(不是Integer)Collectors.summingInt(...)对对象的某个属性求和,Map的value为Integer;...

Collector groupingBy(Function classifier, Collector downstream)

eg: 拿一个数据分组的例子来说,不仅要分组,而且只需要每组的十位数字,那么就可以这样写:groupingBy的第二个参数可以使用mapping收集器,mapping这里的作用和流操作的map类似,将流再次进行映射,然后收集结果作为最后Map的键值对。

    //按个位数字分组,然后只获取十位数字 <组号(取模),List l)>
    //这里Map的value一定是List
    Map<Integer, List<Integer>> map = Stream.of(21, 32, 43, 54, 11, 33, 22) 
            .collect(Collectors.groupingBy(i -> i % 10, Collectors.mapping(i -> i / 10, Collectors.toList())));

    System.out.println(map);
    // keys=values,输出:{1=[2, 1], 2=[3, 2], 3=[4, 3], 4=[5]}

Collectors.groupingBy()的用法示例?

eg: 
@Test public void fuc_13() {//测试Collectors.groupingBy
    List<Fruit> fruitList = Arrays.asList(new Fruit[]{ 
    new Fruit("apple", 6), new Fruit("apple", 6),
    new Fruit("banana", 7), new Fruit("banana", 7),
    new Fruit("banana", 7), new Fruit("grape", 8)});
  //1.计数,value类型为Long
  Map<String, Long> map_f = fruitList.stream()
            .collect(Collectors.groupingBy(Fruit::getName, Collectors.counting()));
  System.out.println("计数1:"+map_f);

  //等效于上面的方法,先map把对象一一转成name对象,将对象(String)本身作为key;
  map_f = fruitList.stream().map(Fruit::getName).
  collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));//计数
  System.out.println("计数2:"+map_f);

  //23333.Map转Entry,再取流,对value排序
  System.out.println("排序:");
  map_f.entrySet().stream().sorted(Map.Entry.<String, Long>comparingByValue())
            .forEachOrdered((i) -> {System.out.print(i + " ");  });

  // 2.累加求和,value类型为int
  Map<String, Integer> map_S = fruitList.stream().collect(Collectors.groupingBy(Fruit::getName, Collectors.summingInt(Fruit::getPrice)));//求和
  System.out.println("int类型-累加:" + map_S);
  //3.分组
  Map<String, List<Fruit>> map_g = fruitList.stream().collect(Collectors.groupingBy(Fruit::getName));//查看JDK源码,第二个参数Collectors.toList()是隐式的,等效于下面一句
  System.out.println("按name-分组:"+map_g);//这里会打印List对象,key=cn.ming.Fruit@...,除非Fruit类重写toString()方法;
  
  map_g = fruitList.stream().collect(Collectors.groupingBy(Fruit::getName, Collectors.toList()));
  System.out.println("按name-分组:"+map_g);
}

10.Parallel Streams-并行流

Stream API 支持方便的数据并行。换句话说,你可以明确地让流管道以并行的方式运行而不用关心底层的具体实现。在这背后,Stream API使用了Fork/Join框架充分利用了你机器的多核架构

你所需要做的无非是用parallelStream()方法替换stream()方法

下面的排序的例子展示了是如何通过并行Stream来提升性能

    @Test
    public void fuc_11() {//Parallel Streams-**并行流 排序测试**

        int max = 1000000;

        List<String> values = new ArrayList<>(max);

        for (int i = 0; i < max; i++) {
            UUID uuid = UUID.randomUUID();
            values.add(uuid.toString());
        }

        //**串行排序**

        long t0 = System.nanoTime();

        long count = values.stream().sorted().count();
        System.out.println(count);

        long t1 = System.nanoTime();
        long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);

        System.out.println(String.format("sequential sort took: %d ms", millis));

        //**并行排序**

        long t2 = System.nanoTime();

        long count2 = values.parallelStream().sorted().count();
        System.out.println(count2);

        long t3 = System.nanoTime();
        long millis2 = TimeUnit.NANOSECONDS.toMillis(t3 - t2);

        System.out.println(String.format("parallel sort took: %d ms", millis2));
    }

惰性求值?

流类库设计的非常精巧,也对性能做了很多优化。所有的流操作都是惰性的,也就是说直到最后调用收集器的时候,整个流操作才开始进行。在这之前,你的流操作只不过类似于SQL的执行计划,这时候还没有真正执行程序。所以下面的代码什么都不会输出。

Stream.of(1, 2, 3, 4, 5)
        .filter(i -> i > 1)
        .filter(i -> {
            System.out.print(i);
            return i <= 3;
        });

关于Map集合类?

Map接口本身没有可用的stream()方法( List和Set都有,eg: .collect(Collectors.toList()); .collect(Collectors.toSet()); ),但是你可以在键key-值value上创建专门的流或者通过map.keySet().stream()map.values().stream()map.entrySet().stream()

Map类型不支持streams,不过Map提供了一些新的有用的方法来处理一些日常任务(对key-value的操作),如下:

    @Test
    public void fuc_12() {//关于Map<K,V> 没有转换成stream()的方法

        Map<Integer, String> map = new HashMap<>();
        for (int i = 0; i < 10; i++) {
            map.putIfAbsent(i, "val-" + i); // 初始化Map,给key-value赋值
        }

        map.forEach((id, val) -> System.out.println(id + " ^" + val)); //forEach遍历 val0 val1 val2 val3 val4 val5 val6 val7 val8 val9

        map.computeIfPresent(3, (num, val) -> val + num); //计算value:computeIfPresent(), 并且把新value的计算结果put进去

        System.out.println(map.get(3));             // “val-3”+3 = "val-33"

        map.computeIfPresent(9, (num, val) -> null);

        System.out.println(map.containsKey(9)); // 上一步key=9对应的value被置为null,返回false,这一步等效于删除(K, V)

        map.computeIfPresent(11, (num, val) -> num + val);

        System.out.println(map.containsKey(11)); // 不存在该key,返回false

        map.computeIfAbsent(23, num -> "val" + num); //**计算value:computeIfAbsent()

        System.out.println(map.containsKey(23)); // true

        map.computeIfAbsent(3, num -> "33333"); //IfAbsent为false,因此value不变

        System.out.println(map.get(3)); // val-33

接下来展示如何在Map里删除一个键值K/V 全都匹配的项:

        System.out.println(map.remove(3, "val-3")); //没匹配成功,返回false

        System.out.println(map.remove(3, "val-33")); //匹配成功,删除,返回true

        System.out.println(map.get(3)); //null

        System.out.println(map.getOrDefault(33, "not found"));//**not found,返回原值或者默认值(如归原value为空),这一步没有put操作**

        System.out.println(map.get(33)); // null,上一步getOrDefault()的操作没有对map进行put操作,仅返回值

对Map的元素做合并也变得很容易了:Merge 做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中;

        System.out.println(map.get(9)); //之前的状态:<9,null>

        map.merge(9, "val-9", (value, newValue) -> value.concat(newValue));//不存在键名key,插入

        System.out.println(map.get(9));             // val9

        map.merge(9, "-concat", (value, newValue) -> value.concat(newValue));//存在key,执行后面的操作,把结果作为新value给put进去;

        System.out.println(map.get(9));  

总结

  • 流是一列支持聚合操作的来自于不同数据源的元素列表;

  • 流有两种类型的操作方法:中间方法和终结方法;

  • 中间方法可以被连接起来形成管道;

  • 中间方法包括filter,map,distinct和sorted;

  • 终结方法处理流管道并返回一个结果;

  • 终结方法包括allMatch,collect和forEach;

  • Collectors是一个第应以了如何将流中的元素聚集到最终结果的方法,包括像List和Map一样的容器;

  • 流管道可以被并行地计算;

  • 当应用parallel stream 来提高性能时有很多个方面需要考虑,包括数据结构划分的难易程度,计算每个元素花费的高低,装箱的难易,数据量的多少和可用核的数量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值