# jdk8 新特性
# 1. 概述
Open JDK 与 Oracle JDK:
- 大多数 JDK 都是基于 Open JDK 开发的,比如 Oracle JDK
- Open JDK 官网: https://openjdk.org/
- 2019年发布的 Oracle Java SE 需要商业授权
# 2. Lambda
# 2.1. 体验
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类...");
}
}).start();
new Thread(() -> {
System.out.println("Lambda表达式...");
}).start();
Lambda 表达式的好处: 简化匿名内部类的书写
# 2.2. 无参数无返回值的 Lambda
说明:
- 调用的方法中,有个参数是接口类型,可以考虑使用 Lambda 表达式
示例:
public interface Swimmable {
void swimming();
}
public class Lambda_02_use {
public static void main(String[] args) {
goSwimming(new Swimmable() {
@Override
public void swimming() {
System.out.println("匿名内部类");
}
});
goSwimming(() -> {
System.out.println("Lambda");
});
}
public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}
# 2.3. 有参数有返回值的 Lambda
示例1:
public interface Smokable {
int smoking(String name);
}
public class Lambda_03_arg_ret {
public static void main(String[] args) {
goSmoking(new Smokable() {
@Override
public int smoking(String name) {
System.out.println("匿名内部类 " + name);
return 1;
}
});
goSmoking((String name) -> {
System.out.println("Lambda " + name);
return 2;
});
}
public static void goSmoking(Smokable s) {
int num = s.smoking("中华");
System.out.println("返回值: " + num);
}
}
示例2, 对象列表排序:
public class Person {
public String name;
public int age;
// ...
}
public class Lambda_04_集合排序 {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Person("张三", 20));
personList.add(new Person("李四", 40));
personList.add(new Person("王五", 30));
// sortList1(personList);
sortList2(personList);
for (Person p : personList) {
System.out.println(p);
}
}
private static void sortList1(List<Person> personList) {
personList.sort(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.age - o2.age;
}
});
}
private static void sortList2(List<Person> personList) {
personList.sort((o1, o2) -> {
return o1.age - o2.age;
});
}
}
# 2.4. Lambda 简写形式
规则:
- 可省略(小括号内的)参数类型
- 如果只有一个形参,则小括号可以省略
- 如果方法体只有一个语句,则可以省略 花括号、return、分号
示例:
(int num) -> {
return num + 1;
};
// 1
(num) -> {
return num + 1;
};
// 2
num -> {
return num + 1;
};
// 3
num -> num + 1;
# 2.5. 使用 Lambda 的前提条件
前提条件:
- Lambda 表达式只能用于 接口类型形参、变量
- 接口中有且仅有一个(抽象)方法
说明:
- 在只有一个方法的接口上,使用
@FunctionalInterface
示例:
public class Lambda_05_前提条件 {
public static void main(String[] args) {
goFlying(() -> System.out.println("Flying ..."));
Flyable f = () -> System.out.println("Flying ...");
}
public static void goFlying(Flyable f) {
f.flying();
}
}
// 该注解,标记该接口是函数式接口(只有一个抽象方法的接口)
@FunctionalInterface
interface Flyable {
void flying();
// void method();
}
# 3. 接口新增的方法
JDK 8 之前的 接口:
interface 接口名称 {
静态常量;
抽象方法;
}
JDK 8 的 接口:
interface 接口名称 {
静态常量;
抽象方法;
默认方法;
静态方法;
}
# 3.1. 接口的默认方法
说明:
- 使用
default关键字 - 实现类,相当于继承该 默认方法,可以直接使用也可以重写
示例:
public class Lambda_06_接口的默认方法 {
public static void main(String[] args) {
new Person().eating("大米");
new Dog().eating("肉");
}
}
interface Eatable {
default void eating(String food) {
System.out.println("eating " + food);
}
}
// 实现类直接使用默认方法
class Person implements Eatable {
}
// 实现类重写默认方法
class Dog implements Eatable {
@Override
public void eating(String food) {
System.out.println("Dog is eating" + food);
}
}
# 3.2. 接口的静态方法
说明:
- 可以在接口里使用静态方法
- 只能通过接口名称来调用,实现类不能重写
示例:
public class Lambda_07_接口的静态方法 {
public static void main(String[] args) {
IMyInterface.print();
}
}
interface IMyInterface {
static void print() {
System.out.println("我是接口的静态方法");
}
}
# 4. 内置函数式接口
# 4.1. Supplier
供给接口,无参数有返回值
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
示例:
public class Inner_lambda_Supplier {
public static void main(String[] args) {
printNum(() -> 100);
}
public static void printNum(Supplier<Integer> supplier) {
Integer num = supplier.get();
System.out.println(num);
}
}
# 4.2. Consumer
消费接口,有参数无返回值
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
示例:
public class InnerLambda_02_Consumer {
public static void main(String[] args) {
printStr(
(str) -> System.out.println(str.toLowerCase()),
(str) -> System.out.println(str.toUpperCase())
);
}
public static void printStr(Consumer<String> consumer1, Consumer<String> consumer2) {
String str = "Hello World";
consumer1.andThen(consumer2).accept(str);
}
}
# 4.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);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
示例:
public class InnerLambda_03_Function {
public static void main(String[] args) {
strToNum((str) -> Integer.parseInt(str) * 2);
}
public static void strToNum(Function<String, Integer> f) {
String str = "123";
System.out.println(f.apply(str));
}
}
# 4.4. Predicate
有参数,有返回值
@FunctionalInterface
public interface Predicate<T, R> {
/**
* 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);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
}
示例:
public class InnerLambda_04_Predicate {
public static void main(String[] args) {
test(
(str) -> str.contains("H"),
(str) -> str.contains("W")
);
}
public static void test(Predicate<String> predicate1, Predicate<String> predicate2) {
if (predicate1.and(predicate2).test("Hello World")) {
System.out.println("包含 H 且 包含 W");
}
if (predicate1.or(predicate2).test("Hello")) {
System.out.println("包含 H 或 包含 W");
}
if (predicate1.negate().test("World")) {
System.out.println("不包含 H");
}
}
}
# 5. 方法引用
如果 Lambda 方法体里只有一个 调用其他的方法 的语句,则可以简写
常见的引用方式:
- 对象::方法名
- 类名::静态方法名
- 类名::普通方法名
- 类名::new
- 数组::new
# 5.1. 对象::方法名
示例:
Date date = new Date();
Supplier<Long> supplier1 = () -> date.getTime();
// 简写
Supplier<Long> supplier2 = date::getTime;
# 5.2. 类名::静态方法名
Supplier<Long> supplier1 = () -> System.currentTimeMillis();
// 简写
Supplier<Long> supplier2 = System::currentTimeMillis;
# 5.3. 类名::普通方法名
说明:
- 第一个参数为 方法的调用者,后续的参数为方法的参数
示例:
Function<String, Integer> getStringLength1 = (String str) -> {
return str.length();
};
// 简写
Function<String, Integer> getStringLength2 = String::length;
System.out.println(getStringLength1.apply("123"));
System.out.println(getStringLength2.apply("1234"));
BiFunction<String, Integer, String> substr1 = (String str, Integer beginIndex) -> {
return str.substring(beginIndex);
};
// 简写
BiFunction<String, Integer, String> substr2 = String::substring;
System.out.println(substr1.apply("0123456", 3));
System.out.println(substr2.apply("0123456", 3));
# 5.4. 类名::new
调用类的构造器方法
示例:
// -- 无参构造器
Supplier<Person> supplier1 = () -> {
return new Person();
};
// 简写
Supplier<Person> supplier2 = Person::new;
// -- 有参构造器
BiFunction<String, Integer, Person> biFunction1 = (String name, Integer age) -> {
return new Person(name, age);
};
// 简写
BiFunction<String, Integer, Person> biFunction2 = Person::new;
# 5.5. 数组::new
调用数组的构造方法
- 类型[]::new
示例:
Function<Integer, String[]> function1 = (Integer length) -> {
return new String[length];
};
// 简写
Function<Integer, String[]> function2 = String[]::new;
# 6. stream 流
# 6.1. 创建方式
方式:
- 通过 Collection.stream() 方法获取流
- 通过 Stream.of(T... values) 方法
示例:
// 1. 通过 Collection.stream() 方法获取流
List<String> list = new ArrayList<>();
Set<String> set = new HashSet<>();
Map<String, String> map = new HashMap<>();
Stream<String> listStream = list.stream();
Stream<String> setStream = set.stream();
Stream<String> mapValuesStream = map.values().stream();
Stream<String> mapKeysStream = map.keySet().stream();
Stream<Map.Entry<String, String>> mapEntriesStream = map.entrySet().stream();
// 2. 通过 Stream.of(T... values) 方法
String[] strArr = { "a", "b" };
Stream<String> strArrStream = Stream.of(strArr);
Integer[] integerArr = { 1, 2 };
Stream<Integer> integerArrStream = Stream.of(integerArr);
# 6.2. 常用方法与注意事项
常用方法:
| 方法名 | 作用 | 返回值类型 | 类别 |
|---|---|---|---|
| count | 统计个数 | long | 终结 |
| forEach | 遍历 | void | 终结 |
| filter | 过滤 | Stream | 函数拼接 |
| limit | 取前几个 | Stream | 函数拼接 |
| skip | 跳过前几个 | Stream | 函数拼接 |
| map | 隐射 | Stream | 函数拼接 |
| concat | 组合 | Stream | 函数拼接 |
类别:
- 终结: 返回值不是 Stream 类型的方法,不支持链式调用
- 函数拼接: 返回值是 Stream 类型的方法,支持链式调用
注意事项:
- Stream 流只能被操作一次
- 函数拼接方法返回的是新流
- 不调用终结方法,Stream 链中的操作不会执行
# 6.3. forEach()
方法:
void forEach(Consumer<? super T> action);
示例:
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四", "王五");
list.stream().forEach((String item) -> {
System.out.println(item);
});
// 简写
list.stream().forEach(item -> System.out.println(item));
// 方法调用
list.stream().forEach(System.out::println);
# 6.4. count()
计数
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四", "王五");
long count = list.stream().count();
System.out.println(count);
# 6.5. filter
过滤
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四", "王五");
list.stream()
.filter(item -> item.contains("李"))
.forEach(System.out::println);
# 6.6. limit()
获取前 N 个
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四", "王五");
list.stream()
.limit(2)
.forEach(System.out::println);
# 6.7. skip()
跳过前 N 个
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四", "王五");
list.stream()
.skip(2)
.forEach(System.out::println);
# 6.8. map()
将一种类型的流转换成另一种类型的流
Stream<String> stringStream = Stream.of("111", "222", "333");
Stream<Integer> integerStream = stringStream.map(Integer::parseInt);
integerStream.forEach(System.out::println);
# 6.9. sorted()
排序
// 无参,字典顺序,升序
Stream.of(2, 1, 3)
.sorted()
.forEach(System.out::println);
// 有参
// Comparator<? super T> comparator
// int compare(T o1, T o2);
Stream.of(2, 1, 3)
.sorted((num1, num2) -> num2 - num1) // 降序
.forEach(System.out::println);
# 6.10. distinct()
去重
// 数字去重
Stream<Integer> integerStream = Stream.of(1, 2, 2, 3);
integerStream.distinct().forEach(System.out::println);
// 字符串去重
Stream<String> stringStream = Stream.of("11", "22", "22", "33");
stringStream.distinct().forEach(System.out::println);
// 对象去重: 需要重写 equals() 和 hashCode()
Stream<Person> personStream = Stream.of(
new Person("张三"),
new Person("张三"),
new Person("李四"),
new Person("李四")
);
personStream.distinct().forEach(System.out::println);
# 6.11. match
匹配。
// 所有元素都满足
boolean isAllMatch = Stream.of(1, 2, 3).allMatch(i -> i > 0);
// 所有元素都不满足
boolean isAllUnMatch = Stream.of(1, 2, 3).noneMatch(i -> i < 0);
// 存在一个满足条件的即可
boolean isAnyMatch = Stream.of(1, 2, 3).anyMatch(i -> i < 2);
# 6.12. find
获取第一个元素,并判断是否存在
// 获取第一个元素
Optional<Integer> firstOptional = Stream.of(1, 2, 3).findFirst();
// 判断是否存在
boolean isExist = firstOptional.isPresent();
if (isExist) {
Integer firstItem = firstOptional.get(); // 1
}
// 如果存在,执行 consumer
firstOptional.ifPresent((target) -> {
System.out.println(target); // 1
});
// 获取任意一个元素
Optional<Integer> anyOptional = Stream.of(1, 2, 3).findAny();
Integer anyItem = anyOptional.get(); // 1
# 6.13. filter 与 findFirst 组合使用
找到第一个匹配条件的元素
List<String> list = Arrays.asList("apple", "banana", "cherry", "date");
Optional<String> firstElement = list.stream()
.filter(s -> s.startsWith("b"))
.findFirst();
firstElement.ifPresent(System.out::println); // 如果存在,打印第一个符合条件的元素
# 6.14. max 和 min
最大值、最小值
// 最大值
// interface Comparator { int compare(T o1, T o2) }
Optional<Integer> maxOptional = Stream.of(1, 2, 3).max((item1, item2) -> item1 - item2);
Integer maxItem = maxOptional.get();
System.out.println("最大值:" + maxItem);
// 最小值
Optional<Integer> minOptional = Stream.of(1, 2, 3).min((item1, item2) -> item1 - item2);
Integer minItem = minOptional.get();
System.out.println("最小值:" + minItem);
# 6.15. reduce
“累加”
// T reduce(T identity, BinaryOperator<T> accumulator);
// T identity: 初始值
// BinaryOperator<T> accumulator: 处理逻辑
/*
result=0 , currItem=1
result=1 , currItem=2
result=3 , currItem=3
*/
Integer sum = Stream.of(1, 2, 3).reduce(0, (result, currItem) -> {
System.out.println("result=" + result + " , currItem=" + currItem);
return result + currItem;
});
System.out.println(sum); //=> 6
// Optional<T> reduce(BinaryOperator<T> accumulator)
// 求最大值
Optional<Integer> max = Stream.of(1, 2, 3).reduce((result, currItem) -> result > currItem ? result : currItem);
max.ifPresent(System.out::println); //=> 3
# 6.16. map 和 reduce 组合使用
先转换成 数字流,再做 “累加”
List<Person> personList = new ArrayList<>();
Collections.addAll(personList,
new Person("张三", 20),
new Person("李四", 30),
new Person("王五", 40));
Optional<Integer> sumOptional = personList.stream().map(Person::getAge).reduce(Integer::sum);
sumOptional.ifPresent(sum -> System.out.println("年龄之和为: " + sum));
Optional<Integer> maxOptional = personList.stream().map(Person::getAge).reduce(Math::max);
maxOptional.ifPresent(max -> System.out.println("最大年龄为: " + max));
# 6.17. mapToInt
将 包装类型的流 转为对应的 基本类型的流,避免频繁拆箱、装箱,以及节省内存(包装类型占用内存比基本类型大)
Stream<Integer> integerStream = Stream.of(1, 2, 3);
IntStream intStream = integerStream.mapToInt((Integer item) -> item.intValue());
intStream.filter(item -> item > 0).forEach(System.out::println);
# 6.18. concat
将两个流合并为一个流(注意 合并后旧流会终止)
Stream<String> stringStream1 = Stream.of("张三");
Stream<String> stringStream2 = Stream.of("李四");
Stream<String> newStream = Stream.concat(stringStream1, stringStream2);
newStream.forEach(System.out::println);
# 6.19. Stream流 转 集合或数组
将 Stream 流中元素收集到 集合或数组 中
// 转 List
List<String> list = Stream.of("a", "a", "b").collect(Collectors.toList());
System.out.println("List: " + list);
// 转 ArrayList
ArrayList<String> arrayList = Stream.of("a", "a", "b").collect(Collectors.toCollection(ArrayList::new));
System.out.println("ArrayList: " + arrayList);
// 转 Set
Set<String> set = Stream.of("a", "a", "b").collect(Collectors.toSet());
System.out.println("Set: " + set);
// 转 HashSet
Set<String> hashSet = Stream.of("a", "a", "b").collect(Collectors.toCollection(HashSet::new));
System.out.println("HashSet: " + hashSet);
// 转 Array
String[] strings = Stream.of("a", "a", "b").toArray(String[]::new);
System.out.println("Array: " + Arrays.toString(strings));
# 6.20. 聚合计算
类似数据中的聚合函数,比如 最大值、最小值、平均值、求和、计数
Comparator<Integer> ascComparator = Comparator.comparingInt(item -> item);
Optional<Integer> maxOptional = Stream.of(3, 2, 1).collect(Collectors.maxBy(ascComparator));
maxOptional.ifPresent(max -> System.out.println("max: " + max));
Optional<Integer> minOptional = Stream.of(3, 2, 1).collect(Collectors.minBy(ascComparator));
minOptional.ifPresent(min -> System.out.println("min: " + min));
Integer sum = Stream.of(3, 2, 1).collect(Collectors.summingInt(item -> item));
System.out.println("sum: " + sum);
Double avg = Stream.of(3, 2, 1).collect(Collectors.averagingInt(item -> item));
System.out.println("avg: " + avg);
Long count = Stream.of(3, 2, 1).collect(Collectors.counting());
System.out.println("count: " + count);
# 6.21. 分组
Student[] students = {
new Student("张三", "男", 18),
new Student("李四", "男", 21),
new Student("王五", "女", 32),
new Student("赵六", "女", 41)
};
Stream<Student> studentStream = Stream.of(students);
// 按 gender 进行分组
Map<String, List<Student>> genderGroups = studentStream.collect(Collectors.groupingBy(Student::getGender));
// Map 直接通过 forEach 遍历
// Map.forEach(BiConsumer<? super K, ? super V> action)
genderGroups.forEach((key, value) -> {
System.out.println(key + " : " + value);
});
/*=>
女 : [Student{name='王五', gender='女', age=32}, Student{name='赵六', gender='女', age=41}]
男 : [Student{name='张三', gender='男', age=18}, Student{name='李四', gender='男', age=21}]
*/
studentStream = Stream.of(students);
// 自定义 分组
Map<String, List<Student>> customAgeGroups = studentStream.collect(Collectors.groupingBy(student -> {
if (student.getAge() > 30) {
return "大龄";
}
return "年轻";
}));
customAgeGroups.forEach((key, value) -> {
System.out.println(key + " : " + value);
});
/*=>
大龄 : [Student{name='王五', gender='女', age=32}, Student{name='赵六', gender='女', age=41}]
年轻 : [Student{name='张三', gender='男', age=18}, Student{name='李四', gender='男', age=21}]
*/
# 6.22. 多级分组
二级分组:
stream.collect(
Collectors.groupingBy(
// 一级
Function,
Collectors.groupingBy(
// 二级
Function
)
)
)
三级分组:
stream.collect(
Collectors.groupingBy(
// 一级
Function,
Collectors.groupingBy(
// 二级
Function,
Collectors.groupingBy(
// 二级
Function
)
)
)
)
示例:
Student[] students = {
new Student("张三", "男", 18),
new Student("李四", "男", 32),
new Student("王五", "女", 41),
new Student("赵六", "女", 28)
};
Stream<Student> studentStream = Stream.of(students);
// groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)
Map<String, Map<String, List<Student>>> groups = studentStream.collect(Collectors.groupingBy(
// 先按 性别 分组
Student::getGender,
// 再在各个 性别分组 里进行 年龄分组
Collectors.groupingBy(student -> {
if (student.getAge() > 30) {
return "大龄";
}
return "年轻";
})
));
groups.forEach((k1, v1) -> {
System.out.println(k1);
v1.forEach((k2, v2) -> {
System.out.println("\t" + k2 + " = " + v2);
});
});
/*=>
女
大龄 = [Student{name='王五', gender='女', age=41}]
年轻 = [Student{name='赵六', gender='女', age=28}]
男
大龄 = [Student{name='李四', gender='男', age=32}]
年轻 = [Student{name='张三', gender='男', age=18}]
*/
# 6.23. 分区
与 分组 类似,不过是分成两个(true 、 false)
Student[] students = {
new Student("张三", "男", 18),
new Student("李四", "男", 32),
new Student("王五", "女", 41),
new Student("赵六", "女", 28)
};
Stream<Student> studentStream = Stream.of(students);
// Collectors.partitioningBy(Predicate)
Map<Boolean, List<Student>> partitions = studentStream.collect(
Collectors.partitioningBy((student) -> student.getAge() > 30)
);
partitions.forEach((k, v) -> {
System.out.println(k + " = " + v);
});
/*=>
false = [Student{name='张三', gender='男', age=18}, Student{name='赵六', gender='女', age=28}]
true = [Student{name='李四', gender='男', age=32}, Student{name='王五', gender='女', age=41}]
*/
# 6.24. 拼接
连接字符串
String[] strings = {"张三", "李四", "王五"};
Stream<String> stringStream1 = Stream.of(strings);
Stream<String> stringStream2 = Stream.of(strings);
Stream<String> stringStream3 = Stream.of(strings);
// 无参: 直接连接
String join1 = stringStream1.collect(Collectors.joining());
System.out.println(join1); //=> "张三李四王五"
// 一个参: 指定分隔符
String join2 = stringStream2.collect(Collectors.joining(", "));
System.out.println(join2); //=> "张三, 李四, 王五"
// 三个参: 分隔符, 最终结果的前缀,最终结果的后缀
String join3 = stringStream3.collect(Collectors.joining(", ", "Prefix[ ", " ]Suffix"));
System.out.println(join3); //=> "Prefix[ 张三, 李四, 王五 ]Suffix"
# 6.25. 并行流
串行流: 一个线程操作流
并行流: 多线程操作流
获取并行流:
- 集合方法:
Collection.parallelStream() - 串 转 并:
stream.parallelStream()
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5);
// 串行流
list.stream().forEach(item -> {
// 打印的数字是顺序的
System.out.println(Thread.currentThread() + " --串--> " + item);
});
// Collection.parallelStream() 直接获取并行流
list.parallelStream().forEach(item -> {
// 打印的数字是随机的
System.out.println(Thread.currentThread() + " --并--> " + item);
});
// 串 转 并
Stream.of(1, 2, 3, 4, 5).parallel().forEach(item -> {
// 打印的数字是随机的
System.out.println(Thread.currentThread() + " --串转并--> " + item);
});
# 6.26. 并行流 和 串行流 效率比较
for 循环, 耗时: 993
long times = 5_000_000_000L; // 50亿
long start = System.currentTimeMillis();
// for 循环, 耗时: 993
long sum = 0;
for (long i = 0; i < times; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("耗时: " + (end - start));
串行流, 耗时: 1117
long times = 5_000_000_000L; // 50亿
long start = System.currentTimeMillis();
// 串行流, 耗时: 1117
long result = LongStream.rangeClosed(0, times).reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("耗时: " + (end - start));
并行流, 耗时: 231
long times = 5_000_000_000L; // 50亿
long start = System.currentTimeMillis();
// 并行流, 耗时: 231
long result = LongStream.rangeClosed(0, times).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("耗时: " + (end - start));
# 6.27. 并行流线程安全问题
并行流是多线程,存在线程安全问题,解决方案:
- 使用 同步锁
- 使用线程安全的集合
- 将集合转为线程安全的集合
- 调用流的 collect 或 toArray 方法(相当于 并转串)
线程安全问题:
List<Integer> list = new ArrayList<>();
IntStream.rangeClosed(1, 1000).parallel().forEach((item) -> {
list.add(item);
});
System.out.println("list 的大小: " + list.size()); //=> list 的大小: 786
方案1: 同步锁
List<Integer> list = new ArrayList<>();
Object lock = new Object();
IntStream.rangeClosed(1, 1000).parallel().forEach((item) -> {
synchronized (lock) {
list.add(item);
}
});
System.out.println("list 的大小: " + list.size()); //=> list 的大小: 1000
方案2: 使用线程安全的集合
Vector<Integer> list = new Vector<>();
IntStream.rangeClosed(1, 1000).parallel().forEach((item) -> {
list.add(item);
});
System.out.println("list 的大小: " + list.size()); //=> list 的大小: 1000
方案3: 将集合转为线程安全的集合
List<Integer> list = new ArrayList<>();
List<Integer> synchronizedList = Collections.synchronizedList(list);
IntStream.rangeClosed(1, 1000).parallel().forEach((item) -> {
synchronizedList.add(item);
});
System.out.println("list 的大小: " + list.size()); //=> list 的大小: 1000
方案4: 调用流的 collect 或 toArray 方法(相当于 并转串)
List<Integer> list = new ArrayList<>();
IntStream.rangeClosed(1, 1000).parallel()
.boxed()
.collect(Collectors.toList())
.forEach((item) -> {
list.add(item);
});
System.out.println("list 的大小: " + list.size()); //=> list 的大小: 1000
# 6.28. Fork/Join 框架
JDK7 引入的一套新的线程框架,并行流就是基于该框架
Fork/Join 框架:
- 分治法,大任务拆成小任务来执行
- 工作窃取算法,空闲的线程会“窃取”其他线程末尾的小任务来执行
# 7. Optional
# 7.1. 传统 null 判断
String username = "";
if (username != null) {
System.out.println("用户名: " + username);
}
# 7.2. 创建 Optional
// 无值 Optional,里面存放 null
Optional<Object> emptyOptional = Optional.empty();
// 有值 Optional,必须存具体值
Optional<String> valueOptional = Optional.of("张三");
// null 和 具体值 都可以存
Optional<Object> nullOptional = Optional.ofNullable(null); // 等价 Optional.empty()
Optional<String> valueOptional2 = Optional.ofNullable("李四");
# 7.3. 基础使用
if (valueOptional.isPresent()) {
// 如果 optional 为空,get() 会报错
String value = valueOptional.get();
System.out.println("value1 = " +value);
}
# 7.4. 高级使用
orElse()
Optional<String> emptyOptional = Optional.empty();
Optional<String> valueOptional = Optional.of("张三");
// 非空则使用具体值,空则使用默认值
String value1 = emptyOptional.orElse("猜猜我是谁");
String value2 = valueOptional.orElse("嘿嘿额");
System.out.println("value1 = " + value1); //=> 猜猜我是谁
System.out.println("value2 = " + value2); //=> 张三
ifPresent()
valueOptional.ifPresent((value) -> {
System.out.println("有值则执行: " + value);
});
map()
public class Optional04 {
public static void main(String[] args) {
Person namePerson = new Person("Zhang San");
Person noNamePerson = new Person(null);
System.out.println(getUpperCaseName(namePerson)); //=> ZHANG SAN
System.out.println(getUpperCaseName(noNamePerson)); //=> 没有姓名
System.out.println(getUpperCaseName(null)); //=> 没有姓名
}
public static String getUpperCaseName(Person person) {
return Optional.ofNullable(person)
.map(Person::getName) // 获取 name
.map(String::toUpperCase) // 将 name 转大写
.orElse("没有姓名") // name 为 null 时的处理
;
}
}
# 8. 日期时间
# 8.1. 旧版日期时间 API 存在问题
问题:
- 设计不合理
- 线程不安全
- 时区处理麻烦
示例:
// 1. 设计不合理
Date date = new Date(2000, 1, 2); // year 会加上 1900
System.out.println(date); //=> Fri Feb 02 00:00:00 CST 3900
// 2. 线程不安全
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 50; i++) {
new Thread(() -> {
try {
Date d = simpleDateFormat.parse("2019-09-09");
System.out.println(d);
} catch (Exception e) {
throw new RuntimeException(e);
}
}).start();
}
# 8.2. 新日期数据 API 介绍
LocalDate: 日期, 示例: 2012-01-02
LocalTime: 时间, 示例: 16:38:54.158549300
LocalDateTime: 日期时间, 示例: 2012-01-02T16:38:54.158
DateTimeFormatter: 日期时间格式化类
Instant: 时间戳
Duration: 计算两个时间(LocalTime)的距离
Period: 计算两个日期(LocalDate)的距离
ZonedDateTime: 包含时区的时间
# 8.3. LocalDate
LocalDate localDate = LocalDate.of(2012, 1, 2);
System.out.println(localDate); // 2012-01-02
LocalDate now = LocalDate.now();
System.out.println("now: " + now); //=> now: 2025-08-25
int year = now.getYear();
int month = now.getMonthValue();
int day = now.getDayOfMonth();
System.out.println(year); //=> 2025
System.out.println(month); //=> 8
System.out.println(day); //=> 25
# 8.4. LocalTime
LocalTime localTime = LocalTime.of(12, 23, 56);
System.out.println(localTime); //=> 12:23:56
LocalTime now = LocalTime.now();
System.out.println(now); //=> 20:14:26.037
int hour = now.getHour();
int minute = now.getMinute();
int second = now.getSecond();
int nano = now.getNano(); // 纳秒
System.out.println(hour); //=> 20
System.out.println(minute); //=> 14
System.out.println(second); //=> 26
System.out.println(nano); //=> 37000000
# 8.5. LocalDateTime
LocalDate + LocalTime
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); //=> now = 2025-08-25T20:18:49.677
# 8.6. 修改时间
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); //=> 2025-08-25T20:26:25.020
// 设置
LocalDateTime setLocalDateTime = now.withYear(9999);
System.out.println("setLocalDateTime = " + setLocalDateTime); //=> 9999-08-25T20:27:26.525
// 增加(单位)时间
LocalDateTime plussedLocalDateTime = now.plusYears(10);
// 减去(单位)时间
LocalDateTime minutedLocalDateTime = now.minusYears(10);
System.out.println("plussedLocalDateTime = " + plussedLocalDateTime); //=> 2035-08-25T20:26:25.020
System.out.println("minutedLocalDateTime = " + minutedLocalDateTime); //=> 2015-08-25T20:26:25.020
# 8.7. 比较时间
LocalDateTime localDateTimeOf2012 = LocalDateTime.of(2012, 1, 2, 12, 34, 56);
LocalDateTime localDateTimeOf2012_2 = LocalDateTime.of(2012, 1, 2, 12, 34, 56);
LocalDateTime localDateTimeOf2013 = LocalDateTime.of(2013, 1, 2, 12, 34, 56);
// localDateTimeOf2012 比 localDateTimeOf2013 大?
boolean isAfter = localDateTimeOf2012.isAfter(localDateTimeOf2013); //=> false
boolean isBefore = localDateTimeOf2012.isBefore(localDateTimeOf2013);
boolean isEqual = localDateTimeOf2012.isEqual(localDateTimeOf2012_2);
System.out.println(isAfter); //=> false
System.out.println(isBefore); //=> true
System.out.println(isEqual); //=> true
# 8.8. 格式化与解析
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
// 格式化
LocalDateTime now = LocalDateTime.now();
String formattedDate = now.format(dateTimeFormatter);
System.out.println("formattedDate = " + formattedDate); //=> 2025年08月25日 20时43分57秒
// 解析
LocalDateTime parsedLocalDateTime = LocalDateTime.parse("2025年08月25日 20时43分57秒", dateTimeFormatter);
System.out.println("parsedLocalDateTime = " + parsedLocalDateTime); //=> 2025-08-25T20:43:57
# 8.9. Instant
从 1970 到现在的秒数
Instant start = Instant.now();
Thread.sleep(1000);
Instant end = Instant.now();
long delta = end.getEpochSecond() - start.getEpochSecond();
System.out.println("delta = " + delta);
# 8.10. 距离
LocalTime localTime1 = LocalTime.of(12, 23, 34);
LocalTime localTime2 = LocalTime.of(13, 24, 35);
// 后面 减 前面 的
Duration duration = Duration.between(localTime1, localTime2);
// 相差 多少 小时
long hours = duration.toHours();
System.out.println("hours = " + hours); //=> 1
// 相差 多少 分钟
long minutes = duration.toMinutes(); //=> 61
System.out.println("minutes = " + minutes);
// 相差 多少 秒
long seconds = duration.getSeconds();
System.out.println("seconds = " + seconds); //=> 3661
LocalDate localDate1 = LocalDate.of(2012, 10, 1);
LocalDate localDate2 = LocalDate.of(2013, 11, 2);
Period period = Period.between(localDate1, localDate2);
int years = period.getYears();
int months = period.getMonths();
int days = period.getDays();
System.out.println("years = " + years); //=> 1
System.out.println("months = " + months); //=> 1
System.out.println("days = " + days); //=> 1
# 8.11. 时间调整器
TemporalAdjuster 与 TemporalAdjusters
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);
//=> 2025-08-26T18:42:54.320
// 自定义时间调整器
TemporalAdjuster temporalAdjuster = (temporal) -> {
LocalDateTime localDateTime = (LocalDateTime) temporal;
LocalDateTime localDateTime2 = localDateTime.plusMonths(1).withDayOfMonth(1);
return localDateTime2;
};
LocalDateTime firstDayOfNextMonth = now.with(temporalAdjuster);
System.out.println("firstDayOfNextMonth = " + firstDayOfNextMonth);
//=> 2025-09-01T18:42:54.320
// 内置时间调整器
LocalDateTime firstDayOfNextMonth2 = now.with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println("firstDayOfNextMonth2 = " + firstDayOfNextMonth2);
//=> 2025-09-01T18:44:28.787
# 8.12. 时区
// 1. 获取所有的时区 ID
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
System.out.println("total: " + availableZoneIds.size()); //=> 599
availableZoneIds.forEach(System.out::println);
/* =>
America/Louisville
Asia/Shanghai 东八区
...
*/
// 不带时区,获取计算机当前时间
LocalDateTime nowWithoutZone = LocalDateTime.now();
System.out.println("nowWithoutZone = " + nowWithoutZone);
//=> 2025-08-26T20:40:07.525
// 2. 操作带时区的类
// 世界标准时间
ZonedDateTime nowWithUTC = ZonedDateTime.now(Clock.systemUTC());
System.out.println("nowWithUTC = " + nowWithUTC);
//=> 2025-08-26T12:40:07.525Z
// 使用系统默认时区
ZonedDateTime nowWithLocalZone = ZonedDateTime.now();
System.out.println("nowWithLocalZone = " + nowWithLocalZone);
//=> 2025-08-26T20:40:07.525+08:00[Asia/Shanghai]
// 使用指定的时区
ZonedDateTime nowWithAmericaZone = ZonedDateTime.now(ZoneId.of("America/Louisville"));
System.out.println("nowWithAmericaZone = " + nowWithAmericaZone);
//=> 2025-08-26T08:40:07.526-04:00[America/Louisville]
// 修改时区
// withZoneSameInstant, 自动转换时间,两者对应的标准时间相等
ZonedDateTime nowWithZoneSameInstant = nowWithAmericaZone.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
System.out.println("nowWithZoneSameInstant = " + nowWithZoneSameInstant);
//=> 2025-08-26T20:49:59.375+08:00[Asia/Shanghai]
// 修改时区
// withZoneSameLocal, 只改时区 不自动转换时间
ZonedDateTime nowWithZoneSameLocal = nowWithAmericaZone.withZoneSameLocal(ZoneId.of("Asia/Shanghai"));
System.out.println("nowWithZoneSameLocal = " + nowWithZoneSameLocal);
//=> 2025-08-26T08:49:59.375+08:00[Asia/Shanghai]
# 9. 注解
# 9.1. 可重复的注解
在 类或方法 上可以使用重复的注解
定义:
// MyTest 注解的容器
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTestContainer {
MyTest[] value();
}
// MyTest 注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTestContainer.class)
public @interface MyTest {
String value();
}
使用与解析:
@MyTest("ClassAnnotationValue1")
@MyTest("ClassAnnotationValue2")
public class Annotation01_repeat {
@MyTest("MethodAnnotationValue1")
@MyTest("MethodAnnotationValue2")
public void test() {
}
public static void main(String[] args) throws NoSuchMethodException {
// 获取类上的注解
MyTest[] myTestAnnotationsFromClass = Annotation01_repeat.class.getAnnotationsByType(MyTest.class);
// 遍历并打印类上的注解
for (MyTest myTestAnnotationFromClass : myTestAnnotationsFromClass) {
System.out.println("myTestAnnotationFromClass = " + myTestAnnotationFromClass);
}
/* =>
@feature08_annotation.commons.MyTest(value=ClassAnnotationValue1)
@feature08_annotation.commons.MyTest(value=ClassAnnotationValue2)
*/
// 获取方法上的注解
MyTest[] myTestAnnotationsFromMethod = Annotation01_repeat.class.getMethod("test").getAnnotationsByType(MyTest.class);
// 遍历并打印方法上的注解
for (MyTest myTestAnnotationFromMethod : myTestAnnotationsFromMethod) {
System.out.println("myTestAnnotationFromMethod = " + myTestAnnotationFromMethod);
}
/* =>
@feature08_annotation.commons.MyTest(value=MethodAnnotationValue1)
@feature08_annotation.commons.MyTest(value=MethodAnnotationValue2)
*/
}
}
# 9.2. 类型注解
定义使用在泛型上的注解:
public class Annotation02_generic <@TypeParam T> {
public <@TypeParam E extends Integer> void test1() {
}
}
@Target(ElementType.TYPE_PARAMETER)
@interface TypeParam {
}
定义使用在类型上的注解:
public class Annotation03_type {
private @NotNull int a = 10;
public void test(@NotNull String str, @NotNull int num) {
@NotNull double d = 10.1;
}
}
@Target(ElementType.TYPE_USE)
@interface NotNull {
}
上一篇: 下一篇: