JAVA入门基础_从零开始的培训_JDK1.8的新特性

目录
  • JDK1.8的新特性
    • 接口(interface)的默认方法与静态方法
    • lambda表达式(是一个匿名函数)
      • 为什么需要使用lambda表达式
        • 举个例子,创建一个线程并调用,采用匿名内部类和lambda表达式的方式
      • lambda表达式的使用条件
        • 应用场景
        • 函数接口 及 @FuncationInterface
        • lambda表达式基本语法
          • lambda的简化规则
      • 匿名内部类的原理与lambda表达式的区别
        • 所需类型不一样
        • 类型的抽象方法数量不一样
        • 实现原理不一样
      • 常用的4个函数式接口
      • 方法引用(代码的简化,主要是函数式接口与需要引用的方法返回值与参数类型及数量一致)
        • 方法引用的三种格式
    • 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流中的数据收集
          • 收集到List、ArrayList集合当中
          • 收集到Set、HashSet集合中
          • 收集到数组当中,Object与或任意引用类型
        • Stream流中的数据聚合,对已经处理过的数据进行聚合计算
          • 最大值
          • 最小值
          • 求和
          • 平均值
          • 统计数量
        • Stream将数据进行分组
          • 单级分组
          • 多级分组(可以分无限级)
          • 分组聚合,分组的同时统计每组有多少人
        • Stream流中的分区操作
      • Stream 并行流与串行流
        • 获得并行流的2种方式
        • 解决并行流在执行过程中线程安全问题的4种办法
          • 演示并行流的线程安全问题
          • 解决方案1:为操作数据的代码添加同步代码块
          • 解决方案2:使用线程安全的容器,比如说Vector
          • 解决方案3:使用Collections的静态方法将一个容器转换为线程安全的
          • 解决方案4:使用Stream流中的collect或toArray进行操作,则会保证线程安全
    • Optional 可以很好的解决空指针异常
      • Optional类的创建方式
      • Optional类的常用方法
        • get()
        • isPresent()
        • orElse(T t)
        • orElseGet(Supplier s)
        • ifPresent(Consumer<? super T> consumer)
        • filter
        • map
      • Optional小案例(帮助理解下源码)
    • 新增的时间日期API
      • 以前的日期时间API有哪些问题,新的API解决了什么
      • 日期时间的信息获取(年月日、时分秒)
      • 日期时间的修改与比较
        • 增加年月日时分秒
        • 减少年月日时分秒
        • 修改为指定具体的年月日时分秒
        • 比较2个日期(之前、之后、相等)
      • 格式化及解析操作
        • 创建格式化类的2种方式
        • 将LocalDateTime格式化为一个String类型(2种方式)
        • 将一个String字符串转换为LocalDateTime对象
      • Instant时间戳类(精确到纳秒)
      • 计算日期时间差
        • 计算日期差Period
        • 计算时间差Duration
        • 计算LocalDateTime之间的时间差,ChronoUnit
      • 时间矫正器(2种)
      • 日期时间的时区(理解为带了时区的日期时间)

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表达式,那么该匿名内部类的类型必须为一个 函数接口 类型

应用场景

  1. 写在方法中作为一个函数式接口的返回值

  2. 成为方法的参数或局部变量(前提是类型为一个函数式接口)

函数接口 及 @FuncationInterface

  1. 该接口中必须 有且只能有一个 抽象方法

  2. 可以在接口中添加@FuncationInterface(函数式接口),一旦加了该注释, 那么该接口中就必须符合上一点的规范

  3. 该接口中定义Object类中的方法

  4. 该接口中可以定义default和static方法

lambda表达式基本语法

lambda表达式的三个组成部分 : () -> {}
1. ( ) 参数列表,不需要写参数类型
2. -> 参数列表与方法体的连接符
3. { } 方法体

lambda的简化规则
  1. 当参数列表的参数数量为1时候,可以省略小括号

  2. 当方法体只有一条语句时,可以省略大括号

匿名内部类的原理与lambda表达式的区别

所需类型不一样

  • 匿名内部类的类型可以是类、抽象类、接口

  • lambda表达式的类型只能是接口

类型的抽象方法数量不一样

  • 匿名内部类的类型可以有多个抽象方法

  • lambda表达式的类型有且只能有一个抽象方法

实现原理不一样

  • 匿名内部类


    • 会创建一个类来实现当前的接口并重写方法,重写的方法中包含我们实际需要运行的代码(我们自己写的那一段方法体)。
  • lambda表达式


    1. 会在 当前类 中创建一个方法,其中包含我们需要运行的代码

    2. 会创建一个类来实现该接口,该接口会调用在 当前类 中生成的方法

常用的4个函数式接口

 -  Consumer<T> : void accept(T t)  消费型接口

- Supplier<T> : T get() 提供型接口

- Function<T,R> : R apply(T t) 函数型接口

- Predicate<T> : boolean test(T t) 断言型接口

方法引用(代码的简化,主要是函数式接口与需要引用的方法返回值与参数类型及数量一致)

方法引用的三种格式

  1. 对象::成员方法名


    • 当函数式接口的方法 返回值、形参数量 与 需要 调用的对象的成员方法一致
 // 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;
  1. 类::静态方法名
    • 当函数式接口的方法 返回值、形参数量 与 需要 调用的静态方法一致
 // 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;
  1. 类::成员方法名
    • 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);
}
}
  1. 类::new
    • 这一种的适用条件其实跟第一种、第二种是一样的,只不过方法返回值变成了一个对象而已。
  2. 类[]::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&lt;? super T&gt; 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&lt;T&gt; filter(Predicate&lt;? super T&gt; predicate);

  • filter方法的基本使用

 Stream<String> stream = Arrays.asList("张三峰", "李四", "王五米", "赵六").stream();

// 过滤出所有字符串长度为3的数据并输出
stream.filter(s -> s.length() == 3).forEach(System.out::println);

limit:获取前几个元素 和 skip跳过前几个元素,都会返回一个新的Stream流

  • 方法定义
    Stream&lt;T&gt; limit(long maxSize);

    Stream&lt;T&gt; 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&lt;? super T&gt; predicate);

    boolean anyMatch(Predicate&lt;? super T&gt; predicate);

    boolean noneMatch(Predicate&lt;? super T&gt; 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&lt;T&gt; findFirst();

    Optional&lt;T&gt; 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&lt;T&gt; max(Comparator&lt;? super T&gt; comparator);

    Optional&lt;T&gt; min(Comparator&lt;? super T&gt; 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&lt;T&gt; accumulator);

Optional&lt;T&gt; reduce(BinaryOperator&lt;T&gt; accumulator);

&lt;U&gt; U reduce(U identity,BiFunction&lt;U, ? super T, U&gt; accumulator,BinaryOperator&lt;U&gt; 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流中的元素转换为另一种类型

  • 方法定义
    &lt;R&gt; Stream&lt;R&gt; map(Function&lt;? super T, ? extends R&gt; 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&lt;? super T&gt; 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:将多个流中的元素合并到一个流中

  • 方法定义

    &lt;R&gt; Stream&lt;R&gt; map(Function&lt;? super T, ? extends R&gt; 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流中的数据收集

  • 方法定义
    &lt;R, A&gt; R collect(Collector&lt;? super T, A, R&gt; collector);

    &lt;R&gt; R collect(Supplier&lt;R&gt; supplier,                 BiConsumer&lt;R, ? super T&gt; accumulator,                 BiConsumer&lt;R, R&gt; 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));

image

多级分组(可以分无限级)
 // 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 &lt;T&gt; Optional&lt;T&gt; of(T value)

public static &lt;T&gt; Optional&lt;T&gt; ofNullable(T value)

public static&lt;T&gt; Optional&lt;T&gt; 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一模一样

标签: Java

添加新评论