乐趣区

关于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 作者:小马哥

退出移动版