zoukankan      html  css  js  c++  java
  • Java8新特性详解

    一、Lambda 表达式

    Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
    Lambda表达式还增强了集合库。 Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及java.util.stream 包。 流(stream)就如同迭代器(iterator),但附加了许多额外的功能。 总的来说,lambda表达式和 stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。

    基本语法:
    (parameters) -> expression

    (parameters) ->{ statements; }

    1. 基本的Lambda例子

     假设有一个玩家List ,程序员可以使用 for 语句 ("for 循环")来遍历,在Java SE 8中可以转换为另一种形式:

    public class LambdaDemo {
    
        public static void main(String[] args) {
            String[] atp = {"Rafael Nadal", "Novak Djokovic",
                    "Stanislas Wawrinka",
                    "David Ferrer", "Roger Federer",
                    "Andy Murray", "Tomas Berdych",
                    "Juan Martin Del Potro"};
            List<String> players = Arrays.asList(atp);
    
            // 以前的循环方式
            for (String player : players) {
                System.out.print(player + "; ");
            }
    
            // 使用 lambda 表达式以及函数操作(functional operation)
            players.forEach((player) -> System.out.print(player + "; "));
    
            // 在 Java 8 中使用双冒号操作符(double colon operator)
            players.forEach(System.out::println);
        }
    }

    lambda表达式可以将我们的代码缩减到一行。 另一个例子是在图形用户界面程序中,匿名类可以使用lambda表达式来代替。 同样,在实现Runnable接口时也可以这样使用:

    // 使用匿名内部类  
    btn.setOnAction(new EventHandler<ActionEvent>() {  
              @Override  
              public void handle(ActionEvent event) {  
                  System.out.println("Hello World!");   
              }  
        });  
       
    // 或者使用 lambda expression  
    btn.setOnAction(event -> System.out.println("Hello World!"));

    lambda实现 Runnable接口 的示例:

    // 1.1使用匿名内部类  
    new Thread(new Runnable() {  
        @Override  
        public void run() {  
            System.out.println("Hello world !");  
        }  
    }).start();  
      
    // 1.2使用 lambda expression  
    new Thread(() -> System.out.println("Hello world !")).start();  
      
    // 2.1使用匿名内部类  
    Runnable race1 = new Runnable() {  
        @Override  
        public void run() {  
            System.out.println("Hello world !");  
        }  
    };  
      
    // 2.2使用 lambda expression  
    Runnable race2 = () -> System.out.println("Hello world !");  
       
    // 直接调用 run 方法(没开新线程哦!)  
    race1.run();  
    race2.run();

    Runnable 的 lambda表达式,使用块格式,将五行代码转换成单行语句。

    2. 使用Lambda排序集合

    在Java中,Comparator 类被用来排序集合。 在下面的例子中,我们将根据球员的 name, surname, name 长度 以及最后一个字母,先使用匿名内部类来排序,然后再使用lambda表达式精简我们的代码。

    根据name来排序list。 使用旧的方式,代码如下所示:

    String[] players = {"Rafael Nadal", "Novak Djokovic",
                "Stanislas Wawrinka", "David Ferrer",
                "Roger Federer", "Andy Murray",
                "Tomas Berdych", "Juan Martin Del Potro",
                "Richard Gasquet", "John Isner"};
    
    // 1.1 使用匿名内部类根据 name 排序 players
    Arrays.sort(players, new Comparator<String>() {
        @Override
        public int compare(String s1, String s2) {
            return (s1.compareTo(s2));
        }
    });

    使用Lambda可以通过下面的代码实现同样的功能:

    // 1.2 使用 lambda expression 排序 players
    Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
    Arrays.sort(players, sortByName);
    
    // 1.3 也可以采用如下形式:
    Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));

    其他的排序例子如下:

    // 1.1 使用匿名内部类根据 surname 排序 players  
    Arrays.sort(players, new Comparator<String>() {  
        @Override  
        public int compare(String s1, String s2) {  
            return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" "))));  
        }  
    });  
      
    // 1.2 使用 lambda expression 排序,根据 surname  
    Comparator<String> sortBySurname = (String s1, String s2) ->   
        ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) );  
    Arrays.sort(players, sortBySurname);  
      
    // 1.3 或者这样,怀疑原作者是不是想错了,括号好多...  
    Arrays.sort(players, (String s1, String s2) ->   
          ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) )   
        );  
      
    // 2.1 使用匿名内部类根据 name lenght 排序 players  
    Arrays.sort(players, new Comparator<String>() {  
        @Override  
        public int compare(String s1, String s2) {  
            return (s1.length() - s2.length());  
        }  
    });  
      
    // 2.2 使用 lambda expression 排序,根据 name lenght  
    Comparator<String> sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length());  
    Arrays.sort(players, sortByNameLenght);  
      
    // 2.3 or this  
    Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length()));  
      
    // 3.1 使用匿名内部类排序 players, 根据最后一个字母  
    Arrays.sort(players, new Comparator<String>() {  
        @Override  
        public int compare(String s1, String s2) {  
            return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));  
        }  
    });  
      
    // 3.2 使用 lambda expression 排序,根据最后一个字母  
    Comparator<String> sortByLastLetter =   
        (String s1, String s2) ->   
            (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));  
    Arrays.sort(players, sortByLastLetter);  
      
    // 3.3 or this  
    Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));
    3. 使用Lambda和Stream

    Stream是对集合的包装,通常和lambda一起使用。 使用lambdas可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。 同样,Stream使用懒运算,他们并不会真正地读取所有数据,遇到像getFirst() 这样的方法就会结束链式语法。

    我们创建了一个Person类并使用这个类来添加一些数据到list中,将用于进一步流操作。 Person 只是一个简单的POJO类:

    public class Person {
        private String firstName;
        private String lastName;
        private String job;
        private String gender;
        private Integer salary;
        private Integer age;
    
        public Person(String firstName, String lastName, String job, String gender, Integer age, Integer salary) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.job = job;
            this.gender = gender;
            this.salary = salary;
            this.age = age;
        }
    
        // Getter and Setter   
        // . . . . .  
    }

    我们将创建两个list,都用来存放Person对象:

    List<Person> javaProgrammers = new ArrayList<Person>() {
        {
            add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000));
            add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500));
            add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800));
            add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600));
            add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200));
            add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900));
            add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300));
            add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700));
            add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000));
            add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300));
        }
    };
    
    List<Person> phpProgrammers = new ArrayList<Person>() {
        {
            add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550));
            add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200));
            add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600));
            add(new Person("Tori", "Sheryl", "PHP programmer", "female", 21, 1000));
            add(new Person("Osborne", "Shad", "PHP programmer", "male", 32, 1100));
            add(new Person("Rosalind", "Layla", "PHP programmer", "female", 25, 1300));
            add(new Person("Fraser", "Hewie", "PHP programmer", "male", 36, 1100));
            add(new Person("Quinn", "Tamara", "PHP programmer", "female", 21, 1000));
            add(new Person("Alvin", "Lance", "PHP programmer", "male", 38, 1600));
            add(new Person("Evonne", "Shari", "PHP programmer", "female", 40, 1800));
        }
    };

    现在我们使用forEach方法来迭代输出上述列表:

    System.out.println("所有程序员的姓名:");
    javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
    phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

    另一个有用的方法是过滤器filter() ,让我们显示月薪超过1400美元的PHP程序员:

    System.out.println("下面是月薪超过 $1,400 的PHP程序员:");
            phpProgrammers.stream()
                    .filter((p) -> (p.getSalary() > 1400))
                    .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

    我们也可以定义过滤器,然后重用它们来执行其他操作:

    // 定义 filters
    Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);
    Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400);
    Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender()));
    
    System.out.println("下面是年龄大于 24岁且月薪在$1,400以上的女PHP程序员:");
    phpProgrammers.stream()
            .filter(ageFilter)
            .filter(salaryFilter)
            .filter(genderFilter)
            .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
    
    // 重用filters
    System.out.println("年龄大于 24岁的女性 Java programmers:");
    javaProgrammers.stream()
            .filter(ageFilter)
            .filter(genderFilter)
            .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

    使用limit方法,可以限制结果集的个数:

    System.out.println("最前面的3个 Java programmers:");
    javaProgrammers.stream()
            .limit(3)
            .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
    
    
    System.out.println("最前面的3个女性 Java programmers:");
    javaProgrammers.stream()
            .filter(genderFilter)
            .limit(3)
            .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

    排序呢? 我们在stream中能处理吗? 答案是肯定的。 在下面的例子中,我们将根据名字和薪水排序Java程序员,放到一个list中,然后显示列表:

    System.out.println("根据 name 排序,并显示前5个 Java programmers:");
    List<Person> sortedJavaProgrammers = javaProgrammers
            .stream()
            .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
            .limit(5)
            .collect(Collectors.toList());
    
    sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
    
    System.out.println("根据 salary 排序 Java programmers:");
    sortedJavaProgrammers = javaProgrammers
            .stream()
            .sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) )
            .collect(Collectors.toList());
    
    sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));

    如果我们只对最低和最高的薪水感兴趣,比排序后选择第一个/最后一个 更快的是min和max方法:

    System.out.println("工资最低的 Java programmer:");
    Person pers = javaProgrammers
            .stream()
            .min((p1, p2) -> (p1.getSalary() - p2.getSalary()))
            .get();
    
    System.out.printf("Name: %s %s; Salary: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary());
    
    System.out.println("工资最高的 Java programmer:");
    Person person = javaProgrammers
            .stream()
            .max((p, p2) -> (p.getSalary() - p2.getSalary()))
            .get();
    
    System.out.printf("Name: %s %s; Salary: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary());

    上面的例子中我们已经看到 collect 方法是如何工作的。 结合 map 方法,我们可以使用 collect 方法来将我们的结果集放到一个字符串,一个 Set 或一个TreeSet中:

    System.out.println("将 PHP programmers 的 first name 拼接成字符串:");
    String phpDevelopers = phpProgrammers
            .stream()
            .map(Person::getFirstName)
            .collect(Collectors.joining(" ; ")); // 在进一步的操作中可以作为标记(token)
    
    System.out.println("将 Java programmers 的 first name 存放到 Set:");
    Set<String> javaDevFirstName = javaProgrammers
            .stream()
            .map(Person::getFirstName)
            .collect(Collectors.toSet());
    
    System.out.println("将 Java programmers 的 first name 存放到 TreeSet:");
    TreeSet<String> javaDevLastName = javaProgrammers
            .stream()
            .map(Person::getLastName)
            .collect(Collectors.toCollection(TreeSet::new));

    Streams 还可以是并行的(parallel)。 示例如下:

    System.out.println("计算付给 Java programmers 的所有money:");
    int totalSalary = javaProgrammers
            .parallelStream()
            .mapToInt(p -> p.getSalary())
            .sum();
    System.out.println(totalSalary);

    我们可以使用summaryStatistics方法获得stream 中元素的各种汇总数据。 接下来,我们可以访问这些方法,比如getMax, getMin, getSum或getAverage:

    //计算 count, min, max, sum, and average for numbers  
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);  
    IntSummaryStatistics stats = numbers  
              .stream()  
              .mapToInt((x) -> x)  
              .summaryStatistics();  
      
    System.out.println("List中最大的数字 : " + stats.getMax());  
    System.out.println("List中最小的数字 : " + stats.getMin());  
    System.out.println("所有数字的总和   : " + stats.getSum());  
    System.out.println("所有数字的平均值 : " + stats.getAverage());

    二、函数式接口

    从 Java 8 开始便出现了函数式接口(Functional Interface,以下简称FI)。

    如果一个接口只有唯一的一个抽象接口,则称之为函数式接口。为了保证接口符合 FI ,通常会在接口类上添加 @FunctionalInterface 注解。

    函数式接口 (Functional Interface) 就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

    函数式接口可以对现有的函数友好地支持 lambda。

    /**
     * 文件描述 函数式接口:有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
     **/
    @FunctionalInterface
    public interface Hello {
    
        /**
         * abstract 方法,只能有一个
         */
        void hello();
    
        /**
         * 允许定义默认方法
         */
        default void hi(){
            System.out.println("this is default method");
        }
    
        /**
         * 允许定义静态方法
         */
        static void hei() {
            System.out.println("this is static method");
        }
    
        /**
         * 允许定义 java.lang.Object 里的 public 方法
         */
        @Override
        boolean equals(Object obj);

    JDK 1.8 之前已有的函数式接口:

    • java.lang.Runnable
    • java.util.concurrent.Callable
    • java.security.PrivilegedAction
    • java.util.Comparator
    • java.io.FileFilter
    • java.nio.file.PathMatcher
    • java.lang.reflect.InvocationHandler
    • java.beans.PropertyChangeListener
    • java.awt.event.ActionListener
    • javax.swing.event.ChangeListener

    JDK 1.8 新增加的函数接口:

    • java.util.function包

    java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口 43 个,但是最主要的是这四个:

    (1)功能性接口:Function<T,R>
    (2)断言性接口:Predicate<T>
    (3)供给性接口:Supplier<T>
    (4)消费性接口:Consumer<T>

    函数式接口参数类型返回类型用途
    Consumer T void 对类型T参数操作,无返回结果,包含方法 void accept(T t)
    Supplier T 返回T类型参数,方法时 T get()
    Function T R 对类型T参数操作,返回R类型参数,包含方法 R apply(T t)
    Predicate T boolean 断言型接口,对类型T进行条件筛选操作,返回boolean,包含方法 boolean test(T t)
    /**
     * Java8内置的四大核心函数式接口:
     * Consumer<T>:消费型接口</T>
     * Supplier<T>供给型接口</T>
     * Function<T,R>函数型接口</T,R>
     * Predicate<T>断言型接口</T>
     */
    
    public class TestLamda3 {
    
        //Consumer<T>
        public void test1() {
            happy(10000, (m) -> System.out.println("这次消费了" + m + "元"));
        }
    
        public void happy(double money, Consumer<Double> con) {
            con.accept(money);
        }
    
        //Supplier<T>
        public void test2() {
            List<Integer> list = getNumList(5, () -> {
                return (int) Math.random() * 100;
            });
            list.forEach(System.out::println);
        }
    
        public List<Integer> getNumList(int num, Supplier<Integer> supplier) {
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < num; i++) {
                Integer n = supplier.get();
                list.add(n);
            }
            return list;
        }
    
        //函数式接口
        public void test4() {
            String newStr = strHandle("			 woshi nide ", (str) -> str.trim());
            System.out.println(newStr);
        }
    
        public String strHandle(String str, Function<String, String> fun) {
            return fun.apply(str);
        }
    
        //断言型接口;将满足条件的字符串放入集合中
        public void test5() {
            List<String> list1 = Arrays.asList("nihao", "hiehei", "woai", "ni");
            List<String> list = filterStr(list1, (s) -> s.length() > 3);
            for (String s : list) {
                System.out.println(s);
            }
        }
    
        public List<String> filterStr(List<String> list, Predicate<String> pre) {
            List<String> strings = new ArrayList<>();
            for (String string : list) {
                if (pre.test(string)) {
                    strings.add(string);
                }
            }
            return strings;
        }

    全部接口:

    序号接口 & 描述
    1 BiConsumer<T,U>

    代表了一个接受两个输入参数的操作,并且不返回任何结果

    2 BiFunction<T,U,R>

    代表了一个接受两个输入参数的方法,并且返回一个结果

    3 BinaryOperator<T>

    代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果

    4 BiPredicate<T,U>

    代表了一个两个参数的boolean值方法

    5 BooleanSupplier

    代表了boolean值结果的提供方

    6 Consumer<T>

    代表了接受一个输入参数并且无返回的操作

    7 DoubleBinaryOperator

    代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。

    8 DoubleConsumer

    代表一个接受double值参数的操作,并且不返回结果。

    9 DoubleFunction<R>

    代表接受一个double值参数的方法,并且返回结果

    10 DoublePredicate

    代表一个拥有double值参数的boolean值方法

    11 DoubleSupplier

    代表一个double值结构的提供方

    12 DoubleToIntFunction

    接受一个double类型输入,返回一个int类型结果。

    13 DoubleToLongFunction

    接受一个double类型输入,返回一个long类型结果

    14 DoubleUnaryOperator

    接受一个参数同为类型double,返回值类型也为double 。

    15 Function<T,R>

    接受一个输入参数,返回一个结果。

    16 IntBinaryOperator

    接受两个参数同为类型int,返回值类型也为int 。

    17 IntConsumer

    接受一个int类型的输入参数,无返回值 。

    18 IntFunction<R>

    接受一个int类型输入参数,返回一个结果 。

    19 IntPredicate

    接受一个int输入参数,返回一个布尔值的结果。

    20 IntSupplier

    无参数,返回一个int类型结果。

    21 IntToDoubleFunction

    接受一个int类型输入,返回一个double类型结果 。

    22 IntToLongFunction

    接受一个int类型输入,返回一个long类型结果。

    23 IntUnaryOperator

    接受一个参数同为类型int,返回值类型也为int 。

    24 LongBinaryOperator

    接受两个参数同为类型long,返回值类型也为long。

    25 LongConsumer

    接受一个long类型的输入参数,无返回值。

    26 LongFunction<R>

    接受一个long类型输入参数,返回一个结果。

    27 LongPredicate

    R接受一个long输入参数,返回一个布尔值类型结果。

    28 LongSupplier

    无参数,返回一个结果long类型的值。

    29 LongToDoubleFunction

    接受一个long类型输入,返回一个double类型结果。

    30 LongToIntFunction

    接受一个long类型输入,返回一个int类型结果。

    31 LongUnaryOperator

    接受一个参数同为类型long,返回值类型也为long。

    32 ObjDoubleConsumer<T>

    接受一个object类型和一个double类型的输入参数,无返回值。

    33 ObjIntConsumer<T>

    接受一个object类型和一个int类型的输入参数,无返回值。

    34 ObjLongConsumer<T>

    接受一个object类型和一个long类型的输入参数,无返回值。

    35 Predicate<T>

    接受一个输入参数,返回一个布尔值结果。

    36 Supplier<T>

    无参数,返回一个结果。

    37 ToDoubleBiFunction<T,U>

    接受两个输入参数,返回一个double类型结果

    38 ToDoubleFunction<T>

    接受一个输入参数,返回一个double类型结果

    39 ToIntBiFunction<T,U>

    接受两个输入参数,返回一个int类型结果。

    40 ToIntFunction<T>

    接受一个输入参数,返回一个int类型结果。

    41 ToLongBiFunction<T,U>

    接受两个输入参数,返回一个long类型结果。

    42 ToLongFunction<T>

    接受一个输入参数,返回一个long类型结果。

    43 UnaryOperator<T>

    接受一个参数为类型T,返回值类型也为T。

    三、Optional的使用

    java8 推出的Optional的目的就是为了杜绝空指针异常,帮助开发者开发出更优雅的代码,使用Optional不正确时,将会违背设计者的初衷。

    1. optional的构造方式

    (1) Optional.of(T)
      该方式的入参不能为null,否则会有NPE,在确定入参不为空时使用该方式。
    (2) Optional.ofNullable(T)
      该方式的入参可以为null,当入参不确定为非null时使用。
    (3) Optional.empty()
      这种方式是返回一个空Optional,等效Optional.ofNullable(null)

    2. 如何正确使用Optional

    尽量避免使用的地方:

    • 避免使用Optional.isPresent()来检查实例是否存在,因为这种方式和null != obj没有区别,这样用就没什么意义了。
    • 避免使用Optional.get()方式来获取实例对象,因为使用前需要使用Optional.isPresent()来检查实例是否存在,否则会出现NPE问题。
    • 避免使用Optional作为类或者实例的属性,而应该在返回值中用来包装返回实例对象。
    • 避免使用Optional作为方法的参数,原因同3。

    正确使用方式:

    (1) 实例对象存在则返回,否则提供默认值或者通过方法来设置返回值,即使用orElse/orElseGet方式:

    //存在则返回
    User king = new User(1, "king");
    Optional<User> userOpt = Optional.of(king);
    User user =  userOpt.orElse(null);
    System.out.println(user.getName());
     //不存在提供默认值
    User user2 = null;
    Optional<User> userOpt2 = Optional.ofNullable(user2);
    User user3 = userOpt2.orElse(unknown);
    System.out.println(user3.getName());
    //通过方法提供值
    User user4 = userOpt2.orElseGet(() -> new User(0, "DEFAULT")); 
    System.out.println(user4.getName())

    不建议这样使用:

    if(userOpt.isPresent()) {
        System.out.println(userOpt.get().getName());
    } else {
        //。。。
    }

    (2) 使用ifPresent()来进行对象操作,存在则操作,否则不操作。

    //实例存在则操作,否则不操作
    userOpt.ifPresent(u -> System.out.println(u.getName()));
    userOpt2.ifPresent(u -> System.out.println(u.getName()));

    (3) 使用map/flatMap来获取关联数据

    //使用map方法获取关联数据
    System.out.println(userOpt.map(u -> u.getName()).orElse("Unknown"));
    System.out.println(userOpt2.map(u -> u.getName()).orElse("Default"));
    //使用flatMap方法获取关联数据
    List<String> interests = new ArrayList<String>();
    interests.add("a");interests.add("b");interests.add("c");
    user.setInterests(interests);
    List<String> interests2 = Optional.of(user)
            .flatMap(u -> Optional.ofNullable(u.getInterests()))
            .orElse(Collections.emptyList());
    System.out.println(interests2.isEmpty());

    四、日期时间

    Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样。

    以下为一些常用时间对象:

    • Instant:表示时刻,不直接对应年月日信息,需要通过时区转换
    • LocalDateTime: 表示与时区无关的日期和时间信息,不直接对应时刻,需要通过时区转换
    • LocalDate:表示与时区无关的日期,与LocalDateTime相比,只有日期信息,没有时间信息
    • LocalTime:表示与时区无关的时间,与LocalDateTime相比,只有时间信息,没有日期信息
    • ZonedDateTime: 表示特定时区的日期和时间
    • ZoneId/ZoneOffset:表示时区
    1. Clock 时钟
    Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。
    Clock clock = Clock.systemDefaultZone();
    long millis = clock.millis();
    
    Instant instant = clock.instant();
    Date legacyDate = Date.from(instant);   // legacy java.util.Date
    2. TimeZones 时区

    在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。

    System.out.println(ZoneId.getAvailableZoneIds());
    // prints all available timezone ids
    
    ZoneId zone1 = ZoneId.of("Europe/Berlin");
    ZoneId zone2 = ZoneId.of("Brazil/East");
    System.out.println(zone1.getRules());
    System.out.println(zone2.getRules());
    
    // ZoneRules[currentStandardOffset=+01:00]
    // ZoneRules[currentStandardOffset=-03:00]
    3. LocalTime 时间
    LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:
    LocalTime now1 = LocalTime.now(zone1);
    LocalTime now2 = LocalTime.now(zone2);
    
    System.out.println(now1.isBefore(now2));  // false
    
    long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
    long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
    
    System.out.println(hoursBetween);       // -3
    System.out.println(minutesBetween);     // -239

    LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。

    LocalTime late = LocalTime.of(23, 59, 59);
    System.out.println(late);       // 23:59:59
    
    DateTimeFormatter germanFormatter =
        DateTimeFormatter
            .ofLocalizedTime(FormatStyle.SHORT)
            .withLocale(Locale.GERMAN);
    
    LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
    System.out.println(leetTime);   // 13:37

    常用方法:

    LocalTime now = LocalTime.now();
    //指定时区
    LocalTime now1 = LocalTime.now(Clock.system(ZoneId.systemDefault()));
    LocalTime now2 = LocalTime.now(Clock.systemUTC());
    
    System.out.println(now);
    System.out.println(now1);
    System.out.println(now2);
    
    System.out.println("************now************");
    //清除毫秒位
    System.out.println(now.withNano(0));
    //获取当前的小时
    System.out.println(now.getHour());
    //解析时间时间也是按照ISO格式识别,但可以识别以下3种格式: 12:00 12:01:02 12:01:02.345
    System.out.println(LocalTime.parse("11:58:12"));
    
    //时间比较
    LocalTime other = LocalTime.of(13, 45, 59);
    System.out.println(now.isBefore(other));
    System.out.println(now.isAfter(other));
    4. LocalDate 本地日期
    LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。
    LocalDate today = LocalDate.now();
    LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
    LocalDate yesterday = tomorrow.minusDays(2);
    
    LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
    DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
    
    System.out.println(dayOfWeek);    // FRIDAY

    从字符串解析一个LocalDate类型和解析LocalTime一样简单:

    DateTimeFormatter germanFormatter =
        DateTimeFormatter
            .ofLocalizedDate(FormatStyle.MEDIUM)
            .withLocale(Locale.GERMAN);
    
    LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
    System.out.println(xmas);   // 2014-12-24

    常用方法:

    LocalDate now = LocalDate.now();
    LocalDate now1 = LocalDate.now(Clock.systemUTC());
    
    System.out.println(now);
    System.out.println(now1);
    
    LocalDate of = LocalDate.of(2019, 3, 6);
    //严格按照ISO yyyy-MM-dd验证,03写成3都不行
    LocalDate parse = LocalDate.parse("2019-03-06");
    System.out.println(of);
    System.out.println(parse);
    
    System.out.println("**************now****************");
    
    //当前开始时间
    System.out.println(now.atStartOfDay());
    //当月第一天日期
    System.out.println(now.with(TemporalAdjusters.firstDayOfMonth()));
    //本月第二天日期
    System.out.println(now.withDayOfMonth(2));
    //当月最后一天
    System.out.println(now.with(TemporalAdjusters.lastDayOfMonth()));
    System.out.println(now.getDayOfMonth());
    //当月下一天
    System.out.println(now.plusDays(1));
    //当月上一天
    System.out.println(now.minusDays(1));
    System.out.println(now.getDayOfWeek());
    //当月下一周
    System.out.println(now.plusWeeks(1));
    //当月上一周
    System.out.println(now.minusWeeks(1));
    System.out.println(now.getMonth() + "-" + now.getMonthValue());
    //当月下一个月
    System.out.println(now.plusMonths(1));
    //当月上一个月
    System.out.println(now.minusMonths(1));
    
    //时间比较
    System.out.println(now.isEqual(LocalDate.of(2019, 03, 06)));
    5. LocalDateTime 本地日期时间
    LocalDateTime 同时表示了时间和日期。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。
    LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
    
    DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
    System.out.println(dayOfWeek);      // WEDNESDAY
    
    Month month = sylvester.getMonth();
    System.out.println(month);          // DECEMBER
    
    long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
    System.out.println(minuteOfDay);    // 1439

    只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。

    Instant instant = sylvester
            .atZone(ZoneId.systemDefault())
            .toInstant();
    
    Date legacyDate = Date.from(instant);
    System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014

    格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:

    DateTimeFormatter formatter =
        DateTimeFormatter
            .ofPattern("MMM dd, yyyy - HH:mm");
    
    LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
    String string = formatter.format(parsed);
    System.out.println(string);     // Nov 03, 2014 - 07:13

    和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。

    Date转换为LocalDateTime:

    /**
         * Date转换为LocalDateTime
         * @return LocalDateTime
         */
        public static LocalDateTime date2LocalDateTime() {
            Date date = new Date();
            LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    
            return localDateTime;
        }

    LocalDateTime转换为Date:

    /**
         * LocalDateTime转换为Date
         * @return Date
         */
        public static Date localDateTime2Date() {
            LocalDateTime now = LocalDateTime.now();
            Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();
    
            Date date = Date.from(instant);
    
            return date;
        }

    时间的比较:

    LocalDateTime from = LocalDateTime.of(2018, Month.OCTOBER, 1, 0, 0, 0);
    LocalDateTime to = LocalDateTime.of(2019, Month.MARCH, 6, 23, 59, 59);
    
    Duration between = Duration.between(from, to);
    System.out.println(between.toDays());
    System.out.println(between.toHours());

    常用方法:

    LocalDateTime now = LocalDateTime.now();
    LocalDateTime now1 = LocalDateTime.now(Clock.system(ZoneId.systemDefault()));
    LocalDateTime now2 = LocalDateTime.now(Clock.systemUTC());
    
    System.out.println(now);
    System.out.println(now1);
    System.out.println(now2);      //时间格式转换
    System.out.println(now.format(DateTimeFormatter.ofPattern(PATTERN_1)));
    System.out.println(now.format(DateTimeFormatter.ofPattern(PATTERN_2)));
    System.out.println(now.format(DateTimeFormatter.ofPattern(PATTERN_3)));
    6. 常用API总结
    public static void main(String[] args) {
        /**
         * java.time包中的类是不可变且线程安全的,有以下关键类:
         * Instant —— 它代表的是时间戳
         * LocalDate —— 不包含具体时间的日期, 比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等。
         * LocalTime —— 不含日期的时间
         * LocalDateTime —— 它包含了日期及时间,不过还是没有偏移信息或者说时区。
         * ZonedDateTime —— 这是一个包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。
         */
        /** 获取当天的日期 **/
        LocalDate today = LocalDate.now();
        //今天日期是2020-02-18
        System.out.println("今天日期是:" + today);
        /** 获取当前的年月日 **/
        LocalDate localDate = LocalDate.now();
        int year = localDate.getYear();
        int month = localDate.getMonthValue();
        int day = localDate.getDayOfMonth();
        System.out.println("年:" +year+"
    月:" +month+"
    日:" + day);
        /** 检查两个日期是否相等 : LocalDate重写了equals方法来进行日期的比较 **/
        LocalDate date = LocalDate.of(2016, 4, 21);
        LocalDate curDay = LocalDate.now();
        System.out.println("日期相等吗?" + date.equals(curDay));
    
    
        /** 获取当前时间(不包含日期) : 默认的格式是hh:mm:ss:nnn **/
        LocalTime localTime = LocalTime.now();
        System.out.println("现在时间是:" + localTime);
    
        /** 如何增加时间里面的小时数:plusXxx **/
        LocalTime afterTwo = localTime.plusHours(2);
        System.out.println("两个小时后,时间是:" + afterTwo);
        /** 如何减少时间里面的小时数:minusXxx **/
    
        /**
         * 如何获取1周后的日期
         * LocalDate是用来表示无时间的日期,它有一个plus()方法可以用来增加日,星期,月,
         * ChronoUnit则用来表示时间单位,LocalDate也是不可变的,因此任何修改操作都会返回一个新的实例
         * **/
        LocalDate curDate = LocalDate.now();
        //我们可以用这个方法来增加一个月,一年,一小时,一分等等
        LocalDate afterOneWeeks = curDate.plus(1, ChronoUnit.WEEKS);
        //LocalDate afterOneWeeks = curDate.plusWeeks(1);
        System.out.println("1周后日期是:" + afterOneWeeks);
    
        /**
         * 时钟
         * java8自带了Clock类,可以用来获取某个时区下(所以对时区是敏感的)当前的瞬时时间、日期。
         * 用来代替System.currentTimelnMillis()与TimeZone.getDefault()方法
         */
        //根据系统时钟或UTC返回当前时间
        Clock clock = Clock.systemUTC();
        //Clock clock = Clock.systemDefaultZone();
        System.out.println("clock:" + clock);
       /**
        * 如何判断某个日期在另一个日期的前面还是后面或者相等,
        * 在java8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期。
        * 如果调用方法的那个日期比给定的日期要早的话,isBefore()方法会返回true。
        */
       /**
        * 如何在java8中检查闰年
        * LocalDate类由一个isLeapYear()方法来返回当前LocalDate对应的那年是否是闰年
        */
       /**
        * 两个日期之间包含多少天,多少月
        * 计算两个日期之间包含多少天、周、月、年。可以用java.time.Period类完成该功能。
        */
        LocalDate curDe = LocalDate.now();
        LocalDate fixDate = LocalDate.of(2016, 4, 21);
        Period period = Period.between(curDe, fixDate);
        System.out.printf("日期%s和日期%s相差%s个月", curDe, fixDate, period.getMonths());
        
        /**
         * 在java8中获取当前时间戳
         * Instant类由一个静态的工厂方法now()可以返回当前时间戳
         * 事实上Instant就是java8以前的Date,可以使用这个两个类中的方法在这两个类型之间进行转换,
         * 比如Date.from(Instant)就是用来把Instant转换成java.util.date的,
         * 而Date。toInstant()就是将Date转换成Instant的
         */
        Instant timestamp = Instant.now();
    
        /**
         * 如何在java8中使用预定义的格式器来对日期进行解析/格式化
         * 在java8之前,时间日期的格式化非常麻烦,经常使用SimpleDateFormat来进行格式化,
         * 但是SimpleDateFormat并不是线程安全的。在java8中,引入了一个全新的线程安全的日期与
         * 时间格式器。并且预定义好了格式。
         */
        String dateStr = "20180404";
        LocalDate localDate1 = LocalDate.parse(dateStr, DateTimeFormatter.BASIC_ISO_DATE);
        System.out.println("格式化的日期为:" + localDate1);
        /**
         * 如何在java中使用自定义的格式器来解析日期
         * 有时预置的不能满足的时候就需要我们自定义日期格式器了,下面的例子中的日期格式是"MM dd yyyy".
         * 你可以给DateTimeFormatter的ofPattern静态方法()传入任何的模式,它会返回一个实例
         */
        DateTimeFormatter mmDdYyyy = DateTimeFormatter.ofPattern("MM dd yyyy");
        /**
         * 如何在java8中对日期进行格式化,转换成字符串
         * LocalDate.format()这个方法会返回一个代表当前日期的字符串
         */
        LocalDateTime localDateTime = LocalDateTime.now();
        DateTimeFormatter yyyyMMddHHmmss = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
        String ds = localDateTime.format(yyyyMMddHHmmss);
        System.out.println("日期格式后:" + ds);
    
    }
  • 相关阅读:
    417 Pacific Atlantic Water Flow 太平洋大西洋水流
    416 Partition Equal Subset Sum 分割相同子集和
    415 Add Strings 字符串相加
    414 Third Maximum Number 第三大的数
    413 Arithmetic Slices 等差数列划分
    412 Fizz Buzz
    410 Split Array Largest Sum 分割数组的最大值
    409 Longest Palindrome 最长回文串
    day22 collection 模块 (顺便对比queue也学习了一下队列)
    day21 计算器作业
  • 原文地址:https://www.cnblogs.com/myitnews/p/12312711.html
Copyright © 2011-2022 走看看