jdk8新特性---------Lambda、Stream操作

为什么要用jdk8的新特性?

1、代码简洁、掌握后编写容易    2、性能高于传统操作

一、函数式接口

函数式接口:就是一个接口中只能包含一个抽象方法。没有/多于一个则不是函数式接口。

Lambda只适用于接口式接口,Lambda一般用作参数(匿名内部类)/返回值

二、Lambda

三、方法引用

 ”::“ 双冒号的方法调用方法:这被称为“方法引用”,是一种新的语法。 

应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用

public class DemoPrintRef {  

    public static void getMax(int[] arr) {         
            int sum = 0;         
            for (int n : arr) {  sum += n;         }         
            System.out.println(sum);     
    } 
 
   /* public static void main(String[] args) { //不用;;引用方法时        
        printMax((int[] arr) -> {             
            int sum = 0;             
            for (int n : arr) {                 
                sum += n;             
            }             
        System.out.println(sum);         
        });     
      } 
   */


    public static void main(String[] args) {  
       printMax(Demo11MethodRefIntro::getMax);    //用::引用上面的方法
    }
 
 
    private static void printMax(Consumer consumer) {         
        int[] arr = {10, 20, 30, 40, 50};         
        consumer.accept(arr);     
    } 
} 

常见引用方式 
方法引用在JDK 8中使用方式相当灵活,有以下几种形式:
1. instanceName::methodName 对象::方法名

2. ClassName::staticMethodName 类名::静态方法

3. ClassName::methodName 类名::普通方法

4. ClassName::new 类名::new 调用的构造器

5. TypeName[]::new String[]::new 调用数组的构造器

小结 
首先了解Lambda表达式的冗余情况,体验了方法引用,了解常见的方法引用方式 

 

四、Stream流

1、整体介绍     

      stream流主要是用来处理一些集合的,在传统对集合的处理一般是使用for、迭代器、增强for,而jdk8后提出了Lambda表达式,该表达式存在延时执行的功能,在一些场景下能使得性能变高(上面Lambda有举例讲到)(stream流中大量的方法结合Lambda表达式),另外jdk8也为stream提供了顺序流和并行流(fork/join),并行流可以利用多核cpu进行并行计算,大大提高计算效率(但需要较大的cpu资源)

2、Stream流式思想概述 

注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!

        Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工 处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
jdk8新特性---------Lambda、Stream操作_第1张图片     jdk8新特性---------Lambda、Stream操作_第2张图片  那么既然把stream比喻做加工厂的生产流水线,那么我们完成加工一个产品需要什么步骤?
(1)原材料:需要获取到Stream流的源

(2)中间加工步骤:对stream流的操作加工过程

(3)加工完成:即对于stream流加工的最后一步(该步骤做完则会对流进行关闭)

那么下面对于这三个步骤进行详细分析:

3、原材料

该步骤是将一些集合、数组转换为stream流,主要有两种方式:

(1)对于集合(属于Collection接口:set、list等)(注意map不属于Collection):一般使用Collection的默认方法stream()获取流。举例:

 List list = new ArrayList<>();         
//获取流         Stream stream1 = list.stream(); 

 Set set = new HashSet<>();         
//获取流      Stream stream2 = set.stream(); 

 Vector vector = new Vector<>();    
// ...         Stream stream3 = vector.stream();   

对于map:java.util.Map 接口不是 Collection 的子接口,所以获取对应的流需要分key、value或entry等情况:

Map map = new HashMap<>();         
// 1、可以分别获取key、value的流         
Stream keyStream = map.keySet().stream();         
Stream valueStream = map.values().stream();
   
// 2、也可以key+value一起,Stream流中的元素是以map的形式存在      
Stream> entryStream = map.entrySet().stream(); 

(2) : Stream中的静态方法of获取流,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of ,使用很简单:

 // Stream中的静态方法: static Stream of(T... values)         
Stream stream6 = Stream.of("aa", "bb", "cc"); 
String[] arr = {"aa", "bb", "cc"};         
Stream stream7 = Stream.of(arr); 
 
Integer[] arr2 = {11, 22, 33};         
Stream stream8 = Stream.of(arr2); 
 
// 注意:基本数据类型的数组不行         
int[] arr3 = {11, 22, 33};         
Stream stream9 = Stream.of(arr3); 

注意:基本类型要转换为包装类,Stream包其实有为基本数据类型提供特殊的stream流(后面讲),其中: of 方法的参数其实是一个可变参数,所以支持数组。 

总结:

1. 通过Collection接口中的默认方法Stream stream()

2. 通过Stream接口中的静态of方法 

4、中间加工步骤

在对于流的加工处理中,存在两种操作,一种叫中间操作,一种叫终结操作。而中间加工步骤则是进行一个/多个中间方法的调用。所以该步骤实质就是调用一/多个中间方法:(注意:终结操作不执行,则中间操作不执行。)

主要的中间操作方法:(一般返回值为Stream的则为中间操作方法)

  1. distinct:去重
  2. filter:过滤
  3. map:映射(int映射成char)
  4. limit: 截取前几个元素
  5. sorted:按照自然排序方式进行排序
  6. skip:返回丢弃了前n个元素的流
  7. concat:对多个流进行合并
  8. mapToInt()、mapToLong() 以及mapToDouble:将流的元素包装类型转换为基本类型

1、distinct

Stream.of(22, 33, 22, 11, 33)             
    .distinct()            
    .forEach(System.out::println); 

//结果:22、33、11

自定义类型是根据对象的hashCode和equals来去除重复元素的

 Stream.of(             
    new Person("刘德华", 58),             
    new Person("张学友", 56),             
    new Person("张学友", 56),             
    new Person("黎明", 52))             
    .distinct()             
    .forEach(System.out::println); 

2、filter(返回筛选完的流)

List one = new ArrayList<>();     
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子"); 
 
one.stream().filter(s -> s.length() == 2).forEach(System.out::println);
//过滤出来长度为2的名字

结果:老子 庄子 孙子 对应的stream流

3、map:

map方法将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同。

下面的代码中将字符元素映射成它的哈希码(ASCII值)。


List l = Stream.of('a','b','c')
        .map( c -> c.hashCode())
        .collect(Collectors.toList());
System.out.println(l); //[97, 98, 99]

4、limit:limit 方法可以对流进行截取,只取用前n个。

对于串行流,这个方法是有效的,这是因为它只需返回前n个元素即可,但是对于有序的并行流,它可能花费相对较长的时间,如果你不在意有序,可以将有序并行流转换为无序的,可以提高性能。

 List one = new ArrayList<>();     
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子"); 
 one.stream().limit(3).forEach(System.out::println); 

//结果:"迪丽热巴", "宋远桥", "苏星河"

5、sorted:如果需要将数据排序,可以使用 sorted 方法

Stream sorted();

Stream sorted(Comparator comparator); 

sorted()将流中的元素按照自然排序方式进行排序,如果元素没有实现Comparable,则终点操作执行时会抛出java.lang.ClassCastException异常。
sorted(Comparator comparator)可以指定排序的方式。

对于有序流,排序是稳定的。对于非有序流,不保证排序稳定。

// sorted(): 根据元素的自然顺序排序     
// sorted(Comparator comparator): 根据比较器指定的规则排序     
Stream.of(33, 22, 11, 55)             
    .sorted()             
    .sorted((o1, o2) -> o2 - o1)    //自定义          
    .forEach(System.out::println); 

6、skip:返回丢弃了前n个元素的流

如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
 

List one = new ArrayList<>();     
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子"); 
 one.stream().skip(2).forEach(System.out::println)

7、concat:多个流合并成一个流

Stream.concat(streamA, streamB); 

8、mapToInt()、mapToLong() 以及mapToDouble:将流的元素包装类型转换为基本类型

// Integer占用的内存比int多,在Stream流操作中会自动装箱和拆箱     
Stream stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5}); 
 
    // 把大于3的和打印出来     
// Integer result = stream     
//         .filter(i -> i.intValue() > 3) 

//         .reduce(0, Integer::sum);     
// System.out.println(result); 
 
    // 先将流中的Integer数据转成int,后续都是操作int类型     
IntStream intStream = stream.mapToInt(Integer::intValue);     
int reduce = intStream             
    .filter(i -> i > 3)             
    .reduce(0, Integer::sum);     
System.out.println(reduce); 
 
    // 将IntStream转化为Stream     
IntStream intStream1 = IntStream.rangeClosed(1, 10);     
Stream boxed = intStream1.boxed();     
boxed.forEach(s -> System.out.println(s.getClass() + ", " + s)); 

注意:中间方法调用完后生成返回新的流,原先的流则不保存。所以流一般也说是只操作一次。可以调用流的方法再创建出一个流对象,再对新创出来的流对象进行操作。

5、加工完成

这一步是流操作的最后一步,该步走完就会关闭流。该步骤主要是对流的终结操作

终结方法:

  1. Match:来检查流中的元素是否满足条件
  2. count:计算元素个数
  3. collect:将流的元素存为某种类型的容器(String、list、set等)
  4. find:返回某个元素
  5. forEach、forEachOrdered:遍历集合
  6. max、min:返回最大、最小值
  7. reduce:将所有数据归纳得到一个数据(常用)
  8. toArray():流中的元素放入到一个数组中

写的太累了,这里就解释几个就好

Match:有三个实现方法,可以得到匹配的值

  boolean b = Stream.of(5, 3, 6, 1)             
    // .allMatch(e -> e > 0); 
    // allMatch: 元素是否全部满足条件             
    // .anyMatch(e -> e > 5); 
    // anyMatch: 元素是否任意有一个满足条件             
      .noneMatch(e -> e < 0); 
    // noneMatch: 元素是否全部不满足条件     
    System.out.println("b = " + b); 

结果返回布尔值

collect:将流的元素存为某种类型的容器(可以配合collections工具类的toList、groupingBy、toSet等方法)

List output = wordList.stream().
     map(String::toUpperCase).collect(Collectors.toList());

//先将流的字母改为大写,再存到list集合中

find:findAny()返回任意一个元素,如果流为空,返回空的Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于findFirst(),但是有可能多次执行的时候返回的结果不一样。
findFirst()返回第一个元素,如果流为空,返回空的Optional。

forEach:遍历

 List one = new ArrayList<>();     
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");          /*one.stream().forEach((String s) -> {         System.out.println(s);     });*/          
// 简写     
// one.stream().forEach(s -> System.out.println(s)); 
 
    one.stream().forEach(System.out::println); 

reduce:

Integer total2 = Stream.of(1,2,3,4,5).reduce(0, (x, y) -> x +y);

其中:先将0赋给x;然后将流元素赋给y;计算后的结果赋给下一次的x。

最后得到的是x=1+2+3+4+5=15

由于x+y的表达式可以变化为其他类型,所以该方法有很多应用

6、上面讲了一些方法,下面讲下stream流的两种形式:串行(单线程)、并行(多线程)

1、获取并行流:两种方式:

直接获取并行流: parallelStream()
将串行流转成并行流: parallel()

parallelStream其实就是一个并行执行的流。它通过默认的ForkJoinPool,可能提高多线程任务的速度。 

2、效率对比

3、parallelStream的安全问题

该流的操作是存在安全问题的,可以通过加锁、使用安全的容器、使用toArrary()/collect()方法

4、parallelStream背后的技术:fork/join

fork/join框架:1.7提出,使用分治的思想,大任务拆成多个小任务异步执行,最后join执行。

Fork/Join原理-工作窃取算法 
Fork/Join最核心的地方就是利用了现代硬件设备多核,在一个操作时候会有空闲的cpu,那么如何利用好这个空闲的 cpu就成了提高性能的关键,而这里我们要提到的工作窃取(work-stealing)算法就是整个Fork/Join框架的核心理念 Fork/Join工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。

那么为什么需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖 的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来 执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的 任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就 去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任 务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永 远从双端队列的尾部拿任务执行。
工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争, 比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。
上文中已经提到了在Java 8引入了自动并行化的概念。它能够让一部分Java代码自动地以并行的方式执行,也就是我 们使用了ForkJoinPool的ParallelStream。
对于ForkJoinPool通用线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量。可以通过设置 系统属性:java.util.concurrent.ForkJoinPool.common.parallelism=N (N为线程数量),来调整ForkJoinPool的线 程数量,可以尝试调整成不同的参数来观察每次的输出结果。
 

小结 
1. parallelStream是线程不安全的

2. parallelStream适用的场景是CPU密集型的,只是做到别浪费CPU,假如本身电脑CPU的负载很大,那还到处用 并行流,那并不能起到作用

3. I/O密集型 磁盘I/O、网络I/O都属于I/O操作,这部分操作是较少消耗CPU资源,一般并行流中不适用于I/O密集 型的操作,就比如使用并流行进行大批量的消息推送,涉及到了大量I/O,使用并行流反而慢了很多

4. 在使用并行流的时候是无法保证元素的顺序的,也就是即使你用了同步集合也只能保证元素都正确但无法保证 其中的顺序
 

7、Optional类

8、新的日期和时间 API

9、重复注解与类型注解 

累了,其他以后再写

可以参考:https://colobu.com/2016/03/02/Java-Stream/#collect

https://juejin.im/post/6844903830254010381

https://www.exception.site/java8/java8-new-features

 

你可能感兴趣的:(jdk8新特性---------Lambda、Stream操作)