# 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 简写形式

规则:

  1. 可省略(小括号内的)参数类型
  2. 如果只有一个形参,则小括号可以省略
  3. 如果方法体只有一个语句,则可以省略 花括号、return、分号

示例:

(int num) -> {
    return num + 1;
};

// 1
(num) -> {
    return num + 1;
};

// 2
num -> {
    return num + 1;
};

// 3
num -> num + 1;

# 2.5. 使用 Lambda 的前提条件

前提条件:

  1. Lambda 表达式只能用于 接口类型形参、变量
  2. 接口中有且仅有一个(抽象)方法

说明:

  • 在只有一个方法的接口上,使用 @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. 接口的默认方法

说明:

  1. 使用 default 关键字
  2. 实现类,相当于继承该 默认方法,可以直接使用也可以重写

示例:

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. 创建方式

方式:

  1. 通过 Collection.stream() 方法获取流
  2. 通过 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 类型的方法,支持链式调用

注意事项:

  1. Stream 流只能被操作一次
  2. 函数拼接方法返回的是新流
  3. 不调用终结方法,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. 分区

与 分组 类似,不过是分成两个(truefalse)

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. 并行流

串行流: 一个线程操作流

并行流: 多线程操作流

获取并行流:

  1. 集合方法: Collection.parallelStream()
  2. 串 转 并: 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. 并行流线程安全问题

并行流是多线程,存在线程安全问题,解决方案:

  1. 使用 同步锁
  2. 使用线程安全的集合
  3. 将集合转为线程安全的集合
  4. 调用流的 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. 设计不合理
  2. 线程不安全
  3. 时区处理麻烦

示例:

// 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 {
}
本章目录