一个简洁的博客网站:http://lss-coding.top,欢迎大家来访
学习娱乐导航页:http://miss123.top/


1. Open JDK 和 Oracle JDK

Java 由 Sun 公司发明,Open JDK 是 Sun 在2006年末把 Java 开源而形成的项目。也就是说 Open JDK 是 JavaSE 平台版的开源和免费实现,它由 SUN 和 Java 社区提供支持,2009年 Oracle 收购了 Sun 公司,自此 Java 的维护方之一的 Sun 也变成了 Oracle。

**关系:**大多数 JDK 都是在 Open JDK 的基础上进一步编写完成的,比如 IBM J9,Oracle JDK 和 Azul Zulu,Azul Zing。Oracle JDK 完全由 Oracle 公司开发,Oracle JDK 是基于 Open JDK 源代码的商业版本。此外,它包含闭源组件。Oracle JDK 根据二进制代码许可协议获得许可,在没有商业许可的情况下,在 2019年1月之后发布的 Oracle Java SE8 的公开更新将无法用于商业或生产用途。但是 Open JDK 是完全开源的,可以自由使用。

Open JDK 官网:http://openjdk.java.net/

2. Lambda 表达式

2.1 分析

public static void main(String[] args) {// 创建一个线程,执行一段代码new Thread(new Runnable() {@Overridepublic void run() {System.out.println("子线程执行:" + Thread.currentThread());}}).start();System.out.println("主线程执行:" + Thread.currentThread());
}

代码分析:

  1. Thread 类需要一个 Runnable 接口作为参数,其中的抽象方法 run 方法是用来执行线程任务的核心
  2. 为了执行 run() 方法,不得不需要 Runnable 的实现类
  3. 为省去定义 Runnable 实现类,不得不使用匿名内部类
  4. 必须覆盖重写抽象的 run 方法,所有的方法名称、参数、返回值不得不重写一遍
  5. 实际上,只在乎方法体的代码

2.2 使用

Lambda 表达式是一个匿名内部类,可以理解为一段可以传递的代码

new Thread(() -> {System.out.println("Lambda执行线程:" + Thread.currentThread());
}).start();

优点:简化匿名内部类使用,语法更加简单。

匿名内部类语法冗余,Lambda表达式是简化匿名内部类的一种方式。

2.3 语法规则

省去了面向对象的一些约束

(参数类型 参数名称,...) -> {方法体}

2.4 练习1

无参 Lambda 表达式

  1. 首先创建一个 UserService 方法
public interface UserService {void show();
}
  1. 调用该方法
public class Demo02 {public static void main(String[] args) {getShow(() -> {System.out.println("调用了UserService接口");});}public static void getShow(UserService userService) {userService.show();}
}

2.5 练习2

有参 Lambda 表达式

  1. 创建一个 User 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private String name;private Integer age;
}
  1. 在主类中创建三个对象加入到 list 集合中
  2. 不适用 lambda 进行排序
public class Demo03 {public static void main(String[] args) {List<User> list = new ArrayList<>();list.add(new User("张三", 22));list.add(new User("李四", 31));list.add(new User("王五", 11));Collections.sort(list, new Comparator<User>() {@Overridepublic int compare(User o1, User o2) {return o1.getAge() - o2.getAge();}});list.forEach(System.out::println);}
}
  1. 可以看到上面 sort 方法中的 new Comparator… 是一个匿名内部类,可以通过 lambda 进行转换
Collections.sort(list, (User o1, User o2) -> {return o1.getAge() - o2.getAge();
});

2.6 @FunctionalInterface 注解

在我们的接口中只能有一个方法,否则在调用的时候会报错,使用该注解标识我们的接口为函数时接口,并且该接口中只能有一个抽象方法(方法)

/*** 标志注解,被该注解修饰的接口只能声明一个抽象方法**/
@FunctionalInterface
public interface UserService {void show();}

2.7 原理

  1. 匿名内部类的本质就是在编译的时候生成一个 Class 文件,类名$1.class
// 该代码执行完成之后
// 创建一个线程,执行一段代码
new Thread(new Runnable() {@Overridepublic void run() {System.out.println("子线程执行:" + Thread.currentThread());}
}).start();

通过反编译可以看到

package com.lss.jdk8NewFeatures.lambda;final class Demo01$1 implements Runnable {Demo01$1() {}public void run() {System.out.println("子线程执行:" + Thread.currentThread());}
}
  1. Lambda 表达式

写有 Lambda 表达式的 class 文件不能反编译工具 Xjad进行查看,可以通过 JDK 自带的工具:javap 对字节码进行反汇编操作。

javap -c -p 文件名.class-c:表示对代码进行反汇编
-p:显示所有类和成员

在反编译的源码中看到了一个静态方法 lambda$main$0(),在代码体其实就是调用这个方法

匿名内部类在编译的收会产生一个 class 文件

Lambda 表达式在程序运行的时候会形成一个类

  1. 在类中新增一个方法,这个方法的方法体就是 Lambda 表达式中的代码
  2. 还会生成一个匿名内部类,实现接口,重写抽象方法
  3. 在接口中重写方法会调用新生成的方法

2.8 省略写法

  1. 小括号内的参数类型可以省略
  2. 如果小括号里面有且只有一个参数,小括号可以省略
  3. 如果花括号中有且只有一个语句,可以省略花括号,return 关键字以及语句分号。
getShow(() -> System.out.println("调用了UserService接口"));
getStudent( name -> System.out.println(name));

Lambda 使用要求:方法的参数或局部变量必须为接口才能使用 Lambda、接口中有且仅有一个抽象方法(@FunctionalInteface)

Lambda 和 匿名内部类对比

  1. 类型不一样

    • 匿名内部类的类型可以是类、抽象类、接口
    • Lambda表达式需要的类型必须是接口
  2. 方法的数量不一样
    • 匿名内部类所需的接口中的抽象方法的数量是随意的
    • Lambda 表达式所需的接口中只能有一个抽象方法
  3. 实现原理不一样
    • 匿名内部类是在编译后生成一个 Class文件
    • Lambda 表达式是在程序运行的时候动态生成 Class 文件

3. 接口中新增的方法

3.1 接口新增

在 JDK8 中对接口进行了增强,接口中可以有默认方法静态方法

interface 接口名{静态常量;抽象方法;默认方法;静态方法;
}

3.2 默认方法

3.2.1 为什么要新增默认方法

在 JDK8 以前的接口中只能由抽象方法和静态常量,会出现一下问题:

如果接口中添加了一个抽象方法,那么它所有的实现类都需要重写这个抽象方法,非常地不利于接口的扩展。

3.2.2 使用

接口中默认方法的语法格式:

interface 接口名{修饰符 default 返回值类型 方法名{方法体;}
}
public class Demo01 {public static void main(String[] args) {A b = new B();System.out.println(b.show2());A c = new C();System.out.println(c.show2());}
}interface A {void show();// 定义一个默认方法public default String show2() {return "执行了A接口中的默认方法";}}class B implements A {@Overridepublic void show() {}
}class C implements A {@Overridepublic void show() {}// 重写默认方法@Overridepublic String show2() {return "在C类中的重写A接口的默认方法";}
}
执行了A接口中的默认方法
在C类中的重写A接口的默认方法

接口中的默认方法有两种使用方式:

  1. 实现类直接调用接口的默认方法
  2. 实现类重写接口的默认方法

3.3 静态方法

接口中的静态方法就是在接口中定义一个抽象方法通过static 关键字进行修饰,该静态方法不能被实现类重写,只能通过接口名.方法名()调用

public class Demo01 {public static void main(String[] args) {A b = new B();System.out.println(b.show2());A c = new C();System.out.println(c.show2());// 调用接口中的静态方法A.show3();}
}interface A {void show();// 定义一个默认方法public default String show2() {return "执行了A接口中的默认方法";}// 定义一个静态方法public static void show3() {System.out.println("执行了接口中的静态方法");}}
  1. 默认方法通过实例调用,静态方法通过接口名调用
  2. 默认方法可以继承,实现类可以直接调用接口默认方法,也可以重写接口默认方法
  3. 静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名调用

4. 函数式接口

4.1 函数式接口由来

使用 Lambda 表达式的前提是需要有函数式接口,而 Lambda 表达式使用时不关心接口名,抽象方法名。只关心抽象方法的参数列表和返回值类型。因此为了让我们使用 Lambda 表达式更加的方便,在 JDK 中提供了大量常用的函数式接口

public class Demo01 {public static void main(String[] args) {fun1((arr -> {int sum = 0;for (int i : arr) {sum += i;}return sum;}));}public static void fun1(Operator operator) {int[] arr = {1,2,3,4};int sum = operator.getSum(arr);System.out.println("sum = " + sum);}
}
@FunctionalInterface
interface Operator{int getSum(int[] arr);
}

4.2 函数式接口介绍

在 java.util.function 包中定义了一些函数式接口,在之后的使用中不一定需要每次都定义一个接口

4.2.1 Supplier

无参有返回值

@FunctionalInterface
public interface Supplier<T> {/*** Gets a result.** @return a result*/T get();
}
/*** Supplier 函数式接口的使用*/
public class Demo02Supplier {public static void main(String[] args) {fun1(() -> {int arr[] = {11,2,33,5,53,99,2};// 计算出数组中的最大值Arrays.sort(arr);return arr[arr.length - 1];});}public static void fun1(Supplier<Integer> supplier) {Integer integer = supplier.get();System.out.println("max = " + integer);}
}
4.2.2 Consumer

有参无返回值

@FunctionalInterface
public interface Consumer<T> {/*** Performs this operation on the given argument.** @param t the input argument*/void accept(T t);
}
/*** Consumer 函数式接口使用*/
public class Demo03Consumer {public static void main(String[] args) {fun1(s -> {System.out.println(s.toLowerCase());});}public static void fun1(Consumer<String> consumer) {consumer.accept("Hello, World");}}
4.2.3 Function

有参有返回值,根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。

@FunctionalInterface
public interface Function<T, R> {/*** Applies this function to the given argument.** @param t the function argument* @return the function result*/R apply(T t);
}
public class Demo04Function {public static void main(String[] args) {test(msg -> {return Integer.parseInt("111111");});}public static void test(Function<String,Integer> function) {Integer apply = function.apply("123");System.out.println("apply = " + apply);}}

在源码中可以看到有一个 andThen 方法,做组合使用

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));
}
public class Demo04Function2 {public static void main(String[] args) {test(msg -> {return Integer.parseInt(msg);}, msg -> {return msg * 10;});}public static void test(Function<String,Integer> f1, Function<Integer, Integer> f2) {//        Integer apply = f1.apply("123");
//        Integer apply1 = f2.apply(apply);Integer apply = f1.andThen(f2).apply("123");System.out.println("i2: " + apply);}}
4.2.4 Predicate

有参且返回结果为 boolean

@FunctionalInterface
public interface Predicate<T> {/*** Evaluates this predicate on the given argument.** @param t the input argument* @return {@code true} if the input argument matches the predicate,* otherwise {@code false}*/boolean test(T t);
}
/*** 根据输入的值进行判断,返回结果 boolean*/
public class Demo05Predicate {public static void main(String[] args) {test(msg -> {return msg.length() > 0;}, "Hello");}public static void test(Predicate<String> predicate, String msg){boolean b = predicate.test(msg);System.out.println("b: " + b);}
}

在 Predicate 中的默认方法提供了逻辑关系操作,negate、and、or等

public class Demo05Predicate1 {public static void main(String[] args) {test(msg -> {return msg.contains("H");}, msg -> {return msg.contains("W");});}public static void test(Predicate<String> p1, Predicate<String> p2){// p1 包含 H,同时 p2 包含Wboolean r1 = p1.and(p2).test("Hello");// p1 包含 H,或者 P2 包含Wboolean r2 = p1.or(p2).test("Hello");// p1 不包含Hboolean r3 = p1.negate().test("Hello");System.out.println(r1);System.out.println(r2);System.out.println(r3);}
}

5. 方法的引用

5.1 使用

在 Lambda 表达式中要执行的代码和我们另一个方法的代码是一样的,这样就没有必要重新写,可以”引用“重复代码

public class Demo01 {public static void main(String[] args) {// :: 方法引用,JDK8新特性printSum(Demo01::getTotal);}// 求数组元素之和public static void getTotal(int a[]) {int sum = 0;for (int i : a) {sum += i;}System.out.println("数组之和:" + sum);}public static void printSum(Consumer<int[]> consumer) {int[] a = {10, 20, 30, 40, 50, 60};consumer.accept(a);}}

5.2 方法引用格式

符号:::

双冒号为方法引用的运算符,它所在的表达式被称为方法引用

**应用场景:**如果 Lambda 表达式所要实现的方案,已经有其他方法存在相同的方案,那么可以使用方法应用。

常见的引用方式:

  1. instanceName::methodName 对象名::方法名
  2. ClassName::staticMethodName 类名::静态方法
  3. ClassName::methodName 类名::普通方法
  4. ClassName::new 类名:::new 调用的构造器
  5. TypeName[]::new String[]::new 调用数组的构造器

5.3 对象名::方法名

最常见用法,如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法

public static void main(String[] args) {Date date = new Date();Supplier<Long> supplier = () -> date.getTime();System.out.println(supplier.get());// 通过方法引用的方式来进行处理Supplier<Long> supplier1 = date::getTime;System.out.println(supplier1.get());
}

注意:被引用的方法参数要和接口中的抽象方法的参数一样;当接口抽象方法有返回值时,被引用的方法也必须有返回值。

5.4 类名::静态方法名

// public static native long currentTimeMillis();
public static void main(String[] args) {Supplier<Long> supplier = () -> {return System.currentTimeMillis();};System.out.println(supplier.get());// 通过方法引用实现Supplier<Long> supplier1 = System::currentTimeMillis;System.out.println(supplier1.get());
}

5.5 类名::引用实例方法

Java 面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者。

public static void main(String[] args) {Function<String, Integer> function = (s) -> {return s.length();};System.out.println(function.apply("hello"));// 通过方法引用实现Function<String, Integer> function1 = String::length;System.out.println(function1.apply("aaaaaaaaaaaaaaaaaaaa"));BiFunction<String, Integer, String> function2 = String::substring;String msg = function2.apply("HelloWorld", 3);System.out.println(msg);
}

5.6 类名::构造器

由于构造器名称与类名完全一致,所以构造器引用使用 ::new 的格式使用

public static void main(String[] args) {Supplier<User> supplier = () ->{ return new User(); };System.out.println(supplier.get());// 通过方法引用的方式Supplier<User> supplier1 = User::new;System.out.println(supplier1.get());BiFunction<String, Integer, User> function = User::new;System.out.println(function.apply("张三", 21));
}

5.7 数组::构造器

public static void main(String[] args) {Function<Integer, String[]> fun1 = (len) -> new String[len];String[] a1 = fun1.apply(3);System.out.println("数组长度为:" + a1.length);// 方法引用方式调用数组的构造器Function<Integer, String[]> fun2 = String[]::new;String[] a2 = fun2.apply(8);System.out.println("数组长度为:" + a2.length);
}

方法引用是对 Lambda 表达式符合特定情况下的一种缩写方式,它使得我们的 Lambda 表达式更加的简洁,也可以理解为 Lambda 表达式的缩写形式,不过需要注意的是方法引用只能引用已经存在的方法。

6. Stream API

6.1 集合处理的弊端

需要对一个集合进行查询的处理,则需要写好几个循环才能实现

  1. 不适用 Stream
public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("李四");list.add("李晓小");list.add("张三");// 1. 查询集合中姓名以 李 开头的List<String> list1 = new ArrayList<>();  // 记录开头为李的元素for (String s : list) {if (s.startsWith("李")) {list1.add(s);}}// 2. 在开头为李的元素中查找长度为3的List<String> list2 = new ArrayList<>(); // 记录长度为3的元素列表for (String s : list1) {if (s.length() == 3) {list2.add(s);}}// 3. 输出结果查看for (String s : list2) {System.out.println(s);}
}
  1. 使用Stream
public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("李四");list.add("李晓小");list.add("张三");list.stream() // 获取流.filter(s -> s.startsWith("李"))  // 通过 Lambda 过滤掉开头不是李的元素.filter(s -> s.length() == 3)  // 通过 Lambda 过滤掉长度不为3 的元素.forEach(System.out::println); // 遍历输出
}

6.2 Stream 流式思想概述

注意:Stream 和 IO 流是没有任何关系的

Stream 流式思想类似于工厂车间的”生产流水线“,Stream 流不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream 可以看作是流水线的一个工序。在流水线上,通过多个工序让一个原料加工成一个商品。

Stream API 能够让我们快速完成许多复杂的操作:筛选、切片、映射、查找、去重、统计、匹配和归约等。

6.3 Stream 获取方式

6.3.1 根据 Collection

java.util.Collection 接口中加入了 default 方法 stream, 也就是说 Collection 接口下的所有的实现都可以通过 stream 方法来获取Stream流。

public static void main(String[] args) {List<String> list = new ArrayList<>();list.stream();Set<String> set = new HashSet<>();set.stream();Vector vector = new Vector();vector.stream();
}

注意:Map 接口没有实现 Collection 接口,可以通过 Map 的 key vlaue 集合获取

Map<String, Object> map = new HashMap<>();
Stream<String> stream = map.keySet().stream(); // key
Stream<Object> stream1 = map.values().stream(); // value
Stream<Map.Entry<String, Object>> stream2 = map.entrySet().stream(); // entry
6.3.2 根据 Stream 的 of 方法

开发中不可避免会需要进行对数据中数据的操作,由于数组对象不可能添加默认方法,所有 Stream 接口中提供了静态方法 of

public static void main(String[] args) {Stream<String> stringStream = Stream.of("1", "2", "3");int[] ints = {11,22,33};Stream<int[]> ints1 = Stream.of(ints);stringStream.forEach(System.out::println);// 基本数据类型的数组是不行的,输出的数组的地址  [I@5b6f7412ints1.forEach(System.out::println);
}

6.4 Stream 常用方法

常用 API,分为两种:终结、函数拼接

方法名 方法作用 返回值类型 方法种类
count 统计个数 long 终结
forEach 逐一处理 void 终结
filter 过滤 Stream 函数拼接
limit 取用前几个 Stream 函数拼接
skip 跳过前几个 Stream 函数拼接
map 映射 Stream 函数拼接
concat 组合 Stream 函数拼接

**终结方法:**返回值类型不再是 Stream 类型的方法,不再支持链式调用。

**非终结方法:**返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结方法)

注意:

  1. Stream 只能操作一次。
  2. Stream 方法返回的是新的流。
  3. Stream 不调用终结方法,中间步骤不会执行。
6.4.1 forEach

遍历流中的数据

void forEach(Consumer<? super T> action);
// 该方法接收一个 Consumer 接口,会将每一个流元素交给函数处理
public static void main(String[] args) {Stream<String> stringStream = Stream.of("1", "2", "3");stringStream.forEach(System.out::println);
}
6.4.2 count

统计流中元素的个数

long count();
public static void main(String[] args) {Stream<String> stringStream = Stream.of("1", "2", "3");long count = stringStream.count();System.out.println(count);
}
6.4.3 filter

过滤数据,返回符合条件的数据。从下图可以看出,将一个流转成成另一个子集流

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

该接口接收一个 Predicate 函数式接口参数,作为筛选条件。

public static void main(String[] args) {Stream<String> stringStream = Stream.of("a1", "a2", "a3");stringStream.filter(s -> s.startsWith("a")).forEach(System.out::println);
}
6.4.4 limit

可以对流进行截取处理,取前 n 个数据

Stream<T> limit(long maxSize);
public static void main(String[] args) {Stream<String> stringStream = Stream.of("a1", "a2", "a3","a4", "a5", "a6");Stream<String> limit = stringStream.limit(2);limit.forEach(System.out::println);
}
6.4.5 skip

如果希望跳过前面的几个元素,可以使用 skip 获取截取之后的新流。

public static void main(String[] args) {Stream<String> stringStream = Stream.of("a1", "a2", "a3","a4", "a5", "a6");stringStream.skip(3).forEach(System.out::println);
}
6.4.6 map

如果需要将流中的元素映射到另一个流中,可以使用 map 方法

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

该接口需要 Function 函数式接口参数,可以将当前的 T 类型数据转换成另一种类型数据。

public static void main(String[] args) {Stream.of("1", "2", "3","4", "5", "6")//                .map(msg -> Integer.parseInt(msg)).map(Integer::parseInt).forEach(System.out::println);
}
6.4.7 sorted

如果需要将数据排序,可以使用该方法

Stream<T> sorted();
public static void main(String[] args) {Stream.of("5", "8", "2","4", "1", "0").map(Integer::parseInt)// .sorted()   // 根据自然顺序排序.sorted((n1, n2 ) -> n2 - n1)  // 通过比较器指定对应的排序规则.forEach(System.out::println);
}
6.4.8 distinct

如果要去掉流元素中的重复元素,可以使用该方法

Stream<T> distinct();
public static void main(String[] args) {Stream.of("5", "8", "2","4","0", "1", "0").distinct().forEach(System.out::println);
}

注意:Stream 流中的 distinct 方法对于基本数据类型是可以直接去重的,但是对于自定义类型,需要重写 hasCode 和 equals 方法来判断。

6.4.9 match

如果需要判断数据是否匹配指定的条件,可以使用该方法 match 是终结方法

// 元素是否有一个满足条件
boolean anyMatch(Predicate<? super T> predicate);// 元素是否都满足条件
boolean allMatch(Predicate<? super T> predicate);// 元素是否都不满足条件
boolean noneMatch(Predicate<? super T> predicate);
public static void main(String[] args) {boolean b = Stream.of("5", "8", "2", "4", "0", "1", "0").map(Integer::parseInt).allMatch(s -> s > 0);   // 判断上面集合中的各个数字是否都大于 0 System.out.println(b);
}
6.4.10 find

如果需要找到某些元素或者数据,可以使用该方法实现

Optional<T> findFirst();Optional<T> findAny();
public static void main(String[] args) {Optional<Integer> first = Stream.of("5", "8", "2", "4", "1", "1", "1").map(Integer::parseInt).findFirst();System.out.println(first.get());
}
6.4.11 max 和 min

如果想要获取最大值和最小值则可以使用该方法

Optional<T> max(Comparator<? super T> comparator);Optional<T> min(Comparator<? super T> comparator);
public static void main(String[] args) {Optional<Integer> max = Stream.of("5", "8", "2", "4", "1", "1", "1").map(Integer::parseInt).max((o1, o2) -> o1 - o2);Optional<Integer> min = Stream.of("5", "8", "2", "4", "1", "1", "1").map(Integer::parseInt).min((o1, o2) -> o1 - o2);System.out.println("最大值:" + max.get());System.out.println("最小值:" + min.get());
}
6.4.12 reduce 方法

如果需要将所有数据归纳得到一个数据,可以使用该方法

T reduce(T identity, BinaryOperator<T> accumulator);
public static void main(String[] args) {Stream<Integer> integerStream = Stream.of(2, 4, 6, 1, 9, 4);// 第一次的时候会将默认值复制给 x// 之后每次会将上一次的操作结果复制给 x, y就是每次从数据中获取的元素Integer reduce = integerStream.reduce(0, (x, y) -> {System.out.println("x=" + x + ",y=" + y);return x + y;});System.out.println(reduce);
}

6.4.13 map 和 reduce 的组合

实际使用的时候都是 map 和 reduct 一块进行使用

public static void main(String[] args) {// 1. 求出列表中所有用户年龄的总和List<User> list = new ArrayList<>();list.add(new User("张三", 20));list.add(new User("李四", 20));list.add(new User("王五", 20));list.add(new User("高成", 20));Integer sum = list.stream().map(User::getAge)  // 实现类型数据的转换.reduce(0,Integer::sum);System.out.println("所有年龄的总和为:" + sum);// 2. 求出所有年龄中的最大值List<User> list1 = new ArrayList<>();list.add(new User("张三", 20));list.add(new User("李四", 20));list.add(new User("王五", 20));list.add(new User("高成", 22));Integer sum1 = list.stream().map(User::getAge)  // 实现类型数据的转换.reduce(0,Integer::max);System.out.println("所有年龄最大的为:" + sum1);// 3. 统计字符 a 出现的次数Integer reduce = Stream.of("a", "b", "c", "a", "b", "e", "a").map(ch -> "a".equals(ch) ? 1 : 0).reduce(0, Integer::sum);System.out.println("字母a出现的次数:" + reduce);
}
6.4.14 mapToInt

Integer 类型占用的内存会比 int 多得多,在 Stream 流操作中会自动进行装箱和拆箱操作, 为了提高代码效率可以先将流中的Integer转换成 int 在操作。

IntStream mapToInt(ToIntFunction<? super T> mapper);
public static void main(String[] args) {Integer arr[] = {1,2,3,4,5,6};Stream.of(arr).filter(i -> i > 4).forEach(System.out::println);System.out.println("-----------------------");Stream.of(arr).mapToInt(Integer::intValue).forEach(System.out::println);
}
6.4.15 concat

如果两个流希望合并称为一个流,那么可以使用 该方法

public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {Objects.requireNonNull(a);Objects.requireNonNull(b);@SuppressWarnings("unchecked")Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>((Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());return stream.onClose(Streams.composedClose(a, b));
}
public static void main(String[] args) {Stream<String> stream1 = Stream.of("a","b","c");Stream<String> stream2 = Stream.of("x","y","z");Stream.concat(stream1, stream2).forEach(System.out::println);
}
6.4.16 案例练习

需求:

  1. 第一个队伍只保留姓名长度为 3 的成员
  2. 第一个筛选之后只要前 3 个
  3. 第二个队伍只要姓张的成员
  4. 第二个队伍筛选之后不要前两个人
  5. 将两个队伍合并为一个队伍
  6. 根据姓名创建 User 对象
  7. 打印整个队伍的User信息
public class Demo18 {public static void main(String[] args) {List<String> list1 = Arrays.asList("邓紫棋", "许巍", "郭富城", "周杰伦", "汪峰", "刘德华", "黄百鸣");List<String> list2 = Arrays.asList("赵磊", "王府井", "张云雷", "郭德纲", "张九南", "张艺兴", "张国荣");//        Stream<String> stream1 = list1
//                .stream()
//                .filter(s -> s.length() == 3)
//                .limit(3);
//        System.out.println("----------------------------------");
//        Stream<String> stream2 = list2
//                .stream()
//                .filter(s -> s.startsWith("张"))
//                .skip(2);
//        Stream.concat(stream1, stream2).map(n -> new User(n)).forEach(System.out::println);Stream.concat(list1.stream().filter(s -> s.length() == 3).limit(3), list2.stream().filter(s -> s.startsWith("张")).skip(2)).map(User::new).forEach(System.out::println);}}

6.5 Stream 结果收集

6.5.1 结果收集到集合中
@Test
public void test01() {// 将数据收集到 list中List<String> list = Stream.of("aa", "bb", "cc", "aa").collect(Collectors.toList());System.out.println(list);// 将数据收集到 set 中Set<String> set = Stream.of("aa", "bb", "cc", "aa").collect(Collectors.toSet());System.out.println(set);// 如果需要获取的类型为具体的实现,比如:ArrayList  HashSetArrayList<String> arrayList = Stream.of("aa", "bb", "cc", "aa").collect(Collectors.toCollection(ArrayList::new));System.out.println(arrayList);// 收集到具体实现 HashSetHashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa").collect(Collectors.toCollection(HashSet::new));System.out.println(hashSet);
}

6.5.2 结果收集到数组中

Stream 中提供了 toArray 方法来将结果放到一个数组中,返回值类型是 Object[],如果我们要指定返回的类型,那么可以使用另一个重载的 toArray(IntFunction f) 方法。

@Test
public void test02() {Object[] objects = Stream.of("aa", "bb", "cc", "aa").toArray(); // 返回的数组中元素是 Object 类型System.out.println(Arrays.toString(objects));// 如果需要指定返回的类型String[] strings = Stream.of("aa", "bb", "cc", "aa").toArray(String[]::new);System.out.println(Arrays.toString(strings));
}
6.5.3 对流中的数据做聚合计算

当我们使用 Stream 流处理数据后,可以像数据库的聚合函数一样对某个字段进行操作,比如获得最大值、最小值、求和、平均值或者统计数量。

@Test
public void test03() {// 获取年龄的最大值Optional<User> maxAge = Stream.of(new User("张三", 12),new User("王五", 14),new User("张三", 12)).collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));System.out.println("最大年龄:" + maxAge.get());// 获取年龄的最小值Optional<User> minAge = Stream.of(new User("张三", 12),new User("王五", 14),new User("张三", 12)).collect(Collectors.minBy((p1, p2) -> p1.getAge() - p2.getAge()));System.out.println("最小年龄:" + minAge.get());// 求所有年龄的总和Integer sumAge = Stream.of(new User("张三", 12),new User("王五", 14),new User("张三", 12)).collect(Collectors.summingInt(User::getAge));System.out.println("年龄总和:" + sumAge);// 求年龄平均值Double aveAge = Stream.of(new User("张三", 12),new User("王五", 14),new User("张三", 12)).collect(Collectors.averagingDouble(User::getAge));System.out.println("平均年龄为:" + aveAge);// 统计个数Long count = Stream.of(new User("张三", 12),new User("王五", 14),new User("张三", 12)).filter(u -> u.getAge() > 10).collect(Collectors.counting());System.out.println("个数:" + count);
}

6.5.4 对流中的数据做分组操作

当使用 Stream 处理数据后,可以根据某个属性将数据分组

@Test
public void test04() {// 根据姓名进行分组Map<String, List<User>> collect = Stream.of(new User("张三", 12),new User("李四", 14),new User("张三", 33),new User("张三", 12),new User("李四", 88),new User("张三", 12),new User("李四", 19)).collect(Collectors.groupingBy(User::getName));System.out.println("根据姓名分组");collect.forEach((k,v) -> System.out.println("k=" + k + ",v=" +v ));System.out.println("根据年龄分组");Map<String, List<User>> collect1 = Stream.of(new User("张三", 12),new User("李四", 14),new User("张三", 33),new User("张三", 12),new User("李四", 88),new User("张三", 12),new User("李四", 19)).collect(Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年"));collect1.forEach((k,v) -> System.out.println("k=" + k + ",v=" +v ));
}

  • 多级分组,先根据 name 分组,再根据年龄分组
@Test
public void test05() {// 根据姓名进行分组Map<String, Map<String, List<User>>> collect = Stream.of(new User("张三", 12),new User("李四", 14),new User("张三", 33),new User("张三", 12),new User("李四", 88),new User("张三", 12),new User("李四", 19)).collect(Collectors.groupingBy(User::getName,Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年")));collect.forEach((k,v) -> {System.out.println(k);v.forEach((k1,v1) -> {System.out.println("k1=" + k1 + ",v1=" + v1);});});
}

6.6.5 对流中的数据做分区操作

Collectors.partitioningBy 会根据值是否为 true, 把集合中的数据分割为两个列表, 一个 true 列表,一个 false 列表

@Test
public void test06() {// 根据姓名进行分组Map<Boolean, List<User>> collect = Stream.of(new User("张三", 12),new User("李四", 14),new User("张三", 33),new User("张三", 12),new User("李四", 88),new User("张三", 12),new User("李四", 19)).collect(Collectors.partitioningBy(p -> p.getAge() > 18));collect.forEach((k,v) -> System.out.println("k=" + k + ",v=" + v));
}

6.6.6 对流中的数据进行拼接

Collectors.joining 会根据指定的连接符,将所有的元素连接成一个字符串。

@Test
public void test07() {// 根据姓名进行分组String collect = Stream.of(new User("张三", 12),new User("李四", 14),new User("张三", 33),new User("张三", 12),new User("李四", 88),new User("张三", 12),new User("李四", 19)).map(User::getName).collect(Collectors.joining("_", "###", "$$$"));System.out.println(collect);
}

6.6 并行 Stream 流

上面使用的 Stream 流都是串行的,在一个线程上执行。

// 串行流
@Test
public void test01() {Stream.of(3,2,5,2,1).filter( s -> {System.out.println(Thread.currentThread() + "--" + s);return s > 3;}).count();
}

并行流

parallelStream 其实就是一个并行执行的流,它通过默认的 ForkJoinpool, 可以提高读线程的执行速度。

// 获取并行流的两种方式
@Test
public void test02() {List<Integer> list = new ArrayList<>();// 通过 list 接口,直接获取并行流Stream<Integer> integerStream = list.parallelStream();// 将已有的串行流转换为并行流Stream<Integer> parallel = Stream.of(1, 2, 3).parallel();
}
@Test
public void test03() {Stream.of(1, 2, 3,6,2,3,6,9).parallel().filter(s -> {System.out.println(Thread.currentThread() + "s=" + s);return s > 2;}).count();
}

测试

Long times = 5000000000L;
private long start;
@Before
public void test05() {start = System.currentTimeMillis();
}
@After
public void test06() {long end = System.currentTimeMillis();System.out.println("共执行:" + (end - start) + "ms");
}
@Test
public void test07() {System.out.println("普通for循环");// 共执行:1999mslong sum = 0;for (long i = 0; i < times; i++) {sum += i;}
}
@Test
public void test08() {System.out.println("串行Stream流");// 共执行:2523msLongStream.range(0, times).reduce(0, Long::sum);
}
@Test
public void test09(){System.out.println("并行Stream流");// 共执行:1128msLongStream.range(0, times).parallel().reduce(0, Long::sum);
}

通过上面的测试,可以看到 parallelStream 的效率是最高的

Stream 并行处理的过程会分而治之,也就是将一个大的任务切分成了多个小任务,这表示每个任务都是一个线程操作。

6.7 线程安全问题

// 并行流中的线程安全问题
@Test
public void test10(){List<Integer> list = new ArrayList<>();for (int i = 0; i < 1000; i++) {list.add(i);}System.out.println(list.size());List<Integer> listNew = new ArrayList<>();list.parallelStream().forEach(listNew::add);System.out.println(listNew.size());
}

要么执行成功,结果不对,要么会抛出异常。

针对问题解决方案:

  1. 加同步锁
list.parallelStream().forEach(s -> {synchronized (obj) {listNew.add(s);}
});
  1. 使用线程安全的容器
@Test
public void test12() {Vector v = new Vector();Object obj = new Object();IntStream.rangeClosed(1 , 1000).parallel().forEach(i -> {v.add(i);});System.out.println(v.size());
}
  1. 将线程不安全的容器转换成线程安全的容器
@Test
public void test13() {List<Integer> listNew = new ArrayList<>();// 将线程不安全的容器包装成线程安全的容器List<Integer> synchronizedList = Collections.synchronizedList(listNew);Object obj = new Object();IntStream.rangeClosed(1 , 1000).parallel().forEach(i -> {synchronizedList.add(i);});System.out.println(synchronizedList.size());
}
  1. 通过 Stream 中的 toArray 方法或者 collect 方法来操作,满足线程安全的要求
@Test
public void test14() {List<Integer> listNew = new ArrayList<>();List<Integer> collect = IntStream.rangeClosed(1, 1000).parallel().boxed().collect(Collectors.toList());System.out.println(collect.size());
}

6.8 Fork/Join 框架

parallelStream 使用的是 Fork/Join 框架。Fork/Join 框架自 JDK7 引入。Fork/Join 框架可以将一个大任务拆分为很多小任务来异步执行。Fork/Join 框架主要包含三个模块:

  1. 线程池:ForkJoinPool
  2. 任务对象:ForkJoinTask
  3. 执行任务的线程:ForkJoinWorkerThread

6.8.1 工作原理-分治法

分治法,ForkJoinPool 主要使用分治法来解决问题。典型的应用比如快速排序算法,ForkJoinPool需要使用相对少的线程来处理大量任务。比如要对 1000万个数据进行排序,那么会将这个任务分割成两个 500万的排序任务和一个针对这两组 500万数据的合并任务。以此类推,对于 500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于 10时,会停止分割,转而使用插入排序对它们进行排序。那么到最后,所有的任务加起来会有大概 20000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。

6.8.2 工作原理-工作窃取法

Fork/Join 最核心的地方就是利用了现代硬件设备多核,在一个操作时候会有空闲的 CPU,那么如何利用好这个空闲的 CPU 就成了提高性能的关键,而这里我们要提到的工作窃取(work-stealing)算法就是整个 Fork/Join 框架的核心理念,Fork/Join工作窃取算法是指某个线程从其他队列里窃取任务来执行。

假如我们需要做一个比较大的任务,我们可以把这个人物分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如 A 线程负责处理 A 队列里的任务。但是有线程会先把自己队列里的任务干完,而其他线程对队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。

工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。

上文中提到 Java8 引入了自动并行化的概念。它能够让一部分 Java 代码自动地以并行的方式执行,也就是我们使用了 ForkJoinPool 的 ParallelStream。

对于 ForkJoinPool 通用线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量。可以通过设置系统属性:java.util.concurrent.ForkJoinPool.common.parallelism=N(N为线程数量),来调整 ForkJoinPool 的线程数量,可以尝试调整成不同的参数来观察每次的输出结果。

7. Option类

主要解决空指针的问题

Option 是一个没有子类的工具类,Option 是一个可以为 null 的容器对象,它的主要作用就是为了避免 Null检查,防止 NullpointException

public final class Optional<T> {}

7.1 创建 Option 对象

public static void main(String[] args) {// 1. 通过 of 方法,of 方法是不支持 null的Optional<String> op1 = Optional.of("张三");Optional<String> op2 = Optional.of(null);// 报错// 2. 通过 ofNullable 方法,支持 nullOptional<String> op3 = Optional.ofNullable("张三");Optional<String> op4 = Optional.ofNullable(null);// 3. 通过 empty 方法直接创建一个空的 Optional 对象Optional<String> op5 = Optional.empty();
}

7.2 常用方法

/*** 常用方法*   get():如果 Optional 有值则返回,否则抛出 NoSuchElementException 异常*          get() 通常和 isPresent 方法一块使用*   isPresent(): 判断是否包含值,包含则返回 true,否则返回 false*   orElse(T t):如果调用对象包含值,返回该值,否则返回 t*   orElseGet(Supplier s):如果调用对象包含值就返回该值,否则返回 Lambda 表达式的返回值*/
@Test
public void test() {Optional<String> op1 = Optional.of("张三");Optional<String> op2 = Optional.empty();// 获取 Optional 中的值if (op1.isPresent()) {String s1 = op1.get();System.out.println("用户名称:" + s1);}if (op2.isPresent()) {System.out.println(op2.get());} else {System.out.println("op2 是一个空的Optional对象");}String s3 = op1.orElse("李四");System.out.println(s3);String s4 = op2.orElse("王五");System.out.println(s4);String s5 = op2.orElseGet(() -> "没有值");System.out.println(s5);
}

@Test
public void test02() {Optional<String> op1 = Optional.of("张三");Optional<String> op2 = Optional.empty();// 如果存在值就做一些什么操作op1.ifPresent(s -> System.out.println("有值:" + s));
}
/*自定义方法,将 User 对象中的 name 转换为大写并返回*/
@Test
public void test03() {User user = new User("zhangsan", 88);Optional<User> op = Optional.of(user);String nameForObject = getNameForObject(op);System.out.println(nameForObject);
}
/*根据 User 对象,将 name 转换为大写并返回通过 Optional 方式实现*/
public String getNameForObject(Optional<User> op) {if (op.isPresent()) {String res = op.map(User::getName).map(String::toUpperCase).orElse("空值");return res;}return null;
}
// 原始写法
/*** 根据 User 对象,将name转换成大写并且返回*/
public String nameToUpper(User user) {if (user != null) {String name = user.getName();if (name != null) {return name;} else return "空值";} else {return "空值";}
}

8. 新时间日期 API

8.1 旧版问题

  1. 设计不合理,在 java.util 和 java.sql 的包中都有日期类,java.util.Date 同时包含日期和时间的,而 java.sql.Date 仅仅包含日期,此外用于格式化和解析的类在 java.text 包下。
  2. 非线程安全,java.util.Date 是非线程安全的,所有的日期类都是可变的
  3. 时区处理麻烦,日期类并不提供国际化,没有时区支持

8.2 新版日期 API 介绍

JDK8 中增加了一套全新的日期时间 API,设计合理,线程安全。新的日期及时间API 位于 java.time 包中,下面是一些关键类

  • LocalDate:表示日期,包含年月日,格式为 2019-10-16
  • LocalTime:表示时间,包含时分秒格式为 16.38.54. 158549300
  • LocalDateTime:表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
  • DateTimeFormatter:日期时间格式化类
  • Instant:时间戳,表示一个特定的时间瞬间
  • Duration:用于计算 2 个时间(LocalTime,时分秒)的距离
  • Period:用于计算 2 个日期(LocalDate,年月日)的距离
  • ZonedDateTime:包含时区的时间

Java 中使用的历法是 ISO 8601 日历系统,它是世界民用历法,也就是我们常说的公历。平年有 365天,闰年有 366天。此外 Java8 还提供了4套其他历法,分别是:

  • ThaiBuddhistDate:泰国佛教历
  • MinguoDate:中国民国历
  • JapaneseDate:日本历
  • HijrahDate:伊斯兰历

8.3 日期时间常用操作

  1. 对日期的操作
public static void main(String[] args) {// 创建指定日期LocalDate date1 = LocalDate.of(2022,4,1);System.out.println(date1);// 获得当前日期LocalDate now = LocalDate.now();System.out.println(now);// 获得日期中的相关信息System.out.println("年份:"  + now.getYear());System.out.println("月份:" + now.getMonth().getValue());System.out.println("日:" + now.getDayOfMonth());System.out.println("星期:" + now.getDayOfWeek().getValue());
}
  1. 对时间的操作
@Test
public void test1() {// 获得指定的时间LocalTime time = LocalTime.of(6,30,00,00);System.out.println(time);// 获得当前的时间LocalTime time1 = LocalTime.now();System.out.println(time1);// 获得当前时间的相关信息System.out.println(time1.getHour());System.out.println(time1.getMinute());System.out.println(time1.getSecond());System.out.println(time1.getNano());
}
  1. 日期时间
@Test
public void test2() {// 获得指定的日期时间LocalDateTime dateTime = LocalDateTime.of(LocalDate.now(), LocalTime.now());System.out.println(dateTime);// 获得当前的日期时间LocalDateTime dateTime1 = LocalDateTime.now();System.out.println(dateTime1);
}

8.4 日期时间的修改

@Test
public void test3() {LocalDateTime dateTime = LocalDateTime.now();// 修改日期时间,对日期时间的修改,对已存在的 LocalDate 对象,创建了它的模板,并不会修改原来的信息LocalDateTime updDateTime = dateTime.withYear(1998);System.out.println("now: " + dateTime);System.out.println("upd: " + updDateTime);// 在当前日期的基础上,加上或者减去指定时间System.out.println("两天后:" + dateTime.plusDays(5));// 减去10年System.out.println("减去10年:" + dateTime.minusYears(10));
}// 日期时间的比较
@Test
public void test04() {LocalDateTime now = LocalDateTime.now();LocalDateTime date = LocalDateTime.of(2022,1,3,1,3,4);// 在 JDK8 中要实现日期的比较 isAfter、isBefore、isEqual 通过这几个方法来直接比较System.out.println(now.isAfter(date));System.out.println(now.isEqual(date));
}

注意:在进行日期时间的修改的时候,原来的 LocalDate 对象是不会被修改的,每次操作都是返回一个新的 LocalDate 对象,所以在多线程场景下是线程安全的。

8.5 格式化

@Test
public void test05() {LocalDateTime now = LocalDateTime.now();// 指定格式  使用系统默认的格式DateTimeFormatter isoLocalDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;// 将日期转换成字符String format = now.format(isoLocalDateTime);System.out.println("系统默认转换 = " + format);// 通过 ofPattern 方法指定特定的格式DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String format1 = formatter.format(now);System.out.println("自定义格式 = " + format1);// 解析日期LocalDateTime parse = LocalDateTime.parse("1999-01-01 22:22:22", formatter);System.out.println("parse = " + parse);
}

8.6 计算日期时间差

JDK8 中提供了两个工具类 Duration/Period:计算日期时间差

  1. Duration:用来计算两个时间差(LocalTime)
  2. Period:用来计算两个日期差(LocalDate)
public static void main(String[] args) {// 计算时间差LocalTime now = LocalTime.now();LocalTime time = LocalTime.of(22, 40, 12);System.out.println("now = " + now);System.out.println("time = " + time);// 通过 Duration 计算时间差Duration duration = Duration.between(now, time);System.out.println(duration.toDays());System.out.println(duration.toHours());System.out.println(duration.toMinutes());System.out.println(duration.toMillis());
}

@Test
public void  test01() {// 计算日期差LocalDate nowDate = LocalDate.now();LocalDate date = LocalDate.of(2001,03,20);Period period = Period.between(date, nowDate);System.out.println(period.getYears());System.out.println(period.getMonths());System.out.println(period.getDays());
}

8.7 时间校正器

有时候我们可能需要如下调整:将日期调整到 下个月的第一天 等操作。这是我们可以通过时间校正器获得更好的效果。

  • TemporalAdjuster 时间校正器
  • TemporalAdjusters 通过该类的静态方法提供了大量TemporalAdjuster的常用操作,简化操作
@Test
public void test02() {LocalDateTime now = LocalDateTime.now();// 将当前的日期调整到下个月的一号TemporalAdjuster adjuster = (temporal -> {LocalDateTime dateTime = (LocalDateTime) temporal;LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);System.out.println("nextMonth = " + nextMonth);return nextMonth;});//        LocalDateTime nextMonth = now.with(adjuster);LocalDateTime nextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());System.out.println(nextMonth);
}

8.8 日期时间的时区

Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime 是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime。

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

ZonedId:该类中包含了所有的时区信息。

public static void main(String[] args) {// 获得所有时区idZoneId.getAvailableZoneIds().forEach(System.out::println);// 获得当前时间,中国时区,东八区时区,比标准时间早8小时LocalDateTime now = LocalDateTime.now();System.out.println("now = " + now);// 获得标准时间ZonedDateTime bzsj = ZonedDateTime.now(Clock.systemUTC());System.out.println("bzsj = " + bzsj);// 使用计算机默认时区,创建日期时间ZonedDateTime now1 = ZonedDateTime.now();System.out.println("now1 = " + now1);// 使用指定的时区创建日期时间ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Marigot"));System.out.println("now2 = " + now2);
}

新日期API 优势

  1. 新版日期时间 API 中,日期和时间对象是不可变,操作日期不会影响原来的值,而是生成一个新的实例
  2. 提供不同的两种方式,有效的区分了人和机器的操作
  3. TemporalAdjuster 可以更精确的操作日期,还可以自定义日期调整器
  4. 线程安全

**学习参考视频:**https://www.bilibili.com/video/BV1HV411W78K?p=1

JDK8新特性知识点总结相关推荐

  1. JDK8新特性(五):JDK8时间日期API

    本文目录: 前言 1.旧版日期时间API存在的问题 2.新日期时间 API 介绍 3.用法介绍 1.JDK8 日期和时间类 2.JDK8 日期时间格式化与解析 3.JDK8 Instant 类 4.J ...

  2. JDK8新特性之函数式接口

    转载自 JDK8新特性之函数式接口 什么是函数式接口 先来看看传统的创建线程是怎么写的 Thread t1 = new Thread(new Runnable() {@Overridepublic v ...

  3. JDK8新特性之重复注解

    转载自 JDK8新特性之重复注解 什么是重复注解 下面是JDK8中的重复注解( java.lang.annotation.Repeatable)定义的源码. @Documented @Retentio ...

  4. JDK8新特性之Optional

    转载自 JDK8新特性之Optional Optional是什么 java.util.Optional Jdk8提供 Optional,一个可以包含null值的容器对象,可以用来代替xx != nul ...

  5. JDK8新特性之方法引用

    转载自 JDK8新特性之方法引用 什么是方法引用 方法引用是只需要使用方法的名字,而具体调用交给函数式接口,需要和Lambda表达式配合使用. 如: List<String> list = ...

  6. JDK8新特性之Lambda表达式

    转载自 JDK8新特性之Lambda表达式 什么是Lambda表达式 Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁.当开发者在编写Lambda表达式时,也会随之被编译成 ...

  7. Java番外篇2——jdk8新特性

    Java番外篇2--jdk8新特性 1.Lambda 1.1.无参无返回值 public class Test {interface Print{void print();}public static ...

  8. 接口与抽象类区别和接口jdk8新特性

    jdk8之前特点对比抽象类 1 接口用interface表示,和类是并列的,定义接口就是定义接口的成员 2 接口只能定义公共抽象方法(public abstract)和全局静态最终变量(public ...

  9. jdk8新特性之出现This inspection finds all usages of methods that have @since tag in their documentation.

    今天在做jdk8新特性测试的时候出现了如上图的bug,提供两种解决办法. 解决思路一:            检查自己项目的JDK是不是JDK8,若不是请选择JDK8. 解决思路二: 打开自己的IDE ...

最新文章

  1. 大话卷积神经网络CNN,小白也能看懂的深度学习算法教程,全程干货建议收藏!...
  2. Android将联系人读取到LISTVIEW中遇到的问题!
  3. eureka服务注册yml配置
  4. Flask中的 url_for() 函数
  5. 假如王撕葱是程序员。。。
  6. 6-2-JSP基本语法
  7. 记一次海洋cms任意代码执行漏洞拿shell(url一句话)
  8. 16进制转base64_《蹲坑学K8S》之19-5:二进制部署Calico网络
  9. 2020美赛M奖感想
  10. 【有限差分法】(三)一维和二维抛物方程CN格式以及长时间稳定性分析(附算例与Python代码)
  11. 预约小程序开发:小程序开发的费用都包含了哪些?
  12. 关于QRCODE二维码使用彩色进行扩容的思考
  13. 在Java中为JFrame添加背景音乐
  14. 出版印刷纸张大小尺寸一览表
  15. JPBC库实现基于身份的签名体制
  16. 学校食堂简易点餐管理系统(含用户登录且密码隐藏)C++
  17. 一起来看流星雨剧情简介/剧情介绍/剧情分集介绍第二十一集
  18. Retrofit 通过刷新头部Token解决token过期
  19. 浅谈电信运营商BMO融合
  20. 几张产生视觉错觉的图片

热门文章

  1. 软件测试-自我介绍-整体框架
  2. 如何用大数据做行为预测的?
  3. 两种方式读取Json文件 数据
  4. http文件服务器(Ubuntu)
  5. C语言源代码系列-管理系统之单项选择题标准化考试系统设计
  6. 【Unity】创建一个自己的AR安卓程序
  7. 读书笔记:《C++ PrimerPlus》 第九章~第十一章
  8. TextCnn原理及实践
  9. R语言中的countif——dplyr包中的filter函数和nrow
  10. html内边距的顺序,html中内边距和外边距之间的区别是什么? - 收获啦