目录
- JDK1.8的新特性
- 接口(interface)的默认方法与静态方法
- lambda表达式(是一个匿名函数)
- stream流
- 什么需要使用stream流、stream流的思想是怎么样的
- Stream流的分类及操作次数提示
- Stream流的三种获取方式
- Stream流中的常用方法
- forEach:消费Stream流中的每个元素
- count:统计Stream流中元素的个数
- filter : 过滤Stream流中的数据
- limit:获取前几个元素 和 skip跳过前几个元素,都会返回一个新的Stream流
- match:判断Stream流中的元素是否满足匹配要求,3个方法
- find:返回Stream流中匹配要求的元素
- max:返回最大值,min:返回最小值
- reduce:对Stream流中的元素进行汇总,最终返回一个值
- map:将Stream流中的元素转换为另一种类型
- mapToInt:将一个Stream流转换为IntStream流
- Stream静态方法concat:将多个流中的元素合并到一个流中
- Stream流中的数据收集
- Stream流中的数据聚合,对已经处理过的数据进行聚合计算
- Stream将数据进行分组
- Stream流中的分区操作
- Stream 并行流与串行流
- Optional 可以很好的解决空指针异常
- 新增的时间日期API
JDK1.8的新特性
接口(interface)的默认方法与静态方法
- 接口的代码示例
public interface JDK8Interface {
// 1. 定义属性,默认修饰符为: public static final(无法修改)
int color = 10;
// 2. 定义方法,默认修饰符为:public abstract(无法修改)
void show();
// 3. jdk8新特性,定义接口默认方法,默认加了public修饰(无法修改)
default void defaultMethod(){
System.out.println("defaultMethod");
}
// 4. jdk8新特性,定义接口静态方法,可以通过类名.的方式调用,默认加了public修饰(无法修改)
static void staticMethod() {
System.out.println("staticMethod");
}
}
- 实现类代码实例
public class JDK8InterfaceImpl implements JDK8Interface {
@Override
public void show() {
System.out.println("show");
}
}
- Test测试类方法
public class Test {
public static void main(String[] args) {
JDK8InterfaceImpl jdk8Interface = new JDK8InterfaceImpl();
// 1. 调用实现的方法
jdk8Interface.show();
// 2. 调用接口的default方法
jdk8Interface.defaultMethod();
// 3. 调用接口的静态方法, 不可以通过实现类的对象调用
JDK8Interface.staticMethod();
}
}
lambda表达式(是一个匿名函数)
为什么需要使用lambda表达式
lambda表达式是为了能够简化实现匿名内部类的代码。
举个例子,创建一个线程并调用,采用匿名内部类和lambda表达式的方式
public class Test {
public static void main(String[] args) {
// 1. 匿名内部类的方式,一共6行代码
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-- 线程正在运行 --");
}
}).start();
// 2.lambda表达式的方式
new Thread(() -> System.out.println(Thread.currentThread().getName() + "-- 线程正在运行 --")).start();
}
}
lambda表达式的使用条件
- 要将匿名内部类简化为lambda表达式,那么该匿名内部类的类型必须为一个函数接口类型
应用场景
-
写在方法中作为一个函数式接口的返回值
-
成为方法的参数或局部变量(前提是类型为一个函数式接口)
函数接口 及 @FuncationInterface
-
该接口中必须有且只能有一个抽象方法
-
可以在接口中添加@FuncationInterface(函数式接口),一旦加了该注释,那么该接口中就必须符合上一点的规范。
-
该接口中定义Object类中的方法
-
该接口中可以定义default和static方法
lambda表达式基本语法
lambda表达式的三个组成部分 : () -> {}
1. ( ) 参数列表,不需要写参数类型
2. -> 参数列表与方法体的连接符
3. { } 方法体
lambda的简化规则
-
当参数列表的参数数量为1时候,可以省略小括号
-
当方法体只有一条语句时,可以省略大括号
匿名内部类的原理与lambda表达式的区别
所需类型不一样
-
匿名内部类的类型可以是类、抽象类、接口
-
lambda表达式的类型只能是接口
类型的抽象方法数量不一样
-
匿名内部类的类型可以有多个抽象方法
-
lambda表达式的类型有且只能有一个抽象方法
实现原理不一样
-
匿名内部类
- 会创建一个类来实现当前的接口并重写方法,重写的方法中包含我们实际需要运行的代码(我们自己写的那一段方法体)。
-
lambda表达式
-
会在当前类中创建一个方法,其中包含我们需要运行的代码
-
会创建一个类来实现该接口,该接口会调用在当前类中生成的方法
-
常用的4个函数式接口
- Consumer<T> : void accept(T t) 消费型接口
- Supplier<T> : T get() 提供型接口
- Function<T,R> : R apply(T t) 函数型接口
- Predicate<T> : boolean test(T t) 断言型接口
方法引用(代码的简化,主要是函数式接口与需要引用的方法返回值与参数类型及数量一致)
方法引用的三种格式
-
对象::成员方法名
- 当函数式接口的方法返回值、形参数量 与 需要调用的对象的成员方法一致时
// Consumer的方法: void accept(T t);因为用了泛型,所以T相当于String
// println方法:public void println(String x) {}
Consumer<String> consumer1 = s -> System.out.println(s);
Consumer<String> consumer2 = System.out::println;
- 类::静态方法名
- 当函数式接口的方法返回值、形参数量 与 需要调用的静态方法一致时
// Comparator的方法:int compare(T o1, T o2);因为用了泛型,所以T相当于String
// Integer的方法:public int compareTo(Integer anotherInteger);
Comparator<Integer> comparator1 = (o1,o2) -> o1 - o2;
Comparator<Integer> comparator2 = Integer::compareTo;
- 类::成员方法名
- x作为方法的调用者,y作为方法的实际参数时
// BiFunction的方法:R apply(T t, U u);由于传递了泛型的参数类型,所以T和U都相当于String,R相当于Boolean
// equals方法:public boolean equals(Object anObject);
public class Test {
public static void main(String[] args) {
// BiFunction<String,String,Boolean> bif = (x,y) -> x.equals(y);
BiFunction<String,String,Boolean> bif = String::equals;
Boolean apply = bif.apply("abc", "abc");
System.out.println(apply);
}
}
- 类::new
- 这一种的适用条件其实跟第一种、第二种是一样的,只不过方法返回值变成了一个对象而已。
- 类[]::new
- 同上
stream流
什么需要使用stream流、stream流的思想是怎么样的
-
平时我们操作集合获取其中的某些元素时,总是不可避免的需要对集合进行遍历遍历再遍历,这就使得代码量不断扩大。而stream流可以帮我们解决这一系列问题,让我们的代码变得更简洁,操作也更加方便。
-
stream流的思想与io中的输入输出流不是一个概念,这个stream流指的是将数据像流水线一样进行加工处理,最终再给予最终的结果。
Stream流的分类及操作次数提示
-
Stream流中的方法共分为两类,分别为终结方法(返回最终结果)与非终结方法(返回一个处理后的Stream流)。若是一个Stream流没有执行终结方法,则非终结方法不会被执行。
-
一个Stream流只能被操作一次(!!!),意思是只要一个流你调用过任何方法,那么该流就无法被再次使用(该流调用后返回的流除外),会抛出异常:IllegalStateException: stream has already been operated upon or closed
Stream流的三种获取方式
public class StreamTest {
public static void main(String[] args) {
// 1. 通过Collection接口中的默认方法stream()获取,Collection接口的子类有List和Set
Stream<String> stream1 = new ArrayList<String>().stream();
Stream<String> stream2 = new HashSet<String>().stream();
// 2. 通过Stream接口的静态方法 Stream<T> of(T t)
Stream<Integer> stream3 = Stream.of(1235);
// 3. 通过Stream接口的静态方法 Stream<T> of(T... values),
// 注意:如果直接传递一个数组的话,不能为基本数据类型的数组
Stream<Integer> stream4 = Stream.of(5, 98, 55, 11, 77, 35, 36);
}
}
Stream流中的常用方法
forEach:消费Stream流中的每个元素
-
forEach的方法定义如下:
void forEach(Consumer<? super T> action);
-
forEach方法的基本使用
Stream<String> stream = Arrays.asList("张三", "李四", "王五", "赵六").stream();
stream.forEach((s) -> System.out.println(s));
- 运行结果如下
张三
李四
王五
赵六
count:统计Stream流中元素的个数
-
count方法的定义如下
long count();
-
count方法的基本使用
Stream<String> stream = Arrays.asList("张三", "李四", "王五", "赵六").stream();
long count = stream.count();
System.out.println(count); // 结果为4
filter : 过滤Stream流中的数据
-
方法的定义如下
Stream<T> filter(Predicate<? super T> predicate);
-
filter方法的基本使用
Stream<String> stream = Arrays.asList("张三峰", "李四", "王五米", "赵六").stream();
// 过滤出所有字符串长度为3的数据并输出
stream.filter(s -> s.length() == 3).forEach(System.out::println);
limit:获取前几个元素 和 skip跳过前几个元素,都会返回一个新的Stream流
- 方法定义
Stream<T> limit(long maxSize);
Stream<T> skip(long n);
- limit的基本使用
Stream<String> stream = Arrays.asList("张三峰", "李四", "王五米", "赵六").stream();
// limit:获取流中的前2个元素并输出,结果为:张三峰、李四
stream.limit(2).forEach(System.out::println);
- skip的基本使用
Stream<String> stream = Arrays.asList("张三峰", "李四", "王五米", "赵六").stream();
// skip: 跳过流中的前2个元素,只获取之后的元素,结果为:王五米、赵六
stream.skip(2).forEach(System.out::println);
match:判断Stream流中的元素是否满足匹配要求,3个方法
- 方法定义
boolean allMatch(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
- match的基本使用
Stream<String> stream = Arrays.asList("张三峰", "李四", "王五米", "赵六").stream();
// 1. 判断Stream流中的字符串元素是否全都满足长度为3,满足返回true,否则为false
// System.out.println(stream.allMatch(s -> s.length() == 3)); // false
// 2. 判断Stream流中的字符串元素是否有任意一个满足长度为3,满足返回true,否则为false
// System.out.println(stream.anyMatch(s -> s.length() == 3)); // true
// 3. 判断Stream流中的字符串元素是否全都 不满足长度为3,满足返回true,否则为false
System.out.println(stream.noneMatch(s -> s.length() == 3)); // fasle
find:返回Stream流中匹配要求的元素
- 方法定义
Optional<T> findFirst();
Optional<T> findAny(); 串行时基本返回第一个元素,并行就未必了。
- find的基本使用
Stream<String> stream = Arrays.asList("张三峰", "李四", "王五米", "赵六").stream();
// 1. 获取Stream流中的第一个元素
// Optional<String> first = stream.findFirst();
// System.out.println(first.get());
// 2. 获取Stream流中的任意一个元素, 串行、或者数据量小时可能会一直返回第一个元素
Optional<String> any = stream.findFirst();
System.out.println(any.get());
max:返回最大值,min:返回最小值
-
方法定义
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
-
max的基本使用
Stream<String> stream = Arrays.asList("z张三峰", "b李四", "a王五米", "c赵六").stream();
Optional<String> max = stream.max((s1, s2) -> s1.compareTo(s2));
System.out.println(max.get()); // 输出结果为z张三峰
- min的基本使用
Stream<Integer> stream = Arrays.asList(23, 44, 95, 11).stream();
Optional<Integer> min = stream.min((i1, i2) -> i1.compareTo(i2));
System.out.println(min.get()); // 输出结果为11
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); 暂不演示
– 解释:第一个参数为初始值,第二个函数化接口父类中的接口及方法的定义如下:
- 接口:`public interface BinaryOperator<T> extends BiFunction<T,T,T>`
- 父类BiFunction中的方法:`R apply(T t, U u);`
- 因此可以看出,是传入2个同类型的参数,再返回一个相同类型的参数。
- reduce的基本使用
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
// 获取Stream流中所有整数之和
// 1. 使用 T reduce(T identity, BinaryOperator<T> accumulator) 实现数据的增加统计
// 这里的x值为0,每次都会把x + y 的值赋给x
// 其中的y就是每次遍历出来的整数
// Integer sum = stream.reduce(2, (x, y) -> x + y);
// System.out.println(sum); // 12
// 2. 使用 Optional<T> reduce(BinaryOperator<T> accumulator) 实现数据的增加统计
Optional<Integer> sum = stream.reduce((x, y) -> x + y);
System.out.println(sum.get()); // 10
map:将Stream流中的元素转换为另一种类型
- 方法定义
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
- map的基本使用
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
// 将strem流中的数据类型转换为String
stream.map((i) -> String.valueOf(i)).forEach(s -> {
System.out.println("当前的数据类型为:" + s.getClass().getSimpleName()); // String
});
mapToInt:将一个Stream流转换为IntStream流
-
存在的意义:会提高处理Int数据的速度,当Stream流中不仅是Int数据且数据量大时可以考虑。
-
方法定义
IntStream mapToInt(ToIntFunction<? super T> mapper);
-
mapToInt的基本使用
Stream<String> stream1 = Arrays.asList("1", "2", "3", "4").stream();
// 将Stream流转换为IntStream流
IntStream intStream = stream1.mapToInt(s -> Integer.parseInt(s));
intStream.forEach(System.out::println);
Stream静态方法concat:将多个流中的元素合并到一个流中
-
方法定义
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
-
map的基本使用
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
// 将strem流中的数据类型转换为String
stream.map((i) -> String.valueOf(i)).forEach(s -> {
System.out.println("当前的数据类型为:" + s.getClass().getSimpleName()); // String
});
Stream流中的数据收集
- 方法定义
<R, A> R collect(Collector<? super T, A, R> collector);
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); 暂不演示
收集到List、ArrayList集合当中
Stream<String> stream = Arrays.asList("1", "2", "3", "4").stream();
// 1. 收集到List集合中
// List<String> collect = stream.collect(Collectors.toList());
// 2. 收集到ArrayList集合中
ArrayList<String> collect = stream.collect(Collectors.toCollection(() -> new ArrayList<>()));
收集到Set、HashSet集合中
Stream<String> stream = Arrays.asList("1", "2", "3", "4").stream();
// 1. 收集到Set集合中
// Set<String> collect = stream.collect(Collectors.toSet());
// 2. 收集到HashSet集合中
HashSet<String> collect = stream.collect(Collectors.toCollection(() -> new HashSet<>()));
收集到数组当中,Object与或任意引用类型
Stream<String> stream = Arrays.asList("aa", "cc", "bb", "dd").stream();
// 1. 获取Object类型的数组
// Object[] objects = stream.toArray();
// 2. 获取任意类型的数组,只能是引用类型
// String[] strings = stream.toArray(s -> new String[4]); // 必须手动指定长度
String[] strings = stream.toArray(String[]::new);
Stream流中的数据聚合,对已经处理过的数据进行聚合计算
当我们使用Stream流处理数据后,可以像数据库的聚合函数一样对数据的某个字段进行聚合操作。最大值、最小值、求和、平均值、统计数量。
最大值
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
// 1. 最大值
Optional<Integer> max = stream.collect(Collectors.maxBy((i1, i2) -> Integer.compare(i1, i2)));
System.out.println(max.get()); // 4
最小值
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
// 2. 最小值
Optional<Integer> min = stream.collect(Collectors.minBy((i1, i2) -> Integer.compare(i1, i2)));
System.out.println(min.get()); // 1
求和
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
// 3. 求和
int sum = stream.collect(Collectors.summingInt(i -> i));
System.out.println(sum); // 10
平均值
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
// 4. 平均值
Double average = stream.collect(Collectors.averagingInt(i -> i));
System.out.println(average); // 2.5
统计数量
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
// 5. 统计数量
Long count = stream.collect(Collectors.counting());
System.out.println(count); // 4
Stream将数据进行分组
单级分组
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("张三",15));
list.add(new Person("李四",26));
list.add(new Person("王五",15));
list.add(new Person("名吉",15));
list.add(new Person("赵六",12));
Stream<Person> stream = list.stream();
// 1. 按照年龄进行分组
Map<Integer, List<Person>> ageGroupList = stream.collect(Collectors.groupingBy(p -> p.getAge()));
ageGroupList.forEach((k, v) -> System.out.println("年龄为:" + k + ",该组人员有:" + v));
多级分组(可以分无限级)
// 1. 按照年龄进行分组、再将分组后的数据按照姓名分组
Map<Integer, Map<String, List<Person>>> groups = stream.collect
(
Collectors.groupingBy
(
p -> p.getAge(),
Collectors.groupingBy(p -> p.getName())
)
);
groups.forEach((k,v) -> {
System.out.println("按照年龄分组:" + k + ",分组内容为:" + v);
v.forEach((k2,v2) -> System.out.println("按照姓名分组:" + k2 + ",分组内容为:" + v2));
System.out.println("-----------------------------------");
});
分组聚合,分组的同时统计每组有多少人
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("张三",15));
list.add(new Person("李四",26));
list.add(new Person("王五",15));
list.add(new Person("名吉",15));
list.add(new Person("赵六",12));
Stream<Person> stream = list.stream();
// 1. 按照年龄进行分组,并统计有多少人
Map<Integer, Long> collect = stream.collect(Collectors.groupingBy(p -> p.getAge(), Collectors.counting()));
collect.forEach((k, v) -> System.out.println("年龄为:" + k + ",该组人数有:" + v));
Stream流中的分区操作
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("张三",15));
list.add(new Person("李四",26));
list.add(new Person("王五",15));
list.add(new Person("张三",15));
list.add(new Person("赵六",12));
Stream<Person> stream = list.stream();
// 将年龄小于15岁的与年龄不小于15岁的人分为2组,分别为true组和false组
Map<Boolean, List<Person>> collect = stream.collect(Collectors.partitioningBy(p -> p.getAge() < 15));
collect.forEach((k,v) -> System.out.println(k + "组的成员有:" + v));
Stream 并行流与串行流
-
Stream串行流:单个线程执行Stream流中的处理操作
-
Stream并行流:多个线程执行Stream流中的处理操作(forkjoin框架,任务窃取算法)
获得并行流的2种方式
- 通过Collection的实现类直接获取,例如
Stream<Integer> parallelStream = Arrays.asList(1, 4, 5, 1, 6).parallelStream();
- 对一个串行流调用parallel()方法来获取一个并行流
Stream<Integer> stream = Arrays.asList(1, 4, 5, 1, 6).stream();
// 转换为一个并行流
Stream<Integer> parallelStream = stream.parallel();
解决并行流在执行过程中线程安全问题的4种办法
演示并行流的线程安全问题
// 1. 创建一个ArrayList<Integer> 集合,往里面存入1000个数据
ArrayList<Integer> oldList = new ArrayList<>();
for (int i = 1; i <= 1000; i++) {
oldList.add(i);
}
// 2. 获取一个并行流
Stream<Integer> parallelStream = oldList.parallelStream();
// 3. 创建一个新的集合
ArrayList<Integer> newList = new ArrayList<>();
// 4. 使用并行流将oldList集合中的数据添加到newList集合中
parallelStream.forEach(i -> newList.add(i));
// 5. 输出newList集合的元素数量
System.out.println(newList.size()); // 数量不定的变换,或者直接抛出异常,出现了线程安全问题
}
解决方案1:为操作数据的代码添加同步代码块
// 4. 使用并行流将oldList集合中的数据添加到newList集合中
// 这里使用了当前的测试类.class作为同步锁
parallelStream.forEach(i -> {
synchronized (StreamTest.class) {
newList.add(i);
}
});
解决方案2:使用线程安全的容器,比如说Vector
// 3. 创建一个新的集合
Vector<Integer> newList = new Vector<>();
解决方案3:使用Collections的静态方法将一个容器转换为线程安全的
// 3. 创建一个新的集合
ArrayList<Integer> newList1 = new ArrayList<>();
List<Integer> newList = Collections.synchronizedList(newList1);
解决方案4:使用Stream流中的collect或toArray进行操作,则会保证线程安全
ArrayList<Integer> newList = parallelStream.collect(Collectors.toCollection(() -> new ArrayList<>()));
或者
Integer[] integers = parallelStream.toArray(Integer[]::new);
Optional 可以很好的解决空指针异常
Optional类的创建方式
public static <T> Optional<T> of(T value)
public static <T> Optional<T> ofNullable(T value)
public static<T> Optional<T> empty()
// 1. 通过Optional类的静态方法of创建一个Optional对象,不能传null
Optional<Person> op1 = Optional.of(new Person("zhangsan", 15));
// 2. 通过Optional类的静态方法ofNullable创建一个Optional对象,可以传null空对象
Optional<Person> op2 = Optional.ofNullable(null);
// 3. 通过Optional类的静态方法empty()获取一个为空对象的Optional对象
Optional<Object> op3 = Optional.empty();
Optional类的常用方法
get()
获取Optional类中存储的对象,如果为null则会抛出异常
Optional<Person> op1 = Optional.of(new Person("zhangsan", 15));
Person person = op1.get();
System.out.println(person); // Person{name='zhangsan', age=15}
isPresent()
判断存储的对象是否包含值,包含返回true、不包含返回flase
Optional<Person> op1 = Optional.ofNullable(null);
boolean present = op1.isPresent();
System.out.println(present); // false
orElse(T t)
如果Optional中的对象为null,返回该方法传递的值,否则返回原Optional对象中的值
Optional<Person> op1 = Optional.ofNullable(null);
Person person = op1.orElse(new Person("zhang", 15));
System.out.println(person); // new Person("zhang", 15)
orElseGet(Supplier s)
同上方法一致,不过是将T换成了一个函数式接口
Optional<Person> op1 = Optional.ofNullable(new Person("lis",11));
Person person = op1.orElseGet(() -> new Person("lisi", 55));
System.out.println(person); // Person{name='lis', age=11}
ifPresent(Consumer<? super T> consumer)
如果Optional中的对象不为空,则执行一段代码
Optional<Person> op1 = Optional.ofNullable(null);
// 当对象为null时不执行
op1.ifPresent(p -> System.out.println("今天天气真好" + p));
filter
判断Optional中的对象是否满足条件,满足则返回该对象,不满足返回null
Optional<Person> op1 = Optional.ofNullable(null);
// 当对象为null时不执行
op1.ifPresent(p -> System.out.println("今天天气真好" + p));
map
将Optional中的对象转换为另一个类型
Optional<Person> op1 = Optional.ofNullable(new Person(null,15));
// 返回的Optional中的对象值为null
Optional<String> opStr = op1.map(p -> p.getName());
// 返回的Optional中的对象值为15
Optional<Integer> opInt = op1.map(p -> p.getAge());
Optional小案例(帮助理解下源码)
/**
* @author codeStars
* @date 2022/8/6 10:13
* 返回Person对象中的name并且转换为大写,如果name值为空,则返回 name值为null
*/
public class StreamTest {
public static void main(String[] args) {
Optional<Person> op1 = Optional.ofNullable(new Person(null,15));
String name = getUpperCaseName(op1);
System.out.println(name);
}
/**
* 返回Person对象中的name并且转换为大写,如果name值为空,则返回 name值为null
* @param op1
* @return
*/
private static String getUpperCaseName(Optional<Person> op1) {
String msg = "";
if(op1.isPresent()) {
/*
注意这里的toUpperCase不会抛空指针异常。因为op1.map(Person::getName)执行后,
得到的Optional中的对象就已经为空对象了
再调用map方法时,会先判断Optional中的对象是否为空对象
源码如下:
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
所以如果Person中的name为空的话,根本就执行不到toUpperCase
*/
msg = op1.map(Person::getName).map(String::toUpperCase).orElse("name值为null");
}
return msg;
}
}
class Person {
private String name;
private int age;
// 省略 get/set/toString/全参construction
}
新增的时间日期API
以前的日期时间API有哪些问题,新的API解决了什么
-
以前的日期时间API设计不合理
- 在java.util中有一个Date类,用来存储年月日时分秒
-
在java.sql中有一个Date类,用来存储年月日
- 在java.text包中存储了格式化日期的SimpleDateFormat类
- 多个处理日期时间的类分别在不同的包中
-
以前的Date是线程不安全的,导致在多线程情况下出现数据异常等问题。
-
新的API解决了线程安全问题,因为它的每一个日期时间类都是固定不变的,每次对其进行任何修改时,都会返回一个新的日期时间类,因此可以保证在多线程的情况下不会出现线程安全问题。
日期时间的信息获取(年月日、时分秒)
// 获取指定的日期
LocalDate localDate = LocalDate.of(2022, 8, 5);
// 获取当前的日期
LocalDate nowDate = LocalDate.now();
// 1. 获取年
int year = localDate.getYear();
// 2. 获取月
int month = localDate.getMonth().getValue();
// 3. 获取日
int dayOfMonth = localDate.getDayOfMonth();
System.out.println("year = " + year);
System.out.println("month = " + month);
System.out.println("dayOfMonth = " + dayOfMonth);
// 获取指定的时间
LocalTime localTime = LocalTime.of(21,24,50,716000000);
// 获取当前的时间
LocalTime nowTime = LocalTime.now();
// 1. 获取小时
int hour = localTime.getHour();
// 2. 获取分钟
int minute = localTime.getMinute();
// 3. 获取秒
int second = localTime.getSecond();
// 4. 获取纳秒
int nano = localTime.getNano();
System.out.println("hour = " + hour);
System.out.println("minute = " + minute);
System.out.println("second = " + second);
System.out.println("nano = " + nano);
日期时间的修改与比较
增加年月日时分秒
// 获取指定的日期时间
LocalDateTime localDateTime =
LocalDateTime.of(2022, 8, 6 ,19, 30, 30);
// 增加年月日时分秒, // 2022-08-06T19:30:30
System.out.println("未增加前的时间:" + localDateTime);
// 1. 增加一年
localDateTime = localDateTime.plusYears(1);
// 2. 增加一个月
localDateTime = localDateTime.plusMonths(1);
// 3. 增加一天
localDateTime = localDateTime.plusDays(1);
// 4. 增加一个小时
localDateTime = localDateTime.plusHours(1);
// 5. 增加5分钟
localDateTime = localDateTime.plusMinutes(5);
// 6. 增加10秒
localDateTime = localDateTime.plusSeconds(10);
// 结果:2022-08-06T19:30:30
System.out.println("增加后的时间:" + localDateTime);
减少年月日时分秒
// 获取指定的日期时间
LocalDateTime localDateTime =
LocalDateTime.of(2022, 8, 6 ,19, 30, 30);
// 减少年月日时分秒, 2022-08-06T19:30:30
System.out.println("未减少前的时间:" + localDateTime);
// 1. 减少一年
localDateTime = localDateTime.minusYears(1);
// 2. 减少一个月
localDateTime = localDateTime.minusMonths(1);
// 3. 减少一天
localDateTime = localDateTime.minusDays(1);
// 4. 减少一个小时
localDateTime = localDateTime.minusHours(1);
// 5. 减少5分钟
localDateTime = localDateTime.minusMinutes(5);
// 6. 减少10秒
localDateTime = localDateTime.minusSeconds(10);
// 结果: 2021-07-05T18:25:20
System.out.println("减少后的时间:" + localDateTime);
修改为指定具体的年月日时分秒
// 获取指定的日期时间
LocalDateTime localDateTime =
LocalDateTime.of(2022, 8, 6 ,19, 30, 30);
// 修改指定的年月日时分秒, // 2022-08-06T19:30:30
System.out.println("未修改前的时间:" + localDateTime);
// 1. 修改年份为2000年
localDateTime = localDateTime.withYear(2000);
// 2. 修改月份为1月
localDateTime = localDateTime.withMonth(1);
// 3. 修改天份为20日
localDateTime = localDateTime.withDayOfMonth(20);
// 4. 修改小时为早上6点
localDateTime = localDateTime.withHour(6);
// 5. 修改分钟为30分钟
localDateTime = localDateTime.withMinute(30);
// 6. 修改秒为30秒
localDateTime = localDateTime.withSecond(30);
// 结果:
System.out.println("修改后的时间:" + localDateTime);
比较2个日期(之前、之后、相等)
// 创建2个日期
LocalDate date1 = LocalDate.of(2020,5 , 10);
LocalDate date2 = LocalDate.of(2019,5 , 10);
// date1是否在date2之前, // false
boolean before = date1.isBefore(date2);
// date1是否在date2之后, // true
boolean after = date1.isAfter(date2);
// date1 与 date2是否一致, // false
boolean equal = date1.isEqual(date2);
System.out.println("before = " + before);
System.out.println("after = " + after);
System.out.println("equal = " + equal);
格式化及解析操作
创建格式化类的2种方式
// 1. 创建指定的格式化类
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
// 2. 创建JDK中提供好的格式化类
DateTimeFormatter basicIsoDate = DateTimeFormatter.BASIC_ISO_DATE;
将LocalDateTime格式化为一个String类型(2种方式)
// 1. 创建指定的格式化类
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
// 2. 创建一个日期时间类
LocalDateTime localDateTime =
LocalDateTime.of(2022, 8, 6, 19, 30, 30);
// 3. 将日期时间格式化为指定格式的字符串,(1)通过DateTimeFormatter对象格式化
String formatDateTime1 = dateTimeFormatter.format(localDateTime);
System.out.println("formatDateTime1 = " + formatDateTime1);
// 4. 将日期时间格式化为指定格式的字符串,(2)通过LocalDateTime对象格式化
String formatDateTime2 = localDateTime.format(dateTimeFormatter);
System.out.println("formatDateTime2 = " + formatDateTime2);
将一个String字符串转换为LocalDateTime对象
// 1. 创建指定的格式化类
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 2. 通过LocalDateTime的parse静态方法
LocalDateTime dateTime1 = LocalDateTime.parse("2020-10-10 20:30:59", dateTimeFormatter);
System.out.println("dateTime1 = " + dateTime1);
Instant时间戳类(精确到纳秒)
// 创建一个时间戳
Instant oldInstant = Instant.now();
System.out.println(oldInstant);
// 为该时间戳增加2天、2小时、2分钟,注意:不支持Period的月和年
Instant newInstance = oldInstant.plus(Duration.ofDays(2).plusHours(2).plusMinutes(2));
System.out.println(newInstance);
// 其余的减少、比较与LocalDateTime等大同小异
计算日期时间差
计算日期差Period
// 1. 创建2个日期
LocalDate localDate1 = LocalDate.of(2020, 8, 5);
LocalDate localDate2 = LocalDate.of(2018, 6, 4);
// 2. 创建Period,获取时是:第二个参数 - 第一个参数
Period period = Period.between(localDate2, localDate1);
// 3. 获取相差的年
int years = period.getYears();
System.out.println("years = " + years);
// 4. 获取相差的月
int months = period.getMonths();
System.out.println("months = " + months);
// 5. 获取相差的天数
int days = period.getDays();
System.out.println("days = " + days);
计算时间差Duration
// 1. 创建2个日期
LocalTime localTime1 = LocalTime.of(20,30,30);
LocalTime localTime2 = LocalTime.of(21,31,35);
// 2. 创建duration,获取时是:第二个参数 - 第一个参数
Duration duration = Duration.between(localTime1, localTime2);
// 3. 获取相差的小时
long hours = duration.toHours();
System.out.println("hours = " + hours);
// 4. 获取相差的分钟
long minutes = duration.toMinutes();
System.out.println("minutes = " + minutes);
// 5. 获取相差的秒数
long seconds = duration.getSeconds();
System.out.println("seconds = " + seconds);
计算LocalDateTime之间的时间差,ChronoUnit
// 获取指定的日期时间
LocalDateTime localDateTime1 =
LocalDateTime.of(2022, 8, 6 ,19, 30, 30);
LocalDateTime localDateTime2 =
LocalDateTime.of(2023, 8, 6 ,19, 30, 30);
// 1. 获取相差的年
long years = ChronoUnit.YEARS.between(localDateTime1, localDateTime2);
System.out.println("years = " + years);
// 2. 获取相差的月
long months = ChronoUnit.MONTHS.between(localDateTime1, localDateTime2);
System.out.println("months = " + months);
// 3. 获取相差的日
long days = ChronoUnit.DAYS.between(localDateTime1, localDateTime2);
System.out.println("days = " + days);
// 4. 获取相差的小时
long hours = ChronoUnit.HOURS.between(localDateTime1, localDateTime2);
System.out.println("hours = " + hours);
// 5. 获取相差的分钟
long minutes = ChronoUnit.MINUTES.between(localDateTime1, localDateTime2);
System.out.println("minutes = " + minutes);
// 6. 获取相差的秒
long seconds = ChronoUnit.SECONDS.between(localDateTime1, localDateTime2);
System.out.println("seconds = " + seconds);
时间矫正器(2种)
- 使用自定义矫正器
LocalDate localDate = LocalDate.of(2000, 10, 20);
System.out.println("矫正前localDate = " + localDate); // 矫正前localDate = 2000-10-20
LocalDate localDate2 = localDate.with(new TemporalAdjuster() {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate adjust1 = (LocalDate) temporal;
// 矫正增加2年
adjust1 = adjust1.plusYears(2);
return adjust1;
}
});
System.out.println("矫正后localDate2 = " + localDate2); // 矫正后localDate2 = 2002-10-20
- 使用JDK提供的矫正器
LocalDate localDate = LocalDate.of(2000, 10, 20);
System.out.println("矫正前localDate = " + localDate);// 矫正前localDate = 2000-10-20
// 例如明年的第一天
LocalDate localDate2 = localDate.with(TemporalAdjusters.firstDayOfNextYear());
System.out.println("矫正后localDate2 = " + localDate2); // 矫正后localDate2 = 2001-01-01
日期时间的时区(理解为带了时区的日期时间)
// 1. 输出所有时区
ZoneId.getAvailableZoneIds().stream().forEach(System.out::println);
// 2. 创建了标准时间。我们中国比标准时间晚了8个小时
ZonedDateTime now = ZonedDateTime.now(Clock.systemUTC());
System.out.println(now);
// 3. 根据指定的时区创建时间
ZonedDateTime now1 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println(now1);
// 4. 其他的增加、减少、修改、比较、计算时间差等都跟LocalDateTime一模一样