关于java8:73万字肝爆Java8新特性我不信你能看完建议收藏

大家好,我是冰河~~

说实话,肝这篇文章花了我一个月的工夫,对于Java8的新个性全在这儿了,倡议先珍藏后浏览

Java8有哪些新个性?

简略来说,Java8新个性如下所示:

  • Lambda表达式
  • 函数式接口
  • 办法援用与结构器援用
  • Stream API
  • 接口的默认办法与静态方法
  • 新工夫日期API
  • 其余新个性

其中,援用最宽泛的新个性是Lambda表达式和Stream API。

Java8有哪些长处?

简略来说Java8长处如下所示。

  • 速度更快
  • 代码更少(减少了新的语法Lambda表达式)
  • 弱小的Stream API
  • 便于并行
  • 最大化缩小空指针异样Optional

Lambda表达式

什么是Lambda表达式?

Lambda表达式是一个匿名函数,咱们能够这样了解Lambda表达式:Lambda是一段能够传递的代码(可能做到将代码像数据一样进行传递)。应用Lambda表达式可能写出更加简洁、灵便的代码。并且,应用Lambda表达式可能使Java的语言表达能力失去晋升。

匿名外部类

在介绍如何应用Lambda表达式之前,咱们先来看看匿名外部类,例如,咱们应用匿名外部类比拟两个Integer类型数据的大小。

Comparator<Integer> com = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return Integer.compare(o1, o2);
    }
};

在上述代码中,咱们应用匿名外部类实现了比拟两个Integer类型数据的大小。

接下来,咱们就能够将上述匿名外部类的实例作为参数,传递到其余办法中了,如下所示。

 TreeSet<Integer> treeSet = new TreeSet<>(com);

残缺的代码如下所示。

@Test
public void test1(){
    Comparator<Integer> com = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1, o2);
        }
    };
    TreeSet<Integer> treeSet = new TreeSet<>(com);
}

咱们剖析下上述代码,在整个匿名外部类中,实际上真正有用的就是上面一行代码。

 return Integer.compare(o1, o2);

其余的代码实质上都是“冗余”的。然而为了书写下面的一行代码,咱们不得不在匿名外部类中书写更多的代码。

Lambda表达式

如果应用Lambda表达式实现两个Integer类型数据的比拟,咱们该如何实现呢?

Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

看到没,应用Lambda表达式,咱们只须要应用一行代码就可能实现两个Integer类型数据的比拟。

咱们也能够将Lambda表达式传递到TreeSet的构造方法中,如下所示。

 TreeSet<Integer> treeSet = new TreeSet<>((x, y) -> Integer.compare(x, y));

直观的感触就是应用Lambda表达式一行代码就能搞定匿名外部类多行代码的性能。

看到这,不少读者会问:我应用匿名外部类的形式实现比拟两个整数类型的数据大小并不简单啊!我为啥还要学习一种新的语法呢?

其实,我想说的是:下面咱们只是简略的列举了一个示例,接下来,咱们写一个略微简单一点的例子,来比照下应用匿名外部类与Lambda表达式哪种形式更加简洁。

比照惯例办法和Lambda表达式

例如,当初有这样一个需要:获取以后公司中员工年龄大于30岁的员工信息。

首先,咱们须要创立一个Employee实体类来存储员工的信息。

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = -9079722457749166858L;
    private String name;
    private Integer age;
    private Double salary;
}

在Employee中,咱们简略存储了员工的姓名、年龄和薪资。

接下来,咱们创立一个存储多个员工的List汇合,如下所示。

protected List<Employee> employees = Arrays.asList(
        new Employee("张三", 18, 9999.99),
        new Employee("李四", 38, 5555.55),
        new Employee("王五", 60, 6666.66),
        new Employee("赵六", 16, 7777.77),
        new Employee("田七", 18, 3333.33)
);

1.惯例遍历汇合

咱们先应用惯例遍历汇合的形式来查找年龄大于等于30的员工信息。

public List<Employee> filterEmployeesByAge(List<Employee> list){
    List<Employee> employees = new ArrayList<>();
    for(Employee e : list){
        if(e.getAge() >= 30){
            employees.add(e);
        }
    }
    return employees;
}

接下来,咱们测试一下下面的办法。

@Test
public void test3(){
    List<Employee> employeeList = filterEmployeesByAge(this.employees);
    for (Employee e : employeeList){
        System.out.println(e);
    }
}

运行test3办法,输入信息如下所示。

Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)

总体来说,查找年龄大于或者等于30的员工信息,应用惯例遍历汇合的形式稍显简单了。

例如,需要产生了变动:获取以后公司中员工工资大于或者等于5000的员工信息。

此时,咱们不得不再次创立一个依照工资过滤的办法。

public List<Employee> filterEmployeesBySalary(List<Employee> list){
    List<Employee> employees = new ArrayList<>();
    for(Employee e : list){
        if(e.getSalary() >= 5000){
            employees.add(e);
        }
    }
    return employees;
}

比照filterEmployeesByAge()办法和filterEmployeesBySalary办法后,咱们发现,大部分的办法体是雷同的,只是for循环中对于条件的判断不同。

如果此时咱们再来一个需要,查找以后公司中年龄小于或者等于20的员工信息,那咱们又要创立一个过滤办法了。 看来应用惯例办法是真的不不便啊!

这里,问大家一个问题:对于这种惯例办法最好的优化形式是啥?置信有不少小伙伴会说:将专用的办法抽取进去。没错,将专用的办法抽取进去是一种优化形式,但它不是最好的形式。最好的形式是啥?那就是应用 设计模式 啊!设计模式可是有数前辈一直实际而总结出的设计准则和设计模式。大家能够查看《设计模式汇总——你须要把握的23种设计模式都在这儿了!》一文来学习设计模式专题。

2.应用设计模式优化代码

如何应用设计模式来优化下面的办法呢,大家持续往下看,对于设计模式不相熟的同学能够先依据《设计模式汇总——你须要把握的23种设计模式都在这儿了!》来学习。

咱们先定义一个泛型接口MyPredicate,对传递过去的数据进行过滤,合乎规定返回true,不合乎规定返回false。

public interface MyPredicate<T> {

    /**
     * 对传递过去的T类型的数据进行过滤
     * 合乎规定返回true,不合乎规定返回false
     */
    boolean filter(T t);
}

接下来,咱们创立MyPredicate接口的实现类FilterEmployeeByAge来过滤年龄大于或者等于30的员工信息。

public class FilterEmployeeByAge implements MyPredicate<Employee> {
    @Override
    public boolean filter(Employee employee) {
        return employee.getAge() >= 30;
    }
}

咱们定义一个过滤员工信息的办法,此时传递的参数不仅有员工的信息汇合,同时还有一个咱们定义的接口实例,在遍历员工汇合时将合乎过滤条件的员工信息返回。

//优化形式一
public List<Employee> filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate){
    List<Employee> employees = new ArrayList<>();
    for(Employee e : list){
        if(myPredicate.filter(e)){
            employees.add(e);
        }
    }
    return employees;
}

接下来,咱们写一个测试方法来测试优化后的代码。

@Test
public void test4(){
    List<Employee> employeeList = this.filterEmployee(this.employees, new FilterEmployeeByAge());
    for (Employee e : employeeList){
        System.out.println(e);
    }
}

运行test4()办法,输入的后果信息如下所示。

Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)

写到这里,大家是否有一种恍然大悟的感觉呢?

没错,这就是设计模式的魅力,对于设计模式不相熟的小伙伴,肯定要参照《设计模式汇总——你须要把握的23种设计模式都在这儿了!》来学习。

咱们持续获取以后公司中工资大于或者等于5000的员工信息,此时,咱们只须要创立一个FilterEmployeeBySalary类实现MyPredicate接口,如下所示。

public class FilterEmployeeBySalary implements MyPredicate<Employee>{
    @Override
    public boolean filter(Employee employee) {
        return employee.getSalary() >= 5000;
    }
}

接下来,就能够间接写测试方法了,在测试方法中持续调用filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate)办法。

@Test
public void test5(){
    List<Employee> employeeList = this.filterEmployee(this.employees, new FilterEmployeeBySalary());
    for (Employee e : employeeList){
        System.out.println(e);
    }
}

运行test5办法,输入的后果信息如下所示。

Employee(name=张三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=赵六, age=16, salary=7777.77)

能够看到,应用设计模式对代码进行优化后,无论过滤员工信息的需要如何变动,咱们只须要创立MyPredicate接口的实现类来实现具体的过滤逻辑,而后在测试方法中调用filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate)办法将员工汇合和过滤规定传入即可。

这里,问大家一个问题:下面优化代码应用的设计模式是哪种设计模式呢?如果是你,你会想到应用设计模式来优化本人的代码吗?小伙伴们本人先思考一下到底应用的设计模式是什么?文末我会给出答案!

应用设计模式优化代码也有不好的中央:每次定义一个过滤策略的时候,咱们都要独自创立一个过滤类!!

3.匿名外部类

那应用匿名外部类是不是可能优化咱们书写的代码呢,接下来,咱们就应用匿名外部类来实现对员工信息的过滤。先来看过滤年龄大于或者等于30的员工信息。

@Test
public void test6(){
    List<Employee> employeeList = this.filterEmployee(this.employees, new MyPredicate<Employee>() {
        @Override
        public boolean filter(Employee employee) {
            return employee.getAge() >= 30;
        }
    });
    for (Employee e : employeeList){
        System.out.println(e);
    }
}

运行test6办法,输入如下后果信息。

Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)

再实现过滤工资大于或者等于5000的员工信息,如下所示。

@Test
public void test7(){
    List<Employee> employeeList = this.filterEmployee(this.employees, new MyPredicate<Employee>() {
        @Override
        public boolean filter(Employee employee) {
            return employee.getSalary() >= 5000;
        }
    });
    for (Employee e : employeeList){
        System.out.println(e);
    }
}

运行test7办法,输入如下后果信息。

Employee(name=张三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=赵六, age=16, salary=7777.77)

匿名外部类看起来比惯例遍历汇合的办法要简略些,并且将应用设计模式优化代码时,每次创立一个类来实现过滤规定写到了匿名外部类中,使得代码进一步简化了。

然而,应用匿名外部类代码的可读性不高,并且冗余代码也比拟多!!

那还有没有更加简化的形式呢?

4.重头戏:Lambda表达式

在应用Lambda表达式时,咱们还是要调用之前写的filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate)办法。

留神看,获取年龄大于或者等于30的员工信息。

@Test
public void test8(){
    filterEmployee(this.employees, (e) -> e.getAge() >= 30).forEach(System.out::println);
}

看到没,应用Lambda表达式只须要一行代码就实现了员工信息的过滤和输入。是不是很6呢。

运行test8办法,输入如下的后果信息。

Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)

再来看应用Lambda表达式来获取工资大于或者等于5000的员工信息,如下所示。

@Test
public void test9(){
    filterEmployee(this.employees, (e) -> e.getSalary() >= 5000).forEach(System.out::println);
}

没错,应用Lambda表达式,又是一行代码就搞定了!!

运行test9办法,输入如下的后果信息。

Employee(name=张三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=赵六, age=16, salary=7777.77)

另外,应用Lambda表达式时,只须要给出须要过滤的汇合,咱们就可能实现从汇合中过滤指定规定的元素,并输入后果信息。

5.重头戏:Stream API

应用Lambda表达式联合Stream API,只有给出相应的汇合,咱们就能够实现对汇合的各种过滤并输入后果信息。

例如,此时只有有一个employees汇合,咱们应用Lambda表达式来获取工资大于或者等于5000的员工信息。

@Test
public void test10(){
    employees.stream().filter((e) -> e.getSalary() >= 5000).forEach(System.out::println);
}

没错,只给出一个汇合,应用Lambda表达式和Stream API,一行代码就可能过滤出想要的元素并进行输入。

运行test10办法,输入如下的后果信息。

Employee(name=张三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=赵六, age=16, salary=7777.77)

如果咱们只想要获取前两个员工的信息呢?其实也很简略,如下所示。

@Test
public void test11(){
    employees.stream().filter((e) -> e.getSalary() >= 5000).limit(2).forEach(System.out::println);
}

能够看到,咱们在代码中增加了limit(2)来限度只获取两个员工信息。运行test11办法,输入如下的后果信息。

Employee(name=张三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)

应用Lambda表达式和Stream API也能够获取指定的字段信息,例如获取工资大于或者等于5000的员工姓名。

@Test
public void test12(){
    employees.stream().filter((e) -> e.getSalary() >= 5000).map(Employee::getName).forEach(System.out::println);
}

能够看到,应用map过滤出了工资大于或者等于5000的员工姓名。运行test12办法,输入如下的后果信息。

张三
李四
王五
赵六

是不是很简略呢?

最初,给出文中应用的设计模式:策略模式。

匿名类到Lambda表达式

咱们先来看看从匿名类如何转换到Lambda表达式呢?

这里,咱们能够应用两个示例来阐明如何从匿名外部类转换为Lambda表达式。

  • 匿名外部类到Lambda表达式

应用匿名外部类如下所示。

Runnable r = new Runnable(){
    @Override
    public void run(){
        System.out.println("Hello Lambda");
    }
}

转化为Lambda表达式如下所示。

Runnable r = () -> System.out.println("Hello Lambda");
  • 匿名外部类作为参数传递到Lambda表达式作为参数传递

应用匿名外部类作为参数如下所示。

TreeSet<Integer> ts = new TreeSet<>(new Comparator<Integer>(){
    @Override
    public int compare(Integer o1, Integer o2){
        return Integer.compare(o1, o2);
    }
});

应用Lambda表达式作为参数如下所示。

TreeSet<Integer> ts = new TreeSet<>(
    (o1, o2) -> Integer.compare(o1, o2);
);

从直观上看,Lambda表达式要比惯例的语法简洁的多。

Lambda表达式的语法

Lambda表达式在Java语言中引入了 “->” 操作符, “->” 操作符被称为Lambda表达式的操作符或者箭头操作符,它将Lambda表达式分为两局部:

  • 左侧局部指定了Lambda表达式须要的所有参数。

Lambda表达式实质上是对接口的实现,Lambda表达式的参数列表实质上对应着接口中办法的参数列表。

  • 右侧局部指定了Lambda体,即Lambda表达式要执行的性能。

Lambda体实质上就是接口办法具体实现的性能。

咱们能够将Lambda表达式的语法总结如下。

1.语法格局一:无参,无返回值,Lambda体只有一条语句

Runnable r = () -> System.out.println("Hello Lambda");

具体示例如下所示。

@Test
public void test1(){
    Runnable r = () -> System.out.println("Hello Lambda");
    new Thread(r).start();
}

2.语法格局二:Lambda表达式须要一个参数,并且无返回值

Consumer<String> func = (s) -> System.out.println(s);

具体示例如下所示。

@Test
public void test2(){
    Consumer<String> consumer = (x) -> System.out.println(x);
    consumer.accept("Hello Lambda");
}

3.语法格局三:Lambda只须要一个参数时,参数的小括号能够省略

Consumer<String> func = s -> System.out.println(s);

具体示例如下所示。

@Test
public void test3(){
    Consumer<String> consumer = x -> System.out.println(x);
    consumer.accept("Hello Lambda");
}

4.语法格局四:Lambda须要两个参数,并且有返回值

BinaryOperator<Integer> bo = (a, b) -> {
    System.out.println("函数式接口");
    return a + b;
};

具体示例如下所示。

@Test
public void test4(){
    Comparator<Integer> comparator = (x, y) -> {
        System.out.println("函数式接口");
        return Integer.compare(x, y);
    };
}

5.语法格局五:当Lambda体只有一条语句时,return和大括号能够省略

BinaryOperator<Integer> bo = (a, b) -> a + b;

具体示例如下所示。

@Test
public void test5(){
    Comparator<Integer> comparator = (x, y) ->  Integer.compare(x, y);
}

6.语法格局六:Lambda表达式的参数列表的数据类型能够省略不写,因为JVM编译器可能通过上下文推断出数据类型,这就是“类型推断”

BinaryOperator<Integer> bo = (Integer a, Integer b) -> {
    return a + b;
};

等同于

BinaryOperator<Integer> bo = (a, b) -> {
    return a + b;
};

上述 Lambda 表达式中的参数类型都是由编译器推断得出的。 Lambda 表达式中无需指定类型,程序仍然能够编译,这是因为 javac 依据程序的上下文,在后盾推断出了参数的类型。 Lambda 表达式的类型依赖于上下文环境,是由编译器推断进去的。这就是所谓的“类型推断”。

函数式接口

Lambda表达式须要函数式接口的反对,所以,咱们有必要来说说什么是函数式接口。

只蕴含一个形象办法的接口,称为函数式接口。

能够通过 Lambda 表达式来创立该接口的对象。(若 Lambda表达式抛出一个受检异样,那么该异样须要在指标接口的形象办法上进行申明)。

能够在任意函数式接口上应用 @FunctionalInterface 注解,这样做能够查看它是否是一个函数式接口,同时 javadoc 也会蕴含一条申明,阐明这个接口是一个函数式接口。

咱们能够自定义函数式接口,并应用Lambda表达式来实现相应的性能。

例如,应用函数式接口和Lambda表达式实现对字符串的解决性能。

首先,咱们定义一个函数式接口MyFunc,如下所示。

@FunctionalInterface
public interface MyFunc <T> {
    public T getValue(T t);
}

接下来,咱们定义一个操作字符串的办法,其中参数为MyFunc接口实例和须要转换的字符串。

public String handlerString(MyFunc<String> myFunc, String str){
    return myFunc.getValue(str);
}

接下来,咱们对自定义的函数式接口进行测试,此时咱们传递的函数式接口的参数为Lambda表达式,并且将字符串转化为大写。

@Test
public void test6(){
    String str = handlerString((s) -> s.toUpperCase(), "binghe");
    System.out.println(str);
}

运行test6办法,得出的后果信息如下所示。

BINGHE

咱们也能够截取字符串的某一部分,如下所示。

@Test
public void test7(){
    String str = handlerString((s) -> s.substring(0,4), "binghe");
    System.out.println(str);
}

运行test7办法,得出的后果信息如下所示。

bing

能够看到,咱们能够通过handlerString(MyFunc<String> myFunc, String str)办法联合Lambda表达式对字符串进行任意操作。

留神:作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接管Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型 。

Lambda表达式典型案例

案例一

需要

调用Collections.sort()办法,通过定制排序比拟两个Employee(先比拟年龄,年龄雷同按姓名比拟),应用Lambda表达式作为参数传递。

实现

这里,咱们先创立一个Employee类,为了满足需要,咱们在Employee类中定义了姓名、年龄和工资三个字段,如下所示。

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = -9079722457749166858L;
    private String name;
    private Integer age;
    private Double salary;
}

接下来,咱们在TestLambda类中定义一个成员变量employees,employees变量是一个List汇合,存储了Employee的一个列表,如下所示。

protected List<Employee> employees = Arrays.asList(
    new Employee("张三", 18, 9999.99),
    new Employee("李四", 38, 5555.55),
    new Employee("王五", 60, 6666.66),
    new Employee("赵六", 8, 7777.77),
    new Employee("田七", 58, 3333.33)
);

后期的筹备工作实现了,接下来,咱们就能够实现具体的业务逻辑了。

@Test
public void test1(){
    Collections.sort(employees, (e1, e2) -> {
        if(e1.getAge() == e2.getAge()){
            return e1.getName().compareTo(e2.getName());
        }
        return Integer.compare(e1.getAge(), e2.getAge());
    });
    employees.stream().forEach(System.out::println);
}

上述代码比较简单,我就不赘述具体逻辑了。运行test1办法,得出的后果信息如下所示。

Employee(name=赵六, age=8, salary=7777.77)
Employee(name=张三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=田七, age=58, salary=3333.33)
Employee(name=王五, age=60, salary=6666.66)

如果想顺叙输入如何解决呢,只须要在将return Integer.compare(e1.getAge(), e2.getAge());批改成-return Integer.compare(e1.getAge(), e2.getAge());即可,如下所示。

@Test
public void test1(){
    Collections.sort(employees, (e1, e2) -> {
        if(e1.getAge() == e2.getAge()){
            return e1.getName().compareTo(e2.getName());
        }
        return -Integer.compare(e1.getAge(), e2.getAge());
    });
    employees.stream().forEach(System.out::println);
}

再次运行test1办法,得出的后果信息如下所示。

Employee(name=王五, age=60, salary=6666.66)
Employee(name=田七, age=58, salary=3333.33)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=张三, age=18, salary=9999.99)
Employee(name=赵六, age=8, salary=7777.77)

后果合乎咱们的需要。

案例二

需要

1.申明函数式接口,接口中申明形象办法public String getValue(String str);

2.申明类TestLambda,类中编写办法应用接口作为参数,将一个字符串转换为大写,并作为办法的返回值。

3.再将一个字符串的第2个和第4个索引地位进行截取子串。

实现

首先,创立一个函数式接口MyFunction,在MyFunction接口上加上注解@FunctionalInterface标识接口是一个函数式接口。如下所示。

@FunctionalInterface
public interface MyFunction {
    public String getValue(String str);
}

在TestLambda类中申明stringHandler办法,参数别离为待处理的字符串和函数式接口的实例,办法中的逻辑就是调用函数式接口的办法来解决字符串,如下所示。

public String stringHandler(String str, MyFunction myFunction){
    return myFunction.getValue(str);
}

接下来,咱们实现将一个字符串转换为大写的逻辑,如下所示。

@Test
public void test2(){
    String value = stringHandler("binghe", (s) -> s.toUpperCase());
    System.out.println(value);
}

运行test2办法,得出如下的后果信息。

BINGHE

咱们再来实现字符串截取的操作,如下所示。

@Test
public void test3(){
    String value = stringHandler("binghe", (s) -> s.substring(1, 3));
    System.out.println(value);
}

留神:需要中是依照第2个和第4个索引地位进行截取子串,字符串的下标是从0开始的,所以这里截取字符串时应用的是substring(1, 3),而不是substring(2, 4),这也是很多小伙伴容易犯的谬误。

另外,应用上述Lambda表达式模式,能够实现字符串的任意解决,并返回解决后的新字符串。

运行test3办法,后果如下所示。

in

案例三

需要

1.申明一个带两个泛型的函数式接口,泛型类型为<T, R>,其中,T作为参数的类型,R作为返回值的类型。

2.接口中申明对象的形象办法。

3.在TestLambda类中申明办法。应用接口作为参数计算两个long型参数的和。

4.再就按两个long型参数的乘积。

实现

首先,咱们依照需要定义函数式接口MyFunc,如下所示。

@FunctionalInterface
public interface MyFunc<T, R> {

    R getValue(T t1, T t2);
}

接下来,咱们在TestLambda类中创立一个解决两个long型数据的办法,如下所示。

public void operate(Long num1, Long num2, MyFunc<Long, Long> myFunc){
    System.out.println(myFunc.getValue(num1, num2));
}

咱们能够应用上面的办法来实现两个long型参数的和。

@Test
public void test4(){
    operate(100L, 200L, (x, y) -> x + y);
}

运行test4办法,后果如下所示。

300

实现两个long型数据的乘积,也很简略。

@Test
public void test5(){
    operate(100L, 200L, (x, y) -> x * y);
}

运行test5办法,后果如下所示。

20000

看到这里,我置信很多小伙伴曾经对Lambda表达式有了更深层次的了解。只有多多练习,就可能更好的把握Lambda表达式的精华。

函数式接口总览

这里,我应用表格的模式来简略阐明下Java8中提供的函数式接口。

四大外围函数式接口总览

首先,咱们来看四大外围函数式接口,如下所示。

函数式接口 参数类型 返回类型 应用场景
Consumer<T>消费型接口 T void 对类型为T的对象利用操作,接口定义的办法:void accept(T t)
Supplier<T>供应型接口 T 返回类型为T的对象,接口定义的办法:T get()
Function<T, R>函数式接口 T R 对类型为T的对象利用操作,并R类型的返回后果。接口定义的办法:R apply(T t)
Predicate<T>断言型接口 T boolean 确定类型为T的对象是否满足约束条件,并返回boolean类型的数据。接口定义的办法:boolean test(T t)

其余函数接口总览

除了四大外围函数接口外,Java8还提供了一些其余的函数式接口。

函数式接口 参数类型 返回类型 应用场景
BiFunction(T, U, R) T, U R 对类型为T,U的参数利用操作,返回R类型的后果。接口定义的办法:R apply(T t, U u)
UnaryOperator<T>(Function子接口) T T 对类型为T的对象进行一 元运算, 并返回T类型的 后果。 蕴含办法为 T apply(T t)
BinaryOperator<T> (BiFunction 子接口) T, T T 对类型为T的对象进行二 元运算, 并返回T类型的 后果。 蕴含办法为 T apply(T t1, T t2)
BiConsumer<T, U> T, U void 对类型为T, U 参数利用 操作。 蕴含办法为 void accept(T t, U u)
ToIntFunction<T> T int 计算int值的函数
ToLongFunction<T> T long 计算long值的函数
ToDoubleFunction<T> T double 计算double值的函数
IntFunction<R> int R 参数为int 类型的函数
LongFunction<R> long R 参数为 long类型的函数
DoubleFunction<R> double R 参数为double类型的函数

四大外围函数式接口

Consumer接口

1.接口阐明

Consumer接口是消费性接口,无返回值。Java8中对Consumer的定义如下所示。

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);
    
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

2.应用示例

public void handlerConsumer(Integer number, Consumer<Integer> consumer){
    consumer.accept(number);
}

@Test
public void test1(){
    this.handlerConsumer(10000, (i) -> System.out.println(i));
}

Supplier接口

1.接口阐明

Supplier接口是供应型接口,有返回值,Java8中对Supplier接口的定义如下所示。

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

2.应用示例

public List<Integer> getNumberList(int num, Supplier<Integer> supplier){
    List<Integer> list = new ArrayList<>();
    for(int i = 0; i < num; i++){
        list.add(supplier.get())
    }
    return list;
}

@Test
public void test2(){
    List<Integer> numberList = this.getNumberList(10, () -> new Random().nextInt(100));
    numberList.stream().forEach(System.out::println);
}

Function接口

1.接口阐明

Function接口是函数型接口,有返回值,Java8中对Function接口的定义如下所示。

@FunctionalInterface
public interface Function<T, R> {
    
    R apply(T t);
    
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

2.应用示例

public String handlerString(String str, Function<String, String> func){
    return func.apply(str);
}

@Test
public void test3(){
    String str = this.handlerString("binghe", (s) -> s.toUpperCase());
    System.out.println(str);
}

Predicate接口

1.接口阐明

Predicate接口是断言型接口,返回值类型为boolean,Java8中对Predicate接口的定义如下所示。

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

2.应用示例

public List<String> filterString(List<String> list, Predicate<String> predicate){
    List<String> strList = new ArrayList<>();
    for(String str : list){
        if(predicate.test(str)){
            strList.add(str);
        }
    }
    return strList;
}

@Test
public void test4(){
    List<String> list = Arrays.asList("Hello", "Lambda", "binghe", "lyz", "World");
    List<String> strList = this.filterString(list, (s) -> s.length() >= 5);
    strList.stream().forEach(System.out::println);
}

留神:只有咱们学会了Java8中四大外围函数式接口的用法,其余函数式接口咱们也就晓得如何应用了!

Java7与Java8中的HashMap

  • JDK7 HashMap构造为数组+链表(产生元素碰撞时,会将新元素增加到链表结尾)
  • JDK8 HashMap构造为数组+链表+红黑树(产生元素碰撞时,会将新元素增加到链表开端,当HashMap总容量大于等于64,并且某个链表的大小大于等于8,会将链表转化为红黑树(留神:红黑树是二叉树的一种))

JDK8 HashMap重排序

如果删除了HashMap中红黑树的某个元素导致元素重排序时,不须要计算待重排序的元素的HashCode码,只须要将以后元素放到(HashMap总长度+以后元素在HashMap中的地位)的地位即可。

筛选与切片

  • filter——接管 Lambda , 从流中排除某些元素。
  • limit——截断流,使其元素不超过给定数量。
  • skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素有余 n 个,则返回一个空流。与 limit(n) 互补
  • distinct——筛选,通过流所生成元素的 hashCode() 和 equals() 去除反复元素

两头操作

  • map——接管 Lambda , 将元素转换成其余模式或提取信息。接管一个函数作为参数,该函数会被利用到每个元素上,并将其映射成一个新的元素。
  • flatMap——接管一个函数作为参数,将流中的每个值都换成另一个流,而后把所有流连接成一个流
  • sorted()——天然排序
  • sorted(Comparator com)——定制排序

终止操作

  • allMatch——查看是否匹配所有元素
  • anyMatch——查看是否至多匹配一个元素
  • noneMatch——查看是否没有匹配的元素
  • findFirst——返回第一个元素
  • findAny——返回以后流中的任意元素
  • count——返回流中元素的总个数
  • max——返回流中最大值
  • min——返回流中最小值

归约

  • reduce(T identity, BinaryOperator) / reduce(BinaryOperator) ——能够将流中元素重复联合起来,失去一个值。
  • collect——将流转换为其余模式。接管一个 Collector接口的实现,用于给Stream中元素做汇总的办法

留神:流进行了终止操作后,不能再次应用

Optional 容器类

用于尽量避免空指针异样

  • Optional.of(T t) : 创立一个 Optional 实例
  • Optional.empty() : 创立一个空的 Optional 实例
  • Optional.ofNullable(T t):若 t 不为 null,创立 Optional 实例,否则创立空实例
  • isPresent() : 判断是否蕴含值
  • orElse(T t) : 如果调用对象蕴含值,返回该值,否则返回t
  • orElseGet(Supplier s) :如果调用对象蕴含值,返回该值,否则返回 s 获取的值
  • map(Function f): 如果有值对其解决,并返回解决后的Optional,否则返回 Optional.empty()
  • flatMap(Function mapper):与 map 相似,要求返回值必须是Optional

办法援用与结构器援用

办法援用

当要传递给Lambda体的操作,曾经有实现的办法了,能够应用办法援用!这里须要留神的是:实现形象办法的参数列表,必须与办法援用办法的参数列表保持一致!

那么什么是办法援用呢?办法援用就是操作符“::”将办法名和对象或类的名字分隔开来。

有如下三种应用状况:

  • 对象::实例办法
  • 类::静态方法
  • 类::实例办法

这里,咱们能够列举几个示例。

例如:

(x) -> System.out.println(x);

等同于:

System.out::println

例如:

BinaryOperator<Double> bo = (x, y) -> Math.pow(x, y);

等同于

BinaryOperator<Double> bo = Math::pow;

例如:

compare((x, y) -> x.equals(y), "binghe", "binghe")

等同于

compare(String::equals, "binghe", "binghe")

留神: 当须要援用办法的第一个参数是调用对象,并且第二个参数是须要援用办法的第二个参数(或无参数)时: ClassName::methodName 。

结构器援用

格局如下所示:

ClassName::new

与函数式接口相结合,主动与函数式接口中办法兼容。能够把结构器援用赋值给定义的办法,与结构器参数列表要与接口中形象办法的参数列表统一!

例如:

Function<Integer, MyClass> fun = (n) -> new MyClass(n);

等同于

Function<Integer, MyClass> fun = MyClass::new;

数组援用

格局如下所示。

type[]::new

例如:

Function<Integer, Integer[]> fun = (n) -> new Integer[n];

等同于

Function<Integer, Integer[]> fun = Integer[]::new;

Java8中的Stream

什么是Stream?

Java8中有两大最为重要的扭转。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)。

Stream 是 Java8 中解决汇合的要害抽象概念,它能够指定你心愿对汇合进行的操作,能够执行非常复杂的查找、过滤和映射数据等操作。应用Stream API 对汇合数据进行操作,就相似于应用 SQL 执行的数据库查问。也能够应用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于应用的解决数据的形式

流是数据渠道,用于操作数据源(汇合、数组等)所生成的元素序列。“汇合讲的是数据,流讲的是计算! ”

留神:
① Stream 本人不会存储元素。
② Stream 不会扭转源对象。相同,他们会返回一个持有后果的新Stream。
③ Stream 操作是提早执行的。这意味着他们会等到须要后果的时候才执行。

Stream操作的三个步骤

  • 创立 Stream

一个数据源(如: 汇合、数组), 获取一个流。

  • 两头操作

一个两头操作链,对数据源的数据进行解决。

  • 终止操作(终端操作)

一个终止操作,执行两头操作链,并产生后果 。

如何创立Stream?

Java8 中的 Collection 接口被扩大,提供了两个获取流的办法:

1.获取Stream

  • default Stream<E> stream() : 返回一个程序流
  • default Stream<E> parallelStream() : 返回一个并行流

2.由数组创立Stream

Java8 中的 Arrays 的静态方法 stream() 能够获取数组流:

  • static <T> Stream<T> stream(T[] array): 返回一个流

重载模式,可能解决对应根本类型的数组:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

3.由值创立流

能够应用静态方法 Stream.of(), 通过显示值创立一个流。它能够接管任意数量的参数。

  • public static<T> Stream<T> of(T… values) : 返回一个流

4.由函数创立流

由函数创立流能够创立有限流。

能够应用静态方法 Stream.iterate() 和Stream.generate(), 创立有限流 。

  • 迭代

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

  • 生成

public static<T> Stream<T> generate(Supplier<T> s)

Stream的两头操作

多个两头操作能够连接起来造成一个流水线,除非流水线上触发终止操作,否则两头操作不会执行任何的解决!而在终止操作时一次性全副解决,称为“惰性求值”

1.筛选与切片

2.映射

3.排序

Stream 的终止操作

终端操作会从流的流水线生成后果。其后果能够是任何不是流的值,例如: List、 Integer,甚至是 void 。

1.查找与匹配

2.规约

3.收集

Collector 接口中办法的实现决定了如何对流执行收集操作(如收集到 List、 Set、 Map)。然而 Collectors 实用类提供了很多静态方法,能够不便地创立常见收集器实例, 具体方法与实例如下表

并行流与串行流

并行流就是把一个内容分成多个数据块,并用不同的线程别离解决每个数据块的流。

Java 8 中将并行进行了优化,咱们能够很容易的对数据进行并行操作。 Stream API 能够申明性地通过 parallel() 与
sequential() 在并行流与程序流之间进行切换

Fork/Join 框架

1.简略概述

Fork/Join 框架: 就是在必要的状况下,将一个大工作,进行拆分(fork)成若干个小工作(拆到不可再拆时),再将一个个的小工作运算的后果进行 join 汇总.

2.Fork/Join 框架与传统线程池的区别

采纳 “工作窃取”模式(work-stealing):
当执行新的工作时它能够将其拆分分成更小的工作执行,并将小工作加到线程队列中,而后再从一个随机线程的队列中偷一个并把它放在本人的队列中。

绝对于个别的线程池实现,fork/join框架的劣势体现在对其中蕴含的工作的解决形式上.在个别的线程池中,如果一个线程正在执行的工作因为某些起因无奈持续运行,那么该线程会处于期待状态.而在fork/join框架实现中,如果某个子问题因为期待另外一个子问题的实现而无奈持续运行.那么解决该子问题的线程会被动寻找其余尚未运行的子问题来执行.这种形式缩小了线程的等待时间,进步了性能。

Stream概述

Java8中有两大最为重要的扭转。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)。

Stream 是 Java8 中解决汇合的要害抽象概念,它能够指定你心愿对汇合进行的操作,能够执行非常复杂的查找、过滤和映射数据等操作。应用Stream API 对汇合数据进行操作,就相似于应用 SQL 执行的数据库查问。也能够应用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于应用的解决数据的形式。

何为Stream?

流(Stream) 到底是什么呢?

能够这么了解流:流就是数据渠道,用于操作数据源(汇合、数组等)所生成的元素序列。

“汇合讲的是数据,流讲的是计算! ”

留神:

①Stream 本人不会存储元素。

②Stream 不会扭转源对象。相同,他们会返回一个持有后果的新Stream。

③Stream 操作是提早执行的。这意味着他们会等到须要后果的时候才执行。

Stream操作步骤

1.创立 Stream

一个数据源(如: 汇合、数组), 获取一个流。

2.两头操作

一个两头操作链,对数据源的数据进行解决。

3.终止操作(终端操作)

一个终止操作,执行两头操作链,并产生后果 。

如何创立Stream流?

这里,创立测试类TestStreamAPI1,所有的操作都是在TestStreamAPI1类中实现的。

(1)通过Collection系列汇合提供的stream()办法或者parallelStream()办法来创立Stream。

在Java8中,Collection 接口被扩大,提供了两个获取流的默认办法,如下所示。

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

其中,stream()办法返回一个程序流,parallelStream()办法返回一个并行流。

咱们能够应用如下代码形式来创立程序流和并行流。

List<String> list = new ArrayList<>();
list.stream();
list.parallelStream();

(2)通过Arrays中的静态方法stream()获取数组流。

Java8 中的 Arrays类的静态方法 stream() 能够获取数组流 ,如下所示。

public static <T> Stream<T> stream(T[] array) {
    return stream(array, 0, array.length);
}

上述代码的的作用为:传入一个泛型数组,返回这个泛型的Stream流。

除此之外,在Arrays类中还提供了stream()办法的如下重载模式。

public static <T> Stream<T> stream(T[] array) {
    return stream(array, 0, array.length);
}

public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
    return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
}

public static IntStream stream(int[] array) {
    return stream(array, 0, array.length);
}

public static IntStream stream(int[] array, int startInclusive, int endExclusive) {
    return StreamSupport.intStream(spliterator(array, startInclusive, endExclusive), false);
}

public static LongStream stream(long[] array) {
    return stream(array, 0, array.length);
}

public static LongStream stream(long[] array, int startInclusive, int endExclusive) {
    return StreamSupport.longStream(spliterator(array, startInclusive, endExclusive), false);
}

public static DoubleStream stream(double[] array) {
    return stream(array, 0, array.length);
}

public static DoubleStream stream(double[] array, int startInclusive, int endExclusive) {
    return StreamSupport.doubleStream(spliterator(array, startInclusive, endExclusive), false);
}

基本上可能满足根本将根本类型的数组转化为Stream流的操作。

咱们能够通过上面的代码示例来应用Arrays类的stream()办法来创立Stream流。

Integer[] nums = new Integer[]{1,2,3,4,5,6,7,8,9};
Stream<Integer> numStream = Arrays.stream(nums);

(3)通过Stream类的静态方法of()获取数组流。

能够应用静态方法 Stream.of(), 通过显示值创立一个流。它能够接管任意数量的参数。

咱们先来看看Stream的of()办法,如下所示。

public static<T> Stream<T> of(T t) {
    return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}
@SafeVarargs
@SuppressWarnings("varargs") 
public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}

能够看到,在Stream类中,提供了两个of()办法,一个只须要传入一个泛型参数,一个须要传入一个可变泛型参数。

咱们能够应用上面的代码示例来应用of办法创立一个Stream流。

Stream<String> strStream = Stream.of("a", "b", "c");

(4)创立有限流

能够应用静态方法 Stream.iterate() 和Stream.generate(), 创立有限流。

先来看看Stream类中iterate()办法和generate()办法的源码,如下所示。

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
    Objects.requireNonNull(f);
    final Iterator<T> iterator = new Iterator<T>() {
        @SuppressWarnings("unchecked")
        T t = (T) Streams.NONE;

        @Override
        public boolean hasNext() {
            return true;
        }

        @Override
        public T next() {
            return t = (t == Streams.NONE) ? seed : f.apply(t);
        }
    };
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
        iterator,
        Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}

public static<T> Stream<T> generate(Supplier<T> s) {
    Objects.requireNonNull(s);
    return StreamSupport.stream(
        new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}

通过源码能够看出,iterate()办法次要是应用“迭代”的形式生成有限流,而generate()办法次要是应用“生成”的形式生成有限流。咱们能够应用上面的代码示例来应用这两个办法生成Stream流。

  • 迭代
Stream<Integer> intStream = Stream.iterate(0, (x) -> x + 2);
intStream.forEach(System.out::println);

运行上述代码,会在终端始终输入偶数,这种操作会始终继续上来。如果咱们只须要输入10个偶数,该如何操作呢?其实也很简略,应用Stream对象的limit办法进行限度就能够了,如下所示。

Stream<Integer> intStream = Stream.iterate(0, (x) -> x + 2);
intStream.limit(10).forEach(System.out::println);
  • 生成
Stream.generate(() -> Math.random()).forEach(System.out::println);

上述代码同样会始终输入随机数,如果咱们只须要输入5个随机数,则只须要应用limit()办法进行限度即可。

Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);

(5)创立空流

在Stream类中提供了一个empty()办法,如下所示。

public static<T> Stream<T> empty() {
    return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);
}

咱们能够应用Stream类的empty()办法来创立一个空Stream流,如下所示。

Stream<String> empty = Stream.empty();

Stream的两头操作

多个两头操作能够连接起来造成一个流水线,除非流水线上触发终止操作,否则两头操作不会执行任何的解决!而在终止操作时一次性全副解决,称为“惰性求值” 。 Stream的两头操作是不会有任何后果数据输入的。

Stream的两头操作在整体上能够分为:筛选与切片、映射、排序。接下来,咱们就别离对这些两头操作进行简要的阐明。

筛选与切片

这里,我将与筛选和切片无关的操作整顿成如下表格。

办法 形容
filter(Predicate p) 接管Lambda表达式,从流中排除某些元素
distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去 除反复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素 有余 n 个,则返回一个空流。与 limit(n) 互补

接下来,咱们列举几个简略的示例,以便加深了解。

为了更好的测试程序,我先结构了一个对象数组,如下所示。

protected List<Employee> list = Arrays.asList(
    new Employee("张三", 18, 9999.99),
    new Employee("李四", 38, 5555.55),
    new Employee("王五", 60, 6666.66),
    new Employee("赵六", 8, 7777.77),
    new Employee("田七", 58, 3333.33)
);

其中,Employee类的定义如下所示。

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = -9079722457749166858L;
    private String name;
    private Integer age;
    private Double salary;
}

Employee类的定义比较简单,这里,我就不赘述了。之后的示例中,咱们都是应用的Employee对象的汇合进行操作。好了,咱们开始具体的操作案例。

1.filter()办法

filter()办法次要是用于接管Lambda表达式,从流中排除某些元素,其在Stream接口中的源码如下所示。

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

能够看到,在filter()办法中,须要传递Predicate接口的对象,Predicate接口又是个什么鬼呢?点进去看下源码。

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

能够看到,Predicate是一个函数式接口,其中接口中定义的次要办法为test()办法,test()办法会接管一个泛型对象t,返回一个boolean类型的数据。

看到这里,置信大家明确了:filter()办法是依据Predicate接口的test()办法的返回后果来过滤数据的,如果test()办法的返回后果为true,合乎规定;如果test()办法的返回后果为false,则不合乎规定。

这里,咱们能够应用上面的示例来简略的阐明filter()办法的应用形式。

//外部迭代:在此过程中没有进行过迭代,由Stream api进行迭代
//两头操作:不会执行任何操作
Stream<Person> stream = list.stream().filter((e) -> {
    System.out.println("Stream API 两头操作");
    return e.getAge() > 30;
});

咱们,在执行终止语句之后,一边迭代,一边打印,而咱们并没有去迭代下面汇合,其实这是外部迭代,由Stream API 实现。

上面咱们来看看内部迭代,也就是咱们人为得迭代。

//内部迭代
Iterator<Person> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

2.limit()办法

次要作用为:截断流,使其元素不超过给定数量。

先来看limit办法的定义,如下所示。

Stream<T> limit(long maxSize);

limit()办法在Stream接口中的定义比较简单,只须要传入一个long类型的数字即可。

咱们能够依照如下所示的代码来应用limit()办法。

//过滤之后取2个值
list.stream().filter((e) -> e.getAge() >30 ).limit(2).forEach(System.out :: println);

在这里,咱们能够配合其余得两头操作,并截断流,使咱们能够获得相应个数得元素。而且在下面计算中,只有发现有2条符合条件得元素,则不会持续往下迭代数据,能够提高效率。

3.skip()办法

跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素 有余 n 个,则返回一个空流。与 limit(n) 互补。

源码定义如下所示。

Stream<T> skip(long n);

源码定义比较简单,同样只须要传入一个long类型的数字即可。其含意是跳过n个元素。

简略示例如下所示。

//跳过前2个值
list.stream().skip(2).forEach(System.out :: println);

4.distinct()办法

筛选,通过流所生成元素的 hashCode() 和 equals() 去 除反复元素。

源码定义如下所示。

Stream<T> distinct();

旨在对流中的元素进行去重。

咱们能够如上面的形式来应用disinct()办法。

list.stream().distinct().forEach(System.out :: println);

这里有一个须要留神的中央:distinct 须要实体中重写hashCode()和 equals()办法才能够应用。

映射

对于映射相干的办法如下表所示。

办法 形容
map(Function f) 接管一个函数作为参数,该函数会被利用到每个元 素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f) 接管一个函数作为参数,该函数会被利用到每个元 素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f) 接管一个函数作为参数,该函数会被利用到每个元 素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f) 接管一个函数作为参数,该函数会被利用到每个元 素上,产生一个新的 LongStream
flatMap(Function f) 接管一个函数作为参数,将流中的每个值都换成另 一个流,而后把所有流连接成一个流

1.map()办法

接管一个函数作为参数,该函数会被利用到每个元 素上,并将其映射成一个新的元素。

先来看Java8中Stream接口对于map()办法的申明,如下所示。

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

咱们能够依照如下形式应用map()办法。

//将流中每一个元素都映射到map的函数中,每个元素执行这个函数,再返回
List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
list.stream().map((e) -> e.toUpperCase()).forEach(System.out::printf);

//获取Person中的每一个人得名字name,再返回一个汇合
List<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toList());

2.flatMap()

接管一个函数作为参数,将流中的每个值都换成另 一个流,而后把所有流连接成一个流。

先来看Java8中Stream接口对于flatMap()办法的申明,如下所示。

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

咱们能够应用如下形式应用flatMap()办法,为了便于大家了解,这里,我就贴出了测试flatMap()办法的所有代码。

/**
     * flatMap —— 接管一个函数作为参数,将流中的每个值都换成一个流,而后把所有流连接成一个流
     */
    @Test
    public void testFlatMap () {
        StreamAPI_Test s = new StreamAPI_Test();
        List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
        list.stream().flatMap((e) -> s.filterCharacter(e)).forEach(System.out::println);

        //如果应用map则须要这样写
        list.stream().map((e) -> s.filterCharacter(e)).forEach((e) -> {
            e.forEach(System.out::println);
        });
    }

    /**
     * 将一个字符串转换为流
     */
    public Stream<Character> filterCharacter(String str){
        List<Character> list = new ArrayList<>();
        for (Character ch : str.toCharArray()) {
            list.add(ch);
        }
        return list.stream();
    }

其实map办法就相当于Collaction的add办法,如果add的是个汇合得话就会变成二维数组,而flatMap 的话就相当于Collaction的addAll办法,参数如果是汇合得话,只是将2个汇合合并,而不是变成二维数组。

排序

对于排序相干的办法如下表所示。

办法 形容
sorted() 产生一个新流,其中按天然程序排序
sorted(Comparator comp) 产生一个新流,其中按比拟器程序排序

从上述表格能够看出:sorted有两种办法,一种是不传任何参数,叫天然排序,还有一种须要传Comparator 接口参数,叫做定制排序。

先来看Java8中Stream接口对于sorted()办法的申明,如下所示。

Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

sorted()办法的定义比较简单,我就不再赘述了。

咱们也能够依照如下形式来应用Stream的sorted()办法。

// 天然排序
List<Employee> persons = list.stream().sorted().collect(Collectors.toList());

//定制排序
List<Employee> persons1 = list.stream().sorted((e1, e2) -> {
    if (e1.getAge() == e2.getAge()) {
        return 0;
    } else if (e1.getAge() > e2.getAge()) {
        return 1;
    } else {
        return -1;
    }
}).collect(Collectors.toList());

Stream的终止操作

终端操作会从流的流水线生成后果。其后果能够是任何不是流的值,例如: List、 Integer、Double、String等等,甚至是 void 。

在Java8中,Stream的终止操作能够分为:查找与匹配、规约和收集。接下来,咱们就别离简略阐明下这些终止操作。

查找与匹配

Stream API中无关查找与匹配的办法如下表所示。

办法 形容
allMatch(Predicate p) 查看是否匹配所有元素
anyMatch(Predicate p) 查看是否至多匹配一个元素
noneMatch(Predicate p) 查看是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回以后流中的任意元素
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 外部迭代(应用 Collection 接口须要用户去做迭代,称为内部迭代。相同, Stream API 应用外部迭代)

同样的,咱们对每个重要的办法进行简略的示例阐明,这里,咱们首先建设一个Employee类,Employee类的定义如下所示。

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = -9079722457749166858L;
    private String name;
    private Integer age;
    private Double salary;
    private Stauts stauts;
    public enum Stauts{
        WORKING,
        SLEEPING,
        VOCATION
    }
}

接下来,咱们在测试类中定义一个用于测试的汇合employees,如下所示。

protected List<Employee> employees = Arrays.asList(
    new Employee("张三", 18, 9999.99, Employee.Stauts.SLEEPING),
    new Employee("李四", 38, 5555.55, Employee.Stauts.WORKING),
    new Employee("王五", 60, 6666.66, Employee.Stauts.WORKING),
    new Employee("赵六", 8, 7777.77, Employee.Stauts.SLEEPING),
    new Employee("田七", 58, 3333.33, Employee.Stauts.VOCATION)
);

好了,筹备工作就绪了。接下来,咱们就开始测试Stream的每个终止办法。

1.allMatch()

allMatch()办法示意查看是否匹配所有元素。其在Stream接口中的定义如下所示。

boolean allMatch(Predicate<? super T> predicate);

咱们能够通过相似如下示例来应用allMatch()办法。

boolean match = employees.stream().allMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
System.out.println(match);

留神:应用allMatch()办法时,只有所有的元素都匹配条件时,allMatch()办法才会返回true。

2.anyMatch()办法

anyMatch办法示意查看是否至多匹配一个元素。其在Stream接口中的定义如下所示。

boolean anyMatch(Predicate<? super T> predicate);

咱们能够通过相似如下示例来应用anyMatch()办法。

boolean match = employees.stream().anyMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
System.out.println(match);

留神:应用anyMatch()办法时,只有有任意一个元素符合条件,anyMatch()办法就会返回true。

3.noneMatch()办法

noneMatch()办法示意查看是否没有匹配所有元素。其在Stream接口中的定义如下所示。

boolean noneMatch(Predicate<? super T> predicate);

咱们能够通过相似如下示例来应用noneMatch()办法。

boolean match = employees.stream().noneMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
System.out.println(match);

留神:应用noneMatch()办法时,只有所有的元素都不符合条件时,noneMatch()办法才会返回true。

4.findFirst()办法

findFirst()办法示意返回第一个元素。其在Stream接口中的定义如下所示。

Optional<T> findFirst();

咱们能够通过相似如下示例来应用findFirst()办法。

Optional<Employee> op = employees.stream().sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())).findFirst();
System.out.println(op.get());

5.findAny()办法

findAny()办法示意返回以后流中的任意元素。其在Stream接口中的定义如下所示。

Optional<T> findAny();

咱们能够通过相似如下示例来应用findAny()办法。

Optional<Employee> op = employees.stream().filter((e) -> Employee.Stauts.WORKING.equals(e.getStauts())).findFirst();
System.out.println(op.get());

6.count()办法

count()办法示意返回流中元素总数。其在Stream接口中的定义如下所示。

long count();

咱们能够通过相似如下示例来应用count()办法。

long count = employees.stream().count();
System.out.println(count);

7.max()办法

max()办法示意返回流中最大值。其在Stream接口中的定义如下所示。

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

咱们能够通过相似如下示例来应用max()办法。

Optional<Employee> op = employees.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(op.get());

8.min()办法

min()办法示意返回流中最小值。其在Stream接口中的定义如下所示。

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

咱们能够通过相似如下示例来应用min()办法。

Optional<Double> op = employees.stream().map(Employee::getSalary).min(Double::compare);
System.out.println(op.get());

9.forEach()办法

forEach()办法示意外部迭代(应用 Collection 接口须要用户去做迭代,称为内部迭代。相同, Stream API 应用外部迭代)。其在Stream接口外部的定义如下所示。

void forEach(Consumer<? super T> action);

咱们能够通过相似如下示例来应用forEach()办法。

employees.stream().forEach(System.out::println);

规约

Stream API中无关规约的办法如下表所示。

办法 形容
reduce(T iden, BinaryOperator b) 能够将流中元素重复联合起来,失去一个值。 返回 T
reduce(BinaryOperator b) 能够将流中元素重复联合起来,失去一个值。 返回 Optional<T>

reduce()办法在Stream接口中的定义如下所示。

T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);

咱们能够通过相似如下示例来应用reduce办法。

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(sum);
System.out.println("----------------------------------------");
Optional<Double> op = employees.stream().map(Employee::getSalary).reduce(Double::sum);
System.out.println(op.get());

咱们也能够搜寻employees列表中“张”呈现的次数。

 Optional<Integer> sum = employees.stream()
   .map(Employee::getName)
   .flatMap(TestStreamAPI1::filterCharacter)
   .map((ch) -> {
    if(ch.equals('六'))
     return 1;
    else
     return 0;
   }).reduce(Integer::sum);
  System.out.println(sum.get());

留神:上述例子应用了硬编码的形式来累加某个具体值,大家在理论工作中再优化代码。

收集

办法 形容
collect(Collector c) 将流转换为其余模式。接管一个 Collector接口的实现,用于给Stream中元素做汇总的办法

collect()办法在Stream接口中的定义如下所示。

<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

<R, A> R collect(Collector<? super T, A, R> collector);

咱们能够通过相似如下示例来应用collect办法。

Optional<Double> max = employees.stream()
   .map(Employee::getSalary)
   .collect(Collectors.maxBy(Double::compare));
  System.out.println(max.get());
  Optional<Employee> op = employees.stream()
   .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
  System.out.println(op.get());
  Double sum = employees.stream().collect(Collectors.summingDouble(Employee::getSalary));
  System.out.println(sum);
  Double avg = employees.stream().collect(Collectors.averagingDouble(Employee::getSalary));
  System.out.println(avg);
  Long count = employees.stream().collect(Collectors.counting());
  System.out.println(count);
  System.out.println("--------------------------------------------");
  DoubleSummaryStatistics dss = employees.stream()
   .collect(Collectors.summarizingDouble(Employee::getSalary));
  System.out.println(dss.getMax());

如何收集Stream流?

Collector接口中办法的实现决定了如何对流执行收集操作(如收集到 List、 Set、 Map)。 Collectors实用类提供了很多静态方法,能够不便地创立常见收集器实例, 具体方法与实例如下表:

办法 返回类型 作用
toList List<T> 把流中元素收集到List
toSet Set<T> 把流中元素收集到Set
toCollection Collection<T> 把流中元素收集到创立的汇合
counting Long 计算流中元素的个数
summingInt Integer 对流中元素的整数属性求和
averagingInt Double 计算流中元素Integer属性的均匀 值
summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值。 如:平均值
joining String 连贯流中每个字符串
maxBy Optional<T> 依据比拟器抉择最大值
minBy Optional<T> 依据比拟器抉择最小值
reducing 归约产生的类型 从一个作为累加器的初始值 开始,利用BinaryOperator与 流中元素一一联合,从而归 约成单个值
collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结 果转换函数
groupingBy Map<K, List<T>> 依据某属性值对流分组,属 性为K,后果为V
partitioningBy Map<Boolean, List<T>> 依据true或false进行分区

每个办法对应的应用示例如下表所示。

办法 应用示例
toList List<Employee> employees= list.stream().collect(Collectors.toList());
toSet Set<Employee> employees= list.stream().collect(Collectors.toSet());
toCollection Collection<Employee> employees=list.stream().collect(Collectors.toCollection(ArrayList::new));
counting long count = list.stream().collect(Collectors.counting());
summingInt int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt double avg= list.stream().collect(Collectors.averagingInt(Employee::getSalary))
summarizingInt IntSummaryStatistics iss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
Collectors String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThen int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy Map<Emp.Status, List<Emp>> map= list.stream() .collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy Map<Boolean,List<Emp>>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));
public void test4(){
    Optional<Double> max = emps.stream()
        .map(Employee::getSalary)
        .collect(Collectors.maxBy(Double::compare));
    System.out.println(max.get());

    Optional<Employee> op = emps.stream()
        .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));

    System.out.println(op.get());

    Double sum = emps.stream()
        .collect(Collectors.summingDouble(Employee::getSalary));

    System.out.println(sum);

    Double avg = emps.stream()
        .collect(Collecors.averagingDouble(Employee::getSalary));
    System.out.println(avg);
    Long count = emps.stream()
        .collect(Collectors.counting());

    DoubleSummaryStatistics dss = emps.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));
    System.out.println(dss.getMax());
 

什么是并行流?

简略来说,并行流就是把一个内容分成多个数据块,并用不同的线程别离解决每个数据块的流。

Java 8 中将并行进行了优化,咱们能够很容易的对数据进行并行操作。 Stream API 能够申明性地通过 parallel() 与sequential() 在并行流与程序流之间进行切换 。

Fork/Join 框架

Fork/Join 框架: 就是在必要的状况下,将一个大工作,进行拆分(fork)成若干个小工作(拆到不可再拆时),再将一个个的小工作运算的后果进行 join 汇总 。

Fork/Join 框架与传统线程池有啥区别?

采纳 “工作窃取”模式(work-stealing):

当执行新的工作时它能够将其拆分成更小的工作执行,并将小工作加到线程队列中,而后再从一个随机线程的队列中偷一个并把它放在本人的队列中。

绝对于个别的线程池实现,fork/join框架的劣势体现在对其中蕴含的工作的解决形式上。在个别的线程池中,如果一个线程正在执行的工作因为某些起因无奈持续运行,那么该线程会处于期待状态。而在fork/join框架的实现中,如果某个子工作因为期待另外一个子工作的实现而无奈持续运行。那么解决该子问题的线程会被动寻找其余尚未运行的子工作来执行。这种形式缩小了线程的等待时间,进步了程序的性能。

Fork/Join框架实例

理解了ForJoin框架的原理之后,咱们就来手动写一个应用Fork/Join框架实现累加和的示例程序,以帮忙读者更好的了解Fork/Join框架。好了,不废话了,上代码,大家通过上面的代码好好领会下Fork/Join框架的弱小。

package io.binghe.concurrency.example.aqs;
 
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
@Slf4j
public class ForkJoinTaskExample extends RecursiveTask<Integer> {
    public static final int threshold = 2;
    private int start;
    private int end;
    public ForkJoinTaskExample(int start, int end) {
        this.start = start;
        this.end = end;
    }
    @Override
    protected Integer compute() {
        int sum = 0;
        //如果工作足够小就计算工作
        boolean canCompute = (end - start) <= threshold;
        if (canCompute) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            // 如果工作大于阈值,就决裂成两个子工作计算
            int middle = (start + end) / 2;
            ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
            ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
 
            // 执行子工作
            leftTask.fork();
            rightTask.fork();
 
            // 期待工作执行完结合并其后果
            int leftResult = leftTask.join();
            int rightResult = rightTask.join();
 
            // 合并子工作
            sum = leftResult + rightResult;
        }
        return sum;
    }
    public static void main(String[] args) {
        ForkJoinPool forkjoinPool = new ForkJoinPool();
 
        //生成一个计算工作,计算1+2+3+4
        ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
 
        //执行一个工作
        Future<Integer> result = forkjoinPool.submit(task);
 
        try {
            log.info("result:{}", result.get());
        } catch (Exception e) {
            log.error("exception", e);
        }
    }
}

Java8中的并行流实例

Java8对并行流进行了大量的优化,并且在开发上也极大的简化了程序员的工作量,咱们只须要应用相似如下的代码就能够应用Java8中的并行流来解决咱们的数据。

LongStream.rangeClosed(0, 10000000L).parallel().reduce(0, Long::sum);

在Java8中如何优雅的切换并行流和串行流呢?

Stream API 能够申明性地通过 parallel() 与sequential() 在并行流与串行流之间进行切换 。

Optional类

什么是Optional类?

Optional<T> 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 示意一个值不存在,当初 Optional 能够更好的表白这个概念。并且能够防止空指针异样。

Optional类罕用办法:

  • Optional.of(T t) : 创立一个 Optional 实例。
  • Optional.empty() : 创立一个空的 Optional 实例。
  • Optional.ofNullable(T t):若 t 不为 null,创立 Optional 实例,否则创立空实例。
  • isPresent() : 判断是否蕴含值。
  • orElse(T t) : 如果调用对象蕴含值,返回该值,否则返回t。
  • orElseGet(Supplier s) :如果调用对象蕴含值,返回该值,否则返回 s 获取的值。
  • map(Function f): 如果有值对其解决,并返回解决后的Optional,否则返回 Optional.empty()。
  • flatMap(Function mapper):与 map 相似,要求返回值必须是Optional。

Optional类示例

1.创立Optional类

(1)应用empty()办法创立一个空的Optional对象:

Optional<String> empty = Optional.empty();

(2)应用of()办法创立Optional对象:

String name = "binghe";
Optional<String> opt = Optional.of(name);
assertEquals("Optional[binghe]", opt.toString());

传递给of()的值不能够为空,否则会抛出空指针异样。例如,上面的程序会抛出空指针异样。

String name = null;
Optional<String> opt = Optional.of(name);

如果咱们须要传递一些空值,那咱们能够应用上面的示例所示。

String name = null;
Optional<String> opt = Optional.ofNullable(name);

应用ofNullable()办法,则当传递进去一个空值时,不会抛出异样,而只是返回一个空的Optional对象,如同咱们用Optional.empty()办法一样。

2.isPresent

咱们能够应用这个isPresent()办法查看一个Optional对象中是否有值,只有值非空才返回true。

Optional<String> opt = Optional.of("binghe");
assertTrue(opt.isPresent());

opt = Optional.ofNullable(null);
assertFalse(opt.isPresent());

在Java8之前,咱们个别应用如下形式来查看空值。

if(name != null){
    System.out.println(name.length);
}

在Java8中,咱们就能够应用如下形式来查看空值了。

Optional<String> opt = Optional.of("binghe");
opt.ifPresent(name -> System.out.println(name.length()));

3.orElse和orElseGet

(1)orElse

orElse()办法用来返回Optional对象中的默认值,它被传入一个“默认参数‘。如果对象中存在一个值,则返回它,否则返回传入的“默认参数”。

String nullName = null;
String name = Optional.ofNullable(nullName).orElse("binghe");
assertEquals("binghe", name);

(2)orElseGet

与orElse()办法相似,然而这个函数不接管一个“默认参数”,而是一个函数接口。

String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(() -> "binghe");
assertEquals("binghe", name);

(3)二者有什么区别?

要想了解二者的区别,首先让咱们创立一个无参且返回定值的办法。

public String getDefaultName() {
    System.out.println("Getting Default Name");
    return "binghe";
}

接下来,进行两个测试看看两个办法到底有什么区别。

String text;
System.out.println("Using orElseGet:");
String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultName);
assertEquals("binghe", defaultText);

System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(getDefaultName());
assertEquals("binghe", defaultText);

在这里示例中,咱们的Optional对象中蕴含的都是一个空值,让咱们看看程序执行后果:

Using orElseGet:
Getting default name...
Using orElse:
Getting default name...

两个Optional对象中都不存在value,因而执行后果雷同。

那么,当Optional对象中存在数据会产生什么呢?咱们一起来验证下。

String name = "binghe001";

System.out.println("Using orElseGet:");
String defaultName = Optional.ofNullable(name).orElseGet(this::getDefaultName);
assertEquals("binghe001", defaultName);

System.out.println("Using orElse:");
defaultName = Optional.ofNullable(name).orElse(getDefaultName());
assertEquals("binghe001", defaultName);

运行后果如下所示。

Using orElseGet:
Using orElse:
Getting default name...

能够看到,当应用orElseGet()办法时,getDefaultName()办法并不执行,因为Optional中含有值,而应用orElse时则照常执行。所以能够看到,当值存在时,orElse相比于orElseGet,多创立了一个对象。如果创建对象时,存在网络交互,那系统资源的开销就比拟大了,这是须要咱们留神的一个中央。

4.orElseThrow

orElseThrow()办法当遇到一个不存在的值的时候,并不返回一个默认值,而是抛出异样。

String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow( IllegalArgumentException::new);

5.get

get()办法示意是Optional对象中获取值。

Optional<String> opt = Optional.of("binghe");
String name = opt.get();
assertEquals("binghe", name);

应用get()办法也能够返回被包裹着的值。然而值必须存在。当值不存在时,会抛出一个NoSuchElementException异样。

Optional<String> opt = Optional.ofNullable(null);
String name = opt.get();

6.filter

接管一个函数式接口,当合乎接口时,则返回一个Optional对象,否则返回一个空的Optional对象。

String name = "binghe";
Optional<String> nameOptional = Optional.of(name);
boolean isBinghe = nameOptional.filter(n -> "binghe".equals(name)).isPresent();
assertTrue(isBinghe);
boolean isBinghe001 = nameOptional.filter(n -> "binghe001".equals(name)).isPresent();
assertFalse(isBinghe001);

应用filter()办法会过滤掉咱们不须要的元素。

接下来,咱们再来看一例示例,例如目前有一个Person类,如下所示。

public class Person{
    private int age;
    public Person(int age){
        this.age = age;
    }
    //省略get set办法
}

例如,咱们须要过滤出年龄在25岁到35岁之前的人群,那在Java8之前咱们须要创立一个如下的办法来检测每个人的年龄范畴是否在25岁到35岁之前。

public boolean filterPerson(Peron person){
    boolean isInRange = false;
    if(person != null && person.getAge() >= 25 && person.getAge() <= 35){
        isInRange =  true;
    }
    return isInRange;
}

看上去就挺麻烦的,咱们能够应用如下的形式进行测试。

assertTrue(filterPerson(new Peron(18)));
assertFalse(filterPerson(new Peron(29)));
assertFalse(filterPerson(new Peron(16)));
assertFalse(filterPerson(new Peron(34)));
assertFalse(filterPerson(null));

如果应用Optional,成果如何呢?

public boolean filterPersonByOptional(Peron person){
     return Optional.ofNullable(person)
       .map(Peron::getAge)
       .filter(p -> p >= 25)
       .filter(p -> p <= 35)
       .isPresent();
}

应用Optional看上去就清新多了,这里,map()仅仅是将一个值转换为另一个值,并且这个操作并不会扭转原来的值。

7.map

如果有值对其解决,并返回解决后的Optional,否则返回 Optional.empty()。

List<String> names = Arrays.asList("binghe001", "binghe002", "", "binghe003", "", "binghe004");
Optional<List<String>> listOptional = Optional.of(names);

int size = listOptional
    .map(List::size)
    .orElse(0);
assertEquals(6, size);

在这个例子中,咱们应用一个List汇合封装了一些字符串,而后再把这个List应用Optional封装起来,对其map(),获取List汇合的长度。map()返回的后果也被封装在一个Optional对象中,这里当值不存在的时候,咱们会默认返回0。如下咱们获取一个字符串的长度。

String name = "binghe";
Optional<String> nameOptional = Optional.of(name);

int len = nameOptional
    .map(String::length())
    .orElse(0);
assertEquals(6, len);

咱们也能够将map()办法与filter()办法联合应用,如下所示。

String password = " password ";
Optional<String> passOpt = Optional.of(password);
boolean correctPassword = passOpt.filter(
    pass -> pass.equals("password")).isPresent();
assertFalse(correctPassword);

correctPassword = passOpt
    .map(String::trim)
    .filter(pass -> pass.equals("password"))
    .isPresent();
assertTrue(correctPassword);

上述代码的含意就是对明码进行验证,查看明码是否为指定的值。

8.flatMap

与 map 相似,要求返回值必须是Optional。

假如咱们当初有一个Person类。

public class Person {
    private String name;
    private int age;
    private String password;
 
    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }
 
    public Optional<Integer> getAge() {
        return Optional.ofNullable(age);
    }
 
    public Optional<String> getPassword() {
        return Optional.ofNullable(password);
    }
    // 疏忽get set办法
}

接下来,咱们能够将Person封装到Optional中,并进行测试,如下所示。

Person person = new Person("binghe", 18);
Optional<Person> personOptional = Optional.of(person);

Optional<Optional<String>> nameOptionalWrapper = personOptional.map(Person::getName);
Optional<String> nameOptional = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
String name1 = nameOptional.orElse("");
assertEquals("binghe", name1);

String name = personOptional
    .flatMap(Person::getName)
    .orElse("");
assertEquals("binghe", name);

留神:办法getName返回的是一个Optional对象,如果应用map,咱们还须要再调用一次get()办法,而应用flatMap()就不须要了。

默认办法

接口中的默认办法

Java 8中容许接口中蕴含具备具体实现的办法,该办法称为“默认办法”,默认办法应用 default 关键字润饰 。

例如,咱们能够定义一个接口MyFunction,其中,蕴含有一个默认办法getName,如下所示。

public interface MyFunction<T>{
    T get(Long id);
    default String getName(){
        return "binghe";
    }
}

默认办法的准则

在Java8中,默认办法具备“类优先”的准则。

若一个接口中定义了一个默认办法,而另外一个父类或接口中又定义了一个同名的办法时,遵循如下的准则。

1.抉择父类中的办法。如果一个父类提供了具体的实现,那么接口中具备雷同名称和参数的默认办法会被疏忽。

例如,当初有一个接口为MyFunction,和一个类MyClass,如下所示。

  • MyFunction接口
public interface MyFunction{
    default String getName(){
        return "MyFunction";
    }
}
  • MyClass类
public class MyClass{
    public String getName(){
        return "MyClass";
    }
}

此时,创立SubClass类继承MyClass类,并实现MyFunction接口,如下所示。

public class SubClass extends MyClass implements MyFunction{
    
}

接下来,咱们创立一个SubClassTest类,对SubClass类进行测试,如下所示。

public class SubClassTest{
    @Test
    public void testDefaultFunction(){
        SubClass subClass = new SubClass();
        System.out.println(subClass.getName());
    }
}

运行上述程序,会输入字符串:MyClass。

2.接口抵触。如果一个父接口提供一个默认办法,而另一个接口也提供了一个具备雷同名称和参数列表的办法(不论办法是否是默认办法), 那么必须笼罩该办法来解决抵触。

例如,当初有两个接口,别离为MyFunction和MyInterface,各自都有一个默认办法getName(),如下所示。

  • MyFunction接口
public interface MyFunction{
    default String getName(){
        return "function";
    }
}
  • MyInterface接口
public interface MyInterface{
    default String getName(){
        return "interface";
    }
}

实现类MyClass同时实现了MyFunction接口和MyInterface接口,因为MyFunction接口和MyInterface接口中都存在getName()默认办法,所以,MyClass必须笼罩getName()办法来解决抵触,如下所示。

public class MyClass{
    @Override
    public String getName(){
        return MyInterface.super.getName();
    }
}

此时,MyClass类中的getName办法返回的是:interface。

如果MyClass中的getName()办法笼罩的是MyFunction接口的getName()办法,如下所示。

public class MyClass{
    @Override
    public String getName(){
        return MyFunction.super.getName();
    }
}

此时,MyClass类中的getName办法返回的是:function。

接口中的静态方法

在Java8中,接口中容许增加静态方法,应用形式接口名.办法名。例如MyFunction接口中定义了静态方法send()。

public interface MyFunction{
    default String getName(){
        return "binghe";
    }
    static void send(){
        System.out.println("Send Message...");
    }
}

咱们能够间接应用如下形式调用MyFunction接口的send静态方法。

MyFunction.send();

本地工夫和工夫戳

次要办法:

  • now:静态方法,依据以后工夫创建对象
  • of:静态方法,依据指定日期/工夫创建对象
  • plusDays,plusWeeks,plusMonths,plusYears:向以后LocalDate 对象增加几天、几周、几个月、几年
  • minusDays,minusWeeks,minusMonths,minusYears:从以后LocalDate 对象减去几天、几周、几个月、几年
  • plus,minus:增加或缩小一个Duration 或Period
  • withDayOfMonth,withDayOfYear,withMonth,withYear:将月份天数、年份天数、月份、年份批改为指定的值并返回新的LocalDate 对象
  • getDayOfYear:取得年份天数(1~366)
  • getDayOfWeek:取得星期几(返回一个DayOfWeek枚举值)
  • getMonth:取得月份, 返回一个Month 枚举值
  • getMonthValue:取得月份(1~12)
  • getYear:取得年份
  • until:取得两个日期之间的Period 对象,或者指定ChronoUnits 的数字
  • isBefore,isAfter:比拟两个LocalDate
  • isLeapYear:判断是否是平年

应用 LocalDate、 LocalTime、 LocalDateTime

LocalDate、 LocalTime、 LocalDateTime 类的实例是不可变的对象,别离示意应用 ISO-8601日历零碎的日期、工夫、日期和工夫。它们提供了简略的日期或工夫,并不蕴含以后的工夫信息。也不蕴含与时区相干的信息。

注: ISO-8601日历零碎是国际标准化组织制订的古代公民的日期和工夫的表示法

办法 形容
now() 静态方法,依据以后工夫创建对象
of() 静态方法,依据指定日期/工夫创立 对象
plusDays, plusWeeks, plusMonths, plusYears 向以后 LocalDate 对象增加几天、 几周、 几个月、 几年
minusDays, minusWeeks, minusMonths, minusYears 从以后 LocalDate 对象减去几天、 几周、 几个月、 几年
plus, minus 增加或缩小一个 Duration 或 Period
withDayOfMonth, withDayOfYear, withMonth, withYear 将月份天数、 年份天数、 月份、 年 份 修 改 为 指 定 的 值 并 返 回 新 的 LocalDate 对象
getDayOfMonth 取得月份天数(1-31)
getDayOfYear 取得年份天数(1-366)
getDayOfWeek 取得星期几(返回一个 DayOfWeek 枚举值)
getMonth 取得月份, 返回一个 Month 枚举值
getMonthValue 取得月份(1-12)
getYear 取得年份
until 取得两个日期之间的 Period 对象, 或者指定 ChronoUnits 的数字
isBefore, isAfter 比拟两个 LocalDate
isLeapYear 判断是否是平年

示例代码如下所示。

// 获取以后零碎工夫
LocalDateTime localDateTime1 = LocalDateTime.now();
System.out.println(localDateTime1);
// 运行后果:2019-10-27T13:49:09.483

// 指定日期工夫
LocalDateTime localDateTime2 = LocalDateTime.of(2019, 10, 27, 13, 45,10);
System.out.println(localDateTime2);
// 运行后果:2019-10-27T13:45:10

LocalDateTime localDateTime3 = localDateTime1
        // 加三年
        .plusYears(3)
        // 减三个月
        .minusMonths(3);
System.out.println(localDateTime3);
// 运行后果:2022-07-27T13:49:09.483

System.out.println(localDateTime1.getYear());       // 运行后果:2019
System.out.println(localDateTime1.getMonthValue()); // 运行后果:10
System.out.println(localDateTime1.getDayOfMonth()); // 运行后果:27
System.out.println(localDateTime1.getHour());       // 运行后果:13
System.out.println(localDateTime1.getMinute());     // 运行后果:52
System.out.println(localDateTime1.getSecond());     // 运行后果:6

LocalDateTime localDateTime4 = LocalDateTime.now();
System.out.println(localDateTime4);     // 2019-10-27T14:19:56.884
LocalDateTime localDateTime5 = localDateTime4.withDayOfMonth(10);
System.out.println(localDateTime5);     // 2019-10-10T14:19:56.884

Instant 工夫戳

用于“工夫戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时候)开始所经验的形容进行运算 。

示例代码如下所示。

Instant instant1 = Instant.now();    // 默认获取UTC时区
System.out.println(instant1);
// 运行后果:2019-10-27T05:59:58.221Z

// 偏移量运算
OffsetDateTime offsetDateTime = instant1.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
// 运行后果:2019-10-27T13:59:58.221+08:00

// 获取工夫戳
System.out.println(instant1.toEpochMilli());
// 运行后果:1572156145000

// 以Unix元年为终点,进行偏移量运算
Instant instant2 = Instant.ofEpochSecond(60);
System.out.println(instant2);
// 运行后果:1970-01-01T00:01:00Z

Duration 和 Period

Duration:用于计算两个“工夫”距离。

Period:用于计算两个“日期”距离 。

Instant instant_1 = Instant.now();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
Instant instant_2 = Instant.now();

Duration duration = Duration.between(instant_1, instant_2);
System.out.println(duration.toMillis());
// 运行后果:1000

LocalTime localTime_1 = LocalTime.now();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
LocalTime localTime_2 = LocalTime.now();

System.out.println(Duration.between(localTime_1, localTime_2).toMillis());
// 运行后果:1000
LocalDate localDate_1 = LocalDate.of(2018,9, 9);
LocalDate localDate_2 = LocalDate.now();

Period period = Period.between(localDate_1, localDate_2);
System.out.println(period.getYears());      // 运行后果:1
System.out.println(period.getMonths());     // 运行后果:1
System.out.println(period.getDays());       // 运行后果:18

日期的操纵

TemporalAdjuster : 工夫校正器。有时咱们可能须要获取例如:将日期调整到“下个周日”等操作。

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

例如获取下个周日,如下所示:

LocalDate nextSunday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));

残缺的示例代码如下所示。

LocalDateTime localDateTime1 = LocalDateTime.now();
System.out.println(localDateTime1);     // 2019-10-27T14:19:56.884

// 获取这个第一天的日期
System.out.println(localDateTime1.with(TemporalAdjusters.firstDayOfMonth()));            // 2019-10-01T14:22:58.574
// 获取下个周末的日期
System.out.println(localDateTime1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));       // 2019-11-03T14:22:58.574

// 自定义:下一个工作日
LocalDateTime localDateTime2 = localDateTime1.with(l -> {
    LocalDateTime localDateTime = (LocalDateTime) l;
    DayOfWeek dayOfWeek =  localDateTime.getDayOfWeek();

    if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
       return localDateTime.plusDays(3);
    } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
       return localDateTime.plusDays(2);
    } else {
       return localDateTime.plusDays(1);
    }
});
System.out.println(localDateTime2);
// 运行后果:2019-10-28T14:30:17.400

解析与格式化

java.time.format.DateTimeFormatter 类:该类提供了三种格式化办法:

  • 预约义的规范格局
  • 语言环境相干的格局
  • 自定义的格局

示例代码如下所示。

DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ISO_DATE;
LocalDateTime localDateTime = LocalDateTime.now();
String strDate1 = localDateTime.format(dateTimeFormatter1);
System.out.println(strDate1);
// 运行后果:2019-10-27

// Date -> String
DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd  HH:mm:ss");
String strDate2 = dateTimeFormatter2.format(localDateTime);
System.out.println(strDate2);
// 运行后果:2019-10-27  14:36:11

// String -> Date
LocalDateTime localDateTime1 = localDateTime.parse(strDate2, dateTimeFormatter2);
System.out.println(localDateTime1);
// 运行后果:2019-10-27T14:37:39

时区的解决

Java8 中退出了对时区的反对,带时区的工夫为别离为:ZonedDate、 ZonedTime、 ZonedDateTime。

其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格局,例如 : Asia/Shanghai 等。

  • ZoneId:该类中蕴含了所有的时区信息
  • getAvailableZoneIds() : 能够获取所有时区时区信息
  • of(id) : 用指定的时区信息获取 ZoneId 对象

示例代码如下所示。

// 获取所有的时区
Set<String> set = ZoneId.getAvailableZoneIds();
System.out.println(set);
// [Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot, Asia/Aqtau, Pacific/Kwajalein, America/El_Salvador, Asia/Pontianak, Africa/Cairo, Pacific/Pago_Pago, Africa/Mbabane, Asia/Kuching, Pacific/Honolulu, Pacific/Rarotonga, America/Guatemala, Australia/Hobart, Europe/London, America/Belize, America/Panama, Asia/Chungking, America/Managua, America/Indiana/Petersburg, Asia/Yerevan, Europe/Brussels, GMT, Europe/Warsaw, America/Chicago, Asia/Kashgar, Chile/Continental, Pacific/Yap, CET, Etc/GMT-1, Etc/GMT-0, Europe/Jersey, America/Tegucigalpa, Etc/GMT-5, Europe/Istanbul, America/Eirunepe, Etc/GMT-4, America/Miquelon, Etc/GMT-3, Europe/Luxembourg, Etc/GMT-2, Etc/GMT-9, America/Argentina/Catamarca, Etc/GMT-8, Etc/GMT-7, Etc/GMT-6, Europe/Zaporozhye, Canada/Yukon, Canada/Atlantic, Atlantic/St_Helena, Australia/Tasmania, Libya, Europe/Guernsey, America/Grand_Turk, US/Pacific-New, Asia/Samarkand, America/Argentina/Cordoba, Asia/Phnom_Penh, Africa/Kigali, Asia/Almaty, US/Alaska, Asia/Dubai, Europe/Isle_of_Man, America/Araguaina, Cuba, Asia/Novosibirsk, America/Argentina/Salta, Etc/GMT+3, Africa/Tunis, Etc/GMT+2, Etc/GMT+1, Pacific/Fakaofo, Africa/Tripoli, Etc/GMT+0, Israel, Africa/Banjul, Etc/GMT+7, Indian/Comoro, Etc/GMT+6, Etc/GMT+5, Etc/GMT+4, Pacific/Port_Moresby, US/Arizona, Antarctica/Syowa, Indian/Reunion, Pacific/Palau, Europe/Kaliningrad, America/Montevideo, Africa/Windhoek, Asia/Karachi, Africa/Mogadishu, Australia/Perth, Brazil/East, Etc/GMT, Asia/Chita, Pacific/Easter, Antarctica/Davis, Antarctica/McMurdo, Asia/Macao, America/Manaus, Africa/Freetown, Europe/Bucharest, Asia/Tomsk, America/Argentina/Mendoza, Asia/Macau, Europe/Malta, Mexico/BajaSur, Pacific/Tahiti, Africa/Asmera, Europe/Busingen, America/Argentina/Rio_Gallegos, Africa/Malabo, Europe/Skopje, America/Catamarca, America/Godthab, Europe/Sarajevo, Australia/ACT, GB-Eire, Africa/Lagos, America/Cordoba, Europe/Rome, Asia/Dacca, Indian/Mauritius, Pacific/Samoa, America/Regina, America/Fort_Wayne, America/Dawson_Creek, Africa/Algiers, Europe/Mariehamn, America/St_Johns, America/St_Thomas, Europe/Zurich, America/Anguilla, Asia/Dili, America/Denver, Africa/Bamako, Europe/Saratov, GB, Mexico/General, Pacific/Wallis, Europe/Gibraltar, Africa/Conakry, Africa/Lubumbashi, Asia/Istanbul, America/Havana, NZ-CHAT, Asia/Choibalsan, America/Porto_Acre, Asia/Omsk, Europe/Vaduz, US/Michigan, Asia/Dhaka, America/Barbados, Europe/Tiraspol, Atlantic/Cape_Verde, Asia/Yekaterinburg, America/Louisville, Pacific/Johnston, Pacific/Chatham, Europe/Ljubljana, America/Sao_Paulo, Asia/Jayapura, America/Curacao, Asia/Dushanbe, America/Guyana, America/Guayaquil, America/Martinique, Portugal, Europe/Berlin, Europe/Moscow, Europe/Chisinau, America/Puerto_Rico, America/Rankin_Inlet, Pacific/Ponape, Europe/Stockholm, Europe/Budapest, America/Argentina/Jujuy, Australia/Eucla, Asia/Shanghai, Universal, Europe/Zagreb, America/Port_of_Spain, Europe/Helsinki, Asia/Beirut, Asia/Tel_Aviv, Pacific/Bougainville, US/Central, Africa/Sao_Tome, Indian/Chagos, America/Cayenne, Asia/Yakutsk, Pacific/Galapagos, Australia/North, Europe/Paris, Africa/Ndjamena, Pacific/Fiji, America/Rainy_River, Indian/Maldives, Australia/Yancowinna, SystemV/AST4, Asia/Oral, America/Yellowknife, Pacific/Enderbury, America/Juneau, Australia/Victoria, America/Indiana/Vevay, Asia/Tashkent, Asia/Jakarta, Africa/Ceuta, Asia/Barnaul, America/Recife, America/Buenos_Aires, America/Noronha, America/Swift_Current, Australia/Adelaide, America/Metlakatla, Africa/Djibouti, America/Paramaribo, Europe/Simferopol, Europe/Sofia, Africa/Nouakchott, Europe/Prague, America/Indiana/Vincennes, Antarctica/Mawson, America/Kralendijk, Antarctica/Troll, Europe/Samara, Indian/Christmas, America/Antigua, Pacific/Gambier, America/Indianapolis, America/Inuvik, America/Iqaluit, Pacific/Funafuti, UTC, Antarctica/Macquarie, Canada/Pacific, America/Moncton, Africa/Gaborone, Pacific/Chuuk, Asia/Pyongyang, America/St_Vincent, Asia/Gaza, Etc/Universal, PST8PDT, Atlantic/Faeroe, Asia/Qyzylorda, Canada/Newfoundland, America/Kentucky/Louisville, America/Yakutat, Asia/Ho_Chi_Minh, Antarctica/Casey, Europe/Copenhagen, Africa/Asmara, Atlantic/Azores, Europe/Vienna, ROK, Pacific/Pitcairn, America/Mazatlan, Australia/Queensland, Pacific/Nauru, Europe/Tirane, Asia/Kolkata, SystemV/MST7, Australia/Canberra, MET, Australia/Broken_Hill, Europe/Riga, America/Dominica, Africa/Abidjan, America/Mendoza, America/Santarem, Kwajalein, America/Asuncion, Asia/Ulan_Bator, NZ, America/Boise, Australia/Currie, EST5EDT, Pacific/Guam, Pacific/Wake, Atlantic/Bermuda, America/Costa_Rica, America/Dawson, Asia/Chongqing, Eire, Europe/Amsterdam, America/Indiana/Knox, America/North_Dakota/Beulah, Africa/Accra, Atlantic/Faroe, Mexico/BajaNorte, America/Maceio, Etc/UCT, Pacific/Apia, GMT0, America/Atka, Pacific/Niue, Australia/Lord_Howe, Europe/Dublin, Pacific/Truk, MST7MDT, America/Monterrey, America/Nassau, America/Jamaica, Asia/Bishkek, America/Atikokan, Atlantic/Stanley, Australia/NSW, US/Hawaii, SystemV/CST6, Indian/Mahe, Asia/Aqtobe, America/Sitka, Asia/Vladivostok, Africa/Libreville, Africa/Maputo, Zulu, America/Kentucky/Monticello, Africa/El_Aaiun, Africa/Ouagadougou, America/Coral_Harbour, Pacific/Marquesas, Brazil/West, America/Aruba, America/North_Dakota/Center, America/Cayman, Asia/Ulaanbaatar, Asia/Baghdad, Europe/San_Marino, America/Indiana/Tell_City, America/Tijuana, Pacific/Saipan, SystemV/YST9, Africa/Douala, America/Chihuahua, America/Ojinaga, Asia/Hovd, America/Anchorage, Chile/EasterIsland, America/Halifax, Antarctica/Rothera, America/Indiana/Indianapolis, US/Mountain, Asia/Damascus, America/Argentina/San_Luis, America/Santiago, Asia/Baku, America/Argentina/Ushuaia, Atlantic/Reykjavik, Africa/Brazzaville, Africa/Porto-Novo, America/La_Paz, Antarctica/DumontDUrville, Asia/Taipei, Antarctica/South_Pole, Asia/Manila, Asia/Bangkok, Africa/Dar_es_Salaam, Poland, Atlantic/Madeira, Antarctica/Palmer, America/Thunder_Bay, Africa/Addis_Ababa, Asia/Yangon, Europe/Uzhgorod, Brazil/DeNoronha, Asia/Ashkhabad, Etc/Zulu, America/Indiana/Marengo, America/Creston, America/Punta_Arenas, America/Mexico_City, Antarctica/Vostok, Asia/Jerusalem, Europe/Andorra, US/Samoa, PRC, Asia/Vientiane, Pacific/Kiritimati, America/Matamoros, America/Blanc-Sablon, Asia/Riyadh, Iceland, Pacific/Pohnpei, Asia/Ujung_Pandang, Atlantic/South_Georgia, Europe/Lisbon, Asia/Harbin, Europe/Oslo, Asia/Novokuznetsk, CST6CDT, Atlantic/Canary, America/Knox_IN, Asia/Kuwait, SystemV/HST10, Pacific/Efate, Africa/Lome, America/Bogota, America/Menominee, America/Adak, Pacific/Norfolk, Europe/Kirov, America/Resolute, Pacific/Tarawa, Africa/Kampala, Asia/Krasnoyarsk, Greenwich, SystemV/EST5, America/Edmonton, Europe/Podgorica, Australia/South, Canada/Central, Africa/Bujumbura, America/Santo_Domingo, US/Eastern, Europe/Minsk, Pacific/Auckland, Africa/Casablanca, America/Glace_Bay, Canada/Eastern, Asia/Qatar, Europe/Kiev, Singapore, Asia/Magadan, SystemV/PST8, America/Port-au-Prince, Europe/Belfast, America/St_Barthelemy, Asia/Ashgabat, Africa/Luanda, America/Nipigon, Atlantic/Jan_Mayen, Brazil/Acre, Asia/Muscat, Asia/Bahrain, Europe/Vilnius, America/Fortaleza, Etc/GMT0, US/East-Indiana, America/Hermosillo, America/Cancun, Africa/Maseru, Pacific/Kosrae, Africa/Kinshasa, Asia/Kathmandu, Asia/Seoul, Australia/Sydney, America/Lima, Australia/LHI, America/St_Lucia, Europe/Madrid, America/Bahia_Banderas, America/Montserrat, Asia/Brunei, America/Santa_Isabel, Canada/Mountain, America/Cambridge_Bay, Asia/Colombo, Australia/West, Indian/Antananarivo, Australia/Brisbane, Indian/Mayotte, US/Indiana-Starke, Asia/Urumqi, US/Aleutian, Europe/Volgograd, America/Lower_Princes, America/Vancouver, Africa/Blantyre, America/Rio_Branco, America/Danmarkshavn, America/Detroit, America/Thule, Africa/Lusaka, Asia/Hong_Kong, Iran, America/Argentina/La_Rioja, Africa/Dakar, SystemV/CST6CDT, America/Tortola, America/Porto_Velho, Asia/Sakhalin, Etc/GMT+10, America/Scoresbysund, Asia/Kamchatka, Asia/Thimbu, Africa/Harare, Etc/GMT+12, Etc/GMT+11, Navajo, America/Nome, Europe/Tallinn, Turkey, Africa/Khartoum, Africa/Johannesburg, Africa/Bangui, Europe/Belgrade, Jamaica, Africa/Bissau, Asia/Tehran, WET, Europe/Astrakhan, Africa/Juba, America/Campo_Grande, America/Belem, Etc/Greenwich, Asia/Saigon, America/Ensenada, Pacific/Midway, America/Jujuy, Africa/Timbuktu, America/Bahia, America/Goose_Bay, America/Virgin, America/Pangnirtung, Asia/Katmandu, America/Phoenix, Africa/Niamey, America/Whitehorse, Pacific/Noumea, Asia/Tbilisi, America/Montreal, Asia/Makassar, America/Argentina/San_Juan, Hongkong, UCT, Asia/Nicosia, America/Indiana/Winamac, SystemV/MST7MDT, America/Argentina/ComodRivadavia, America/Boa_Vista, America/Grenada, Asia/Atyrau, Australia/Darwin, Asia/Khandyga, Asia/Kuala_Lumpur, Asia/Famagusta, Asia/Thimphu, Asia/Rangoon, Europe/Bratislava, Asia/Calcutta, America/Argentina/Tucuman, Asia/Kabul, Indian/Cocos, Japan, Pacific/Tongatapu, America/New_York, Etc/GMT-12, Etc/GMT-11, Etc/GMT-10, SystemV/YST9YDT, Europe/Ulyanovsk, Etc/GMT-14, Etc/GMT-13, W-SU, America/Merida, EET, America/Rosario, Canada/Saskatchewan, America/St_Kitts, Arctic/Longyearbyen, America/Fort_Nelson, America/Caracas, America/Guadeloupe, Asia/Hebron, Indian/Kerguelen, SystemV/PST8PDT, Africa/Monrovia, Asia/Ust-Nera, Egypt, Asia/Srednekolymsk, America/North_Dakota/New_Salem, Asia/Anadyr, Australia/Melbourne, Asia/Irkutsk, America/Shiprock, America/Winnipeg, Europe/Vatican, Asia/Amman, Etc/UTC, SystemV/AST4ADT, Asia/Tokyo, America/Toronto, Asia/Singapore, Australia/Lindeman, America/Los_Angeles, SystemV/EST5EDT, Pacific/Majuro, America/Argentina/Buenos_Aires, Europe/Nicosia, Pacific/Guadalcanal, Europe/Athens, US/Pacific, Europe/Monaco]

// 通过时区构建LocalDateTime
LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("America/El_Salvador"));
System.out.println(localDateTime1);
// 2019-10-27T00:46:21.268

// 以时区格局显示工夫
LocalDateTime localDateTime2 = LocalDateTime.now();
ZonedDateTime zonedDateTime1 = localDateTime2.atZone(ZoneId.of("Africa/Nairobi"));
System.out.println(zonedDateTime1);
// 2019-10-27T14:46:21.273+03:00[Africa/Nairobi]

与传统日期解决的转换

JDK注解

JDK5中的注解

1.注解(@)

注解就相当于一种标记,在程序中加了注解就等于为程序加了某种标记。(JDK1.5新个性)。

2.作用

通知javac编译器或者java开发工具……向其传递某种信息,作为一个标记。

3.如何了解注解?

一个注解就是一个类。

标记能够加在包、类、字段、办法,办法参数以及局部变量上。能够同时存在多个注解。

每一个注解结尾都没有“;”或者其余特地符号。

定义注解须要的根底注解信息如下所示。

@SuppressWarnings("deprecation")  //编译器正告过期(source阶段)
@Deprecated                        //过期(Runtime阶段)
@Override                        //重写(source阶段)
@Retention(RetentionPolicy.RUNTIME)    
//保留注解到程序运行时。(Runtime阶段)
@Target({ElementType.METHOD,ElementType.TYPE})
//标记既能定义在办法上,又能定义在类、接口、枚举上等。

留神:

1)增加注解须要有注解类。RetentionPolicy是一个枚举类(有三个成员)。

2)Target中能够寄存数组。它的默认值为任何元素。

  • ElementType.METHOD:示意只能标记在办法上。
  • ElementType.TYPE:示意只能标记定义在类上、接口上、枚举上等

    3)ElementType也是枚举类。成员包含:ANNOTATION_TYPE(注解)、CONSTRUCTOR(构造方法)、FIEID(成员变量)、LOCAL_VARIABLE(变量)、METHOD(办法)、PACKAGE(包)、PARAMETER(参数)、TYPE。

4.对于注解

  • 元注解:注解的注解(了解:给一个注解类再加注解)
  • 元数据:数据的数据
  • 元信息:信息的信息

5.注解分为三个阶段

java源文件–> class文件 –> 内存中的字节码。

Retention的注解有三种取值:(别离对应注解的三个阶段)

  • RetentionPolicy.SOURCE
  • RetentionPolicy.CLASS
  • RetentionPolicy.RUNTIME

留神:注解的默认阶段是Class。

6.注解的属性类型

原始类型(就是八个根本数据类型)、String类型、Class类型、数组类型、枚举类型、注解类型。

7.为注解减少属性

value:是一个非凡的属性,若在设置值时只有一个value属性须要设置或者其余属性都采纳默认值时 ,那么value=能够省略,间接写所设置的值即可。

例如:@SuppressWarnings("deprecation")

为属性指定缺省值(默认值):
例如:String value() default "blue"; //定义在注解类中

数组类型的属性:
例如:int[] arrayArr() default {3,4,5,5};//定义在注解类中
SunAnnotation(arrayArr={3,9,8}) //设置数组值
留神:如果数组属性中只有一个元素时,属性值局部能够省略大括号。
例如:SunAnnotation(arrayArr=9)

枚举类型的属性:
例如:EnumDemo.TrafficLamp lamp()
////枚举类型属性, 定义在注解类中,这里应用了自定义的枚举类EnumDemo.java并没有给出相干代码,这里只是举个例子
default EnumDemo.TrafficLamp.RED;

注解类型的属性:
例如:MetaAnnotation annotationAttr()
//定义在一个注解类中,并指定缺省值,
//此属性关联到注解类:MetaAnnotation.java, 
default @MetaAnnotation("lhm");
//设置注解属性值
@SunAnnotation(annotationAttr=@MetaAnnotation("flx"))

Java8中的注解

对于注解(也被称做元数据),Java 8 次要有两点改良:类型注解和反复注解。

1.类型注解

1)Java 8 的类型注解扩大了注解应用的范畴。

在java 8之前,注解只能是在申明的中央所应用,java8开始,注解能够利用在任何中央。

例如:

创立类实例

new @Interned MyObject();

类型映射

myString = (@NonNull String) str;

implements 语句中

class UnmodifiableList<T> implements@Readonly List<@Readonly T> { ... }

throw exception申明

void monitorTemperature() throws@Critical TemperatureException { ... }

留神:

在Java 8外面,当类型转化甚至调配新对象的时候,都能够在申明变量或者参数的时候应用注解。
Java注解能够反对任意类型。

类型注解只是语法而不是语义,并不会影响java的编译工夫,加载工夫,以及运行工夫,也就是说,编译成class文件的时候并不蕴含类型注解。

2)新增ElementType.TYPE_USE 和ElementType.TYPE_PARAMETER(在Target上)

新增的两个正文的程序元素类型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER用来形容注解的新场合。

  • ElementType.TYPE_PARAMETER 示意该注解能写在类型变量的申明语句中。
  • ElementType.TYPE_USE 示意该注解能写在应用类型的任何语句中(例如:申明语句、泛型和强制转换语句中的类型)。

例如,上面的示例。

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

3)类型注解的作用

类型注解被用来反对在Java的程序中做强类型查看。配合第三方插件工具Checker Framework(注:此插件so easy,这里不介绍了),能够在编译的时候检测出runtime error(例如:UnsupportedOperationException; NumberFormatException;NullPointerException异样等都是runtime error),以进步代码品质。这就是类型注解的作用。

留神:应用Checker Framework能够找到类型注解呈现的中央并查看。

例如上面的代码。

import checkers.nullness.quals.*;
public class TestDemo{
    void sample() {
        @NonNull Object my = new Object();
    }
}

应用javac编译下面的类:(当然若下载了Checker Framework插件就不须要这么麻烦了)

javac -processor checkers.nullness.NullnessChecker TestDemo.java

下面编译是通过的,但若批改代码:

@NonNull Object my = null;

但若不想应用类型注解检测进去谬误,则不须要processor,失常javac TestDemo.java是能够通过编译的,然而运行时会报 NullPointerException 异样。

为了能在编译期间就主动查看出这类异样,能够通过类型注解联合 Checker Framework 提前排查进去谬误异样。

留神java 5,6,7版本是不反对注解@NonNull,但checker framework 有个向下兼容的解决方案,就是将类型注解@NonNull 用/**/正文起来。

import checkers.nullness.quals.*;
public class TestDemo{
    void sample() {
        /*@NonNull*/ Object my = null;
    }
}

这样javac编译器就会疏忽掉正文块,但用checker framework外面的javac编译器同样可能检测出@NonNull谬误。
通过 类型注解 + checker framework 能够在编译时就找到runtime error。

2.反复注解

容许在同一申明类型(类,属性,或办法)上屡次应用同一个注解。

Java8以前的版本应用注解有一个限度是雷同的注解在同一地位只能应用一次,不能应用屡次。

Java 8 引入了反复注解机制,这样雷同的注解能够在同一中央应用屡次。反复注解机制自身必须用 @Repeatable 注解。

实际上,反复注解不是一个语言上的扭转,只是编译器层面的改变,技术层面依然是一样的。

例如,咱们能够应用如下示例来具体比照Java8之前的版本和Java8中的注解。

1) 自定义一个包装类Hints注解用来搁置一组具体的Hint注解

@interface MyHints {
    Hint[] value();
}
 
@Repeatable(MyHints.class)
@interface Hint {
    String value();
}

应用包装类当容器来存多个注解(旧版本办法)

@MyHints({@Hint("hint1"), @Hint("hint2")})
class Person {}

应用多重注解(新办法)

@Hint("hint1")
@Hint("hint2")
class Person {}

2) 残缺类测试如下所示。

public class RepeatingAnnotations {
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Filters {
        Filter[] value();
    }
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(Filters.class)
    public @interface Filter {
        String value();
    }
    @Filter("filter1")
    @Filter("filter2")
    public interface Filterable {
    }
    public static void main(String[] args) {
        for (Filter filter : Filterable.class.getAnnotationsByType(Filter.class)) {
            System.out.println(filter.value());
        }
    }
}

输入后果:

filter1
filter2

剖析:

正文Filter被@Repeatable( Filters.class )正文。Filters 只是一个容器,它持有Filter, 编译器尽力向程序员暗藏它的存在。通过这样的形式,Filterable接口能够被Filter正文两次。

另外,反射的API提供一个新办法getAnnotationsByType() 来返回反复正文的类型(留神Filterable.class.getAnnotation( Filters.class )将会返回编译器注入的Filters实例。

3) java 8之前也有重复使用注解的解决方案,但可读性不好。

public @interface MyAnnotation {  
     String role();  
}  
 
public @interface Annotations {  
    MyAnnotation[] value();  
}  
 
public class RepeatAnnotationUseOldVersion {  
    @Annotations({@MyAnnotation(role="Admin"),@MyAnnotation(role="Manager")})  
    public void doSomeThing(){  
    }  
}

Java8的实现形式(由另一个注解来存储反复注解,在应用时候,用存储注解Authorities来扩大反复注解),可读性更强。

@Repeatable(Annotations.class) 
public @interface MyAnnotation {  
     String role();  
}  
 
public @interface Annotations {  
    MyAnnotation[] value();  
}  
 
public class RepeatAnnotationUseOldVersion {  
    @MyAnnotation(role="Admin")  
    @MyAnnotation(role="Manager")
    public void doSomeThing(){  
    }  
} 

什么?没看懂?那就再来一波!!!

Java8对注解的加强

Java 8对注解解决提供了两点改良:可反复的注解及可用于类型的注解。总体来说,比较简单,上面,咱们就以实例的模式来阐明Java8中的反复注解和类型注解。

首先,咱们来定义一个注解类BingheAnnotation,如下所示。

package io.mykit.binghe.java8.annotition;

import java.lang.annotation.*;

/**
 * @author binghe
 * @version 1.0.0
 * @description 定义注解
 */
@Repeatable(BingheAnnotations.class)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface BingheAnnotation {
    String value();
}

留神:在BingheAnnotation注解类上比一般的注解多了一个@Repeatable(BingheAnnotations.class)注解,有小伙伴会问:这个是啥啊?这个就是Java8中定义可反复注解的要害,至于BingheAnnotations.class,大家别急,持续往下看就明确了。

接下来,咱们定义一个BingheAnnotations注解类,如下所示。

package io.mykit.binghe.java8.annotation;

import java.lang.annotation.*;

/**
 * @author binghe
 * @version 1.0.0
 * @description 定义注解
 */
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface BingheAnnotations {
    BingheAnnotation[] value();
}

看到这里,大家明确了吧!!没错,BingheAnnotations也是一个注解类,它相比于BingheAnnotation注解类来说,少了一个@Repeatable(BingheAnnotations.class)注解,也就是说,BingheAnnotations注解类的定义与一般的注解简直没啥区别。值得注意的是,咱们在BingheAnnotations注解类中,定义了一个BingheAnnotation注解类的数组,也就是说,在BingheAnnotations注解类中,蕴含有多个BingheAnnotation注解。所以,在BingheAnnotation注解类上指定@Repeatable(BingheAnnotations.class)来阐明能够在类、字段、办法、参数、构造方法、参数上重复使用BingheAnnotation注解。

接下来,咱们创立一个Binghe类,在Binghe类中定义一个init()办法,在init办法上,重复使用@BingheAnnotation注解指定相应的数据,如下所示。

package io.mykit.binghe.java8.annotation;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试注解
 */
@BingheAnnotation("binghe")
@BingheAnnotation("class")
public class Binghe {

    @BingheAnnotation("init")
    @BingheAnnotation("method")
    public void init(){

    }
}

到此,咱们就能够测试反复注解了,创立类BingheAnnotationTest,对反复注解进行测试,如下所示。

package io.mykit.binghe.java8.annotation;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试注解
 */
public class BingheAnnotationTest {

    public static void main(String[] args) throws NoSuchMethodException {
        Class<Binghe> clazz = Binghe.class;
        BingheAnnotation[] annotations = clazz.getAnnotationsByType(BingheAnnotation.class);
        System.out.println("类上的反复注解如下:");
        Arrays.stream(annotations).forEach((a) -> System.out.print(a.value() + " "));

        System.out.println();
        System.out.println("=============================");

        Method method = clazz.getMethod("init");
        annotations = method.getAnnotationsByType(BingheAnnotation.class);
        System.out.println("办法上的反复注解如下:");
        Arrays.stream(annotations).forEach((a) -> System.out.print(a.value() + " "));
    }
}

运行main()办法,输入如下的后果信息。

类上的反复注解如下:
binghe class 
=============================
办法上的反复注解如下:
init method 

好了,明天就到这儿吧,我是冰河,大家有啥问题能够在下方留言,也能够加我微信:sun_shine_lyz,我拉你进群,一起交换技术,一起进阶,一起牛逼~~

整顿自Github:https://github.com/MaRuifu/Ja… Github作者:小马哥

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理