石一歌的Java8新特性笔记

Java8新特性

函数式接口

有一个且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

接口的特性:

  • 接口是隐式抽象的,默认修饰符 abstract

  • 接口中每一个方法也是隐式抽象的,默认修饰符 public abstract

  • 接口中的方法都是公有的

  • 接口的成员变量只能是常量,默认修饰符 public static final

  • java1.8支持接口内包含:抽象方法、默认接口方法、静态接口方法、来自Object继承的方法

    综上,可以得出结论只有一个无修饰符的方法的接口为函数式接口,我们可以用@FunctionInterface来检验是否为函数式接口

@FunctionalInterface
public interface Lambda {
    /**
     * 隐式抽象
     *
     * @author 石一歌
     * @date 2022/1/23 18:34
     */
    void helloLambda();
}
  • java.util.function

    Jdk1.8引入的函数式接口的包

    • 常见函数式接口

      • Function(函数式接口)

        • 接受一个参数,返回一个结果,类型不限定。
      • Supplier(生产型接口)

        • java.util.function.Supplier接口,只有一个get无参方法,用于获得泛型指定类型的对象数据,是一个函数式接口
        • Supper接口称为生产性接口,指定接口泛型类型,那么接口中的get方法就会产生什么类型的数据
      • Consumer接口(消费型接口)

        • java.util.functiond.Consumer,该接口刚好和supplier相反,不是用来生产一个数据的是拿来消费一个数据的。
        • 数据类型由泛型来指定
        • 抽象方法accept方法,意思就是消费一个指定泛型的数据
      • Predicate接口(断言函数接口)

        • 接受一个输入参数,返回一个布尔值结果。
        • Predicate接口适合用于过滤,测试对象是否符合某个条件
      • Stream流:生产流水线(重点,后面会具体讲)

      • 示例

        public class Test {
            public static void main(String[] args) {
                // 断言函数接口 当满足时返回true
                Predicate<Integer> predicate = i -> i > 10;
                System.out.println(predicate.test(9)); // false
                System.out.println(predicate.test(11)); // true
                // 消费型接口没有返回值
                Consumer<String> consumer = i -> System.out.println(i);
                consumer.accept("hello word");  // 直接打印
                // 生产型接口返回指定类型的数据
                Supplier<String> supplier = () -> "add";
                System.out.println(supplier.get()); // 返回add
                // 类型转换 可以把字符串类型转换成整形
                Function<String, Integer> function = i -> Integer.parseInt(i);
                System.out.println(function.apply("123"));  // 123
            }
        }
        

lambda表达式

  • 函数式编程

    • 演进过程

      • 外部类

        • @FunctionalInterface
          public interface Lambda {
              /**
               * 隐式抽象
               *
               * @author 石一歌
               * @date 2022/1/23 18:34
               */
              void helloLambda();
          }
          
        • public class LambdaImpl implements Lambda{
              @Override
              public void helloLambda() {
                  System.out.println("First Lambda");
              }
          }
          
        • public class TestLambda {
              public static void main(String[] args) {
                  Lambda lambda = new LambdaImpl();
                  lambda.helloLambda();
              }
          }
          
      • 内部类

        • 静态内部类

          • @FunctionalInterface
            public interface Lambda {
                /**
                 * 隐式抽象
                 *
                 * @author 石一歌
                 * @date 2022/1/23 18:34
                 */
                void helloLambda();
            }
            
          • public class TestLambda {
                static class LambdaImpl implements Lambda {
                    @Override
                    public void helloLambda() {
                        System.out.println("Second Lambda");
                    }
                }
            
                public static void main(String[] args) {
                    Lambda lambda = new LambdaImpl();
                    lambda.helloLambda();
                }
            }
            
        • 局部内部类

          • @FunctionalInterface
            public interface Lambda {
                /**
                 * 隐式抽象
                 *
                 * @author 石一歌
                 * @date 2022/1/23 18:34
                 */
                void helloLambda();
            }
            
          • public class TestLambda {
                public static void main(String[] args) {
                    class LambdaImpl implements Lambda {
                        @Override
                        public void helloLambda() {
                            System.out.println("Third Lambda");
                        }
                    }
                    Lambda lambda = new LambdaImpl();
                    lambda.helloLambda();
                }
            }
            
        • 匿名内部类

          • @FunctionalInterface
            public interface Lambda {
                /**
                 * 隐式抽象
                 *
                 * @author 石一歌
                 * @date 2022/1/23 18:34
                 */
                void helloLambda();
            }
            
          • public class TestLambda {
                public static void main(String[] args) {
                    Lambda lambda = new Lambda() {
                        @Override
                        public void helloLambda() {
                            System.out.println("Fourth Lambda");
                        }
                    };
                    lambda.helloLambda();
                }
            }
            
      • Lambda

        • @FunctionalInterface
          public interface Lambda {
              /**
               * 隐式抽象
               *
               * @author 石一歌
               * @date 2022/1/23 18:34
               */
              void helloLambda();
          }
          
        • public class TestLambda {
              public static void main(String[] args) {
                  Lambda lambda = ()->{
                      System.out.println("Lambda");
                  };
                  lambda.helloLambda();
              }
          }
          
  • 语法

    • (parameters) -> expression 或 (parameters) ->
  • 特征

    • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
    • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
  • 示例

    • Runnable接口的lambda实现

      • new Thread(() -> System.out.println("Use lambda")).start();
        
    • List迭代的lambda实现

      •     List<Integer> features = Arrays.asList(1,2);
            features.forEach(n -> System.out.println(n));
        
      •     List<Integer> features = Arrays.asList(1,2);
            features.forEach(System.out::println);//方法引用再次强化
        

方法引用

lambda表达式的主体仅包含一个表达式,且该表达式仅调用了一个已经存在的方法。方法引用的通用特性:方法引用所使用方法的入参和返回值与lambda表达式实现的函数式接口的入参和返回值一致

  • 形式

    • 引用 形式
      静态方法引用 ClassName :: staticMethodName
      构造器引用 ClassName :: new
      类的任意对象的实例方法引用 ClassName :: instanceMethodName
      特定对象的实例方法引用 object :: instanceMethodName
    • 静态方法引用

      • 静态方法引用的语法格式为: 类名::静态方法名

        静态方法引用适用于lambda表达式主体中仅仅调用了某个类的静态方法的场景

        public class Test
        {
            public static void main(String[] args)
            {
                Arrays.asList(new String[] {"a", "c", "b"}).stream().forEach(s -> Test.println(s));
                Arrays.asList(new String[] {"a", "c", "b"}).stream().forEach(Test::println);
            }
            public static void println(String s)
            {
                System.out.println(s);
            }
        }
        
    • 构造器引用

      • 构造器引用的语法格式为: 类名::new

        构造器引用适用于lambda表达式主体中仅仅调用了某个类的构造函数返回实例的场景

        Supplier<List<String>> supplier1= () -> new  ArrayList<String>();
        Supplier<List<String>> supplier = ArrayList<String>::new;
        
    • 类的任意对象的实例方法引用(不建议使用,太不利于理解)

      • 类的任意对象的实例方法引用的语法格式为: 类名::实例方法名

        lambda表达式的第一个入参为实例方法的调用者,后面的入参与实例方法的入参一致

        Arrays.sort(strs,(s1,s2)->s1.compareToIgnoreCase(s2));
        Arrays.sort(strs, String::compareToIgnoreCase);
        
    • 特定对象的实例方法引用

      • 特定对象的实例方法引用的语法格式为: 对象::实例方法名

        特定对象的实例方法引用适用于lambda表达式的主体中仅仅调用了某个对象的某个实例方法的场景

        public class Test
        {
            public static void main(String[] args)
            {
                Arrays.asList(new String[] {"a", "c", "b"}).stream().forEach(s -> System.out.println(s));
                Arrays.asList(new String[] {"a", "c", "b"}).stream().forEach(System.out::println);
            }
        }
        

默认方法

默认方法是指接口的默认方法,它是java8的新特性之一。顾名思义,默认方法就是接口提供一个默认实现,且不强制实现类去覆写的方法。默认方法用default关键字来修饰。

  • 解决问题

    • java8之前,修改接口功能通常会给接口添加新的方法,这时对已经实现该接口的所有实现类,都要一一添加对新方法的实现,换言之,在给接口定义新方法的同时无法不影响到已有的实现类,这时,java8的默认方法特性就可以解决这种接口修改与已有实现类不兼容的问题,比如java8Iterable接口添加的forEach方法就是一个默认方法,以此可以对集合直接用forEach方法结合lambda表达式方便的实现集合的遍历计算。

          default void forEach(Consumer<? super T> action) {
              Objects.requireNonNull(action);
              for (T t : this) {
                  action.accept(t);
              }
          }
      
  • 多重继承

    • 当一个类实现多个接口时,若多个接口中存在相同默认方法(方法名、参数、返回值相同),此时实现类必须要覆写默认方法。
      • 实现类自己实现方法逻辑
      • 采用super关键字来调用指定接口的默认方法

Stream API

新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。

  • 概念

    • 元素 Stream是一个来自数据源的元素队列,Stream本身并不存储元素。
    • 数据源(即Stream的来源)包含集合、数组、I/O channel、generator(发生器)等。
    • 聚合操作 类似SQL中的filter、map、find、match、sorted等操作
    • 管道运算 Stream在Pipeline中运算后返回Stream对象本身,这样多个操作串联成一个Pipeline,并形成fluent风格的代码。这种方式可以优化操作,如延迟执行(laziness)和短路( short-circuiting)。
    • 内部迭代 不同于java8以前对集合的遍历方式(外部迭代),Stream API采用访问者模式(Visitor)实现了内部迭代。
    • 并行运算 Stream API支持串行(stream() )或并行(parallelStream() )的两种操作方式。
  • 特点

    • Stream API的使用和同样是java8新特性的lambda表达式密不可分,可以大大提高编码效率和代码可读性。
    • Stream API提供串行和并行两种操作,其中并行操作能发挥多核处理器的优势,使用fork/join的方式进行并行操作以提高运行速度。
    • Stream API进行并行操作无需编写多线程代码即可写出高效的并发程序,且通常可避免多线程代码出错的问题。
  • 典型接口

    • Stream的生成

      • 串行流 : stream()
      • 并行流 : parallelStream()
        • 注意,使用parallelStream()生成并行流后,对集合元素的遍历是无序的。
    • forEach()方法

      • forEach()方法的参数为一个Consumer(函数式接口)对象,forEach()方法用来迭代流中的每一个数据

            public static void main(String[] args)
            {  
                List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
                numbers.stream().forEach(n ->  System.out.println("List element: " + n));
            }
        
        • 注意,集合的顶层接口Iterable中也投forEach方法,可以直接对数组元素进行遍历
    • map()方法

      • map()方法的参数为Function(函数式接口)对象,map()方法将流中的所有元素用Function对象进行运算,生成新的流对象(流的元素类型可能改变)

            public static void main(String[] args)
            {  
                List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
                numbers.stream().map( n -> Math.abs(n)).forEach(n ->  System.out.println("Element abs: " + n));
            }
        
    • flatMap()方法

      • Function函数的返回值类型是Stream<? extends R>类型,而不是R类型,即Function函数返回一个Stream流,这样flatMap()能够将一个二维的集合映射成一个一维的集合,比map()方法拥有更高的映射深度

            public static void main(String[] args)
            {  
        		List<String> list = Arrays.asList("1 2", "3 4", "5 6");
        		list.stream().flatMap(item -> Arrays.stream(item.split(" "))).forEach(System.out::println);
            }
        
    • filter()方法

      • filter()方法的参数为Predicate(函数式接口)对象,一般用它进行过滤

            public static void main(String[] args)
            {  
                List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
                long count = numbers.parallelStream().filter(i -> i>0).count();
                System.out.println("Positive count: " + count);
            }
        
    • reduce()方法

      • reduce操作又称为折叠操作,用于将流中的所有值合成一个。reduce()方法参数为BinaryOperator类型的累加器(它接受两个类型相同的参数,返回值类型跟参数类型相同),返回一个Optional对象。

            public static void main(String[] args)
            {
                List<Integer> numbers = Arrays.asList(-1, -2, 0, -1, 4, 5, 1);
                Integer total = numbers.stream().reduce((t, n) -> t + n).get();
                System.out.println("Total: " + total);
            }
        
    • collect()方法

      • collect()方法的参数为一个java.util.stream.Collector类型对象,可以用java.util.stream.Collectors工具类提供的静态方法来生成,Collectors类实现很多的归约操作,如Collectors.toList()Collectors.toSet()Collectors.joining()(joining适用于字符串流)等

            public static void main(String[] args)
            {  
                List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
                List<Integer> abss = numbers.stream().map( n -> Math.abs(n)).collect(Collectors.toList());   
                System.out.println("Abs list: " + abss);
            }
        
    • summaryStatistics()方法

      • Stream API采用mapToInt()mapToLong()mapToDouble()三个方法分别生成IntStreamLongStreamDoubleStream 三个接口类型的对象的方法,这个方法的参数分别为3个函数式接口ToIntFunctionToLongFunctionToDoubleFunction,使用时可以用lambda表达式计算返回对应的int、long、double类型

      • IntSummaryStatisticsLongSummaryStatisticsDoubleSummaryStatistics 三个接口类型(位于java.util包下)中,都有诸如统计数量、最大值、最小值、求和、平均值等方法(方法名和返回类型可能不同),利用这些方法可以方便的进行数值统计

            public static void main(String[] args)
            {
                List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
                IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
                System.out.println("Max : " + stats.getMax());
                System.out.println("Min : " + stats.getMin());
                System.out.println("Sum : " + stats.getSum());
                System.out.println("Average : " + stats.getAverage());
                System.out.println("Count : " + stats.getCount());
            }
        
    • 其它方法

      • 方法名 功能
        limit() 获取指定数量的流
        sorted() 对流进行排序
        distinct() 去重
        skip() 跳过指定数量的元素
        peek() 生成一个包含原Stream的所有元素的新Stream,并指定消费函数
        count() 计算元素数量
    • 区分

      QQ截图20220124000846

Date

  • Local(本地) − 简化了日期时间的处理,没有时区的问题。
  • Zoned(时区) − 通过制定的时区处理日期时间。
  • 原有问题

    • 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
    • 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
    • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
  • 新的日期时间

    • LocalDate

      LocalDate类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不携带任何与时区相关的信息。

      LocalDate localDate1 = LocalDate.now();
      System.out.println(localDate1);
      LocalDate localDate2 = LocalDate.of(2019, 06, 06);
      LocalDate localDate3 = LocalDate.of(2019, Month.JUNE, 06);
      System.out.println(localDate2 + " " + localDate3);
      System.out.println(localDate2.getDayOfYear());
      System.out.println(localDate2.getDayOfWeek());
      System.out.println(localDate2.getDayOfMonth());
      System.out.println(localDate2.getMonth());
      System.out.println(localDate2.get(ChronoField.DAY_OF_WEEK));
      System.out.println(localDate2.get(ChronoField.DAY_OF_YEAR));
      
    • LocalTime

      一天中的时间,可以使用LocalTime类表示。你可以使用of重载的两个工厂方法创建LocalTime的实例。第一个重载函数接收小时和分 ,第二个重载函数同时还接收秒。同LocalDate一样,LocalTime类也提供了一些getter方法访问这些变量的值。

      LocalTime localTime1 = LocalTime.now();
      System.out.println(localTime1);
      LocalTime localTime2 = LocalTime.of(12, 02);
      LocalTime localTime3 = LocalTime.of(12, 02, 03);
      LocalTime localTime4 = LocalTime.of(12, 02, 03, 900_000_000);
      System.out.println(localTime2 + " " + localTime3 + " " + localTime4);
      System.out.println(localTime4.getHour());
      System.out.println(localTime4.getMinute());
      System.out.println(localTime4.getSecond());
      
    • LocalDateTime

      LocalDateTime,是LocalDateLocalTime的合体。它同时表示了日期和时间,但不带有时区信息,你可以直接创建,也可以通过合并日期和时间对象进行构造。

      LocalDateTime localDateTime1 = LocalDateTime.now();
      System.out.println(localDateTime1);
      LocalDateTime localDateTime2 = LocalDateTime.of(2000, Month.AUGUST, 22, 04, 05, 06);
      System.out.println(localDateTime2);
      System.out.println(LocalDateTime.of(localDate1, localTime1));
      System.out.println(localDate2.atTime(localTime2));
      System.out.println(localTime3.atDate(localDate3));
      System.out.println(localTime3.get(ChronoField.HOUR_OF_DAY));
      System.out.println(localTime3.get(ChronoField.MINUTE_OF_HOUR));
      
    • Instant

      以Unix元年时间(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的秒数进行计算的时间,适合计算机。

      // Instant
      Instant instant = Instant.now();
      System.out.println(instant);
      // 时间戳,秒,毫秒
      System.out.println(instant.toEpochMilli());
      System.out.println(instant.getEpochSecond());
      // Date、Calendar、System
      Date date = new Date();
      System.out.println(date.getTime());
      System.out.println(Calendar.getInstance().getTimeInMillis());
      System.out.println(System.currentTimeMillis());
      System.out.println(instant.toEpochMilli() + " " + instant.getNano());
      
    • Duration和Period

      Duration类的静态工厂方法between就是为获取两个时间的间隔。可以创建使用两个LocalTime对象或两个LocalDateTime对象。

      由于LocalDateTime和Instant是为不同的目的而设计的,一个是为了便于人阅读使用, 另一个是为了便于机器处理,所以你不能将二者混用。如果试图在这两类对象之间创建duration,会触发一个DateTimeException异常。此外,由于Duration类主要用于以秒和纳秒衡量时间的长短,你不能仅向between方法传递一个LocalDate对象做参数。

      Period类的静态工厂方法between,可以得到两个LocalDate之间的时长。

      QQ截图20220124212051

      // Duration 用在LocalTime、LocalDateTime
      System.out.println(Duration.between(localTime1, localTime2));
      System.out.println(Duration.between(localDateTime1, localDateTime2));
      System.out.println(Duration.ofDays(2));
      System.out.println(Duration.ofHours(5));
      System.out.println(Duration.ofMinutes(100));
      System.out.println(Duration.of(2, ChronoUnit.MINUTES));
      System.out.println(Duration.of(2, ChronoUnit.SECONDS));
      
      // Period用在LocalDate上面
      Period period = Period.between(localDate1, localDate2);
      System.out.println(period);
      System.out.println(Period.ofYears(2));
      
  • 操纵日期和时间

    • 简单操纵对象

      LocalDate date1 = LocalDate.of(2014, 3, 18);
      LocalDate date2 = date1.withYear(2011);
      LocalDate date3 = date2.withDayOfMonth(25);
      LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);
      

      QQ截图20220124212551

    • 使用TemporalAdjuster操纵对象

      import static java.time.temporal.TemporalAdjusters.*;
      LocalDate date1 = LocalDate.of(2014, 3, 18);
      LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY));
      LocalDate date3 = date2.with(lastDayOfMonth());
      
      // 使用TemporalAdjuster中预定义的方法快速操纵和修改日期
      System.out.println(localDate.with(TemporalAdjusters.lastDayOfYear()));
      System.out.println(localDate.with(TemporalAdjusters.firstDayOfNextMonth()));
      System.out.println(localDate.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)));
      System.out.println(localDate.with(TemporalAdjusters.firstInMonth(DayOfWeek.TUESDAY)));
      // 函数式接口
      LocalDate firstMonthOfYear = localDate.with((temporal) -> localDate.withMonth(1));
      LocalDate lastMonthOfYear = localDate.with((temporal) -> localDate.withMonth(12));
      System.out.println(firstMonthOfYear + " " + lastMonthOfYear);
      

      QQ截图20220124212953

  • 解析日期对象

    • 解析日期时间

      格式化以及解析日期和时间对象是另一个非常重要的功能。新的java.time.format包就是特别为这个目的而设计的。这个包中,最重要的类是DateTimeFormatter。创建格式器最简单的方法是通过它的静态工厂方法以及常量。像BASIC_ISO_DATE和ISO_LOCAL_DATE这样的常量是DateTimeFormatter类的预定义实例。所有的DateTimeFormatter实例都能用于以一定的格式创建代表特定日期或时间的字符串。

      //预设常量创建格式器
      ocalDate ld4 = LocalDate.of(2019, 06, 06);
      System.out.println(ld1.format(DateTimeFormatter.BASIC_ISO_DATE));
      LocalTime lt3 = LocalTime.now();
      System.out.println(lt3.format(DateTimeFormatter.ISO_LOCAL_TIME));
      System.out.println(LocalDate.parse("20190606", DateTimeFormatter.BASIC_ISO_DATE));
      System.out.println(LocalTime.parse(“12:02:03”, DateTimeFormatter.ISO_LOCAL_TIME));
      
      //特定模式创建格式器
      DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
      String ld4Str = ld4.format(dateTimeFormatter);
      System.out.println(ld4Str);
      System.out.println(LocalDate.parse(ld4Str, dateTimeFormatter));
      
    • 时区

      时区是按照一定的规则将区域划分成的标准时间相同的区间。在ZoneRules这个类中包含了40个这样的实例。

      • 创建ZoneId

        //每个特定的ZoneId对象都由一个地区ID标识创建
        ZoneId zoneId = ZoneId.of("Asia/Tokyo");
        //方法toZoneId将一个老的时区对象转换为ZoneId
        ZoneId zoneId = TimeZone.getDefault().toZoneId();
        
      • 合成ZonedDateTime

        // 不同时区
        ZoneId zoneId = ZoneId.of("Asia/Tokyo");
        ZoneId defaultZoneId = TimeZone.getDefault().toZoneId();
        LocalDateTime localDateTime2 = LocalDateTime.now();
        // 标记这个是哪个时区的时间
        System.out.println(localDateTime2);
        System.out.println(" !! " + localDateTime2.atZone(zoneId));
        // 把时间戳转成指定时区的时间
        System.out.println(Instant.now().atZone(zoneId));
        
      • LocalDateTime与Instant的转换

        //LocalDateTime->Instant
        LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); 
        Instant instantFromDateTime = dateTime.toInstant(romeZone);
        //Instant->LocalDateTime
        Instant instant = Instant.now();
        LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);
        
      • 利用当前时区和UTC/格林尼治时间的固定偏差计算时区

        ZoneOffset类,是ZoneId的一个子类,表示的是当前时间和伦敦格林尼治子午线时间的差异

        ZoneOffset beijing = ZoneOffset.of("+08:00");
        OffsetDateTime offsetDateTime = OffsetDateTime.of(LocalDateTime.now(), beijing);
        System.out.println(offsetDateTime);
        

Optional

Optional类是Java8为了解决null值判断问题,借鉴google guava类库的Optional类而引入的一个同名Optional类,使用Optional类可以避免显式的null值判断(null的防御性检查),避免null导致的NPE

  • Optional对象的创建

    • Optional类的两个构造方法都是private型的,因此类外部不能显示的使用new Optional()的方式来创建Optional对象,但是Optional类提供了三个静态方法empty()、of(T value)、ofNullable(T value)来创建Optinal对象,

      // 1、创建一个包装对象值为空的Optional对象
      Optional<String> optStr = Optional.empty();
      // 2、创建包装对象值非空的Optional对象
      Optional<String> optStr1 = Optional.of("optional");
      // 3、创建包装对象值允许为空的Optional对象
      Optional<String> optStr2 = Optional.ofNullable(null);
      
    • 典型使用

      • get()方法

        • get()方法主要用于返回包装对象的实际值,但是如果包装对象值为null,会抛出NoSuchElementException异常

        • 源码

          public T get() {
              if (value == null) {
                  throw new NoSuchElementException("No value present");
              }
              return value;
          }
          
      • isPresent()方法

        • isPresent()方法用于判断包装对象的值是否非空

        • 源码

              public boolean isPresent() {
                  return value != null;
              }
          
      • ifPresent()方法

        • ifPresent()方法接受一个Consumer对象(消费函数),如果包装对象的值非空,运行Consumer对象的accept()方法

        • 源码

              public void ifPresent(Consumer<? super T> consumer) {
                  if (value != null)
                      consumer.accept(value);
              }
          
        • 示例

              public static void printName(Student student)
              {
                  Optional.ofNullable(student).ifPresent(u ->  System.out.println("The student name is : " + u.getName()));
              }
          
      • filter()方法

        • filter()方法接受参数为Predicate对象,用于对Optional对象进行过滤,如果符合Predicate的条件,返回Optional对象本身,否则返回一个空的Optional对象

        • 源码

              public Optional<T> filter(Predicate<? super T> predicate) {
                  Objects.requireNonNull(predicate);
                  if (!isPresent())
                      return this;
                  else
                      return predicate.test(value) ? this : empty();
              }
          
        • 示例

              public static void filterAge(Student student)
              {
                  Optional.ofNullable(student).filter( u -> u.getAge() > 18).ifPresent(u ->  System.out.println("The student age is more than 18."));
              }
          
      • map()方法

        • map()方法的参数为Function(函数式接口)对象,map()方法将Optional中的包装对象用Function函数进行运算,并包装成新的Optional对象(包装对象的类型可能改变)

        • 源码

              public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
                  Objects.requireNonNull(mapper);
                  if (!isPresent())
                      return empty();
                  else {
                      return Optional.ofNullable(mapper.apply(value));
                  }
              }
          
        • 示例

              public static Optional<Integer> getAge(Student student)
              {
                  return Optional.ofNullable(student).map(u -> u.getAge()); 
              }
          
      • flatMap()方法

        • flatMap()方法的参数Function函数的返回值类型为Optional<U>类型,而不是U类型,这样flatMap()能将一个二维的Optional对象映射成一个一维的对象。

        • 源码

              public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
                  Objects.requireNonNull(mapper);
                  if (!isPresent())
                      return empty();
                  else {
                      return Objects.requireNonNull(mapper.apply(value));
                  }
              }
          
        • 示例

              public static Optional<Integer> getAge(Student student)
              {
                  return Optional.ofNullable(student).flatMap(u -> Optional.ofNullable(u.getAge())); 
              }
          
      • orElse()方法

        • 如果包装对象值非空,返回包装对象值,否则返回入参other的值(默认值)

        • 源码

              public T orElse(T other) {
                  return value != null ? value : other;
              }
          
        • 示例

              public static String getGender(Student student)
              {
                 return Optional.ofNullable(student).map(u -> u.getGender()).orElse("Unkown");
                  
              }
          
      • orElseGet()方法

        • orElseGet()方法与orElse()方法类似,区别在于orElseGet()方法的入参为一个Supplier对象,用Supplier对象的get()方法的返回值作为默认值

        • 源码

              public T orElseGet(Supplier<? extends T> other) {
                  return value != null ? value : other.get();
              }
          
        • 示例

              public static String getGender(Student student)
              {
                  return Optional.ofNullable(student).map(u -> u.getGender()).orElseGet(() -> "Unkown");      
              }
          
      • orElseThrow()方法

        • orElseThrow()方法其实与orElseGet()方法非常相似了,入参都是Supplier对象,只不过orElseThrow()的Supplier对象必须返回一个Throwable异常,并在orElseThrow()中将异常抛出

        • 源码

              public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
                  if (value != null) {
                      return value;
                  } else {
                      throw exceptionSupplier.get();
                  }
              }
          
          
        • 示例

              public static String getGender1(Student student)
              {
                  return Optional.ofNullable(student).map(u -> u.getGender()).orElseThrow(() -> new RuntimeException("Unkown"));      
              }
          
    • 实战

      • 传统

        • 深层质疑

          public static String getInsuranceName(User user) {
                  if (user != null) {
                      Car car = user.getCar();
                      if (car != null) {
                          Insurance insurance = car.getInsurance();
                          if (insurance != null) {
                              return insurance.getInsuranceName();
                          }
                      }
                  }
                  return "not found";
              }
          
        • 及时退出

             public static String getInsuranceNameBak(User user) {
                  if (user == null) {
                      return "not found";
                  }
                  if (user.getCar() == null) {
                      return "not found";
                  }
                  if (user.getCar().getInsurance() == null) {
                      return "not found";
                  }
                  return user.getCar().getInsurance().getInsuranceName();
              }
          
      • Optional

           public static String getInsuranceName(User user) {
                String insuranceName = Optional.ofNullable(user).orElse(new User()).getUserName();
                return user.getCar().getInsurance().getInsuranceName();
            }
        
        • 常用

          • 尝试获取用户的用户名称,不存在则返回默认值

            String userName = Optional.ofNullable(user).orElse(new User()).getUserName();
            
          • 尝试获取用户的carName,不存在则返回null

            String carName = Optional.ofNullable(user).map(u -> u.getCar()).map(c -> c.getCarName()).orElse(null);
            
          • 将用户名转为大写

            String optionMap = Optional.ofNullable("abc").map(value -> value.toUpperCase()).get();
            
          • 过滤出来用户名称是张三的用户

            Optional<String> filterOptional = Optional.ofNullable("张三").filter(value -> Objects.equals(value, "张三"));
            
          • 将张三的用户名称更改为李四

            Optional.ofNullable(user).filter(z -> Objects.equals(z.getUserName(), "张三")).ifPresent(l -> {l.setUserName("李四");});
            

Nashorn

从 JDK 1.8 开始,Nashorn取代Rhino(JDK 1.6, JDK1.7) 成为 Java 的嵌入式 JavaScript 引擎。Nashorn 完全支持 ECMAScript 5.1 规范以及一些扩展。它使用基于 JSR 292 的新语言特性,其中包含在 JDK 7 中引入的 invokedynamic,将 JavaScript 编译成 Java 字节码。

与先前的 Rhino 实现相比,这带来了 2 到 10倍的性能提升。

(了解即可,java15废弃)

参考链接

posted @ 2022-01-24 22:03  Faetbwac  阅读(75)  评论(0编辑  收藏  举报