目录
-
-
一、Lambda表达式基本语法
-
二、Java内置四大函数式接口
-
三、方法引用、构造器引用、数组引用
-
四、Stream
-
-
1、使用步骤
-
2、创建Stream
-
3、中间操作
-
4、终止操作
-
-
4.1、查找与匹配
-
4.2、查找与匹配
-
4.3、收集
-
-
4.3.1、Collectors.toList()
-
4.3.2、Collectors.toSet()
-
4.3.3、Collectors.toMap()
-
4.3.4、Collectors.counting()
-
4.3.5、Collectors.averagingInt()
-
4.3.6、Collectors.summingDouble()
-
4.3.7、Collectors.maxBy()
-
4.3.8、Collectors.minBy()
-
4.3.9、Collectors.groupingBy()
-
4.3.10、Collectors.partitioningBy()
-
4.3.11、Collectors.summarizingDouble()
-
4.3.12、Collectors.joining()
-
-
-
-
五、Lambda表达式、函数式接口、引用、Stream流——练习题
-
六、并行流和串行流
-
七、Optional
-
八、接口中的默认方法和静态方法
-
九、新时间日期API
-
十、重复注解与类型注解
-
一、Lambda表达式基本语法
1、基本概念
Java8中引入了一个新的
操作符
->
,该操作符称为
箭头操作符
或者
Lambda操作符
,其中
操作符左侧
是Lambda表达式的
参数列表
,
操作符右侧
是Lambda表达式
所需要执行的功能
,即
Lambda体
例如:
public class Test {
public static void main(String[] args) {
// ->是箭头操作符
// ()是参数列表
// System.out.println("hello world!")是Lambda体
Runnable run = () -> System.out.println("hello world!");
}
}
2、语法格式
2.1、参数列表写法
2.1.1、无参
说明:
必须写括号
举例:
Runnable run = () -> System.out.println("hello world!");
2.1.2、单参
说明:
可以省略括号
举例:
// 情况1:省略括号
Predicate<String> predicate = s -> s != null;
// 情况2:加上括号
Predicate<String> predicate = (s) -> s != null;
2.1.3、多参
说明:
必须写括号
举例:
Comparator<Integer> comparator = (o1, o2) -> o1.compareTo(o2);
扩展(类型推断)
其实括号中也是可以写参数名称的,就拿上面的多参情况为例,代码也可以写成:
Comparator<Integer> comparator = (Integer o1, Integer o2) -> o1.compareTo(o2);
我们省略的原因是java中存在类型推断,所以可以推断出o1和o2的类型,因此可以省略不写
2.2、Lambda体写法
2.2.1、单条语句
2.2.1.1、无返回值
说明:
可以省略大括号
举例:
// 情况1:省略大括号
Runnable run = () -> System.out.println("hello world!");
// 情况2:加上大括号
Runnable run = () -> {
System.out.println("hello world!");
};
2.2.1.2、有返回值
说明:
可以省略大括号,也可以省略
return
关键字
举例:
// 情况1、省略大括号,并且省略return关键字
Comparator<Integer> comparator = (o1, o2) -> o1.compareTo(o2);
// 情况2、加上大括号,并且加上return关键字
Comparator<Integer> comparator = (o1, o2) -> {
return o1.compareTo(o2);
};
2.2.2、多条语句
说明:
必须加上大括号,不能省略
return
关键字
举例:
Comparator<Integer> comparator = (o1, o2) -> {
System.out.println("hello world!");
return o1.compareTo(o2);
};
二、Java内置四大函数式接口
1、函数式接口定义
概念:
有且仅有一个抽象方法
的接口
注意:
如果说函数式接口中只有一个方法,这句话是不对的,原因是java8中可以有默认方法;然后
@FunctionalInterface
注解的作用是在编译期检查接口是否为函数式接口,示例代码如下:
@FunctionalInterface
interface MyFunctionalInterface1 {
String getValue(String str);
}
2、四大函数式接口
2.1、消费型接口Consumer
// @since 1.8
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
2.2、供给型接口Supplier
// @since 1.8
@FunctionalInterface
public interface Supplier<T> {
T get();
}
2.3、函数型接口Function
// @since 1.8
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
2.4、断言型接口Predicate
// @since 1.8
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
3、其他函数式接口
三、方法引用、构造器引用、数组引用
1、方法引用
1.1、语法格式
- 对象 :: 实例方法名
- 类 :: 静态方法名
- 类 :: 实例方法名
1.2、使用要求
-
方法体只有一条语句
-
参数列表和返回值类型相同
;即
实例 / 静态方法
的参数列表和返回值类型,要与函数式接口中抽象方法的对应,完全一致最好,如果不能完全一致,可以自动转换也行,比如自动拆装箱,例如下图
-
如果Lambda体中实例方法的调用者是参数列表中的第1个参数,实例方法的参数是第2个参数,可以使用
类名 :: 方法名
格式
说明:
所有方法引用类型都
必须符合
第1种和第2种引用格式,
类 :: 实例方法名
方法引用
还需要满足
第3条要求
1.3、举例
1.3.1、对象 :: 实例方法名
// 1、原有写法
Consumer<String> consumer = t -> System.out.println(t);
// 2、方法引用写法
Consumer<String> consumer = System.out::println;
解释:
System.out
代表
PrintStream printStream = System.out
,所以这就是对象,然后
println()
方法是该对象中的方法
1.3.2、类 :: 静态方法名
// 1、原有写法
Comparator<Integer> comparator = (o1, o2) -> Integer.compare(o1, o2);
// 2、方法引用写法
Comparator<Integer> comparator = Integer::compare;
解释:
Integer类中的静态方法compare结构如下:
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
然后我们解释一下
1、方法引用》1.2、使用要求
中的第2点要求,如下图:
1.3.3、类 :: 实例方法名
// 1、原有写法
BiPredicate<String, String> biPredicate = (x, y) -> x.equals(y);
// 2、方法引用写法
BiPredicate<String, String> biPredicate = String::equals;
解释:
我们解释一下
2、区分方法引用格式
中的第3点要求,可以看到Lambda体中实例方法调用者是参数1,而方法参数是参数2,所以符合要求
2、构造器引用
2.1、语法格式
- 类名 :: new
2.2、使用要求
构造方法中的参数列表和函数式接口中抽象方法的要对应,如果不能完全一致,可以自动转换也行,比如自动拆装箱,例如下图:
2.3、举例
// 1、无参
// 1.1、原有写法
Supplier<String> supplier = () -> new String();
// 1.2、构造器引用写法
Supplier<String> supplier = String::new;
// 2、有参
// 2.1、原有写法
Function<String, String> function= x -> new String(x);
// 2.2、构造器引用写法
Function<String, String> function= String::new;
3、数组引用
3.1、语法格式
- 类名[] :: new
3.2、使用要求
函数式接口中抽象方法有且仅有一个参数,并且只能是Integer类型,该整数值将会成为数组大小
3.3、举例
// 1、原有写法
Function<Integer, String[]> function= x -> new String[x];
// 2、数组引用写法
Function<Integer, String[]> function= String[]::new;
四、Stream
1、使用步骤
- 创建Stream
- 中间操作
- 终止操作(终端操作)
2、创建Stream
2.1、通过Collection系列集合的stream()或者parallelStream()方法
举例如下:
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
2.2、通过Arrays工具类的静态方法stream()方法
举例如下:
String[] array = {"1", "2"};
Stream<String> stream = Arrays.stream(array);
2.3、通过Stream类中的静态方法of()方法
举例如下:
Stream<String> stream = Stream.of("1", "2");
2.4、通过Stream类的iterate()或者generate()方法来创建无限流
举例如下:
// 1、迭代
Stream<Integer> iterate = Stream.iterate(0, x -> x += 2);
iterate.limit(10).forEach(System.out::println);
// 2、生成
Stream<Double> generate = Stream.generate(Math::random);
generate.limit(10).forEach(System.out::println);
说明:
无限流代表可以无限成功,如果没有截止条件,那么生成操作不会停止,以上述迭代为例,0是起始值,然后x每次以2递增,由于我们限制结果是10个,所以迭代例子结果如下:
0
2
4
6
8
10
12
14
16
18
3、中间操作
3.1、筛选与切片
3.1.1、filter()
作用:
根据筛选条件排除元素
示例:
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9};
IntStream intStream = Arrays.stream(array).filter(x -> x > 3);
intStream.forEach(System.out::println);
惰性求值:
使用Stream的时候,在执行终止操作的时候会执行全部中间操作+终止操作,这就是惰性求值,我们来验证一下:
public class Test {
public static void main(String[] args) {
System.out.println("》》》start中间操作……");
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9};
IntStream intStream = Arrays.stream(array).filter(x -> {
System.out.println("当前被判断数据:" + x);
return x > 3;
});
System.out.println("》》》end中间操作……");
System.out.println("》》》start终止操作……");
intStream.forEach(System.out::println);
System.out.println("》》》end终止操作……");
}
}
执行结果如下:
》》》start中间操作……
》》》end中间操作……
》》》start终止操作……
当前被判断数据:1
当前被判断数据:2
当前被判断数据:3
当前被判断数据:4
4
当前被判断数据:5
5
当前被判断数据:6
6
当前被判断数据:7
7
当前被判断数据:8
8
当前被判断数据:9
9
》》》end终止操作……
可以看出执行终止操作之前,并没有执行中间操作
3.1.2、limit()
作用:
截取前面给定数量的数据,如果给定数量大于元素个数,那么就返回全部数据的流
示例:
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9};
IntStream intStream = Arrays.stream(array).limit(2);
intStream.forEach(System.out::println);
3.1.3、skip()
作用:
跳过前面给定数量的数据,如果给定数量大于元素个数,那么将返回一个空流
示例:
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9};
IntStream intStream = Arrays.stream(array).skip(6);
intStream.forEach(System.out::println);
3.1.4、distinct()
作用:
去重;如果是基本数据类型,直接通过
==
比较去重,如果是引用数据类型,将根据类型的
hashCode()
和
equals()
进行比较去重
示例:
int[] array = {1, 1, 2};
IntStream intStream = Arrays.stream(array).distinct();
intStream.forEach(System.out::println);
3.2、映射
3.2.1、map()
作用:
通过给定数据,然后根据该数据获取到其他数据
示例:
int[] array = {1, 2};
IntStream intStream = Arrays.stream(array).map(x -> x + 1);
intStream.forEach(System.out::println);
3.2.2、flatMap()
作用:
flatMap()用于拼接处理之后的数据,而map()方法用于组合处理之后的流
示例:
// 1、flatMap()
public class Test {
public static void main(String[] args) {
Integer[] array = {3};
Stream<Integer> stream = Arrays.stream(array).flatMap(Test::dealNum);
stream.forEach(System.out::println);
}
private static Stream<Integer> dealNum(Integer num) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
list.add(i);
}
return list.stream();
}
}
// 2、map()
public class Test {
public static void main(String[] args) {
Integer[] array = {3};
Stream<Stream<Integer>> stream = Arrays.stream(array).map(Test::dealNum);
stream.forEach(x -> x.forEach(System.out::println));
}
private static Stream<Integer> dealNum(Integer num) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
list.add(i);
}
return list.stream();
}
}
说明:
对于flatMap而言,它接收的是流,相当于集合对象的addAll()方法,可以把所有的流中数据放到同一个流对象之中,类似于:
// 最终list中装的是List<String>对象
List list = new ArrayList();
List<String> a = Arrays.asList(new String[]{"hello", "world"});
list.add(a);
对于map而言,它接收什么都行,假设它接收的是流,相当于集合对象的add()方法,可以将Stream流对象存入同一个流对象之中,类似于:
// 最终list中装的是String对象
List list = new ArrayList();
List<String> a = Arrays.asList(new String[]{"hello", "world"});
list.addAll(a);
3.3、排序
3.3.1、sorted()自然排序
作用:
类实现了Comparable接口,然后重写了compareTo()方法,然后根据该方法进行排序
示例:
Integer[] array = {1, 3, 2};
Stream<Integer> stream = Arrays.stream(array).sorted();
stream.forEach(System.out::println);
3.3.2、sorted()定制排序
作用:
sorted()里面的参数类型是Comparator类型,所以我们可以重写比较代码进行数据排序
示例:
Integer[] array = {1, 3, 2};
Stream<Integer> stream = Arrays.stream(array).sorted(Integer::compare);
stream.forEach(System.out::println);
拓展:
示例中按照正序进行排序,如果想按照倒序进行排序,可以将o1和o2参数位置对调,如下:
Integer[] array = {1, 3, 2};
Stream<Integer> stream = Arrays.stream(array).sorted((o1, o2) -> Integer.compare(o2, o1));
stream.forEach(System.out::println);
当然也可以将原有方式结果加上负号取反,示例如下:
Integer[] array = {1, 3, 2};
Stream<Integer> stream = Arrays.stream(array).sorted((o1, o2) -> -Integer.compare(o1, o2));
stream.forEach(System.out::println);
4、终止操作
4.1、查找与匹配
4.1.1、allMatch()
作用:
必须所有元素都符合要求才返回true,只要有一个不符合要求,就返回false
示例:
Integer[] array = {1, 3, 2};
boolean flag = Arrays.stream(array).allMatch(x -> x > 1);
System.out.println(flag);
4.1.2、anyMatch()
作用:
只要有任意一个元素符合要求就返回true
示例:
Integer[] array = {1, 3, 2};
boolean flag = Arrays.stream(array).anyMatch(x -> x > 1);
System.out.println(flag);
4.1.3、noneMatch()
作用:
必须所有元素都不符合要求才返回true,只要有一个符合要求,就返回false
示例:
Integer[] array = {1, 3, 2};
boolean flag = Arrays.stream(array).noneMatch(x -> x > 1);
System.out.println(flag);
4.1.4、findFirst()
作用:
返回第一个元素
示例:
Integer[] array = {1, 3, 2};
Optional<Integer> first = Arrays.stream(array).findFirst();
System.out.println(first.get());
4.1.5、findAny()
作用:
随机返回任意一个元素
示例:
Integer[] array = {1, 3, 2};
Optional<Integer> first = Arrays.stream(array).findAny();
System.out.println(first.get());
4.1.6、count()
作用:
计算元素总数
示例:
Integer[] array = {1, 3, 2};
long count = Arrays.stream(array).count();
System.out.println(count);
4.1.7、max()
作用:
获取元素最大值,其中值的比较使用自定义比较器
示例:
Integer[] array = {1, 3, 2};
Optional<Integer> max = Arrays.stream(array).max(Integer::compareTo);
System.out.println(max.get());
4.1.8、min()
作用:
获取元素最小值,其中值的比较使用自定义比较器
示例:
Integer[] array = {1, 3, 2};
Optional<Integer> max = Arrays.stream(array).min(Integer::compareTo);
System.out.println(max.get());
4.1.9、foreach()
作用:
内部迭代
示例:
Integer[] array = {1, 3, 2};
Arrays.stream(array).forEach(System.out::println);
4.2、查找与匹配
4.2.1、reduce()
作用:
将元素进行归集操作
示例:
// 1、单参
// 说明:默认起始值是0,但是只有在reduce()方法之前的结果中有内容才有用
Integer[] array = {1, 2, 3};
Optional<Integer> reduce = Arrays.stream(array).reduce(Integer::sum);
System.out.println(reduce.get());
// 2、多参
// 说明:起始值设置为0,然后起始值0被赋值给x,y按照顺序对应array中的值,进行一次x+y之后,然后将x+y的值赋值给x,之后在进行下一次x+y操作……
Integer[] array = {1, 2, 3};
Integer reduce = Arrays.stream(array).reduce(0, (x, y) -> x + y);
System.out.println(reduce);
4.3、收集
4.3.1、Collectors.toList()
作用:
将结果转换为List集合
示例:
Integer[] array = {1, 2, 3};
List<Integer> collect = Arrays.stream(array).collect(Collectors.toList());
System.out.println(collect);
4.3.2、Collectors.toSet()
作用:
将结果转换为Set集合
示例:
Integer[] array = {1, 2, 3};
Set<Integer> collect = Arrays.stream(array).collect(Collectors.toSet());
System.out.println(collect);
4.3.3、Collectors.toMap()
作用:
将结果转换为Map集合
示例:
Integer[] array = {1, 2, 3};
Map<Integer, Integer> map = Arrays.stream(array).collect(Collectors.toMap(x -> x, y -> y + 1));
System.out.println(map);
4.3.4、Collectors.counting()
作用:
计数
示例:
Integer[] array = {1, 2, 3};
Long collect = Arrays.stream(array).collect(Collectors.counting());
System.out.println(collect);
4.3.5、Collectors.averagingInt()
作用:
计算平均值
示例:
Integer[] array = {1, 2, 3};
Double collect = Arrays.stream(array).collect(Collectors.averagingInt(x -> x));
System.out.println(collect);
4.3.6、Collectors.summingDouble()
作用:
计算总和
示例:
Integer[] array = {1, 2, 3};
Double collect = Arrays.stream(array).collect(Collectors.summingDouble(x -> x));
System.out.println(collect);
4.3.7、Collectors.maxBy()
作用:
获取最大值
示例:
Integer[] array = {1, 2, 3};
Optional<Integer> collect = Arrays.stream(array).collect(Collectors.maxBy(Integer::compare));
System.out.println(collect.get());
4.3.8、Collectors.minBy()
作用:
获取最小值
示例:
Integer[] array = {1, 2, 3};
Optional<Integer> collect = Arrays.stream(array).collect(Collectors.minBy(Integer::compare));
System.out.println(collect.get());
4.3.9、Collectors.groupingBy()
作用:
分组
示例1:
// 按照年龄分组
Employee e1 = new Employee(1, "小亮");
Employee e2 = new Employee(1, "小红");
Employee e3 = new Employee(2, "小白");
List<Employee> list = new ArrayList<>();
list.add(e1);
list.add(e2);
list.add(e3);
Map<Integer, List<Employee>> collect = list.stream().collect(Collectors.groupingBy(Employee::getAge));
System.out.println(collect);
示例2:
// 先按照年龄分组,在按照名称分组
Employee e1 = new Employee(1, "小亮");
Employee e2 = new Employee(1, "小红");
Employee e3 = new Employee(1, "小红");
Employee e4 = new Employee(2, "小白");
List<Employee> list = new ArrayList<>();
list.add(e1);
list.add(e2);
list.add(e3);
list.add(e4);
Map<Integer, Map<String, List<Employee>>> collect = list.stream().collect(Collectors.groupingBy(Employee::getAge, Collectors.groupingBy(e -> {
if ("小红".equals(e.getName())) {
return "女孩";
} else {
return "男孩";
}
})));
System.out.println(collect);
公共类:
// 记得添加Lombok依赖
@Data
@AllArgsConstructor
class Employee {
private Integer age;
private String name;
}
4.3.10、Collectors.partitioningBy()
作用:
分区;返回值也是Map,符合条件的进入key为true的List集合中,否则进入key为false的List集合中
示例:
Integer[] array = {1, 2, 3};
Map<Boolean, List<Integer>> collect = Arrays.stream(array).collect(Collectors.partitioningBy(x -> x > 1));
System.out.println(collect);
4.3.11、Collectors.summarizingDouble()
作用:
综合性组函数,可以通过方法获取计算数量、计算和、最大值、最小值、平均值
示例:
Integer[] array = {1, 2, 3};
DoubleSummaryStatistics collect1 = Arrays.stream(array).collect(Collectors.summarizingDouble(x -> x));
IntSummaryStatistics collect2 = Arrays.stream(array).collect(Collectors.summarizingInt(x -> x));
LongSummaryStatistics collect3 = Arrays.stream(array).collect(Collectors.summarizingLong(x -> x));
System.out.println(collect1.getCount());
System.out.println(collect1.getSum());
System.out.println(collect1.getMax());
System.out.println(collect1.getMin());
System.out.println(collect1.getAverage());
4.3.12、Collectors.joining()
作用:
连接函数,只能针对字符串使用
示例:
// 1、仅仅相连,不加分隔符
String[] array = {"1", "2", "3"};
String collect = Arrays.stream(array).collect(Collectors.joining());
System.out.println(collect);
// 2、相连的时候添加分隔符
String[] array = {"1", "2", "3"};
String collect = Arrays.stream(array).collect(Collectors.joining(","));
System.out.println(collect);
// 3、相连的时候添加分隔符,并且首尾添加符号
String[] array = {"1", "2", "3"};
String collect = Arrays.stream(array).collect(Collectors.joining(",", "》》》", "《《《"));
System.out.println(collect);
五、Lambda表达式、函数式接口、引用、Stream流——练习题
1、给定一个数字列表,返回每个数的平方构成的列表,比如给出【1, 2, 3, 4, 5】,返回【1, 4, 9, 16, 25】
public class Test {
public static void main(String[] args) {
Integer[] array = {1, 2, 3, 4, 5};
List<Integer> list = Arrays.stream(array).map(x -> x * x).collect(Collectors.toList());
System.out.println(list);
}
}
2、使用map和reduce计算Employee对象的个数
public class Test {
public static void main(String[] args) {
Employee e1 = new Employee(1, "小亮");
Employee e2 = new Employee(1, "小红");
Employee e3 = new Employee(1, "小红");
Employee e4 = new Employee(2, "小白");
List<Employee> list = new ArrayList<>();
list.add(e1);
list.add(e2);
list.add(e3);
list.add(e4);
Optional<Integer> reduce = list.stream().map(x -> 1).reduce(Integer::sum);
System.out.println(reduce.get());
}
}
@Data
@AllArgsConstructor
class Employee {
private Integer age;
private String name;
}
3、根据交易员类和交易类,来完成下面的几个问题
原始类:
/**
* 交易员类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
class Trader {
private String name;
private String city;
}
/**
* 交易类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
class Transaction {
private Trader trader;
private int year;
private int value;
}
代码:
public class Test {
public static void main(String[] args) {
// 数据准备
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950));
// 问题1、找出2011年发生的所有交易,并按照交易顺序从低到高排序
transactions.stream().filter(t -> t.getYear() == 2011).sorted((t1, t2) -> Integer.compare(t1.getValue(), t2.getValue())).forEach(System.out::println);
// 问题2、交易员都在哪些不同的城市工作过
transactions.stream().map(t -> t.getTrader().getCity()).distinct().forEach(System.out::println);
// 问题3、查找所有来自剑桥的交易员,并按照姓名排序
transactions.stream().filter(t -> "Cambridge".equals(t.getTrader().getCity())).map(Transaction::getTrader).distinct().sorted((t1, t2) -> t1.getName().compareTo(t2.getName())).forEach(System.out::println);
// 问题4、返回所有交易员的姓名字符串,并按照字符排序
transactions.stream().map(t -> t.getTrader().getName()).distinct().sorted().forEach(System.out::println);
// 问题5、判断是否存在在米兰工作的交易员
System.out.println(transactions.stream().anyMatch(t -> "Milan".equals(t.getTrader().getCity())));
// 问题6、找到在剑桥工作的交易员的所有交易额
System.out.println(transactions.stream().filter(t -> "Cambridge".equals(t.getTrader().getCity())).map(Transaction::getValue).reduce(Integer::sum).get());
// 问题7、找到最高交易额
System.out.println(transactions.stream().map(Transaction::getValue).max(Integer::compare).get());
// 问题8、找到最低交易额
System.out.println(transactions.stream().map(Transaction::getValue).min(Integer::compare).get());
}
}
/**
* 交易员类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
class Trader {
private String name;
private String city;
}
/**
* 交易类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
class Transaction {
private Trader trader;
private int year;
private int value;
}
六、并行流和串行流
1、Fork/Join框架
1.1、概念
在必要情况下,将一个大任务拆分(fork)成若干个小任务(拆到不可在拆为止,截止条件需要程序员控制),然后在将一个个小任务的运算结果进行汇总(join)
1.2、线程窃取
无论是拆分方式、拆分截止条件、汇总方式都是程序员来控制的,被拆分的小块会以线程的形式分配到CPU核上执行,其中一个CPU核上会执行线程队列,如果有些CPU核上的线程执行结束,ForkJoin框架还会去其他正在执行的CPU核上偷取线程来执行,这叫做线程窃取,作用是充分利用资源,加快计算效率,尚硅谷给出的解释如下:
1.3、充分利用CPU资源
使用ForkJoin框架可以充分利用CPU的所有核心,而普通for循环执行代码只会利用CPU单核。在小数据量情况下,由于任务拆分也会占用时间,所以普通代码可能更胜一筹,但是在大数据量计算的时候,CPU多核同时计算的速度肯定更快,时间肯定更短。我们测试
1.4、JDK1.7:使用ForkJoin框架计算0~100000000000之和
代码,在执行过程中将会看到几乎所有CPU核心的资源利用率都是100%,通过任务管理器查看CPU资源占用情况如下所示:
1.4、并行流和串行流的区别
串行流使用一个线程去做事情,而并行流使用了ForkJoin框架,可以进行线程拆分、汇总、窃取等操作,用以加快CPU运行效率,在Java8中默认使用串行流,也就是顺序流。通过parallel()方法可以使用并行流,通过sequential()方法可以使用顺序流,示例如下:
// 1、并行流
long sum = LongStream.rangeClosed(0, 100000000000L).parallel().reduce(0, Long::sum);
// 2、串行流
long sum = LongStream.rangeClosed(0, 100000000000L).sequential().reduce(0, Long::sum);
1.5、JDK1.7下使用ForkJoin框架计算0~100000000000之和
public class Test {
public static void main(String[] args) {
// 计时
Instant start = Instant.now();
// 池
ForkJoinPool pool = new ForkJoinPool();
MyForkJoin myForkJoin = new MyForkJoin(0L, 100000000000L);
// 执行
Long sum = pool.invoke(myForkJoin);
// 打印结果
System.out.println("加和结果:" + sum);
// 打印耗费时间
Instant end = Instant.now();
System.out.println("耗费时间:" + Duration.between(start, end).toMillis() + "ms");
}
}
class MyForkJoin extends RecursiveTask<Long> {
private Long start;
private Long end;
public MyForkJoin(Long start, Long end) {
this.start = start;
this.end = end;
}
// 截止条件(门槛)
private static final long THRESHOLD = 10000L;
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
long middle = (start + end) / 2;
// 左侧拆分
MyForkJoin left = new MyForkJoin(start, middle);
left.fork();
// 右侧拆分
MyForkJoin right = new MyForkJoin(middle + 1, end);
right.fork();
// 汇总
return left.join() + right.join();
}
}
}
1.6、JDK1.8下使用并行流计算0~100000000000之和
public class Test {
public static void main(String[] args) {
// 计时
Instant start = Instant.now();
// 计算
long sum = LongStream.rangeClosed(0, 100000000000L).parallel().reduce(0, Long::sum);
// 打印结果
System.out.println("加和结果:" + sum);
// 打印耗费时间
Instant end = Instant.now();
System.out.println("耗费时间:" + Duration.between(start, end).toMillis() + "ms");
}
}
七、Optional
1、基本概念
Option类是一个容器类,用来装载数据,可以判断一个值是否为null,并且可以避免空指针异常
2、常用方法
默认类写到这里,下面例子中不在写了
@Data
class Employee {
private Integer age;
private String name;
}
2.1、of()
示例:
Optional<Employee> optional = Optional.of(new Employee());
System.out.println(optional.get());
说明:
如果of()方法参数不为空,那么Optional.get()就能获取到值;
如果of()方法参数为null,那么Optional.of()执行的时候会出现空指针异常
2.2、empty()
示例:
Optional<Employee> optional = Optional.empty();
System.out.println(optional.get());
说明:
创建一个空的Optiona实例,在上面代码执行过程中,执行optional.get()方法会出现空指针异常
2.3、ofNullable()
示例:
Optional<Employee> optional = Optional.ofNullable(new Employee());
System.out.println(optional.get());
说明:
如果ofNullable()参数不为空,那么将返回正常的Optional实例,可以正常调用get()方法;
如果ofNullable()参数为null,那么将返回空实例,并且调用get()方法的时候将出现空指针异常;
2.4、isPresent()
示例:
Optional<Employee> optional = Optional.ofNullable(null);
System.out.println(optional.isPresent());
说明:
判断Optional中值是否为空;如果不为空则返回true,否则返回false
2.5、orElse()
示例:
Optional<Employee> optional = Optional.ofNullable(null);
System.out.println(optional.orElse(new Employee()));
说明:
如果Optional中对象不为空,则返回该对象,否则返回orElase()方法中的参数值
2.6、orElseGet()
示例:
Optional<Employee> optional = Optional.ofNullable(null);
System.out.println(optional.orElseGet(Employee::new));
说明:
如果Optional中对象不为空,则返回该对象,否则返回orElseGet()方法中Supplier函数式接口返回的参数值
2.7、map()
示例:
Optional<Employee> optional = Optional.ofNullable(null);
System.out.println(optional.map(x -> {
x.setName("小亮");
x.setAge(17);
return x;
}).get());
说明:
我们可以对Optional中对象进行处理,然后在返回该对象,因为map()方法中的Function函数式接口要求返回值就是Optional中的对象类型
2.8、flatMap()
示例:
Optional<Employee> optional = Optional.of(new Employee());
System.out.println(optional.flatMap(x -> {
x.setName("小亮");
x.setAge(17);
return Optional.ofNullable(x);
}).get());
说明:
对Optional中的对象进行处理,然后在返回一个Optional对象,返回Optional对象中的对象类型还是原来的类型,比如上面就是Employee类型
3、方法实战
public class Test {
public static void main(String[] args) {
System.out.println(getEmployeeName(Optional.ofNullable(null)));
}
private static String getEmployeeName(Optional<Boss> boss) {
String name = boss.orElse(new Boss()).getEmployee().orElse(new Employee(17, "李华")).getName();
return name;
}
}
@Data
class Boss {
private Optional<Employee> employee = Optional.empty();
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Employee {
private Integer age;
private String name;
}
解释:
在填写默认值,避免出现空指针异常问题上,倒是一把好手,也避免我们写很多的if判空判断
八、接口中的默认方法和静态方法
1、默认方法
1.1、类优先原则
示例:
public class Test {
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.test();
}
}
class MyTest extends MyClass implements MyInterface {
}
interface MyInterface {
default void test() {
System.out.println("MyInterface》test()");
}
}
class MyClass {
public void test() {
System.out.println("MyClass》test()");
}
}
说明:
如果某实现类的父类和实现接口的某方法相同,如果我们调用该类的该方法,那么父类中的该方法将被调用,原因就是类优先原则
1.2、两个接口中存在相同方法,实现类必须实现该方法来解决冲突
示例:
public class Test {
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.test();
}
}
class MyTest implements MyInterface1, MyInterface2 {
@Override
public void test() {
MyInterface1.super.test();
}
}
interface MyInterface1 {
default void test() {
System.out.println("MyInterface1》test()");
}
}
interface MyInterface2 {
default void test() {
System.out.println("MyInterface2》test()");
}
}
说明:
MyTest类实现了MyInterface1和MyInterface2接口,然而两个接口中有相同的test()方法,所以MyTest实现类必须自己实现test()方法,不然在调用MyTest类的时候无法知道调用的是哪个方法,因此必须实现
2、静态方法
示例:
public class Test {
public static void main(String[] args) {
MyInterface.test();
}
}
interface MyInterface {
static void test() {
System.out.println("静态方法");
}
}
说明:
对于函数式接口来说,只允许接口中有一个抽象方法,也就是没有方法体的方法,因此函数式接口中有一个或者多个默认方法或者静态方法也是没有任何问题的,它依然是函数式接口
九、新时间日期API
1、传统时间格式化的线程安全问题
1.1、演示SimpleDateFormat不是线程安全的现象
示例:
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<Date>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<Date> future = executorService.submit(() -> sdf.parse("2022-07-02"));
list.add(future);
}
for (Future<Date> future : list) {
System.out.println(future.get());
}
}
}
结果:
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NumberFormatException: multiple points
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.atguigu.test3.Test.main(Test.java:56)
Caused by: java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.atguigu.test3.Test.lambda$main$0(Test.java:52)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
说明:
通过执行结果可以看出,代码执行过程中出现了并发执行异常,其实我之前还真的写过这种代码,场景是在多线程代码执行执行过程中,我把SimpleDateFormat类定义在线程池外边,然后在线程执行过程中使用了format()方法进行格式化,用以获取当前执行时间,这就出现了线程安全问题,当时我的解决办法是把SimpleDatefo对象定义在线程执行的时候,这样就避免了线程安全问题,但是每一个线程执行的时候都需要创建一次SimpleDateFormat对象,这其实也造成了资源浪费,我们来看一下讲师如何使用ThreadLocal来解决这个问题的
1.2、Jdk1.8之前使用ThreadLocal解决SimpleDateFormat线程不安全问题
代码:
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<Date>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// ThreadLocal对象属于线程的,在线程池中线程可以重复利用,那么该对象也可以重复利用,如果对这一点持有疑惑,可以把线程池线程数调小一点,执行次数调大一点,在MyDateFormat类中的ThreadLocal类的initialValue()方法处打印当前线程名称,你会发现不会打印太多的线程名称
Future<Date> future = executorService.submit(() -> MyDateFormat.format("2022-07-02"));
list.add(future);
}
for (Future<Date> future : list) {
System.out.println(future.get());
}
}
}
/**
* 日期格式化类
* @date 2022/7/2 12:28
**/
class MyDateFormat {
private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>() {
// 调用ThreadLocal对象get()方法,如果里面没值,那么会调用initialValue()方法先进行初始化,再将初始化结果返回
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
public static Date format(String time) throws ParseException {
return THREAD_LOCAL.get().parse(time);
}
}
说明:
如果对ThreadLocal为什么可以保证线程安全,以及调用get()方法的时候返回的为什么是initialValue()方法的结果,可以看我转载的另外一篇文章:
进阶良药之ThreadLocal
1.3、Jdk1.8使用新时间API解决获取由于时间日期导致的线程不安全问题
代码:
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<LocalDate>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<LocalDate> future = executorService.submit(() -> LocalDate.parse("2022-07-02", dtf));
list.add(future);
}
for (Future<LocalDate> future : list) {
System.out.println(future.get());
}
}
}
2、Java8新时间API——本地时间与时间戳
2.1、LocalDateTime、LocalDate、LocalTime
示例代码:
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1、获取当前时间
LocalDateTime t1 = LocalDateTime.now();
System.out.println(t1);
// 2、获取年月日时分秒
int year = t1.getYear();
int month = t1.getMonthValue();
int day = t1.getDayOfMonth();
int hour = t1.getHour();
int minute = t1.getMinute();
int second = t1.getSecond();
System.out.printf("%s-%s-%s %s:%s:%s\n", year, month, day, hour, minute, second);
// 3、获取约定时间
LocalDateTime t2 = LocalDateTime.of(2022, 7, 2, 16, 52, 0, 0);
System.out.println(t2);
// 3、在t1基础上加上2年
LocalDateTime t3 = t1.plusYears(2);
System.out.println(t3);
// 4、在t1基础上减去2月
LocalDateTime t4 = t1.minusMonths(2);
System.out.println(t4);
}
}
2.2、时间戳—Instant
示例如下:
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1、默认获取UTC时区的时间,也就是格林威治时间,和我们北京时间相比慢了8个小时
Instant now = Instant.now();
System.out.println(now);
// 2、校正时区,采用北京时间展示日期
OffsetDateTime odt = Instant.now().atOffset(ZoneOffset.ofHours(8));
System.out.println(odt);
// 3、获取毫秒
long l = Instant.now().toEpochMilli();
System.out.println(l);
// 4、在1970-1-1 00:00:00.000基础上添加时间,本次在此基础上增加1s
Instant instant = Instant.ofEpochSecond(1);
System.out.println(instant);
}
}
2.3、时间间隔计算—Duration
示例代码1(使用Instant 测试):
// 说明:Duration用于计算两个时间之间的间隔
public class Test {
public static void main(String[] args) throws Exception {
Instant start = Instant.now();
Thread.sleep(1000);
Instant end = Instant.now();
Duration between = Duration.between(start, end);
// 不仅可以使用toMinutes(),还可以使用getSeconds()方法,如果找不到合适的方法,可以使用toXXX()或getXXX()试下
System.out.println("相差毫秒数:" + between.toMillis());
}
}
示例代码2(使用LocalDateTime 测试):
public class Test {
public static void main(String[] args) throws Exception {
LocalDateTime start = LocalDateTime.now();
Thread.sleep(1000);
LocalDateTime end = LocalDateTime.now();
Duration between = Duration.between(start, end);
System.out.println("相差毫秒数:" + between.toMillis());
}
}
2.4、日期间隔计算—Period
示例代码:
public class Test {
public static void main(String[] args) throws Exception {
LocalDate start = LocalDate.of(2002, 1, 1);
LocalDate end = LocalDate.now();
Period between = Period.between(start, end);
System.out.println("相差年:" + between.getYears());
System.out.println("相差月:" + between.getMonths());
System.out.println("相差日(只和当前日比较):" + between.getDays());
}
}
结果:
相差年:20
相差月(只和当前月比较):6
相差日(只和当前日比较):1
3、Java8新时间API——时间校正器TemporalAdjuster
示例代码:
public class Test {
public static void main(String[] args) throws Exception {
LocalDateTime now = LocalDateTime.now();
// 1、设置day等于10
LocalDateTime LocalDateTime = now.withDayOfMonth(10);
System.out.println(LocalDateTime);
// 2、获取本月第一天
LocalDateTime monthFirstDay = now.with(TemporalAdjusters.firstDayOfMonth());
System.out.println(monthFirstDay);
// 3、获取下周1
LocalDateTime nextMonday = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
System.out.println(nextMonday);
// 4、获取下一个工作日
LocalDateTime nextWorkDay = now.with(l -> {
// l和now类型相同,所以可以强转
LocalDateTime ldt = (LocalDateTime) l;
DayOfWeek dayOfWeek = ldt.getDayOfWeek();
switch (dayOfWeek) {
case FRIDAY:
return ldt.plusDays(3);
case SATURDAY:
return ldt.plusDays(2);
default:
return ldt.plusDays(1);
}
});
System.out.println(nextWorkDay);
}
}
4、Java8新时间API——时间格式化DateTimeFormatter
public class Test {
public static void main(String[] args) throws Exception {
// 格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
// 1、日期转字符串
String format = LocalDateTime.now().format(formatter);
System.out.println(format);
// 2、字符串转日期
LocalDateTime localDateTime = LocalDateTime.parse(format, formatter);
System.out.println(localDateTime);
}
}
十、重复注解与类型注解
public class Test {
// 1、重复注解用在方法上
@MyInterface("hello")
@MyInterface("world")
private void test(
// 2、将注解放在参数上
@MyInterface("name") String name) {}
}
// 重复注解:@Repeatable中的值是另外一个注解,该注解中的值是当前注解数组
@Repeatable(MyInterfaces.class)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@interface MyInterface {
String value();
}
// target中的类型只能比属性数组中注解的多
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@interface MyInterfaces {
MyInterface[] value();
}