文章目录
jdk8新特性
- 速度更快
- 代码更少(增加了新的语法Lambda表达式):
- 强大的Stream API
- 便于并行
- 最大化减少空指针异常Optional
- 其中最为核心的为Lambda表达式与Stream API
Lambda表达式
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
Lambda表达式语法
Lambda表达式在Java语言中引入了一个操作符==“ -> ”==,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两个部分:
- 左侧:指定了Lambda表达式需要的所有参数
- 右侧:制定了Lambda体,即Lambda表达式要执行的功能。
(parameters) -> expression
或
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
下面对每个语法格式的特征进行举例说明:
(1)语法格式一:无参,无返回值,Lambda体只需一条语句。如下:
@Test
public void test1(){
Runnable runnable = () -> System.out.println("Runnable运行");
runnable.run();
}
(2)语法格式二:Lambda需要一个参数,无返回值。如下:
@Test
public void test2(){
Consumer<String> con = (e) -> System.out.println(e);
con.accept("yybs");
}
(3)语法格式三:Lambda只需要一个参数时,参数的小括号可以省略,如下:
@Test
public void test2(){
Consumer<String> con = (e) -> System.out.println(e);
con.accept("yybs");
System.out.println("============================");
Consumer<String> con1 = e -> System.out.println(e);
con.accept("yybs");
}
(4)语法格式四:Lambda需要两个参数,并且Lambda体中有多条语句。
@Test
public void test3(){
Comparator<Integer> com = (x,y) -> {
System.out.println("函数式接口");
return Integer.compare(x,y);
};
System.out.println("com.compare(1,23) = " + com.compare(1, 23));
}
(5)语法格式五:有两个以上参数,有返回值,若Lambda体中只有一条语句,return和大括号都可以省略不写
@Test
public void test4(){
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
System.out.println("com.compare(1,23) = " + com.compare(1, 23));
}
(6)Lambda表达式的参数列表的数据类型可以省略不写,因为JVM可以通过上下文推断出数据类型,即“类型推断”
@Test
public void test4(){
Comparator<Integer> com = (Integer x,Integer y) -> Integer.compare(x,y);
System.out.println("com.compare(1,23) = " + com.compare(1, 23));
System.out.println("=======================================");
Comparator<Integer> com2 = (x,y) -> Integer.compare(x,y);
System.out.println("com2.compare(1,23) = " + com2.compare(1, 23));
}
类型推断:在执行javac编译程序时,JVM根据程序的上下文推断出了参数的类型。Lambda表达式依赖于上下文环境。
语法背诵口诀:左右遇一括号省,左侧推断类型省,能省则省。
函数式接口
只包含一个抽象方法的接口,就称为函数式接口。我们可以通过Lambda表达式来创建该接口的实现对象。
我们可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以用于检测它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
自定义函数式接口
按照函数式接口的定义,自定义一个函数式接口,如下:
@FunctionalInterface
public interface MyFun<T>{
public Integer getValue(Integer num);
}
定义一个方法将函数式接口作为方法参数。
public Integer operation(Integer num,MyFun<Integer> mf){
return mf.getValue(num);
}
将Lambda表达式实现的接口作为参数传递。
@Test
public void test5(){
Integer operation = operation(300, (x) -> x * x);
System.out.println("operation = " + operation);
}
Java内置函数式接口
四大核心函数式接口的介绍,如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2VTZB3r9-1625838033341)(jdk8%E6%96%B0%E7%89%B9%E6%80%A7.assets/1625646211605.png)]
使用示例:
(1),Consumer:消费型接口void accept(T t)
@Test
public void test(){
happy(99.9,(money)-> System.out.println("大保健消费"+money+"园"));
}
public void happy(double money, Consumer<Double> con){
con.accept(money);
}
(2),Supplier:供给型接口 T get()
//Supplier<T> ︰供给型接口
@Test
public void test1(){
List<Integer> numList = getNumList(6, () -> (int) (Math.random() * 100));
numList.forEach(System.out::println);
}
//需求:产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup){
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0;i<num;i++ ){
Integer integer = sup.get();
list.add(integer);
}
return list;
}
(3),Function<T,R>:函数型接口 R apply(T t)
//Function<T,R> :函数型接口
@Test
public void test2(){
String handler = strHandler("\t\t\t\t\t尚硅谷yybs", (str) -> str.trim());
System.out.println("handler = " + handler);
}
//需求:用于处理字符串
public String strHandler(String str, Function<String,String> fun){
return fun.apply(str);
}
(4),Predicate:断言型接口 boolean test(T t)
//Predicate<T> ∶断言型接口
@Test
public void test3(){
List<String> arrayList = Arrays.asList("java","html","Lambd","www","js");
List<String> strings = filterStr(arrayList, (str) -> str.length() >= 3);
strings.forEach(System.out::println);
}
//需求:将满足条件的字符串,放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre){
ArrayList<String> arrayList = new ArrayList<>();
for (String str : list) {
if (pre.test(str)){
arrayList.add(str);
}
}
return arrayList;
}
其他接口的定义,如图所示:
方法引用
方法引用:若Lambda 体中的内容有方法已经实现了,我们可以使用"方法引用"(可以理解为方法引用是Lambda表达式的另外一种表现形式)
主要有三种语法格式:
-
对象::实例方法名
-
类::静态方法名
-
类::实例方法名
使用示例:
(1),对象::实例方法名
@Test
public void test1(){
PrintStream out = System.out;
Consumer<String> con = (x) -> out.println(x);
PrintStream out1 = System.out;
Consumer<String> con1 = out::println;
Consumer<String> con2 = System.out::println;
con2.accept("abcd");
}
(2),类::静态方法名
@Test
public void test3(){
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
Comparator<Integer> com1 = Integer::compare;
}
(3),类::实例方法名
@Test
public void test4(){
BiPredicate<String,String> bp = (x,y) -> x.equals(y);
//当第一个参数是实例方法的调用者,而第二个参数是这个实例方法的参数时那么就可以使用下可以使用ClassName::method
BiPredicate<String,String> bp1 = String::equals;
}
注意:
@Lambda 体中调用方法的参数列表与返回值类型,[要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
构造器引用
格式:类名::new
与函数式接口相结合,自动与函数式接口中方法兼容,可以把构造器引用赋值给定义的方法。需要注意构造器参数列表要与接口中抽象方法的参数列表一致。
使用示例:
创建一个实体类Employee:
public class Employee {
private String name;
private int age;
private double salary;
public Employee(){}
public Employee(Integer age){
this.age = age;
}
public Employee(String name,Integer age){
this.name = name;
this.age = age;
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
使用构造器引用与函数式接口相结合
@Test
public void test01(){
//引用无参构造器
Supplier<Employee> supplier=Employee::new;
System.out.println(supplier.get());
//引用有参构造器
Function<Integer,Employee> function=Employee::new;
System.out.println(function.apply(21));
BiFunction<String,Integer,Employee> biFunction=Employee::new;
System.out.println(biFunction.apply("张三",24));
}
注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致!
数组引用
数组引用的格式:type[]:new
使用示例:
@Test
public void test7(){
Function<Integer,Integer[]> fun = (x) -> new Integer[x];
Integer[] apply = fun.apply(1);
System.out.println("apply.length = " + apply.length);
Function<Integer,String[]> fun1 = String[]::new;
String[] apply1 = fun1.apply(12);
System.out.println("apply.length = " + apply.length);
}
Stream
Java8中有两大最为重要的改变。第一个是Lambda 表达式;另外一个则是 Stream API (java.util.stream.*)。
Stream是 Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。
什么是 Stream
流(Stream)到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”
注意:
①Stream自己不会存储元素。
②Stream不会改变源对象。相反,他们会返回一个持有结果的新Str
③Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream的操作三个步骤
- 创建Stream
- 一个数据源(如:集合、数组),获取一个流
- 中间操作
- 一个中间操作链,对数据源的数据进行处理
- 终止操作(终端操作)
- 一个终止操作,执行中间操作链,并产生结果
创建Stream
//创建Stream
@Test
public void test1(){
//1。可以通过Collection系列集合提供的stream()或parallelstream()
ArrayList<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
//2。通过Arrays中的静态方法stream()获取数组流
Employee[] employee = new Employee[10];
Stream<Employee> stream1 = Arrays.stream(employee);
//3。通过Stream类中的静态方法of()
Stream<String> stringStream = Stream.of("", "");
//创建无限流迭代
//迭代
Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
//生成
Stream<Double> generate = Stream.generate(() -> Math.random());
generate.forEach(System.out::println);
}
Stream的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作丕会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”
筛选与切片
(1),创建一个实体类Employee
public class Employee {
private Long id;
private String name;
private int age;
private double salary;
//get&set...
}
(2),创建一个list集合来进行测试
List<Employee> employees = Arrays.asList(
new Employee("张三",32,9999),
new Employee("李四",22,8888),
new Employee("王五",42,7777),
new Employee("赵六",52,6666),
new Employee("赵六",52,6666),
new Employee("赵六",52,6666)
);
(3),filter——接收Lambda ,从流中排除某些元素。
@Test
public void test1(){
//中间操作:不会拉行任何操作
Stream<Employee> stream = employees.stream()
.filter((e) -> {
System.out.println("stream api 操作");
return e.getAge() >= 30;
});
//终止操作:一次性却行全部内容,即“惰性求值
stream.forEach(System.out::println);
}
(4),limit——截断流,使其元素不超过给定数量。
@Test
public void test2(){
employees.stream()
.filter((e) -> {
System.out.println("短路");
return e.getAge() >= 30;
})
.limit(2)
.forEach(System.out::println);
}
(5),skip(n)—跳过元素,返回一个扔掉了前n 个元素的流。若流中元素不足n 个,则返回一个空流。与limit(n)互补
@Test
public void test3(){
employees.stream()
.filter((e) -> {
System.out.println("stream api 操作");
return e.getAge() >= 30;
})
.skip(2)
.forEach(System.out::println);
}
(6),distinct—筛选,通过流所生成元素的hashCode()和equals()去除重复元素
需要重写实体类的hashCode()和equals()方法
@Test
public void test4(){
employees.stream()
.filter((e) -> {
System.out.println("stream api 操作");
return e.getAge() == 52;
})
.limit(3)
.distinct()
.forEach(System.out::println);
}
映射
map—接收Lambda ,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
@Test
public void test1(){
List<String> strings = Arrays.asList("aa", "bb", "cc", "dd", "ee");
strings.stream()
.map((str)->str.toUpperCase())
.forEach(System.out::println);
System.out.println("==================================");
employees.stream()
.map(Employee::getName)
.forEach(System.out::println);
}
mapToDouble接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。
@Test
public void test2(){
DoubleStream doubleStream = employees.stream()
.mapToDouble(Employee::getSalary);
System.out.println("doubleStream.sum() = " + doubleStream.sum());
//System.out.println("doubleStream.count() = " + doubleStream.count());
//System.out.println("doubleStream.max() = " + doubleStream.max());
//System.out.println("doubleStream.min() = " + doubleStream.min());
}
mapToInt接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。
@Test
public void test3(){
IntStream intStream = employees.stream()
.mapToInt(Employee::getAge);
System.out.println("intStream.sum() = " + intStream.sum());
//System.out.println("intStream.count() = " + intStream.count());
//System.out.println("intStream.max() = " + intStream.max());
//System.out.println("intStream.min() = " + intStream.min());
}
mapToLong接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。
@Test
public void test4(){
LongStream longStream = employees.stream()
.mapToLong(Employee::getId);
System.out.println("longStream.sum() = " + longStream.sum());
//System.out.println("longStream.count() = " + longStream.count());
//System.out.println("longStream.max() = " + longStream.max());
//System.out.println("longStream.min() = " + longStream.min());
}
flatMap—接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
@Test
public void test1(){
Stream<Stream<Character>> rStream = strings.stream()
.map(TestStream3::filterCharacter);
rStream.forEach((item)->{
item.forEach(System.out::println);
});
System.out.println("==================================");
strings.stream()
.flatMap(TestStream3::filterCharacter)
.forEach(System.out::println);
}
public static Stream<Character> filterCharacter(String str){
ArrayList<Character> strings = new ArrayList<>();
for(Character cha : str.toCharArray()){
strings.add(cha);
}
return strings.stream();
}
排序
sorted()——自然排序(Comparable
@Test
public void test1(){
List<String> strings = Arrays.asList("aa", "bb", "cc", "dd", "ee");
strings.stream()
.sorted()
.forEach(System.out::println);
}
sorted(Comparator com)—定制排序(Comparator)
@Test
public void test1(){
employees.stream()
.sorted((x,y)->{
if (x.getName().equals(y.getName())){
return x.getName().compareTo(y.getName());
}else {
return x.getAge().compareTo(y.getAge());
}
})
.forEach(System.out::println);
}
Stream的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如: List、Integer,甚至是void 。
查找与匹配
示例
public class TestStream5 {
List<Employee> employees = Arrays.asList(
new Employee("张三",32,9999, Employee.Status.BUSY),
new Employee("李四",22,8888,Employee.Status.FREE),
new Employee("王五",42,7777,Employee.Status.FREE),
new Employee("赵六",52,6666,Employee.Status.VOCATION),
new Employee("赵六",52,6666,Employee.Status.VOCATION),
new Employee("赵六",52,6666,Employee.Status.BUSY)
);
@Test
public void test1(){
//a1lMatch—检查是否匹配所有元素
boolean b1 = employees.stream()
.allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println("b1 = " + b1);
//anyMatch——检查是否至少匹配一个元素
boolean b2 = employees.stream()
.anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println("b2 = " + b2);
//noneMatch——检查是否没有匹配所有元素
boolean b3 = employees.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println("b3 = " + b3);
//findFirst——返回第一个元素
Optional<Employee> op = employees.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findFirst();
System.out.println("op = " + op);
//findAny——返回当前流中的任意元素
Optional<Employee> op2 = employees.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findAny();
System.out.println("op2 = " + op2);
//count——返回流中元素的总个数
long count = employees.stream()
.count();
System.out.println("count = " + count);
//max——返回流中最大值
Optional<Employee> max = employees.stream()
.max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println("max = " + max);
//min—返回流中最小值
Optional<Double> min = employees.stream()
.map(Employee::getSalary)
.min(Double::compare);
System.out.println("min = " + min);
}
}
归约
reduce(T identity,BinaryOperator) / reduce(BinaryOperator)—可以将流中元素反复结合起来,得到一个值。
public class TestStream6 {
List<Employee> employees = Arrays.asList(
new Employee("张三",32,9999, Employee.Status.BUSY),
new Employee("李四",22,8888,Employee.Status.FREE),
new Employee("王五",42,7777,Employee.Status.FREE),
new Employee("赵六",52,6666,Employee.Status.VOCATION),
new Employee("赵六",52,6666,Employee.Status.VOCATION),
new Employee("赵六",52,6666,Employee.Status.BUSY)
);
/**
* reduce(T identity,BinaryOperator) / reduce(BinaryOperator)—可以将流中元素反复结合起来,得到一个值。
*/
@Test
public void test1(){
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer reduce = integers.stream()
.reduce(0, (x, y) -> x + y);
System.out.println("reduce = " + reduce);
System.out.println("===========================");
Optional<Double> reduce1 = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println("reduce1 = " + reduce1);
}
}
备注: map和reduce的连接通常称为map-reduce模式,因.Google用它来进行网络搜索而出名。
收集
collect—将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
public class TestStream7 {
List<Employee> employees = Arrays.asList(
new Employee("张三",32,9999, Employee.Status.BUSY),
new Employee("李四",22,8888,Employee.Status.FREE),
new Employee("王五",42,7777,Employee.Status.FREE),
new Employee("赵六",52,6666,Employee.Status.VOCATION),
new Employee("赵六",52,6666,Employee.Status.VOCATION),
new Employee("赵六",52,6666,Employee.Status.BUSY)
);
/**
* 收集
* collect—将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
*/
@Test
public void test1(){
List<String> collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
collect.forEach(System.out::println);
System.out.println("==============================");
Set<String> collect1 = employees.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
collect1.forEach(System.out::println);
System.out.println("==============================");
HashSet<String> collect2 = employees.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));
collect2.forEach(System.out::println);
}
@Test
public void test2(){
//总数
long count = employees.stream()
.collect(Collectors.counting());
System.out.println("count = " + count);
//平均值
Double collect = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println("collect = " + collect);
//总合
Double collect1 = employees.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println("collect1 = " + collect1);
//最大值
Optional<Employee> max = employees.stream()
.collect(Collectors.maxBy((x,y)->Double.compare(x.getSalary(),y.getSalary())));
System.out.println("max = " + max);
//最小值
Optional<Double> min = employees.stream()
.map(Employee::getSalary)
.collect(Collectors.minBy(Double::compare));
System.out.println("min = " + min);
}
//分组
@Test
public void test3(){
Map<Employee.Status, List<Employee>> collect = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
for (Employee.Status key : collect.keySet()){
System.out.println(key+"======================");
List<Employee> employees = collect.get(key);
employees.forEach(System.out::println);
}
}
//多级分组
@Test
public void test4(){
Map<Employee.Status, Map<String, List<Employee>>> collect = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
if (e.getAge() <= 35) {
return "青年";
} else if (e.getAge() <= 50) {
return "中年";
} else {
return "老年";
}
})));
System.out.println("collect = " + collect);
}
//分区
@Test
public void test5(){
Map<Boolean, List<Employee>> collect = employees.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 5000));
System.out.println("collect = " + collect);
}
@Test
public void test6(){
DoubleSummaryStatistics collect = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println("collect.getSum() = " + collect.getSum());
System.out.println("collect.getMax() = " + collect.getMax());
System.out.println("collect.getMin() = " + collect.getMin());
System.out.println("collect.getCount() = " + collect.getCount());
System.out.println("collect.getAverage() = " + collect.getAverage());
}
@Test
public void test7(){
String collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(","));
System.out.println("collect = " + collect);
}
}
Optional类
Optional<T〉类(java.util.Optional)是一个容器类,代表一个值存在或不存在,原来用null表示一个值不存在,现在 Optional可以更好的表达这个概念。并且可以避免空指针异常。
名称 | 描述 |
---|---|
Optional.of(T t) | 创建一个Optional 实例 |
Optional.empty() | 创建一个空的 Optional实例 |
Optional.ofNullable(T t) | 若t不为 null,创建0ptional实例,否则创建空实例 |
isPresent() | 判断是否包含值 |
orElse(T t) | 如果调用对象包含值,返回该值,否则返回t |
orElseGet(Supplier s) | 如果调用对象包含值,返回该值,否则返回s获取的值 |
map(Function f) | 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty() |
flatMap(Function mapper) | 与map类似,要求返回值必须是Optional |
示例
/**
* Optional 容器类的常用方法:
*/
public class TestOptional {
//Optional.of(T t) :创建一个 Optional 实例
@Test
public void test1(){
Optional<Employee> employee = Optional.of(new Employee());
Employee employee1 = employee.get();
System.out.println("employee1 = " + employee1);
}
//Optional.empty() :创建一个空的 Optional实例
@Test
public void test2(){
Optional<Employee> empty = Optional.empty();
System.out.println("empty = " + empty.get());
}
//Optional.ofNullable(T t):若t不为null,创建Optional 实例,否则创建空实例
@Test
public void test3(){
Optional<Employee> optional = Optional.ofNullable(null);
System.out.println("optional.get() = " + optional.get());
}
//isPresent() :判断是否包含值
@Test
public void test4(){
Optional<Employee> optional = Optional.ofNullable(null);
if (optional.isPresent()){
System.out.println("optional.get() = " + optional.get());
}
}
//orElse(T t) :如果调用对象包含值,返回该值,否则返回t
@Test
public void test5(){
Optional<Employee> optional = Optional.ofNullable(null);
Employee employee = optional.orElse(new Employee("张三", 19, 900));
System.out.println("employee = " + employee);
}
//orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回s 获取的值
@Test
public void test6(){
Optional<Employee> optional = Optional.ofNullable(null);
Employee employee = optional.orElseGet(() -> new Employee("张三", 19, 9000));
System.out.println("employee = " + employee);
}
//map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
@Test
public void test7(){
Optional<Employee> optional = Optional.ofNullable(new Employee("张三", 19, 9000));
Optional<String> optionalS = optional.map((e) -> e.getName());
System.out.println("optionalS = " + optionalS.get());
}
//flatMap(Function mapper):与 map类似,要求返回值必须是Optional
@Test
public void test8(){
Optional<Employee> optional = Optional.ofNullable(new Employee("张三", 19, 9000));
Optional<String> optionalS = optional.flatMap((e) -> Optional.of(e.getName()));
System.out.println("optionalS = " + optionalS.get());
}
}
orElse()和 orElseGet()的不同之处
乍一看,这两种方法似乎起着同样的作用。然而事实并非如此。我们创建一些示例来突出二者行为上的异同。
我们先来看看对象为空时他们的行为:
//orElse() 和 orElseGet() 的不同之处
@Test
public void test10(){
Man man = null;
Man man1 = Optional.ofNullable(man).orElse(getMan());
Man man2 = Optional.ofNullable(man).orElseGet(() -> getMan());
}
//创建Man对象
public Man getMan(){
System.out.println("创建了Man");
return new Man();
}
上面的代码中,两种方法都调用了 createNewUser() 方法,这个方法会记录一个消息并返回 User 对象。
代码输出如下:
由此可见,当对象为空而返回默认对象时,行为并无差异。
我们接下来看一个类似的示例,但这里 Optional 不为空
//orElse() 和 orElseGet() 的不同之处
@Test
public void test10(){
Man man = new Man();
Man man1 = Optional.ofNullable(man).orElse(getMan("orElse"));
Man man2 = Optional.ofNullable(man).orElseGet(() -> getMan("orElseGet"));
}
//创建Man对象
public Man getMan(String name){
System.out.println(name+"创建了Man");
return new Man();
}
这次的输出:
这个示例中,两个 Optional 对象都包含非空值,两个方法都会返回对应的非空值。不过,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不创建 User对象。
在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。
例题
创建Man,Goddess类
public class Goddess {
private String name;
//get&set..
}
public class Man {
private Goddess goddess;
//get&set..
}
测试
//例题
@Test
public void test9(){
String name = getManGoddessName(new Man());
System.out.println("name = " + name);
}
//需求:获取男人心中的女神
public String getManGoddessName(Man man){
if (man!=null){
if (man.getGoddess()!=null){
return man.getGoddess().getName();
}
}
return null;
}
使用Optional
新增NewMan类
public class NewMan {
private Optional<Goddess> goddess = Optional.empty();
}
测试
//例题
@Test
public void test9(){
Optional<Goddess> goddess = Optional.ofNullable(null);
Optional<NewMan> newMan = Optional.ofNullable(new NewMan(goddess));
String name2 = getManGoddessName2(newMan);
System.out.println("name2 = " + name2);
}
//需求:获取男人心中的女神
public String getManGoddessName2(Optional<NewMan> newMan){
return newMan.orElse(new NewMan())
.getGoddess()
.orElse(new Goddess("仓老师"))
.getName();
}
结果现在的代码看起来比之前采用条件分支的冗长代码简洁多了。
接口中的默认方法与静态方法
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。 静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。 默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。 比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。
接口中的默认方法
public interface MyFun<T>{
public Integer getValue(Integer num);
default String getName(){
return "Hello word";
}
}
接口默认方法的”类优先”原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时
-
选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
-
接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突
public class test {
interface Fun1{
default String getName(){
return "Hello word";
}
}
interface Fun2{
default String getName(){
return "你好世界";
}
}
/**
* 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法
* (不管方法是否是默认方法),那么必须覆盖该方法来解决冲突
*/
class FunImpl implements Fun1,Fun2{
@Override
public String getName() {
return Fun1.super.getName();
}
}
}
接口中的静态方法
Java8 中,接口中允许添加静态方法。
public interface test2 {
String test1();
//接口中的默认方法
default String test2(){
return "Hello Word";
}
//接口中的静态方法
static String test3(){
return "你好世界";
}
}
新时间日期API
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。在旧版的 Java 中,日期时间 API 存在诸多问题,比如:
-
非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
-
设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
-
时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
-
Local(本地) − 简化了日期时间的处理,没有时区的问题。
-
Zoned(时区) − 通过制定的时区处理日期时间。
-
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
使用 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) |
etDayOfYear | 获得年份天数(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日历系统是国际标准化组织制定的现代公民的日期和时间的表示法
*/
public class testDateTimeApi {
//now:静态方法,根据当前时间创建对象
@Test
public void test1(){
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("localDateTime = " + localDateTime);
}
//of:静态方法,根据指定日期/时间创建 对象
@Test
public void test2(){
LocalDateTime localDateTime = LocalDateTime.of(2021, 1, 1, 1, 1);
System.out.println("localDateTime = " + localDateTime);
}
//plusDays, plusWeeks, plusMonths, plusYears
//向当前 LocalDate 对象添加 几天、 几周、 几个月、 几年
@Test
public void test3(){
LocalDateTime localDateTime = LocalDateTime.of(2021, 1, 1, 1, 1);
LocalDateTime localDateTime1 = localDateTime
//增加一天
.plusDays(1)
//增加一周
.plusWeeks(1)
//增加一个月
.plusMonths(1)
//增加一年
.plusYears(1);
System.out.println("localDateTime1 = " + localDateTime1);
}
//minusDays, minusWeeks, minusMonths, minusYears
//从当前 LocalDate 对象减去 几天、 几周、 几个月、 几年
@Test
public void test4(){
LocalDateTime localDateTime = LocalDateTime.of(2021, 1, 1, 1, 1);
LocalDateTime localDateTime1 = localDateTime
//减少一天
.minusDays(1)
//减少一周
.minusWeeks(1)
//减少一个月
.minusMonths(1)
//减少一年
.minusYears(1);
System.out.println("localDateTime1 = " + localDateTime1);
}
//plus, minus
//添加或减少一个 Duration 或 Period
@Test
public void test5(){
// TODO plus, minus 如何使用
LocalDateTime localDateTime = LocalDateTime.of(2021, 1, 1, 1, 1);
//localDateTime.plus()
}
//withDayOfMonth, withDayOfYear, withMonth, withYear
//将月份天数、年份天数、月份、年 份修改为指定的值并返回新的 LocalDate 对象
@Test
public void test6(){
LocalDateTime localDateTime = LocalDateTime.of(2021, 1, 3, 1, 1);
LocalDateTime localDateTime1 = localDateTime
//修改当月天数且不能当月的最大天数或者小于零
.withDayOfMonth(29)
//修改当年天数且不能当年的最大天数或者小于零
//.withDayOfYear(180)
//修改月份且不能大于12个月或者小于零
.withMonth(1)
//修改年份
//自07的1970年第一时刻的纪元以来,Joda-Time跟踪时间作为计数milliseconds.此计数使用64-bit长整数保留.因此,从技术上讲,最大值和最小值是long的限制.
.withYear(1000);
System.out.println("localDateTime1 = " + localDateTime1);
}
/**
* getDayOfMonth获得月份天数(1-31)
* etDayOfYear获得年份天数(1-366)
* getDayOfWeek获得星期几(返回一个 DayOfWeek 枚举值)
* getMonth获得月份, 返回一个 Month 枚举值
* getMonthValue获得月份(1-12)getYear获得年份
*/
@Test
public void test7(){
LocalDateTime localDateTime = LocalDateTime.of(2021, 1, 3, 1, 1);
System.out.println("获得月份天数(1-31) = " + localDateTime.getDayOfMonth());
System.out.println("获得年份天数(1-366) = " + localDateTime.getDayOfYear());
System.out.println("获得星期几(返回一个 DayOfWeek 枚举值) = " + localDateTime.getDayOfWeek());
System.out.println("获得月份, 返回一个 Month 枚举值 = " + localDateTime.getMonth());
System.out.println("获得月份(1-12)getYear获得年份 = " + localDateTime.getMonthValue());
}
//until
//获得两个日期之间的 Period 对象, 或者指定 ChronoUnits 的数字
@Test
public void test8(){
// TODO until 如何使用
}
//isBefore, isAfter
//比较两个 LocalDate
@Test
public void test9(){
LocalDateTime localDateTime = LocalDateTime.of(2021, 1, 3, 1, 1);
LocalDateTime localDateTime1 = LocalDateTime.now();
boolean before = localDateTime.isBefore(localDateTime1);
boolean after = localDateTime.isAfter(localDateTime1);
System.out.println("before = " + before);
System.out.println("after = " + after);
}
//isLeapYear
//判断是否是闰年
@Test
public void test10(){
LocalDate localDate = LocalDate.now();
boolean leapYear = localDate.isLeapYear();
System.out.println("是否是闰年 = " + leapYear);
}
}
Instant 时间戳
用于“时间戳”的运算。它是以Unix元年(传统 的设定为UTC时区1970年1月1日午夜时分)开始 所经历的描述进行运算
/**
* Instant :时间戳(以 Unix元年:1970年1月1日00:00:00到某个时间之间的毫秒值)
*/
public class TestInstant {
@Test
public void test1(){
Instant instant = Instant.now();
System.out.println("默认获取UTC时区 = " + instant);//默认获取UTC时区
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println("时间偏移量 = " + offsetDateTime);
long milli = instant.toEpochMilli();
System.out.println("毫秒数 = " + milli);
Instant ofEpochMilli = Instant.ofEpochMilli(60);
System.out.println("时间运算 = " + ofEpochMilli);
}
}
Duration 和 Period
Duration:用于计算两个“时间”间隔
Period:用于计算两个“日期”间隔
/**
* Duration:用于计算两个“时间”间隔
* Period:用于计算两个“日期”间隔
*/
public class TestDurationAndPeriod {
//Duration:用于计算两个“时间”间隔
@Test
public void test1(){
Instant instant1 = Instant.now();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant instant2 = Instant.now();
Duration between = Duration.between(instant1, instant2);
System.out.println("两个时间的间隔 = " + between.toMillis());
System.out.println("======================================");
LocalTime localTime1 = LocalTime.now();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
LocalTime localTime2 = LocalTime.now();
Duration between1 = Duration.between(localTime1, localTime2);
System.out.println("两个时间的间隔 = " + between1.toMillis());
}
//Period:用于计算两个“日期”间隔
@Test
public void test2(){
LocalDate localDate1 = LocalDate.of(2015, 1, 1);
LocalDate localDate2 = LocalDate.now();
Period between = Period.between(localDate1, localDate2);
System.out.println("两个日期的间隔 = " + between);
System.out.println("年 = " + between.getYears());
System.out.println("月 = " + between.getMonths());
System.out.println("日 = " + between.getDays());
}
}
日期的操作
TemporalAdjuster : 时间校正器。有时我们可能需要获 取例如:将日期调整到“下个周日”等操作。
TemporalAdjusters : 该类通过静态方法提供了大量的常 用 TemporalAdjuster 的实现。
/**
* TemporalAdjuster : 时间校正器。有时我们可能需要获 取例如:将日期调整到“下个周日”等操作。
* TemporalAdjusters : 该类通过静态方法提供了大量的常 用 TemporalAdjuster 的实现。
*/
public class TestTemporalAdjuster {
@Test
public void test1(){
LocalDateTime ldt = LocalDateTime.now();
System.out.println("当前时间 = " + ldt);
LocalDateTime withDayOfMonth = ldt.withDayOfMonth(10);
System.out.println("增加10天 = " + withDayOfMonth);
LocalDateTime with = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
System.out.println("下一个星期日 = " + with);
//自定义:下一个工作日
LocalDateTime dateTime = ldt.with((l) -> {
LocalDateTime ldt1 = (LocalDateTime) l;
DayOfWeek day = ldt1.getDayOfWeek();
if (day.equals(DayOfWeek.FRIDAY)) {
return ldt1.plusDays(3);
} else if (day.equals(DayOfWeek.SATURDAY)) {
return ldt1.plusDays(2);
} else {
return ldt1.plusDays(1);
}
});
System.out.println("下一个工作日 = " + dateTime);
}
}
解析与格式化
java.time.format.DateTimeFormatter 类:该类提供了三种 格式化方法:
- 预定义的标准格式
- 语言环境相关的格式
- 自定义的格式
/**
* java.time.format.DateTimeFormatter 类:该类提供了三种 格式化方法:
* 1. 预定义的标准格式
* 2. 语言环境相关的格式
* 3. 自定义的格式
*/
public class TestFormatDateTime {
@Test
public void test1(){
DateTimeFormatter isoDateTime = DateTimeFormatter.ISO_DATE_TIME;
LocalDateTime localDateTime = LocalDateTime.now();
String format = localDateTime.format(isoDateTime);
System.out.println("默认格式 = " + format);
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String format1 = localDateTime.format(dateTimeFormatter);
System.out.println("format1 = " + format1);
}
}
时区的处理
java8 中加入了对时区的支持,带时区的时间为分别为: ZonedDate、ZonedTime、ZonedDateTime,其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式 例如 :Asia/Shanghai 等。
ZoneId:该类中包含了所有的时区信息
-
getAvailableZoneIds() : 可以获取所有时区时区信息
-
of(id) : 用指定的时区信息获取 ZoneId 对象
public class TestZonedDate {
@Test
public void test2(){
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
zoneIds.forEach(System.out::println);
}
@Test
public void test3(){
LocalDateTime ldt1 = LocalDateTime.now(ZoneId.of("Europe/Tallinn"));
System.out.println("ldt1 = " + ldt1);
LocalDateTime ldt2 = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("ldt2 = " + ldt2);
ZonedDateTime zonedDateTime = ldt2.atZone(ZoneId.of("Asia/Shanghai"));
System.out.println("zonedDateTime = " + zonedDateTime);
}
}
重复注解与类型注解
Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。
重复注解
可以在一个类上或方法上重复多次使用的注解
示例
1、自定义可重复注解:使用@Repeatable元注解,参数为可重复注解的容器
@Repeatable(MyAnnotations.class)
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "java8";
}
2、注解容器定义
注意:可重复注解的容器的Target和Retention必须要比可重复注解的范围大
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
3、使用:
重复注解的作用:可以用来指定联合主键等
public class TestAnnotation {
@Test
public void test() throws NoSuchMethodException, SecurityException {
Class<TestAnnotation> clazz = TestAnnotation.class;
Method method = clazz.getMethod("show");
MyAnnotation[] annotationsByType = method.getAnnotationsByType(MyAnnotation.class);
for (MyAnnotation myAnnotation : annotationsByType) {
System.out.println(myAnnotation.value());
}
}
@MyAnnotation("hello")
@MyAnnotation("world")
public void show() {
}
}
类型注解
在入参的地方可以使用自定义注解,只需要将自定义注解的目标类型标注为ElementType.PARAMETER
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "java8";
}
使用:
public void show(@MyAnnotation("str") String str) {
System.out.println(str);
}