JDK1.8新特性(常用语法及测试)

Lambda表达式
  1. Lambda表达式是一个匿名函数,可以理解为一段可以传递的代码。

  2. 优点:简化了匿名内部类的使用,语法更加简单,是简化匿名内部类的一种方式。

  3. Lambda使用的前提条件:

    • 方法的参数或局部变量类型必须为接口才能使用Lambda

    • 接口中有且仅有一个抽象方法(@Functionallinterface)

  4. 格式:

    (参数类型 参数名) -> {
        代码体;
    }
  5. 省略写法规则:

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

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

    • 如果大括号内有且仅有一条语句,可以同时省略大括号,return关键字及语句分号;

      // 不简写
      test((int age) -> {
          return age + 1;
      }, 23);
      ​
      // 简写
      test(age -> age + 1, 23);
  6. 原理(了解):

    • 匿名内部类在编译的时候会产生一个class文件;

    • 在类中新增了一个方法,这个方法的方法体就是Lambda表达式中的代码;

    • 还会形成一个匿名内部类,实现接口,重写抽象方法;

    • 在接口中重写方法会调用新生成的方法;

  7. Lambda与匿名内部类的对比:

    1. 所需类型不一样:

      • 匿名内部类的类型可以是类,抽象类,接口;

      • Lambda表达式需要的类型必须是接口;

    2. 抽象方法的数量不一样:

      • 匿名内部类所需的接口中的抽象方法的数量是随意的;

      • Lambda表达式所需的接口中只能有一个抽象方法;

    3. 实现原理不一样:

      • 匿名内部类是在编译后形成一个class;

      • Lambda表达式是在程序运行的时候动态生成class;

函数式接口

使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法名。只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda表达式更加的方法,在JDK中提供了大量常用的函数式接口。

  1. 注解 @FunctionalInterface :使用此注解修饰的接口,只能有一个抽象方法,声明此接口是一个函数式接口;

  2. 四大父函数接口:

    • Supplier(无参,有返回值):

      // 源码:
      @FunctionalInterface
      public interface Supplier<T> {
          T get();
      }
      ​
      // 应用
      public static void main(String[] args) {
          int[] arr = { 12, 16, 55, 48, 22, 45, 66 };
          test(() -> {
              Arrays.sort(arr);
              return arr[arr.length - 1];
          });
      }
      ​
      public static void test(Supplier<Integer> supplier) {
          System.out.println(supplier.get());
      }

    • Consumer(有参,无返回值):

      // 源码:
      @FunctionalInterface
      public interface Consumer<T> {
          void accept(T t);
      }
      ​
      // 应用
      public static void main(String[] args) {
          test(str -> {
              System.out.println(str.toLowerCase());
          }, "HEllO,WorLD");
      }
      ​
      public static void test(Consumer<String> consumer, String str) {
          consumer.accept(str);
      }

    • Function(有参,有返回值):

      // 源码:
      @FunctionalInterface
      public interface Function<T, R> {
          R apply(T t);
      }
      ​
      // 应用
      public static void main(String[] args) {
          test(str -> Integer.parseInt(str), "123");
      }
      ​
      public static void test(Function<String, Integer> function, String str) {
          Integer apply = function.apply(str);
          System.out.println(apply);
      }

    • Predicate(有参,返回值为布尔类型):

      // 源码:
      @FunctionalInterface
      public interface Predicate<T> {
          boolean test(T t);
      }
      ​
      // 应用
      public static void main(String[] args) {
          test(num -> num == 0, 10);
      }
      ​
      public static void test(Predicate<Integer> predicate, Integer num) {
          boolean test = predicate.test(num);
          System.out.println(test);
      }

方法的引用(双冒号的使用)
  1. 符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用。

  2. 应用场景:在使用Lambda表达式的时候,也会出现代码冗余的情况,比如:用Lambda表达式求一个数组的和。如果在Lambda表达式中要执行的代码和我们另一个方法中的代码是一样的,这时就没有必要重写一份逻辑了,这时我们就可以"引用"重复代码。

  3. 语法格式:

    • instanceName::methodName(对象::方法名):

      // 完整格式
      Supplier<Long> s1 = () -> date.getTime();
      System.out.println(s1.get());
      ​
      // 方法引用;方法不需要加 ()
      Supplier<Long> s2 =  date::getTime;
      System.out.println(s2.get());

    • ClassName::staticMethodName(类名::静态方法):

      // 完整格式
      Supplier<Long> s3 = () -> System.currentTimeMillis();
      System.out.println(s3.get());
      ​
      // 方法引用
      Supplier<Long> s4 =  System::currentTimeMillis;
      System.out.println(s4.get());

    • ClassName::methodName(类名::普通方法):

      // 完整格式
      BiFunction<String, Integer, String> b1 = (str, i) -> str.substring(i);
      System.out.println(b1.apply("helloworld", 5));
      ​
      // 方法引用:类名引用普通方法
      BiFunction<String, Integer, String> b2 = String::substring;
      System.out.println(b2.apply("helloworld", 5));

    • ClassName::new(类名::new 调用的构造器):

      // 完整格式
      BiFunction<String, Integer, Student> b3 = (name, age) -> new Student(name, age);
      System.out.println(b3.apply("张三", 23));
      ​
      // 方法引用:类名引用构造器
      BiFunction<String, Integer, Student> b4 = Student::new;
      System.out.println(b4.apply("李四", 41));

    • TypeName[]::new(String[]::new 调用数组的构造器):

      // 完整格式
      Function<Integer, String[]> b5 = (length) -> new String[length];
      System.out.println(b5.apply(5).length);
      ​
      // 方法引用:数组引用构造器
      Function<Integer, String[]> b6 = String[]::new;
      System.out.println(b6.apply(6).length);

  4. 注意

    1. 方法引用是对Lambda表达式符合特定情况下的一种缩写方式,它使得我们的Lambda表达式更加的精简,也可以理解为lambda表达式的缩写形式,不过要注意的是方法引用只能引用已经存在的方法

    2. 被引用的方法,参数要和接口中的抽象方法的参数一样;

    3. 当接口抽象方法有返回值时,被引用的方法也必须有返回值;

Optional类

Optional使用了final修饰,所以是一个没有子类的工具类,Optional是一个可以为 null 的容器对象,它的主要作用就是为了避免Null检查,防止空指针异常的出现。

  1. Optional创建对象的方式:

    1. of() 方法(不支持传入null值):

      Optional<String> o1 = Optional.of("hello");
    2. ofNullable() 方法(可以传入null值):

      Optional<String> o2 = Optional.ofNullable("test");
    3. empty() 方法(创建一个空对象):

      Optional<Object> o3 = Optional.empty();

  2. Optional的常用方法:

    • isPresent():判断Optional对象中是否有值;如果有,返回ture。

    • get():获取Optional中的值,如果值为空,抛出NoSuchElementException;通常结合isPresent()使用。

    • orElse(T other):如果调用者中值不为null,则返回该值;若值为null,则返回other参数自己。

    • orElseGet(Supplier other):如果调用者中值不为null,则返回该值;否则,返回Lambda表达式的返回值。

    • orElseThrow(Supplier exceptionSupplier):如果调用者中值不为null,则返回该值;否则抛出自定义异常。

    • ifPresent(Consumer consumer):如果有值,则自定义Lambda表达式,做对应的事情。

    • map(Function mapper):如果有值,则使用Lambda表达式处理值(处理后的Optional可以为空值),否则返回一个空的Optional对象。

    • flatMap(Function mapper):如果有值,则使用Lambda表达式处理值(处理后的Optional不为空值),否则返回一个空的Optional对象。

    • filter(Predicate predicate):如果有值,则使用Lambda表达式自定义判断该值,否则返回一个空的Optional对象。

Stream Api(流式编程)
  1. 思想概述:

    • Stream用于简化循环,是一种更加高效的处理方式。

    • Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。

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

  2. Stream对象的创建:

    • Collection获取(Collection接口加入了default修饰的方法stream()):

      • 单列集合:

        // 1.List集合
        Stream<Object> stream = list.stream();
        ​
        // 2.Set集合
        Stream<Object> stream2 = set.stream();
      • 双列集合:

        // 1.keySet获取键的Stream
        Stream<Object> stream4 = map.keySet().stream();
        ​
        // 2.values获取值的Stream
        Stream<Object> stream5 = map.values().stream();
        ​
        // 3.entrySet获取键值对的Stream
        Stream<Entry<Object, Object>> stream6 = map.entrySet().stream();
    • Stream静态方法获取(为数组对象提供的方法):

      Stream<String> stream = Stream.of("hello","world","test","hahaha");
      ​
      String[] arr1 = {"12","aa","ss"};
      Stream<String> stream1 = Stream.of(arr1);
      ​
      Integer[] arr2 = {1,3,4,0};
      Stream<Integer> stream2 = Stream.of(arr2);
  3. 方法分类:

    • 终结方法:返回值类型不是Stream类型的方法,不再支持链式调用。

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

  4. 常用API:

    方法名方法作用返回值类型方法种类
    count统计元素个数long终结
    forEach逐一处理void终结
    match匹配元素boolean终结
    find查找元素Optional终结
    min / max最大值 / 最小值Optional终结
    reduce将结果归纳为一个值,通常结合mapT t终结
    collect使用Collector类将流进行处理T t终结
    filter过滤Stream函数拼接
    limit截取前几个Stream函数拼接
    skip跳过前几个Stream函数拼接
    map映射,类型转换Stream函数拼接
    sorted排序Stream函数拼接
    distinct去重(重写equals和hashCode)Stream函数拼接
    mapToInt数据转换,减少不必要的拆装箱IntStream函数拼接
    concat两个Stream合并为一个StreamStream函数拼接
  5. 注意:

    1. Stream只能操作一次;

    2. Stream方法返回的是新的流;

    3. Stream不调用终结方法,中间的操作不会执行;

  6. Collector类常用方法:

    1. 收集结果到集合:

      • List收集:Collectors.toList()

      • Set收集:Collectors.toSet()

      • Collection收集:Collectors.toCollection(ArrayList::new),入参为自定义的集合;

      • Map收集:Collectors.toMap(String::new, String::new, String::concat)

        • 参数一为key的生成策略;

        • 参数二为value的生成策略;

        • 参数三(可省略)为key冲突时,对value的处理策略;

    2. 收集结果到数组:

      • 默认Object数组收集:Stream.of("aa","bb").toArray()

      • 自定义数组收集:Stream.of("aa","bb").toArray(String[]::new)

    3. 聚合计算:

      • maxBy(Comparator):最大值;

      • minBy(Comparator):最小值;

      • summingInt(Function):求和;

      • averagingInt(Function):平均值;

      • counting():求总数量;

    4. 分组操作:

      • groupingBy(Function):分组;

      • groupingBy(Function,Collectors.groupingBy(Function)):多级分组。如先分学校,再分班级。

    5. 拼接组合操作:

      • joining():直接拼接;

      • joining(分隔符):使用分隔符做拼接;

      • joining(分隔符,前缀,后缀):使用分隔符做拼接,并在最终结果上加入前缀、后缀;

  7. 并行流:

    1. 说明:

      • 我们前面使用的Stream流都是串行,也就是在一个线程上面执行。

      • 并行流(SerialStream)通过默认的ForkjoinPool,系统会使用多线程去执行,可以提高执行速度。

      • 并行处理的过程会分而治之,也就是将一个大的任务切分成了多个小任务,这表示每个任务都是一个线程操作。

    2. 获取方式:

      • 通过集合获取:parallelstream() 方法;

        ArrayList<Object> list = new ArrayList<>();
        Stream<Object> stream = list.parallelStream();

      • 通过流获取:parallel() 转换现有的流;

        Stream<String> stream = Stream.of("aa", "bb").parallel();

    3. 并行流的线程安全问题:

      • 操作时,在同步代码块中执行;

      • 使用线程安全的容器,如Vector、HashTable等;

      • 将原容器转为线程安全的容器,如使用 Collections 工具类中的 synchronizedXxxx() 方法;

      • 使用Stream中的 toArray()collect() 方法来操作;

新日期时间类
  1. 旧版日期类存在的问题:

    • 设计不合理,在 java.utiljava.sql 的包中都有日期类,java.util.Date 同时包含日期和时间,而 java.sql.Date 仅仅包含日期。此外,用于格式化和解析的类在 java.text 包下。

    • java.util.Date 是非线程安全的,所有日期类都是可变的,这是java日期类最大的问题之一。

    • 时区处理麻烦,日期类并不提供国际化,没有时区支持。

  2. 新的日期及时间API位于 java.time 包中,下面是一些关键类:

    • LocalDate:表示日期,包含年月日,格式为2019-10-16;

      // 1.获取日期
      LocalDate now = LocalDate.now();
      LocalDate date = LocalDate.of(2023, 12, 23);        // 年,月,日
      ​
      // 2.根据日期获取信息
      int year = date.getYear();      // 获取年份
      Month month = date.getMonth();      // 获取月份,返回枚举对象
      int dayOfMonth = date.getDayOfMonth();      // 获取所在月的天数
      DayOfWeek dayOfWeek = date.getDayOfWeek();      // 获取星期,返回枚举对象
      int dayOfYear = date.getDayOfYear();        // 获取所在年的天数

    • LocalTime:表示时间,包含时分秒,格式为16:38:54.158549300;

      // 1.获取时间
      LocalTime now = LocalTime.now();
      LocalTime time = LocalTime.of(23, 59, 59, 999);     // 时,分,秒,纳秒
      ​
      // 2.根据时间获取信息
      int hour = time.getHour();      // 获取时
      int minute = time.getMinute();      // 获取分
      int second = time.getSecond();      // 获取秒
      int nano = time.getNano();      // 获取纳秒

    • LocalDateTime:表示日期时间,包含年月日,时分秒,格式为2018-09-06T15:33:56.750;

      // 1.获取日期时间
      LocalDateTime dateTime = LocalDateTime.now();
      LocalDateTime dateTime2 = LocalDateTime.of(2023, 12, 23, 23, 59, 59, 999);
      LocalDateTime dateTime3 = LocalDateTime.of(LocalDate.now(), LocalTime.now());
      ​
      // 2.根据日期时间获取信息
      int year = dateTime.getYear();      // 获取年份
      Month month = dateTime.getMonth();      // 获取月份,返回枚举对象
      int dayOfMonth = dateTime.getDayOfMonth();      // 获取所在月的天数
      DayOfWeek dayOfWeek = dateTime.getDayOfWeek();      // 获取星期,返回枚举对象
      int dayOfYear = dateTime.getDayOfYear();        // 获取所在年的天数
      ​
      int hour = time.getHour();      // 获取时
      int minute = time.getMinute();      // 获取分
      int second = time.getSecond();      // 获取秒
      int nano = time.getNano();      // 获取纳秒

    • DateTimeFormatter:日期时间格式化类;

      // a.日期格式化对象,使用正则表达式
      DateTimeFormatter fm = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:ss:mm");
      // b.日期格式化对象,使用常量
      DateTimeFormatter fm = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
      ​
      // 1.日期转字符串
      String format = LocalDateTime.now().format(fm);
      // 2.字符串转日期
      LocalDateTime date = LocalDateTime.parse("2023-05-25 15:25:59", fm);

    • Instant:时间戳,表示一个特定的时间瞬间;

      // 计算程序时间差
      Instant old = Instant.now();
      Thread.sleep(5000);
      Instant now = Instant.now();
      System.out.println("耗时:" + (old.getNano() - now.getNano()));

    • Duration:用于计算2个时间(LocalTime,时分秒)的距离;

      LocalTime t1 = LocalTime.of(0, 0, 0, 0);
      LocalTime t2 = LocalTime.of(23, 59, 59, 999);
      ​
      Duration duration = Duration.between(t1, t2);
      ​
      // 获取支持的时间单位集合(秒/纳秒)
      List<TemporalUnit> units = duration.getUnits();
      ​
      long seconds = duration.getSeconds();   // 获取秒速的差
      int nano = duration.getNano();          // 获取纳秒的差
      ​
      // 获取时间差后转换为指定的时间单位
      long toDays = duration.toDays();

    • Period:用于计算2个日期(LocalDate,年月日)的距离;

      LocalDate d1 = LocalDate.of(2022, 12, 31);
      LocalDate d2 = LocalDate.of(2025, 10, 30);
      ​
      Period period = Period.between(d1, d2);
      ​
      // 获取支持的时间单位集合(年/月/日)
      List<TemporalUnit> periodUnits = period.getUnits();
      ​
      int years = period.getYears();      // 获取年份之差
      int months = period.getMonths();    // 获取月份之差
      int days = period.getDays();        // 获取天数之差
      ​
      // 获取时间差后转换为指定的时间单位
      long toTotalMonths = period.toTotalMonths();

    • ZonedDateTime:带时区的日期时间类分别为: ZonedDateZonedTimeZonedDateTime,其中每个时区都对应着ID,ID的格式为"区域/城市"。例:Asia/Shanghai 等。

      // 1.获取世界所有时区的集合,使用ZoneId类获取
      Set<String> zoneIds = ZoneId.getAvailableZoneIds();
      ​
      // 2.获取计算机默认日期
      ZonedDateTime now = ZonedDateTime.now();
      // 3.获取标准日期
      ZonedDateTime now = ZonedDateTime.now(Clock.systemUTC());
      // 4.获取指定时区的日期
      ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
      ​
      // 5.将LocalDateTime转为ZonedDateTime
      ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("Asia/Shanghai"));
      // 6.将LocalDate与LocalTime合并转为ZonedDateTime
      ZonedDateTime.of(LocalDate.now(), LocalTime.now(), ZoneId.of("Asia/Shanghai"));

  3. 日期的比较及修改(修改将返回一个新的日期对象):

    • 更改值:

      • 更改日期值

        // 1.使用日期单位的枚举类
        LocalDate date = LocalDate.now();
        LocalDate year = date.with(ChronoField.YEAR, 2024);
        LocalDate month = date.with(ChronoField.MONTH_OF_YEAR, 12);
        ​
        // 2.指定日期单位
        LocalDate date = LocalDate.now();
        LocalDate withYear = date.withYear(2024);
        LocalDate withDayOfYear = date.withDayOfYear(11);

      • 更改时间值:

        // 1.使用时间单位的枚举类
        LocalDate phours = date.plus(2, ChronoUnit.HOURS);
        LocalDate pseconds = date.plus(50, ChronoUnit.SECONDS);
        ​
        LocalDate mhours = date.minus(2, ChronoUnit.HOURS);
        LocalDate mseconds = date.minus(50, ChronoUnit.SECONDS);
        ​
        // 2.指定时间单位
        LocalDate pyears = date.plusYears(2);
        LocalDate pmonths = date.plusMonths(2);
        ​
        LocalDate pyears = date.minusYears(2);
        LocalDate pmonths = date.minusMonths(2);

    • 比较值:

      // 1.比较当前日期是否在指定日期之后
      boolean after = d1.isAfter(d2);
      // 2.比较当前日期是否在指定日期之前
      boolean before = d1.isBefore(d2);
      // 3.比较当前日期是否等于指定日期
      boolean equal = d1.isEqual(d2);
  4. 时间矫正器:

    • TemporalAdjuster 接口:时间日期类的父类接口;使用重写 adjustInto() 方法来更改时间;

      LocalDateTime now = LocalDateTime.now();
      ​
      // 矫正为下个月份的第一天
      LocalDateTime date = now.with(temporal -> {
          LocalDateTime time = (LocalDateTime) temporal;
          return time.plusMonths(1).withDayOfMonth(1);
      });

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

      LocalDateTime now = LocalDateTime.now();
      ​
      // 矫正为下个月份的第一天
      TemporalAdjuster adjuster = TemporalAdjusters.firstDayOfNextMonth();
      LocalDateTime date = now.with(adjuster);

  5. 历法:Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。此外java8还提供了4套其他历法的类,分别是:

    • ThaiBuddhistDate:泰国佛教历;

    • MinguoDate:中华民国历;

    • JapaneseDate:日本历;

    • HijrahDate:伊斯兰历;

  6. JDK新的日期和时间API的优势:

    • 新版日期时间API中,日期和时间对象是不可变的,操作日期不会影响原来的值,而是生成新的实例;

    • 提供不同的两种方式,有效的区分了人和机器的操作;

    • TemporalAdjuster 接口可以更精确的操作日期,还可以自定义日期调整期;

    • 线程安全的;

  • 26
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风与尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值