Java8新特性学习笔记

Java8新特性学习笔记

文章目录

一、接口和日期处理

1、接口增强

1.1、JDK8以前 VS JDK8
1)接口定义:
  • jdk8以前
 interface 接口名{
 	静态常量;
 	抽象方法;
 }
  • jdk8
 //JDK8之后对接口做了增加,接口中可以有默认方法和静态方法
 interface 接口名{
 	静态常量;
 	抽象方法;
    默认方法;
 	静态方法;
 }
1.2、默认方法(default)
1)默认方法格式
interface 接口名{
	default 返回值类型 方法名(参数类型 参数名...){
		方法体;
	}
}
2)默认方法的两种使用方式
  • 实现类直接调用接口的默认方法
  • 实现类重写接口的默认方法
3)为什么要增加默认方法
在JDK8以前接口中只能有抽象方法和静态常量,会存在以下的问题:
	- 如果接口中新增抽象方法,那么实现类都必须要抽象这个抽象方法,非常不利于接口的扩展(例如Map接口)。
1.3、静态方法(static)
1)静态方法格式
interface 接口名{
   static 返回值类型 方法名(参数类型 参数名...){
       方法体;
   }
}
2)静态方法的使用
  • 接口中的静态方法在实现类中是不能被重写的,调用的话只能通过接口类型来实现: 接口名.静态方法名();
1.4、默认和静态的区别
  • 默认方法通过实例调用,静态方法通过接口名调用。
  • 默认方法可以被继承,实现类可以直接调用接口默认方法,也可以重写接口默认方法。
  • 静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名调用。
1.5、最佳实践
/**
* 场景1、类似使用模版方法设计模式:定义一个default方法,设计其他抽象方法的执行流程。缺点:抽象方法不能私有。
* 场景2、接口定义一系列default空方法,在实现类需要的时候再重写相关方法,不需要全部实现。
* 场景3、接口定义的default方法类似钩子函数(before、after),返回默认值或者啥也不干。绝大部分实现类不需要重写即可满足使用。
*/

2、日期处理类

2.1、旧版日期时间的问题
1)设计不合理
  • java.util.Date构造方法是从1900年开始,月份从0开始,且很多方法是弃用的,已经标记为@Deprecated。

  • java.util.Date 和 java.util.Calendar 类易用性差,不支持时区,有些地方隐含的使用了系统默认时区进行转化(例如toString()方法)。

  • Date如果不格式化,打印出的日期可读性差。Wed Mar 23 18:45:56 CST 2022

2)非线程安全
  • java.util.Date是非线程安全的,所有的日期类都是可变的。

  • 使用 SimpleDateFormat 对Date进行格式化(format)和解析(parse)是线程不安全的。

    • format:格式化的时候,内部使用的 calendar 是共享变量,并且这个共享变量没有做线程安全控制。当多个线程同时使用相同的 SimpleDateFormat 对象调用format方法时,多个线程会同时调用 calendar.setTime 方法。可能一个线程刚设置好 time 值另外的一个线程马上把设置的 time 值给修改了。

    • parse:解析的时候,parse 方法实际调用 CalendarBuilder.establish(calendar).getTime() 方法来解析,CalendarBuilder.establish(calendar) 方法里主要完成了以下步骤:(这三步不是原子操作,多线程环境下导致解析出来的时间可能是错误的或者抛出异常

      • 重置日期对象calendar的属性值
      • 使用CalendarBuilder中的属性设置calendar
      • 返回设置好的calendar对象

  • 多线程并发如何保证SimpleDateFormat线程安全?

    • 避免线程之间共享一个 SimpleDateFormat 对象。

      • 每个线程使用时都创建一次 SimpleDateFormat 对象 。(创建和销毁对象的开销大)

      • 对使用 format 和 parse 方法的地方进行加锁。(线程阻塞性能差)

      • 使用 ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本。

         @Test
             public void test00() {
         
                 ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MINUTES, new LinkedBlockingQueue<>(10));
                 int i = 10;
         
         //        1、使用全局共享SimpleDateFormat
         //        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         
                 //3、使用ThreadLocal
                 final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>() {
                     @Override
                     protected SimpleDateFormat initialValue() {
                         return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                     }
                 };
         
                 while (i-- > 0) {
                     poolExecutor.execute(new Runnable() {
                         @Override
                         public void run() {
         //                    2、使用局部SimpleDateFormat
         //                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         
                             //3、使用ThreadLocal
                             SimpleDateFormat simpleDateFormat = THREAD_LOCAL.get();
                             
                             String dateString = simpleDateFormat.format(new Date());
                             try {
                                 Date parseDate = simpleDateFormat.parse(dateString);
                                 String dateString2 = simpleDateFormat.format(parseDate);
                                 System.out.println(dateString.equals(dateString2));
                             } catch (Exception e) {
                                 e.printStackTrace();
                             }
                         }
                     });
                 }
             }
        
2.2、新版日期时间API介绍
1)简单介绍:
  • JDK 8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于 java.time 包中。
  • 关键类:
    • LocalDate :表示日期,包含年月日,格式为 2022-03-23
    • LocalTime :表示时间,包含时分秒,格式为 18:52:51.564
    • LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2022-03-23T18:52:51.564
    • DateTimeFormatter :日期时间格式化类。
    • Instant:时间戳,表示自1970-01-01T00:00:00以来的某个时间,类似Date(只精确到毫秒,1个long类型字段)。Instant可以精确到纳秒(两个long类型字段)。
    • Duration:用来度量秒和纳秒之间的时间值。可以用于计算2个日期时间(LocalTime、LocalDateTime)的间隔。
    • Period:用来度量年月日和几天之间的时间值。可以用于计算2个日期(LocalDate,年月日)的间隔。
    • ZonedDateTime :包含时区的时间。
2)日期时间的常见操作
  • LocalDate
     /**
      * 日期操作
      */
     @Test
     public void test01() {
         // 1.创建指定的日期
         LocalDate date1 = LocalDate.of(2021, 05, 06);
         System.out.println("date1 = " + date1);//date1 = 2021-05-06
         // 2.得到当前的日期
         LocalDate now = LocalDate.now();
         System.out.println("now = " + now);//now = 2022-03-23
         // 3.根据LocalDate对象获取对应的日期信息
         System.out.println("年:" + now.getYear());//年:2022
         System.out.println("月:" + now.getMonth().getValue());//月:3
         System.out.println("日:" + now.getDayOfMonth());//日:23
         System.out.println("星期:" + now.getDayOfWeek().getValue());//星期:3
     }
  • LocalTime
     /**
      * 时间操作
      */
     @Test
     public void test02() {
         // 1.得到指定的时间
         LocalTime time = LocalTime.of(5, 26, 33, 100);
         System.out.println(time);//05:26:33.000000100
         // 2.获取当前的时间
         LocalTime now = LocalTime.now();
         System.out.println(now);//19:15:48.547
         // 3.获取时间信息
         System.out.println(now.getHour());//19
         System.out.println(now.getMinute());//15
         System.out.println(now.getSecond());//48
         System.out.println(now.getNano());//547000000
     }
  • LocalDateTime
     /**
      * 日期时间操作
      */
     @Test
     public void test03() {
         // 获取指定的日期时间
         LocalDateTime dateTime =
                 LocalDateTime.of(2020
                         , 06
                         , 01
                         , 12
                         , 12
                         , 33
                         , 213);
         System.out.println(dateTime);//2020-06-01T12:12:33.000000213
         // 获取当前的日期时间
         LocalDateTime now = LocalDateTime.now();
         System.out.println(now);//2022-03-23T19:18:22.029
         // 获取日期时间信息
         System.out.println(now.getYear());//2022
         System.out.println(now.getMonth().getValue());//3
         System.out.println(now.getDayOfMonth());//23
         System.out.println(now.getDayOfWeek().getValue());//3
         System.out.println(now.getHour());//19
         System.out.println(now.getMinute());//18
         System.out.println(now.getSecond());//22
         System.out.println(now.getNano());//29000000
     }
3)日期时间的修改和比较
  • 日期时间的修改
 	/**
      * 日期时间的修改
      */
     @Test
     public void test04(){
         LocalDateTime now = LocalDateTime.now();
         // 在进行日期时间修改的时候,原来的LocalDat对象是不会被修改,每次操作都是返回了一个新的对象,所以在多线程场景下是数据安全的。
         LocalDateTime localDateTime = now.withYear(1998);
         System.out.println("now :"+now);//now :2022-03-23T19:21:05.334
         System.out.println("修改后的:" + localDateTime);//修改后的:1998-03-23T19:21:05.334
         System.out.println("月份:" + now.withMonth(10));//月份:2022-10-23T19:21:05.334
         System.out.println("天:" + now.withDayOfMonth(6));//天:2022-03-06T19:21:05.334
         System.out.println("小时:" + now.withHour(8));//小时:2022-03-23T08:21:05.334
         System.out.println("分钟:" + now.withMinute(15));//分钟:2022-03-23T19:15:05.334
         // 在当前日期时间的基础上 加上或者减去指定的时间
         System.out.println("两天后:" + now.plusDays(2));//两天后:2022-03-25T19:21:05.334
         System.out.println("10年后:"+now.plusYears(10));//10年后:2032-03-23T19:21:05.334
         System.out.println("6个月后:" + now.plusMonths(6));//6个月后:2022-09-23T19:21:05.334
         System.out.println("10年前:" + now.minusYears(10));//10年前:2012-03-23T19:21:05.334
         System.out.println("半年前:" + now.minusMonths(6));//半年前:2021-09-23T19:21:05.334
         System.out.println("一周前:" + now.minusDays(7));//一周前:2022-03-16T19:21:05.334
     }
  • 日期时间的比较
     /**
      * 日期时间的比较
      */
     @Test
     public void test05() {
         LocalDate now = LocalDate.now();//2022-03-23
         LocalDate date = LocalDate.of(2020, 1, 3);
         // 在JDK8中要实现 日期的比较 isAfter isBefore isEqual 通过这几个方法来直接比较
         System.out.println(now.isAfter(date)); // true
         System.out.println(now.isBefore(date)); // false
         System.out.println(now.isEqual(date)); // false
     }
4)格式化和解析操作
  • 在JDK8中可以通过 java.time.format.DateTimeFormatter 类可以进行日期的解析和格式化操作,类不可变且线程安全。
     /**
      * 日期格式化和解析操作
      */
     @Test
     public void test06() {
         LocalDateTime now = LocalDateTime.now();
         // 指定格式 使用系统默认的格式
         DateTimeFormatter isoLocalDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
         // 将日期时间转换为字符串
         String format = now.format(isoLocalDateTime);
         System.out.println("format = " + format);//format = 2022-03-23T19:30:34.372
         // 通过 ofPattern 方法来指定特定的格式
         DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
         String format1 = now.format(dateTimeFormatter);
         System.out.println("format1 = " + format1);//format1 = 2022-03-23 19:30:34
         // 将字符串解析为一个 日期时间类型
         LocalDateTime parse = LocalDateTime.parse("1997-05-06 22:45:16",
                 dateTimeFormatter);
         System.out.println("parse = " + parse);//parse = 1997-05-06T22:45:16
     }
5)Instant类
  • JDK8新增一个Instant类(时间戳),内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。
     /**
      * Instant 时间戳 从1970年1月1日 00:00:00开始,默认是UTC时区。
      */
     @Test
     public void test07() throws Exception {
         Instant now = Instant.now();
         System.out.println(now);//2022-03-24T06:22:59.301Z
         Thread.sleep(500);
         Instant now1 = Instant.now();
         //Instant.toEpochMilli()和Date.getTime()一样,同一时间得到的结果和System.currentTimeMillis()一样。
         System.out.println("now时间戳:" + now.toEpochMilli());//now时间戳:1648102979301
         System.out.println("now1时间戳:" + now1.toEpochMilli());//now1时间戳:1648102979863
         System.out.println("耗时(毫秒):" + (now1.toEpochMilli() - now.toEpochMilli()));//耗时(毫秒):562
     }
6)计算日期时间差
  • JDK8中提供了两个工具类 Duration/Period 计算日期时间差。
 	/**
      * 计算日期时间差
      */
     @Test
     public void test08(){
         LocalTime now = LocalTime.now();
         LocalTime time = LocalTime.of(22, 48, 59);
         System.out.println("now = " + now);//now = 15:06:40.742
         // 通过Duration来计算时间差(每个结果都是两个区间的真实间隔)
         Duration duration = Duration.between(now, time);
         System.out.println(duration.toDays()); // 0
         System.out.println(duration.toHours()); // 7
         System.out.println(duration.toMinutes()); // 462
         System.out.println(duration.toMillis()); // 27738258
         // 通过Period计算日期差(每个结果只是区间同一属性字段的间隔)
         LocalDate nowDate = LocalDate.now();
         LocalDate date = LocalDate.of(1997, 12, 5);
         Period period = Period.between(date, nowDate);
         System.out.println(period.getYears()); // 24 (1997->2022)
         System.out.println(period.getMonths()); // 3 (12->3)
         System.out.println(period.getDays()); // 19 (5->24)
     }
7)时间校正器
  • TemporalAdjuster:时间校正器

  • TemporalAdjusters:通过该类静态方法提供了大量的常用TemporalAdjuster的实现。

     /**
      * 时间校正器
      */
     @Test
     public void test09(){
         LocalDateTime now = LocalDateTime.now();
         // 将当前的日期调整到下个月的一号
         TemporalAdjuster adJuster = new TemporalAdjuster() {
             @Override
             public Temporal adjustInto(Temporal temporal) {
                 LocalDateTime dateTime = (LocalDateTime) temporal;
                 LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);
                 System.out.println("nextMonth = " + nextMonth);//nextMonth = 2022-04-01T15:41:28.243
                 return nextMonth;
             }
         };
         // 通过TemporalAdjusters 来实现
         // LocalDateTime nextMonth = now.with(adJuster);
         LocalDateTime nextMonth =
                 now.with(TemporalAdjusters.firstDayOfNextMonth());
         System.out.println("nextMonth = " + nextMonth);//nextMonth = 2022-04-01T15:41:28.243
     }

8)日期时间的时区
  • ZonedDateTime:Java8中加入了对时区的支持。

  • ZoneId:该类中包含了所有的时区信息。每个时区都对应着 ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。

     /**
      * 时区操作,ZonedDateTime可以和LocalDateTime相互转换。
      */
     @Test
     public void test10() {
         // 获取当前时间 中国使用的 东八区的时区,比标准时间早8个小时
         LocalDateTime now = LocalDateTime.now();
         System.out.println("now = " + now); //now = 2022-03-24T15:36:22.102
         // 获取标准时间
         ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
         System.out.println("bz = " + bz); //bz = 2022-03-24T07:36:22.103Z
         // 使用计算机默认的时区,创建日期时间
         ZonedDateTime now1 = ZonedDateTime.now();
         System.out.println("now1 = " + now1); //now1 = 2022-03-24T15:36:22.104+08:00[Asia/Shanghai]
         // 使用指定的时区创建日期时间
         ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Marigot"));
         System.out.println("now2 = " + now2);//now2 = 2022-03-24T03:36:22.104-04:00[America/Marigot]
     }
2.3、总结
  • 新版日期时间API中,日期和时间对象是不可变,操作日期不会影响原来的值,而是生成一个新的实例。
  • 新版日期的修改以及计算日期时间差比旧版要方便得多,日期时间格式化和解析是线程安全的。

二、Lambda表达式

1、Lambda初体验

  • 使用匿名内部类
     @Test
     public void test01() {
         // 开启一个新的线程
         new Thread(new Runnable() {
             @Override
             public void run() {
                 System.out.println("新线程中执行的代码 : " + Thread.currentThread().getName());
             }
         }).start();
     }
  • 使用Lambda表达式
     @Test
     public void test02() {
         // 开启一个新的线程
         new Thread(() -> {
             System.out.println("新线程Lambda表达式..." + Thread.currentThread().getName());
         }).start();
     }

2、语法规则

  • Lambda省去了面向对象的条条框框,标准格式由3个部分组成:
 ([参数类型 参数名称]) -> {
 	代码体;
 }
  • 格式说明

    • ([参数类型 参数名称]):参数列表,可以没有参数。
    • {代码体;} :方法体。
    • -> : 箭头,分割参数列表和方法体。

3、省略写法

  • 在lambda表达式的标准写法基础上,可以使用省略写法的规则为:

    • 小括号内的参数类型可以省略。

    • 如果小括号内有且仅有一个参数,则小括号可以省略。

    • 如果大括号内有且仅有一个语句,可以同时省略大括号,return 关键字及语句分号(需要一起省略)。

           @Test
           public void test02() {
               // 开启一个新的线程
       		new Thread(() -> System.out.println("新线程Lambda表达式..." + Thread.currentThread().getName())).start();
           }
      

4、实现原理

  • 例子:
 public class Test {
 
     public static void main(String[] args) {
         // 开启一个新的线程
         new Thread(new Runnable() {
             @Override
             public void run() {
                 System.out.println("新线程中执行的代码 : " + Thread.currentThread().getName());
             }
         }).start();
         System.out.println("主线程中的代码:" + Thread.currentThread().getName());
         System.out.println("---------------");
 //        new Thread(() -> {
 //            System.out.println("新线程Lambda表达式..." + Thread.currentThread().getName());
 //        }).start();
     }
 
 }
  • 匿名内部类
    • 匿名内部类的本质是在编译时生成一个Class 文件。XXXXX$1.class。

    • 通过反编译工具XJad来查看生成的代码。

  • Lambda表达式
    • Lambda在编译时只有一个本身的Class 文件。通过反编译工具XJad无法打开

    • 通过JDK自带的一个工具 javap 对字节码进行反汇编操作。

      //javap -c -p 文件名.class
      //-c:表示对代码进行反汇编
      //-p:显示所有的类和成员
      
      javap -c -p Test.class
      

      • 上面的效果可以理解为如下:

        public class Test {
            public static void main(String[] args) {
                ....
            }
        
            private static void lambda$main$0() {
                System.out.println("新线程Lambda表达式..." + Thread.currentThread().getName());
            }
        }
        
    • 为了更加直观的理解这个内容,在运行的时候添加 - Djdk.internal.lambda.dumpProxyClasses(加上这个参数会将内部class码输出到一个文件中)

      //java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名
      

      在这里插入图片描述

      • 可以看到这个匿名的内部类实现了Runnable接口,并重写了run()方法。在run方法中调用了 Test.lambda$main$0(),也就是调用了Lambda中的内容。
  • 匿名内部类 VS Lambda表达式
    • 所需类型不一样
      • 匿名内部类的类型可以是 类,抽象类,接口。
      • Lambda表达式需要的类型必须是接口。
    • 抽象方法的数量不一样
      • 匿名内部类所需的接口中的抽象方法的数量是随意的。
      • Lambda表达式所需的接口中只能有一个抽象方法。
    • 实现原理不一样
      • 匿名内部类是在编译后形成一个class。
      • Lambda表达式是在程序运行的时候动态生成class。

三、函数式编程

1、函数式接口注解

1)FunctionalInterface注解
  • 使用Lambda表达式的前提:
    • 方法的参数或局部变量类型必须为接口。
    • 接口中有且仅有一个抽象方法。
  • @FunctionalInterface:被该注解修饰的接口表明是一个函数接口,只能声明一个抽象方法。
2)自定义函数式接口
  • 用@FunctionalInterface修饰接口。

  • 只能声明一个抽象方法。允许有多个default、static方法。

2、函数式接口实战

  • Lambda表达式使用时不关心接口名和抽象方法名,只关心抽象方法的参数列表和返回值类型。因此在JDK中提供了大量常用的函数式接口,主要是在 java.util.function 包中。
1)Supplier
  • 介绍:无参,有返回值的接口。需要提供一个返回数据的类型。作用类似生产者

    @FunctionalInterface
    public interface Supplier<T> {
    
        /**
         * Gets a result.
         *
         * @return a result
         */
        T get();
    }
    
  • 使用:

    public ResponseData<SipResponse> syncRequest(Supplier<SendRequestContext> supplier) {
        //由调用方决定是SendRequestContext怎么生成的
        SendRequestContext context = supplier.get();
        //...
        return response;
    }
    
    SendRequestContext doRequest1(XXX xxx, YYY yyy) {
    	//...
        return context;
    }
    SendRequestContext doRequest2(XXX xxx, YYY yyy) {
    	//...
        return context;
    }
    
    //调用方
    ResponseData<SipResponse> broadcastResponse = sipRequestSupport.syncRequest(
    				() -> doRequest1(xxx, yyy));
    ResponseData<SipResponse> broadcastResponse = sipRequestSupport.syncRequest(
    				() -> doRequest2(xxx, yyy));
    
2)Consumer
  • 介绍:有参,无返回值的接口。需要指定一个泛型来定义参数类型。作用类似消费者

    @FunctionalInterface
    public interface Consumer<T> {
    
        /**
         * Performs this operation on the given argument.
         *
         * @param t the input argument
         */
        void accept(T t);
    }
    
  • 使用:

    public class ConsumerTest {
        public static void main(String[] args) {
            test("Hello World", msg -> {
                System.out.println(msg + "-> 转换为小写:" + msg.toLowerCase());
            });
        }
    
        public static void test(String msg, Consumer<String> consumer) {
            consumer.accept(msg);
        }
    }
    
    //类似定义前置或者后置操作,由调用方决定具体行为
    protected void responseOk(XXX xxx, YYY yyy, Consumer<Response> consumer) {
        //...
        if (consumer != null) {
            consumer.accept(response);
        }
    }
    
    • andThen方法

      //参数和返回值全部是Consumer类型。 效果:消费一个数据的时候,首先做一个操作,然后再做一个操作。(将两个行为重新定义为一个行为)
      default Consumer<T> andThen(Consumer<? super T> after) {
          Objects.requireNonNull(after);
          return (T t) -> { accept(t); after.accept(t); };
      }
      
      
      //使用
      public static void test2(String msg, Consumer<String> c1, Consumer<String> c2){
          c1.andThen(c2).accept(msg);//先消费c1,再消费c2。
      }
      
3)Function
  • 介绍:有参,有返回值的接口。是根据一个类型的数据得到另一个类型的数据。作用类似map映射

    @FunctionalInterface
    public interface Function<T, R> {
    
        /**
         * Applies this function to the given argument.
         *
         * @param t the function argument
         * @return the function result
         */
        R apply(T t);
    }
    
  • 使用:传入一个字符串返回一个数字。

    public class FunctionTest {
        public static void main(String[] args) {
            test("666", msg -> {
                return Integer.parseInt(msg);
            });
        }
    
        public static void test(String msg, Function<String, Integer> function) {
            Integer apply = function.apply(msg);
            System.out.println("apply = " + apply);//apply = 666
        }
    }
    
    • andThen方法:

      //参数和返回值全部是Function类型。 效果:转换一个数据的时候,先转换为一个中间值,然后中间值再转换为最终值。
      default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
          Objects.requireNonNull(after);
          return (T t) -> after.apply(apply(t));
      }
      
      //使用
      public static void test2(Function<String,Integer> f1, Function<Integer,Integer> f2){
          Integer i = f1.andThen(f2).apply("666");//先通过f1把String转换为中间值Integer,再通过f2把中间值Integer转换为结果Integer。
      }
      
    • compose方法:

      //作用和andThen类似,执行顺序和andThen方法刚好相反。
      default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
          Objects.requireNonNull(before);
          return (V v) -> apply(before.apply(v));
      }
      
      //使用
      public static void test2(Function<String,Integer> f1, Function<Integer,Integer> f2){
          Integer i = f2.compose(f1).apply("666");//先通过f1把String转换为中间值Integer,再通过f2把中间值Integer转换为结果Integer。
      }
      
    • identity方法:

      //静态方法,返回一个Function,这个function的行为是:输入什么参数就返回什么参数
      static <T> Function<T, T> identity() {
          return t -> t;
      }
      
4)Predicate
  • 介绍:有参,返回值为Boolean的接口。主要用于判断、断言

    @FunctionalInterface
    public interface Predicate<T> {
    
        /**
         * Evaluates this predicate on the given argument.
         *
         * @param t the input argument
         * @return {@code true} if the input argument matches the predicate,
         * otherwise {@code false}
         */
        boolean test(T t);
    }
    
  • 使用:判断传入的字符串长度是否大于3。

    public class PredicateTest {
        public static void main(String[] args) {
            test("HelloWorld", msg -> {
                return msg.length() > 3;
            });
        }
    
        private static void test(String msg, Predicate<String> predicate) {
            boolean b = predicate.test(msg);
            System.out.println("b:" + b);//b:true
        }
    }
    
    • Predicate中的默认方法提供了逻辑关系操作:and、or、negate方法

      //将两个判断逻辑重新组织为一个新的判断逻辑。(&&:两个逻辑都要满足)
      default Predicate<T> and(Predicate<? super T> other) {
          Objects.requireNonNull(other);
          return (t) -> test(t) && other.test(t);
      }
      
      //将两个判断逻辑重新组织为一个新的判断逻辑。(||:两个逻辑满足一个即可)
      default Predicate<T> or(Predicate<? super T> other) {
          Objects.requireNonNull(other);
          return (t) -> test(t) || other.test(t);
      }
      
      //将该判断逻辑重新组织为一个新的判断逻辑。(!:判断结果取反)
      default Predicate<T> negate() {
          return (t) -> !test(t);
      }
      
      private static void test(Predicate<String> p1, Predicate<String> p2) {
          // 同时满足p1和p2则为true
          boolean bb1 = p1.and(p2).test("Hello");
          // 满足p1或者p2则为true
          boolean bb2 = p1.or(p2).test("Hello");
          // 不满足p1则为true
          boolean bb3 = p1.negate().test("Hello");
      }
      
    • isEqual方法:

      //静态方法,返回一个Predicate,这个predicate的判断逻辑是:输入的参数是否与默认的targetRef相等。
      static <T> Predicate<T> isEqual(Object targetRef) {
          return (null == targetRef)
              ? Objects::isNull
              : object -> targetRef.equals(object);
      }
      
5)其他内置接口

在这里插入图片描述

3、方法引用

1)使用场景
  • 如果Lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用。

  • 例子:

public class FunctionRefTest01 {
    public static void main(String[] args) {
        printMax(a -> {
            // Lambda表达式中的代码和 getTotal中的代码冗余了
            int sum = 0;
            for (int i : a) {
                sum += i;
            }
            System.out.println("数组之和:" + sum);
        });
        
        //使用方法引用简化代码
        printMax(FunctionRefTest01::getTotal);
    }

    /**
     * 求数组中的所有元素的和
     *
     * @param a
     */
    public static void getTotal(int a[]) {
        int sum = 0;
        for (int i : a) {
            sum += i;
        }
        System.out.println("数组之和:" + sum);
    }

    private static void printMax(Consumer<int[]> consumer) {
        int[] a = {10, 20, 30, 40, 50, 60};
        consumer.accept(a);
    }
}
  • 注意:
  • 被引用的方法,参数要和接口中的抽象方法的参数一样。
  • 当接口抽象方法有返回值时,被引用的方法也必须有返回值。
  • 方法引用只能引用已经存在的方法。
2)使用格式
  • 对象名::成员方法名

  • 类名::静态方法名

  • 类名::成员方法名(Java面向对象中,类名只能调用静态方法,类名引用成员方法是有前提的,实际上是拿第一个参数作为方法的调用者)

//该例子中,apply方法的第一个参数是String类型,所以可以使用String::length。length()是成员方法。
public class FunctionRefTest02 {
    public static void main(String[] args) {
        Function<String, Integer> function = (s) -> {
            return s.length();
        };
        System.out.println(function.apply("hello"));
        // 通过方法引用来实现
        Function<String, Integer> function1 = String::length;
        System.out.println(function1.apply("hello"));
    }
}

//该例子改造getTotal为成员方法,使用FunctionRefTest01::getTotal引用。consumer.accept的第一个参数为FunctionRefTest01,所以可以引用。
public class FunctionRefTest01 {
    public static void test(String[] args) {
        BiConsumer<FunctionRefTest01, int []> consumer = FunctionRefTest01::getTotal;
        printMax(consumer);
    }

    /**
     * 求数组中的所有元素的和
     *
     * @param a
     */
    public void getTotal(int a[]) {
        int sum = 0;
        for (int i : a) {
            sum += i;
        }
        System.out.println("数组之和:" + sum);
    }

    private static void printMax(BiConsumer<FunctionRefTest01, int []> consumer) {
        int[] a = {10, 20, 30, 40, 50, 60};
        consumer.accept(new FunctionRefTest01(), a);
    }
}
  • 类名::new(构造器方法)

  • 数组::new(数组的构造方法)例如:String[]::new

public static void main(String[] args) {
    Function<Integer, String[]> fun1 = (len) -> {
        return new String[len];
    };
    String[] a1 = fun1.apply(3);
    System.out.println("数组的长度是:" + a1.length);//数组的长度是:3
    // 方法引用 的方式来调用数组的构造器
    Function<Integer, String[]> fun2 = String[]::new;
    String[] a2 = fun2.apply(5);
    System.out.println("数组的长度是:" + a2.length);//数组的长度是:5
}

四、Optional类

1、简单介绍

  • Optional是一个没有子类的工具类。

  • Optional是一个可以为null的容器对象,它的主要作用就是为了避免Null显式检查,防止NullpointerException。

2、创建方式

  • of(T t)方法,of方法是不支持null的。
  • ofNullable(T t)方法,ofNullable方法支持null。
  • empty()方法
  /**
   * Optional对象的创建方式
   */
  @Test
  public void test01() {
      // 第一种方式 通过of方法 of方法是不支持null的
      Optional<String> op1 = Optional.of("zhangsan");
      //Optional<Object> op2 = Optional.of(null);//报错,of方法不支持null
      // 第二种方式通过 ofNullable方法 支持null
      Optional<String> op3 = Optional.ofNullable("lisi");
      Optional<Object> op4 = Optional.ofNullable(null);
      // 第三种方式 通过empty方法直接创建一个空的Optional对象
      Optional<Object> op5 = Optional.empty();
  }

3、常用方法

  • **isPresent()**方法:判断是否包含值,包含值返回true,不包含值返回false。
  • **get()**方法:如果Optional有值则返回,否则抛出NoSuchElementException异常。 get()通常和isPresent方法一块使用
  • **orElse(T t)**方法:如果调用对象包含值,就返回该值,否则返回t。
  • orElseGet(Supplier)方法:如果调用对象包含值,就返回该值,否则返回通过Supplier生产的值。
  • orElseThrow(Supplier)方法:如果调用对象包含值,就返回该值,否则抛出通过Supplier生产的异常。
  • isPresent(Consumer)方法:如果存在值就执行传进来的consumer行为。
  • filter(Predicate)方法:如果存在值且通过断言判断为true,则返回自身,否则返回空。
  • map(Function)方法:如果存在值就通过function行为来转换为另一个可以为空的值,否则返回空。
  /**
   * Optional常用方法:isPresent()、get()、orElse(T t)
   */
	@Test
  public void test02() {
      Optional<String> op1 = Optional.of("zhangsan");
      Optional<String> op2 = Optional.empty();
      // 获取Optional中的值
      if (op1.isPresent()) {
          String s1 = op1.get();
          System.out.println("用户名称:" + s1);//用户名称:zhangsan
      }
      if (op2.isPresent()) {
          System.out.println(op2.get());
      } else {
          System.out.println("op2是一个空Optional对象");//op2是一个空Optional对象
      }
      String s3 = op1.orElse("李四");
      System.out.println(s3);//zhangsan
      String s4 = op2.orElse("王五");
      System.out.println(s4);//王五
  }

4、最佳实践

/**
 * 1、定义方法的返回值为Optional<T>对象。提醒调用方注意是否需要null值处理。
 * 2、通过Optional.ofNullable(thisValue).orElse(otherValue)设置默认值,简化代码。
 * 3、通过Optional.ofNullable(thisValue).orElseThrow(Supplier)检查null值并且抛出业务异常,简化代码。
 */

五、Stream API

1、Stream流式思想概述

  • Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构不保存数据,而是对数据进行加工处理

  • Stream流的各种操作可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。

  • Stream API能完成许多复杂的操作,如筛选、切片、映射、查找、去重,统计,匹配和归约等。

2、Stream流处理过程

  • 元素集合——》生成流——》中间操作——》终结操作

3、Stream流的获取方式

3.1、根据Collection获取
  • java.util.Collection 接口中加入了default方法 stream(),所以Collection接口下的所有实现类对象都可以通过steam方法来获取Stream流。

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.stream();
        Set<String> set = new HashSet<>();
        set.stream();
        Vector vector = new Vector();
        vector.stream();
    }
    
  • Map接口没有实现Collection接口,可以根据Map先获取对应的key-value的集合,再获取Stream流。

    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 static void main(String[] args) {
        Stream<String> a1 = Stream.of("a1", "a2", "a3");
        
        String[] arr1 = {"aa", "bb", "cc"};
        Stream<String> arr11 = Stream.of(arr1);
        
        Integer[] arr2 = {1, 2, 3, 4};
        Stream<Integer> arr21 = Stream.of(arr2);
        
        // 注意:基本数据类型的数组创建的流的元素不是数组里元素,而是数组本身。
        int[] arr3 = {1, 2, 3, 4};
        Stream<int[]> arr31 = Stream.of(arr3);
        arr31.forEach(System.out::println);//[I@16c0663d
    }
    

4、Stream流的常用方法

4.1、非终结方法

返回值类型仍然是 Stream 类型的方法,支持链式调用。

  • filter:作用是用来过滤数据,返回符合条件的数据。该方法接收一个Predicate函数式接口参数作为筛选条件。

    Stream<T> filter(Predicate<? super T> predicate);
    

    public static void main(String[] args) {
        Stream.of("a1", "a2", "a3", "bb", "cc", "aa", "dd")
            .filter((s) -> s.contains("a"))//筛选包含a的元素,返回的是新的流。
            .forEach(System.out::println);
    }
    
  • limit:作用是对流进行截取处理,只取前n个数据。参数是一个long类型的数值,如果集合当前长度大于该参数就进行截取,否则不操作。

    Stream<T> limit(long maxSize);
    

    public static void main(String[] args) {
        Stream.of("a1", "a2", "a3", "bb", "cc", "aa", "dd")
            .limit(3)//只取前3个数据,返回的是新的流。
            .forEach(System.out::println);
    }
    
  • skip:作用是跳过流前面几个元素。参数是一个long类型的数值。

    Stream<T> skip(long n);
    

    public static void main(String[] args) {
        Stream.of("a1", "a2", "a3", "bb", "cc", "aa", "dd")
            .skip(3)//跳过前3个数据,返回的是新的流。
            .forEach(System.out::println);
    }
    
  • map:作用是将流中的元素映射到另一个流中(一一对应)。需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的数据。

    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    

    public static void main(String[] args) {
        Stream.of("1", "2", "3", "4", "5", "6", "7")
            //.map(msg->Integer.parseInt(msg))//将String元素转换为Integer元素
            .map(Integer::parseInt)//这里使用方法引用
            .forEach(System.out::println);
    }
    
    //将Stream中的元素转换成int类型。例如将Integer元素转为int,作用:Integer占用的内存比int多很多,在Stream流操作中会自动装修和拆箱操作。
    IntStream mapToInt(ToIntFunction<? super T> mapper);
    
    
    //扁平化映射,把每个元素映射成小流,最终汇成一个流。作用:可以将多个维度的数据映射成一个维度的数据。
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    /**
    * 例如(多个年级的学生数据先映射成每个年级一个学生小数据流,最后映射成一个总的学生数据流)
    * [{id:1, stus: [{name:a},{name:b}]}, {id:2, stus: [{name:c},{name:d}]}, {id:3, stus: [{name:e},{name:f},{name:g}]}]  => 
    * [{name:a},{name:b},{name:c},{name:d},{name:e},{name:f},{name:g}]  3个元素集合变成7个元素集合。
    */
    
  • distinct:作用是去掉流中重复的数据。

    Stream<T> distinct();
    

    public static void main(String[] args) {
        Stream.of(1, 3, 3, 4, 0, 1, 7)
            .distinct() // 去掉重复的记录
            .forEach(System.out::println);
    }
    
  • sorted:作用是将数据排序,可以根据自然规则排序,也可以通过比较器来指定对应的排序规则。

    Stream<T> sorted();
    
    Stream<T> sorted(Comparator<? super T> comparator);
    
    public static void main(String[] args) {
        Stream.of(1, 3, 2, 4, 0, 9, 7)
            //.sorted() // 根据数据的自然顺序排序
            .sorted((o1, o2) -> o2 - o1) // 根据比较器指定排序规则
            .forEach(System.out::println);
    }
    
  • concat:静态方法,作用是将两个流合并成为一个流。

    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("a", "b", "c");
        Stream<String> stream2 = Stream.of("x", "y", "z");
        // 通过concat方法将两个流合并为一个新的流
        Stream.concat(stream1, stream2).forEach(System.out::println);
    }
    
4.2、终结方法

返回值类型不再是 Stream 类型的方法。

  • forEach:作用是遍历流中的数据。该方法接受一个Consumer接口,会将每一个流元素交给函数处理。
void forEach(Consumer<? super T> action);
  • count:作用是统计流中的元素个数。该方法返回一个long值,代表元素的个数。

    long count();
    
    public static void main(String[] args) {
        long count = Stream.of("a1", "a2", "a3").count();
        System.out.println(count);
    }
    
  • match:判断数据是否匹配指定的条件。

    boolean anyMatch(Predicate<? super T> predicate);// 元素是否有任意一个满足条件
    boolean allMatch(Predicate<? super T> predicate);// 元素是否都满足条件
    boolean noneMatch(Predicate<? super T> predicate);// 元素是否都不满足条件
    
    public static void main(String[] args) {
        boolean b = Stream.of(1, 3, 3, 4, 5, 1, 7)
            //.allMatch(s -> s > 0)
            //.anyMatch(s -> s >4)
            .noneMatch(s -> s > 4);
        System.out.println(b);
    }
    
  • find:作用是找到某些数据。

    Optional<T> findFirst();//返回流中的第一个元素,
    Optional<T> findAny();//返回流中的元素是随机的,一般情况,串行流返回的是第一个元素。并行流返回的元素是不确定的。
    //如果流为空则返回空的optional,如果选中的元素为null则抛出异常。
    

    public static void main(String[] args) {
        Optional<String> first = Stream.of("1", "3", "3", "4", "5", "1", "7").findFirst();
        System.out.println(first.get());
        Optional<String> any = Stream.of("1", "3", "3", "4", "5", "1", "7").findAny();
        System.out.println(any.get());
    }
    
  • max和min:作用是获取流中的最大值、最小值。接受一个Comparator比较器参数。

    Optional<T> max(Comparator<? super T> comparator);
    Optional<T> min(Comparator<? super T> comparator);
    

    public static void main(String[] args) {
        Optional<Integer> max = Stream.of(1, 3, 3, 4, 5, 1, 7)
            .max((o1, o2) -> o1 - o2);
        System.out.println(max.get());
        Optional<Integer> min = Stream.of(1, 3, 3, 4, 5, 1, 7)
            .min((o1, o2) -> o1 - o2);
        System.out.println(min.get());
    }
    
  • reduce:作用是要将所有数据归纳得到一个数据。

    T reduce(T identity, BinaryOperator<T> accumulator);
    

    public static void main(String[] args) {
        Integer sum = Stream.of(4, 5, 3, 9)
            // identity初始值
            // 第一次的时候会将初始值赋值给x
            // 之后每次会将 上一次的操作结果赋值给x y就是每次从数据中获取的元素
            .reduce(0, (x, y) -> {
                System.out.println("x=" + x + ",y=" + y);
                return x + y;
            });
        System.out.println(sum);
    }
    
4.3、使用注意
  • Stream流只能操作一次,每次操作返回的Stream是新的流。
  • Stream流不调用终结方法,中间的非终结方法的操作不会被执行。

5、Stream流的结果收集

5.1、结果收集到集合中
  • 通过collect(Collector)方法。

    <R, A> R collect(Collector<? super T, A, R> collector);
    
    /**
    * Stream结果收集
    * 收集到集合中
    */
    @Test
    public void test01() {
        // 收集到 List集合中
        List<String> list = Stream.of("aa", "bb", "cc", "aa")
            .collect(Collectors.toList());
        System.out.println(list);
        // 收集到 Set集合中
        Set<String> set = Stream.of("aa", "bb", "cc", "aa")
            .collect(Collectors.toSet());
        System.out.println(set);
        // 如果需要获取的类型为具体的实现,比如:ArrayList HashSet
        ArrayList<String> arrayList = Stream.of("aa", "bb", "cc", "aa")
            //.collect(Collectors.toCollection(() -> new ArrayList<>()));
            .collect(Collectors.toCollection(ArrayList::new));
        System.out.println(arrayList);
        HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa")
            .collect(Collectors.toCollection(HashSet::new));
        System.out.println(hashSet);
    }
    
5.2、结果收集到数组中
  • 通过toArray方法。

    Object[] toArray();//返回的数组类型为Object
    <A> A[] toArray(IntFunction<A[]> generator);//返回的数组类型为A
    
    /**
    * Stream结果收集到数组中
    */
    @Test
    public void test02() {
        Object[] objects = Stream.of("aa", "bb", "cc", "aa").toArray(); // 返回的数组中的元素是 Object类型
        System.out.println(Arrays.toString(objects));
        // 如果需要指定返回的数组中的元素类型
        String[] strings = Stream.of("aa", "bb", "cc", "aa").toArray(String[]::new);
        System.out.println(Arrays.toString(strings));
    }
    
5.3、对流中的数据做聚合计算

使用Stream流处理数据后,可以通过Collectors工具类像数据库的聚合函数一样对某个字段进行操作,比如获得最大值,最小值,求和,平均值,统计数量。

  • maxBy(Comparator)、minBy(Comparator)、summingInt(ToIntFunction)、averagingInt(ToIntFunction)、counting()

        /**
         * Stream流中数据的聚合计算
         */
        @Test
        public void test03() {
            // 获取年龄的最大值
            Optional<Person> maxAge = Stream.of(
                    new Person("张三", 18)
                    , new Person("李四", 22)
                    , new Person("张三", 13)
                    , new Person("王五", 15)
                    , new Person("张三", 19)
            ).collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));
            System.out.println("最大年龄:" + maxAge.get());
    
            // 获取年龄的最小值
            Optional<Person> minAge = Stream.of(
                    new Person("张三", 18)
                    , new Person("李四", 22)
                    , new Person("张三", 13)
                    , new Person("王五", 15)
                    , new Person("张三", 19)
            ).collect(Collectors.minBy((p1, p2) -> p1.getAge() - p2.getAge()));
            System.out.println("最新年龄:" + minAge.get());
    
            // 求所有人的年龄之和
            Integer sumAge = Stream.of(
                    new Person("张三", 18)
                    , new Person("李四", 22)
                    , new Person("张三", 13)
                    , new Person("王五", 15)
                    , new Person("张三", 19)
            ).collect(Collectors.summingInt(Person::getAge));
            System.out.println("年龄总和:" + sumAge);
    
            // 年龄的平均值
            Double avgAge = Stream.of(
                    new Person("张三", 18)
                    , new Person("李四", 22)
                    , new Person("张三", 13)
                    , new Person("王五", 15)
                    , new Person("张三", 19)
            ).collect(Collectors.averagingInt(Person::getAge));
            System.out.println("年龄的平均值:" + avgAge);
    
            // 统计数量
            Long count = Stream.of(
                    new Person("张三", 18)
                    , new Person("李四", 22)
                    , new Person("张三", 13)
                    , new Person("王五", 15)
                    , new Person("张三", 19)
            ).filter(p -> p.getAge() > 18).collect(Collectors.counting());
            System.out.println("满足条件的记录数:" + count);
        }
    
5.4、对流中数据做分组操作

使用Stream流处理数据后,可以通过Collectors工具类根据某个属性将数据分组。

  • groupingBy(Function)、groupingBy(Function, Collector)等

        /**
         * 分组操作
         */
        @Test
        public void test04() {
            // 根据账号对数据进行分组
            Map<String, List<Person>> map1 = Stream.of(
                    new Person("张三", 18, 175)
                    , new Person("李四", 22, 177)
                    , new Person("张三", 14, 165)
                    , new Person("李四", 15, 166)
                    , new Person("张三", 19, 182)
            ).collect(Collectors.groupingBy(Person::getName));
            map1.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
            System.out.println("-----------");
            
            // 根据年龄分组 如果大于等于18 成年否则未成年
            Map<String, List<Person>> map2 = Stream.of(
                    new Person("张三", 18, 175)
                    , new Person("李四", 22, 177)
                    , new Person("张三", 14, 165)
                    , new Person("李四", 15, 166)
                    , new Person("张三", 19, 182)
            ).collect(Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年"));
            map2.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
        }
    
    
    输出结果:
        
    k=李四 v=[Person{name='李四', age=22, height=177}, Person{name='李四', age=15,
    height=166}]
    k=张三 v=[Person{name='张三', age=18, height=175}, Person{name='张三', age=14,
    height=165}, Person{name='张三', age=19, height=182}]
    -----------
    k=未成年 v=[Person{name='张三', age=14, height=165}, Person{name='李四', age=15,
    height=166}]
    k=成年 v=[Person{name='张三', age=18, height=175}, Person{name='李四', age=22,
    height=177}, Person{name='张三', age=19, height=182}]
    
    • 多级分组: 先根据name分组然后根据年龄分组

          /**
           * 分组计算--多级分组
           */
          @Test
          public void test05() {
              // 先根据name分组,然后根据age(成年和未成年)分组
              Map<String, Map<Object, List<Person>>> map = Stream.of(
                      new Person("张三", 18, 175)
                      , new Person("李四", 22, 177)
                      , new Person("张三", 14, 165)
                      , new Person("李四", 15, 166)
                      , new Person("张三", 19, 182)
              ).collect(Collectors.groupingBy(
                      Person::getName
                      , Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年")
              ));
      
              map.forEach((k, v) -> {
                  System.out.println(k);
                  v.forEach((k1, v1) -> {
                      System.out.println("\t" + k1 + "=" + v1);
                  });
              });
          }
      
      输出结果:
          
      李四
      	未成年=[Person{name='李四', age=15, height=166}]
      	成年=[Person{name='李四', age=22, height=177}]
      张三
      	未成年=[Person{name='张三', age=14, height=165}]
      	成年=[Person{name='张三', age=18, height=175}, Person{name='张三', age=19, height=182}]
      
5.5、对流中的数据做分区操作
  • **Collectors.partitioningBy(Predicate) **会根据值是否为true,把集合中的数据分割为两个列表,一个true列表,一个 false列表。

        /**
         * 分区操作
         */
        @Test
        public void test06() {
            Map<Boolean, List<Person>> map = Stream.of(
                    new Person("张三", 18, 175)
                    , new Person("李四", 22, 177)
                    , new Person("张三", 14, 165)
                    , new Person("李四", 15, 166)
                    , new Person("张三", 19, 182)
            ).collect(Collectors.partitioningBy(p -> p.getAge() > 18));
            map.forEach((k, v) -> System.out.println(k + "\t" + v));
        }
    
    
    输出结果:
        
    false	[Person{name='张三', age=18, height=175}, Person{name='张三', age=14, height=165}, Person{name='李四', age=15, height=166}]
    
    true	[Person{name='李四', age=22, height=177}, Person{name='张三', age=19, height=182}]
    
5.6、对流中的数据做拼接
  • Collectors.joining 会根据指定的连接符,将所有的元素连接成一个字符串。

        /**
         * 对流中的数据做拼接操作
         */
        @Test
        public void test07() {
            String s1 = Stream.of(
                    new Person("张三", 18, 175)
                    , new Person("李四", 22, 177)
                    , new Person("张三", 14, 165)
                    , new Person("李四", 15, 166)
                    , new Person("张三", 19, 182)
            ).map(Person::getName).collect(Collectors.joining());
            // 张三李四张三李四张三
            System.out.println(s1);
            String s2 = Stream.of(
                    new Person("张三", 18, 175)
                    , new Person("李四", 22, 177)
                    , new Person("张三", 14, 165)
                    , new Person("李四", 15, 166)
                    , new Person("张三", 19, 182)
            ).map(Person::getName).collect(Collectors.joining("_"));
            // 张三_李四_张三_李四_张三
            System.out.println(s2);
            String s3 = Stream.of(
                    new Person("张三", 18, 175)
                    , new Person("李四", 22, 177)
                    , new Person("张三", 14, 165)
                    , new Person("李四", 15, 166)
                    , new Person("张三", 19, 182)
            ).map(Person::getName).collect(Collectors.joining("_", "###", "$$$"));
            // ###张三_李四_张三_李四_张三$$$
            System.out.println(s3);
        }
    

6、并行Stream流介绍

  • 前面使用的Stream流都是串行,也就是在一个线程上面执行。
  • 并行Stream流就是一个并行执行的流,通过默认的ForkJoinPool,可以提高多线程任务的速度。
6.1、获取并行流
  • 通过Collection接口中的parallelStream方法来获取。
  • 通过已有的串行流转换为并行流( parallel()方法 )
    /**
     * 获取并行流的两种方式
     */
    @Test
    public void test02() {
        List<Integer> list = new ArrayList<>();
        // 通过集合接口 直接获取并行流
        Stream<Integer> integerStream = list.parallelStream();
        // 将已有的串行流转换为并行流
        Stream<Integer> parallel = Stream.of(1, 2, 3).parallel();
    }
6.2、并行流操作
  • 例子:

        /**
         * 并行流操作
         */
        @Test
        public void test03() {
            Stream.of(1, 4, 2, 6, 1, 5, 9)
                    .parallel() // 将流转换为并发流,Stream处理的时候就会通过多线程处理
                    .filter(s -> {
                        System.out.println(Thread.currentThread() + " s=" + s);
                        return s > 2;
                    }).count();
        }
    
  • 结果:

    Thread[main,5,main] s=1
    Thread[ForkJoinPool.commonPool-worker-1,5,main] s=9
    Thread[ForkJoinPool.commonPool-worker-2,5,main] s=4
    Thread[ForkJoinPool.commonPool-worker-3,5,main] s=5
    Thread[ForkJoinPool.commonPool-worker-1,5,main] s=1
    Thread[main,5,main] s=6
    Thread[ForkJoinPool.commonPool-worker-2,5,main] s=2
    

Stream并行处理的过程会分而治之,将一个大的任务切分成了多个小任务,每个任务都是一个线程操作。参考Fork/Join原理。

6.3、线程安全问题
  • 在多线程的处理下,肯定会出现数据安全问题。如下:

        @Test
        public void test04() {
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < 1000; i++) {
                list.add(i);
            }
            System.out.println(list.size());
            List<Integer> listNew = new ArrayList<>();
            // 使用并行流来向集合中添加数据
            list.parallelStream().forEach(listNew::add);
            System.out.println(listNew.size());
        }
    
    结果:
        1000
    	983
        
    或者抛出java.lang.ArrayIndexOutOfBoundsException异常。
    
  • 解决方案:

    • 加同步锁。
    • 使用线程安全的容器。
    • 通过Stream中的toArray/collect操作。

// 通过集合接口 直接获取并行流

    Stream<Integer> integerStream = list.parallelStream();
    // 将已有的串行流转换为并行流
    Stream<Integer> parallel = Stream.of(1, 2, 3).parallel();
}
6.2、并行流操作
  • 例子:

        /**
         * 并行流操作
         */
        @Test
        public void test03() {
            Stream.of(1, 4, 2, 6, 1, 5, 9)
                    .parallel() // 将流转换为并发流,Stream处理的时候就会通过多线程处理
                    .filter(s -> {
                        System.out.println(Thread.currentThread() + " s=" + s);
                        return s > 2;
                    }).count();
        }
    
  • 结果:

    Thread[main,5,main] s=1
    Thread[ForkJoinPool.commonPool-worker-1,5,main] s=9
    Thread[ForkJoinPool.commonPool-worker-2,5,main] s=4
    Thread[ForkJoinPool.commonPool-worker-3,5,main] s=5
    Thread[ForkJoinPool.commonPool-worker-1,5,main] s=1
    Thread[main,5,main] s=6
    Thread[ForkJoinPool.commonPool-worker-2,5,main] s=2
    

Stream并行处理的过程会分而治之,将一个大的任务切分成了多个小任务,每个任务都是一个线程操作。参考Fork/Join原理。

6.3、线程安全问题
  • 在多线程的处理下,肯定会出现数据安全问题。如下:

        @Test
        public void test04() {
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < 1000; i++) {
                list.add(i);
            }
            System.out.println(list.size());
            List<Integer> listNew = new ArrayList<>();
            // 使用并行流来向集合中添加数据
            list.parallelStream().forEach(listNew::add);
            System.out.println(listNew.size());
        }
    
    结果:
        1000
    	983
        
    或者抛出java.lang.ArrayIndexOutOfBoundsException异常。
    
  • 解决方案:

    • 加同步锁。
    • 使用线程安全的容器。
    • 通过Stream中的toArray/collect操作。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值