Java8新特性
# Interface
为了解决接口的修改与现有的实现不兼容的问题。新 interface 的方法可以用default
或 static
修饰,这样就可以有方法体,实现类也不必重写此方法。
一个 interface 中可以有多个方法被它们修饰,这 2 个修饰符的区别主要也是普通方法和静态方法的区别。
default
修饰的方法,是普通实例方法,可以用this
调用,可以被子类继承、重写。static
修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用Interface
调用。
public interface Interface1 {
static void sm() {
System.out.println("interface提供的方式实现");
}
default void def() {
System.out.println("interface default方法");
}
//须要实现类重写
void f();
}
public interface Interface2 {
default void def() {
System.out.println("InterfaceNew1 default方法");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
如果有一个类既实现了 Interface1
接口又实现了 Interface2
接口,它们都有def()
,并且 Interface1
接口和 Interface2
接口没有继承关系的话,这时就必须重写def()
。不然的话,编译的时候就会报错。
public class InterfaceNewImpl implements Interface1 , Interface2{
public static void main(String[] args) {
Interface1Impl interface1 = new Interface1Impl();
interfaceNew.def();
}
@Override
public void def() {
Interface1.super.def();
}
@Override
public void f() {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# functional interface 函数式接口
也称 SAM 接口,有且只有一个抽象方法,但可以有多个非抽象方法的接口。
在 java 8 中专门有一个包放函数式接口java.util.function
,该包下的所有接口都有 @FunctionalInterface
注解,提供函数式编程。在其他包中也有函数式接口,其中一些没有@FunctionalInterface
注解,但是只要符合函数式接口的定义就是函数式接口,与是否有@FunctionalInterface
注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用
# Lamda 表达式
# 语法格式
(parameters) -> expression 或
(parameters) ->{ statements; }
2
# Lambda 实战
# 替代匿名内部类
过去给方法传动态参数的唯一方法是使用内部类。比如
1.Runnable
接口
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("The runable now is using!");
}
}).start();
//用lambda
new Thread(() -> System.out.println("It's a lambda function!")).start();
2
3
4
5
6
7
8
2.Comparator
接口
List<Integer> strings = Arrays.asList(1, 2, 3);
Collections.sort(strings, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
//Lambda
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2);
//分解开
Comparator<Integer> comparator = (Integer o1, Integer o2) -> o1 - o2;
Collections.sort(strings, comparator);
2
3
4
5
6
7
8
9
10
11
12
13
14
3.Listener
接口
JButton button = new JButton();
button.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
e.getItem();
}
});
//lambda
button.addItemListener(e -> e.getItem());
2
3
4
5
6
7
8
9
4.自定义接口
Lamda
对接口有没有要求呢?这些匿名内部类只重写了接口的一个方法,当然也只有一个方法须要重写。这就是函数式接口,也就是说只要方法的参数是函数式接口都可以用 Lambda 表达式。
@FunctionalInterface
public interface Comparator<T>{}
@FunctionalInterface
public interface Runnable{}
2
3
4
5
自定义一个函数式接口
@FunctionalInterface
public interface LambdaInterface {
void f();
}
//使用
public class LambdaClass {
public static void forEg() {
lambdaInterfaceDemo(()-> System.out.println("自定义函数式接口"));
}
//函数式接口参数
static void lambdaInterfaceDemo(LambdaInterface i){
i.f();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 集合迭代
void lamndaFor() {
List<String> strings = Arrays.asList("1", "2", "3");
//传统foreach
for (String s : strings) {
System.out.println(s);
}
//Lambda foreach
strings.forEach((s) -> System.out.println(s));
//or
strings.forEach(System.out::println);
//map
Map<Integer, String> map = new HashMap<>();
map.forEach((k,v)->System.out.println(v));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 方法的引用
Java 8 允许使用 ::
关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。
public class LambdaClassSuper {
LambdaInterface sf(){
return null;
}
}
public class LambdaClass extends LambdaClassSuper {
public static LambdaInterface staticF() {
return null;
}
public LambdaInterface f() {
return null;
}
void show() {
//1.调用静态函数,返回类型必须是functional-interface
LambdaInterface t = LambdaClass::staticF;
//2.实例方法调用
LambdaClass lambdaClass = new LambdaClass();
LambdaInterface lambdaInterface = lambdaClass::f;
//3.超类上的方法调用
LambdaInterface superf = super::sf;
//4. 构造方法调用
LambdaInterface tt = LambdaClassSuper::new;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 访问变量
int i = 0;
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - i);
2
lambda 表达式可以引用外边变量,但是该变量默认拥有 final 属性,不能被修改,如果修改,编译时就报错
# 流式编程
# 流式编程入门
# 基础示例
# 需求
有一个菜肴类,其定义如下:
public class Dish {
/**
* 名称
*/
private final String name;
/**
* 是否是素食
*/
private final boolean vegetarian;
/**
* 卡路里
*/
private final int calories;
/**
* 类型
*/
private final Type type;
//类型枚举 分别是是:肉类 鱼类 其他
public enum Type {MEAT, FISH, OTHER}
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
@Override
public String toString() {
return name;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
初始化一个菜肴集合:
public static final List<Dish> menu =
Arrays.asList( new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 400, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
2
3
4
5
6
7
8
9
10
从中筛选出400卡的菜肴名,并且我们希望筛选出来的结果按照热量升序排列。
# 不使用流的做法
使用java8
之前版本的写法步骤比较繁琐,逻辑也很清晰,整体步骤为:
- 筛选出400卡以下的菜肴。
- 创建一个匿名比较器,并基于这个比较器对菜肴进行升序排列。
- 将排列后的菜肴集合的名字存入字符串列表。
- 输出结果。
public static void main(String[] args) {
List<Dish> lowCaloricDishes = new ArrayList<>();
//筛选出小于400卡的菜肴
for (Dish d : menu) {
if (d.getCalories() < 400) {
lowCaloricDishes.add(d);
}
}
//按照升序进行排序
List<String> lowCaloricDishesName = new ArrayList<>();
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
public int compare(Dish d1, Dish d2) {
return Integer.compare(d1.getCalories(), d2.getCalories());
}
});
//存入string列表
for (Dish d : lowCaloricDishes) {
lowCaloricDishesName.add(d.getName());
}
//输出
for (Dish lowCaloricDish : lowCaloricDishes) {
System.out.println(lowCaloricDish);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 使用流的做法
public static void main(String[] args) {
menu.stream()
//找出低于400卡的菜肴
.filter(d -> d.getCalories() < 400)
//按照升序排列
.sorted(comparing(Dish::getCalories))
//得到这些菜肴的名称
.map(Dish::getName)
//组成list
.collect(toList())
//输出结果
.forEach(m -> System.out.println(m));
}
2
3
4
5
6
7
8
9
10
11
12
13
# 流的优势
- 声明性:流式编程的所有操作都是语义化的,我们完全可以通过方法名大致猜出操作目的。
- 可复合:流式编程无需像
jdk8
之前的版本为了实现组合操作而取创建各种临时集合,取而代之的是基于一段连续的流自顶而下的实现组合操作。 - 可并行:当我们需要提高计算密集型任务的性能时,只需将
stream
改为parallelStream
,就可以开启并行流开启多个线程一起工作(默认情况下,线程数和计算机的CPU核心数是一样的)。 - 流水线:流式编程会将组合操作构成一个大的流水线,这使得我们代码能够更进一步优化,例如延迟和短路操作,使得代码可以实现类似于数据库式的查询。
- 内部迭代:流式编程将迭代操作封装起来,对开发者透明,如此一来,开发者专注与对流内部的元素的操作,而无需关注繁琐的非业务代码
# 流的工作原理
比如从菜肴中找到前三个菜肴大于300卡的菜名:
public static void main(String[] args) {
List<String> menuNameList = menu.stream()
//找到大于300卡的菜肴
.filter(d -> d.getCalories() > 300)
//取出这个菜肴的名称
.map(Dish::getName)
//取前3个
.limit(3)
//存入list中
.collect(Collectors.toList());
//输出结果
System.out.println(menuNameList);
}
2
3
4
5
6
7
8
9
10
11
12
13
流式编程的工作原理非常高效,它将组合操作比作一条流水线,将集合中的每一个元素都放到这个流水线上进行操作。上面的代码 工作过程是这样的:
- 拿到
pork
,进入filter
操作,因为其热量大于300卡,继续走到流水线下一个步骤。 - 进入
map
操作,拿到pork对象的name。 - 进入
limit
操作,pork还在limit限定范围,继续走到下一个操作。 - 进入
toList
操作,将pork
存入list
中。 - 同理对
beef
和chicken
完成同样的操作,然后limit达到上限,停止流水线。
# 流与集合的关系
# 每一个集合的流只能使用一次
如下代码所示,每一个集合对应的流只能操作一次,每次操作完成之后这个流就会被关闭,这意味着用户再次使用这个流就会报错:
List<String> strings = Arrays.asList("java8", "in", "action");
Stream<String> stream = strings.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println);
2
3
4
输出结果如下,可以看到对于同一个流的多次操作抛出IllegalStateException
错误:
java8
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
in
action
at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at com.sharkChili.lambda.Main.main(Main.java:27)
2
3
4
5
6
7
# 集合的外部迭代和流的内部迭代
流式编程将循环操作内置,对于用户是无感的,如下代码所示,我们使用peek方法打印经过这个组合操作的元素有哪些:
public static void main(String[] args) {
List<String> menuNameList = menu.stream()
//找到大于300卡的菜肴
.filter(d -> d.getCalories() > 300)
.peek(d -> System.out.println("步骤1:" + d))
//取出这个菜肴的名称
.map(Dish::getName)
.peek(d -> System.out.println("步骤2:" + d))
//取前3个
.limit(3)
.peek(d -> System.out.println("步骤3:" + d))
//存入list中
.collect(Collectors.toList());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
我们就会得到这样一个结果,可以看到每一个元素都会依次经过filter
、和map
、limit
,这就是jdk8
流式编程的循环合并技术:
步骤1:pork
步骤2:pork
步骤3:pork
步骤1:beef
步骤2:beef
步骤3:beef
步骤1:chicken
步骤2:chicken
步骤3:chicken
2
3
4
5
6
7
8
9
# 流的两种操作
# 中间操作
例如filter
、map
、limit
、sort
等只涉及元素的流水线单一步骤且最终会返回stream
类型的方法都是中间操作,它们只有在调用终端操作(即真正要结果的方法)才会开始工作:
filter
:过滤出符合预期的元素,如果不符合预期就不会走到流水线的下一步。map
:映射,将流水线的A元素转为B元素,例如上文示例中基于Menu
的name
字段将Menu
对象转为String
对象。limit
:截取前n个对象,例如上文limit(3)
,这意味着流水线收集到3个元素之后就停止工作。sorted
:排序操作,将流水线的元素按照指定顺序排序。distinct
:去重,收集当前流水线上不重复的元素。
# 终端操作
终端操作通俗的理解就是将中间操作得到的元素放到流水线的终点开始真正的输出,一旦中间操作得到的元素都经过终端操作后,这个流就会被关闭。例如上文中经过filter
、map
、limit
得到的元素都走到collect
这个收集元素的终端操作后,流就关闭了,一旦我们再次使用这个流就会报错。
java8
对应的终端操作有:
forEach
:将中间操作得到的元素进行一次遍历。count
:统计中间操作得到的元素个数,个数的类型为long类型。collect
:将中间操作得到的元素归为一个集合。
# 流式编程详解
# 元素的筛选
# 常规过滤筛选
菜肴类的vegetarian
这个布尔值决定了菜肴是否为蔬菜,我们希望从这个集合中找到所有的蔬菜并返回一个list。
public static void main(String[] args) {
List<Dish> vegetarianMenu =
menu.stream()
//使用filter 结合函数式编程筛选出vegetarian 为true的菜肴
.filter(Dish::isVegetarian)
//将这些流组成一个list数组
.collect(toList());
//遍历vegetarianMenu
vegetarianMenu.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
将每一个元素放到流式操作的流水线上,符合预期的存入list,不符合预期的淘汰
# 找出不重复元素
现有一个无序且包含重复元素的整型数组,从中筛选出能够被2整除的数字并构成一个list集合:
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
在java8
之前:
- for循环找到被2整除的元素。
- 将该元素存到set中。
- 将set转转为list。
java8
之后使用流:
- 拿到集合流。
- 使用filter过滤出被2整除的元素,传到流水线下一步工序。
- 使用distinct判断是否重复。
- 存入list中。
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
List<Integer> integerList = numbers.stream()
//过滤出能够被2整除的数字
.filter(i -> i % 2 == 0)
//去重
.distinct()
//循环遍历
.collect(Collectors.toList());
}
2
3
4
5
6
7
8
9
10
# 限制筛选元素
找到热量大于300卡的前3道菜
java8
之前:for循环加上一个count变量,当找到三道菜了就停止循环。
java8
之后:提供语义化的操作limit
方法,只需通过filter
找到大于300卡的菜肴后,直接使用limit
完成限制筛选
public static void main(String[] args) {
List<Dish> menuList = menu.stream()
//过滤出300卡的菜肴
.filter(d -> d.getCalories() > 300)
//筛选出最先3个符合预期的菜肴
.limit(3)
.collect(toList());
}
2
3
4
5
6
7
8
# 跳过某些元素进行筛选
跳过前两个高于300
卡的菜,再筛选出3道高于300卡的菜肴
对于java8
而言,只需加一个skip
操作即可完成:
public static void main(String[] args) {
List<Dish> menuList = menu.stream()
//过滤出300卡的菜肴
.filter(d -> d.getCalories() > 300)
//跳过前两道热量高于300卡的菜
.skip(2)
//筛选出最先3个符合预期的菜肴
.limit(3)
.collect(toList());
}
2
3
4
5
6
7
8
9
10
# 映射
从菜肴中拿到所有菜肴的名字
java8
之前:声明一个List<String>
的临时集合,然后遍历菜肴集合,获取到每个菜肴的名字,添加到List<String>
这个集合中。
java8
之后:基于map
方法即可映射出所需要的成员属性,结合终端操作collect(toList())
将映射结果转为list
public static void main(String[] args) {
List<String> dishNames = menu.stream()
//拿到每一个菜肴的名字
.map(Dish::getName)
//存入list集合中
.collect(toList());
}
2
3
4
5
6
7
# 映射扁平化
从 List<String>
的单词数组中找出所有不重复的字母,对应的单词数组定义如下:
List<String> words = Arrays.asList("Hello", "World");
①第一时间可能想到这样,将数组中每个单词切成一个个字母,然后distinct
去重,最后转成list
输出
public static void main(String[] args) {
List<String> words = Arrays.asList("Hello", "World");
List<String[]> resultList = words.stream()
//这里映射的是stream<String[]> 后续的中间操作没有什么作用
.map(w -> w.split(""))
.distinct()
.collect(toList());
for (String[] strings : resultList) {
for (String string : strings) {
System.out.print(string);
}
System.out.println();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这样的做法,最终得到的是一个 List<String[]>
,map(w -> w.split(""))
会将hello World
分别切割成String[]
,然后两个String[]
,很显然对一个数组distinct
自然是没有任何作用的
②通过map(Arrays::stream)
,将切割后的string[]
转为Stream<String>
再进行去重:
public static void main(String[] args) {
List<String> words = Arrays.asList("Hello", "World");
List<Stream<String>> list = words.stream()
.map(w -> w.split(""))
//将stream<String[]>转为stream<String> 但还是没有解决问题 因为将数组变成string流
.map(Arrays::stream)
.distinct()
.collect(toList());
for (Stream<String> stream : list) {
List<String> stringList = stream.collect(Collectors.toList());
for (String s : stringList) {
System.out.print(s);
}
System.out.println();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
但是去重再转为数组后得到类型却是 List<Stream<String>>
,这意味着我们只是将两个单词转为两个独立的stream<String>
,去重的对象并不是我们希望的
③正确的写法:通过flatMap
调用Arrays::stream
,即可完成将两个独立的Stream<String>
合并成一个扁平的Stream<String>
public static void main(String[] args) {
List<String> words = Arrays.asList("Hello", "World");
List<String> wordList = words.stream()
.map(w -> w.split(""))
//将数组扁平化合并为流
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
for (String s : wordList) {
System.out.println(s);
}
/*
words.stream()
.flatMap((String line) -> Arrays.stream(line.split("")))
.distinct()
.forEach(System.out::println);
*/
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查找和匹配
# 检查是否至少匹配一个元素
想知道菜肴集合中是否存在素菜,可以使用anyMatch
方法,该方法是一个终端操作,返回一个boolean
值,只要集合中看到一个素食的菜肴,那么就会返回true
:
public static void main(String[] args) {
System.out.println(menu.stream().anyMatch(Dish::isVegetarian));
}
2
3
输出结果:
anyMatch
只要匹配到符合要求的元素之后,就立即停止并关闭流
true
# 检查流元素是否都匹配
同理如果需要全部匹配,就用allMatch
,例如判断菜肴中的菜肴是否都是低于1000卡的:
menu.stream()
.allMatch(d -> d.getCalories() < 1000);
2
# 检查流元素是否都不都匹配
反过来找低于1000卡的
menu.stream()
.noneMatch(d -> d.getCalories() >= 1000);
2
# 查找元素
找出菜肴中是否包含素食,如果有则告知查到的素食是什么菜,可以通过使用findAny
方法做到这一点:
public static void main(String[] args) {
Optional<Dish> optionalDish = menu.stream()
.filter(Dish::isVegetarian)
//只要找到符合要求的菜肴,就直接停止并关闭流
.findAny();
//如果可以找到符合要求的元素,则直接输出打印
if (optionalDish.isPresent()){
System.out.println(optionalDish.get());
}
}
2
3
4
5
6
7
8
9
10
11
但是返回值不是Dish
而是Optional<Dish>
Optional的相关概念:
Optional
有个isPresent()
方法,就以本例来说,假如集合中包含素食,我们调用这个方法就会返回true
,说明找到了素食,反之返回false
get()
方法,以本示例来说,假如找到了素食,get就会返回菜肴对象,若没找到则报出NoSuchElement
异常orElse()
相比上一个方法更加友好,假如我们找到值就返回值,反之就返回orElse
传入的参数值。
输出结果:
french fries
# 查找第一个元素
相比于查找元素,查找第一个元素语义化更加明显,例如想找到第一道素食,就可以使用findFirst
,实际上关于findAny
和findFirst
的使用场景区别不大,但是在并行的情况下,你想找到第一道素食的话,建议你使用findFirst
,反之使用findAny
即可,因为它使用并行流来说限制较少一些:
public static void main(String[] args) {
Optional<Dish> optionalDish = menu.stream()
.filter(Dish::isVegetarian)
//只要找到符合要求的菜肴,就直接停止并关闭流
.findFirst();
//如果可以找到符合要求的元素,则直接输出打印
if (optionalDish.isPresent()){
System.out.println(optionalDish.get());
}
}
2
3
4
5
6
7
8
9
10
11
# 规约
# 元素求和
在java8
之前,元素求和计算都需要经过这三步:
- 创建
sum
变量。 - 遍历集合,取出集合元素。
- 元素就和。
java8
之后只需使用reduce
结合求和表达式即可实现元素求和:
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(4, 5, 3, 9);
//起始数字为0,将列表中的元素全部累加
int res = numbers.stream()
.reduce(0, (sum, num) ->sum+num);
//输出结果
System.out.println(res);
}
2
3
4
5
6
7
8
输出结果如下:
21
基于上述代码我们使用Integer
内置的一个相加的方法sum
,于是代码就可以简化成下面这样:
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(4, 5, 3, 9);
//起始数字为0,将列表中的元素全部累加
int res = numbers.stream()
.reduce(0, Integer::sum);
//输出结果
System.out.println(res);
}
2
3
4
5
6
7
8
9
假如统计操作无需初始值的话,也可以像下文这种写法,这正是java8的高明之处,返回一个Optional操作,让你有余地进行判空操作
·//无需初始值
Optional<Integer> res = numbers.stream().reduce(Integer::sum);
System.out.println(res.get());
2
3
# 求最大值和最小值
//求最大值
int max = numbers.stream().reduce(0, (a, b) -> Integer.max(a, b));
System.out.println(max);
//求最最小值
Optional<Integer> min = numbers.stream().reduce(Integer::min);
min.ifPresent(System.out::println);
2
3
4
5
6
例子:用流式编程统计出菜肴的数量,可以看到笔者的做法很巧妙,通过映射将每道菜计算为1,传到reduce流中统计计算
Optional<Integer> menuCount = menu.stream()
.map(d -> 1)
.reduce(Integer::sum);
System.out.println(menuCount.get());
2
3
4
甚至可以简写成这样,因为java8
为我们提供了这样的终端操作
long count = menu.stream().count();
System.out.println(count);
2
# 延迟执行
执行返回 Stream
的方法时,并不立刻执行,而是等返回一个非 Stream
的方法后才执行。因为拿到 Stream
并不能直接用,而是需要处理成一个常规类型。
@Test
public void laziness(){
List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");
Stream<Integer> stream = strings.stream().filter(new Predicate() {
@Override
public boolean test(Object o) {
System.out.println("Predicate.test 执行");
return true;
}
});
System.out.println("count 执行");
stream.count();
}
/*-------执行结果--------*/
count 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
按执行顺序应该是先打印 4 次「Predicate.test
执行」,再打印「count
执行」。实际结果恰恰相反。说明 filter 中的方法并没有立刻执行,而是等调用count()
方法后才执行。
上面都是串行 Stream
的实例。并行 parallelStream
在使用方法上和串行一样。主要区别是 parallelStream
可多线程执行,是基于 ForkJoin
框架实现的.
@Test
public void parallelStreamTest(){
List<Integer> numbers = Arrays.asList(1, 2, 5, 4);
numbers.parallelStream() .forEach(num->System.out.println(Thread.currentThread().getName()+">>"+num));
}
//执行结果
main>>5
ForkJoinPool.commonPool-worker-2>>4
ForkJoinPool.commonPool-worker-11>>1
ForkJoinPool.commonPool-worker-9>>2
2
3
4
5
6
7
8
9
10
# 常用方法
# 方法
/**
* 返回一个串行流
*/
default Stream<E> stream()
/**
* 返回一个并行流
*/
default Stream<E> parallelStream()
/**
* 返回T的流
*/
public static<T> Stream<T> of(T t)
/**
* 返回其元素是指定值的顺序流。
*/
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
/**
* 过滤,返回由与给定predicate匹配的该流的元素组成的流
*/
Stream<T> filter(Predicate<? super T> predicate);
/**
* 此流的所有元素是否与提供的predicate匹配。
*/
boolean allMatch(Predicate<? super T> predicate)
/**
* 此流任意元素是否有与提供的predicate匹配。
*/
boolean anyMatch(Predicate<? super T> predicate);
/**
* 返回一个 Stream的构建器。
*/
public static<T> Builder<T> builder();
/**
* 使用 Collector对此流的元素进行归纳
*/
<R, A> R collect(Collector<? super T, A, R> collector);
/**
* 返回此流中的元素数。
*/
long count();
/**
* 返回由该流的不同元素(根据 Object.equals(Object) )组成的流。
*/
Stream<T> distinct();
/**
* 遍历
*/
void forEach(Consumer<? super T> action);
/**
* 用于获取指定数量的流,截短长度不能超过 maxSize 。
*/
Stream<T> limit(long maxSize);
/**
* 用于映射每个元素到对应的结果
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
/**
* 根据提供的 Comparator进行排序。
*/
Stream<T> sorted(Comparator<? super T> comparator);
/**
* 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。
*/
Stream<T> skip(long n);
/**
* 返回一个包含此流的元素的数组。
*/
Object[] toArray();
/**
* 使用提供的 generator函数返回一个包含此流的元素的数组,以分配返回的数组,以及分区执行或调整大小可能需要的任何其他数组。
*/
<A> A[] toArray(IntFunction<A[]> generator);
/**
* 合并流
*/
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# 例子
@Test
public void test() {
List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");
//返回符合条件的stream
Stream<String> stringStream = strings.stream().filter(s -> "abc".equals(s));
//计算流符合条件的流的数量
long count = stringStream.count();
//forEach遍历->打印元素
strings.stream().forEach(System.out::println);
//limit 获取到1个元素的stream
Stream<String> limit = strings.stream().limit(1);
//toArray 比如我们想看这个limitStream里面是什么,比如转换成String[],比如循环
String[] array = limit.toArray(String[]::new);
//map 对每个元素进行操作返回新流
Stream<String> map = strings.stream().map(s -> s + "22");
//sorted 排序并打印
strings.stream().sorted().forEach(System.out::println);
//Collectors collect 把abc放入容器中
List<String> collect = strings.stream().filter(string -> "abc".equals(string)).collect(Collectors.toList());
//把list转为string,各元素用,号隔开
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(","));
//对数组的统计,比如用
List<Integer> number = Arrays.asList(1, 2, 5, 4);
IntSummaryStatistics statistics = number.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : "+statistics.getMax());
System.out.println("列表中最小的数 : "+statistics.getMin());
System.out.println("平均数 : "+statistics.getAverage());
System.out.println("所有数之和 : "+statistics.getSum());
//concat 合并流
List<String> strings2 = Arrays.asList("xyz", "jqx");
Stream.concat(strings2.stream(),strings.stream()).count();
//注意 一个Stream只能操作一次,不能断开,否则会报错。
Stream stream = strings.stream();
//第一次使用
stream.limit(2);
//第二次使用
stream.forEach(System.out::println);
//报错 java.lang.IllegalStateException: stream has already been operated upon or closed
//但是可以这样, 连续使用
stream.limit(2).forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 实践
定义一个交易员的类
public class Trader {
/**
* 姓名
*/
private String name;
/**
* 居住城市
*/
private String city;
public Trader(String n, String c) {
this.name = n;
this.city = c;
}
public String getName() {
return this.name;
}
public String getCity() {
return this.city;
}
public void setCity(String newCity) {
this.city = newCity;
}
public String toString() {
return "Trader:" + this.name + " in " + this.city;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
交易订单类
public class Transaction{
/**
* 交易员
*/
private Trader trader;
/**
* 交易年份
*/
private int year;
/**
* 交以金额
*/
private int value;
public Transaction(Trader trader, int year, int value)
{
this.trader = trader;
this.year = year;
this.value = value;
}
public Trader getTrader(){
return this.trader;
}
public int getYear(){
return this.year;
}
public int getValue(){
return this.value;
}
public String toString(){
return "{" + this.trader + ", " +
"year: "+this.year+", " +
"value:" + this.value +"}";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
用这两个类,构成了一个关于订单的列表:
//4个交易员
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");
//4个交易员的订单总表
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)
);、
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 问题
基于上述代码,给出下面几道问题:
(1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
(2) 交易员都在哪些不同的城市工作过?
(3) 查找所有来自于剑桥的交易员,并按姓名排序。
(4) 返回所有交易员的姓名字符串,按字母顺序排序。
(5) 有没有交易员是在米兰工作的?
(6) 打印生活在剑桥的交易员的所有交易额。
(7) 所有交易中,最高的交易额是多少?
(8) 找到交易额最小的交易。
2
3
4
5
6
7
8
# 对应答案
问题1:
//找出2011年发生的所有交易,并按交易额排序(从低到高)
List<Transaction> transactions_2011 = transactions.stream()
.filter(t -> t.getYear() == 2011)
.sorted(comparing(Transaction::getValue))
.collect(toList());
System.out.println(transactions_2011);
2
3
4
5
6
问题2:
//交易员都在哪些不同的城市工作过
List<String> citys = transactions.stream()
.map(Transaction::getTrader)
.map(Trader::getCity)
.distinct()
.collect(toList());
System.out.println(citys);
2
3
4
5
6
7
问题3:
//查找所有来自于剑桥的交易员,并按姓名排序
List<Trader> cambridgeTraders = transactions.stream()
.map(Transaction::getTrader)
.filter(t -> "Cambridge".equals(t.getCity()))
.distinct()
.sorted(comparing(Trader::getName))
.collect(toList());
System.out.println(cambridgeTraders);
2
3
4
5
6
7
8
问题4:
//返回所有交易员的姓名字符串,按字母顺序排序。×
String names = transactions.stream()
.map(Transaction::getTrader)
.map(Trader::getName)
.distinct()
.sorted()
.reduce("", (s1, s2) -> s1 + s2);
System.out.println(names);
2
3
4
5
6
7
8
问题5:
//有没有交易员是在米兰工作的?
boolean hasMilan = transactions.stream()
.anyMatch(t -> "Milan".equals(t.getTrader().getCity()));
2
3
问题6:
//打印生活在剑桥的交易员的所有交易额。
Optional<Integer> sum = transactions.stream()
.filter(t -> "Cambridge".equals(t.getTrader().getCity()))
.map(Transaction::getValue)
.reduce(Integer::sum);
System.out.println(sum.get());
2
3
4
5
6
问题7:
//所有交易中,最高的交易额是多少?
Optional<Integer> max = transactions.stream()
.map(Transaction::getValue)
.reduce(Integer::max);
System.out.println(max.get());
2
3
4
5
问题8:
//找到交易额最小的交易
Optional<Transaction> min = transactions.stream()
.min(comparing(Transaction::getValue));
System.out.println(min);
2
3
4
# Optional
# 简介
NPE
产生的场景:
1) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE
。
2) 数据库的查询结果可能为 null
。
3) 集合里的元素即使 isNotEmpty
,取出的数据元素也可能为 null
。
4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE
。
5) 对于 Session
中获取的数据,建议进行 NPE
检查,避免空指针。
6) 级联调用 obj.getA().getB().getC()
;一连串调用,易产生 NPE
。
建议使用 Optional
解决 NPE
(java.lang.NullPointerException
)问题,它就是为 NPE
而生的,其中可以包含空值或非空值。
传统解决 NPE
的办法如下:
A a = getA();
if(a != null){
B b = a.getB();
if(b != null){
int age = b.getAge();
System.out.println(age);
}
}
2
3
4
5
6
7
8
Optional
是这样的实现的:
Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).ifPresent(age ->
System.out.println(age)
);
2
3
# 如何创建使用Optional
/**
* Common instance for {@code empty()}. 全局EMPTY对象
*/
private static final Optional<?> EMPTY = new Optional<>();
/**
* Optional维护的值
*/
private final T value;
/**
* 如果value是null就返回EMPTY,否则就返回of(T)
*/
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
/**
* 返回 EMPTY 对象
*/
public static<T> Optional<T> empty() {
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
/**
* 返回Optional对象
*/
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
/**
* 私有构造方法,给value赋值
*/
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
/**
* 所以如果of(T value) 的value是null,会抛出NullPointerException异常,这样貌似就没处理NPE问题
*/
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
ofNullable
方法和of
方法唯一区别就是当 value 为 null 时,ofNullable
返回的是EMPTY
,of 会抛出 NullPointerException
异常。如果需要把 NullPointerException
暴漏出来就用 of
,否则就用 ofNullable
。
map()
和 flatMap()
有什么区别的?
map
和 flatMap
都是将一个函数应用于集合中的每个元素,但不同的是map
返回一个新的集合,flatMap
是将每个元素都映射为一个集合,最后再将这个集合展平。
在实际应用场景中,如果map
返回的是数组,那么最后得到的是一个二维数组,使用flatMap
就是为了将这个二维数组展平变成一个一维数组。
public class MapAndFlatMapExample {
public static void main(String[] args) {
List<String[]> listOfArrays = Arrays.asList(
new String[]{"apple", "banana", "cherry"},
new String[]{"orange", "grape", "pear"},
new String[]{"kiwi", "melon", "pineapple"}
);
List<String[]> mapResult = listOfArrays.stream()
.map(array -> Arrays.stream(array).map(String::toUpperCase).toArray(String[]::new))
.collect(Collectors.toList());
System.out.println("Using map:");
System.out.println(mapResult);
List<String> flatMapResult = listOfArrays.stream()
.flatMap(array -> Arrays.stream(array).map(String::toUpperCase))
.collect(Collectors.toList());
System.out.println("Using flatMap:");
System.out.println(flatMapResult);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
运行结果:
Using map:
[[APPLE, BANANA, CHERRY], [ORANGE, GRAPE, PEAR], [KIWI, MELON, PINEAPPLE]]
Using flatMap:
[APPLE, BANANA, CHERRY, ORANGE, GRAPE, PEAR, KIWI, MELON, PINEAPPLE]
2
3
4
5
当使用map()
时,如果映射函数返回的是一个普通值,它会将这个值包装在一个新的Optional
中。而使用flatMap
时,如果映射函数返回的是一个Optional
,它会将这个返回的Optional
展平,不再包装成嵌套的Optional
。(在上文映射扁平化中有详细对比)
# 判断 value 是否为 null
/**
* value是否为null
*/
public boolean isPresent() {
return value != null;
}
/**
* 如果value不为null执行consumer.accept
*/
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
2
3
4
5
6
7
8
9
10
11
12
13
# 获取value
/**
* Return the value if present, otherwise invoke {@code other} and return
* the result of that invocation.
* 如果value != null 返回value,否则返回other的执行结果
*/
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
/**
* 如果value != null 返回value,否则返回T
*/
public T orElse(T other) {
return value != null ? value : other;
}
/**
* 如果value != null 返回value,否则抛出参数返回的异常
*/
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
/**
* value为null抛出NoSuchElementException,不为空返回value。
*/
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 过滤值
/**
* 1. 如果是empty返回empty
* 2. predicate.test(value)==true 返回this,否则返回empty
*/
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
2
3
4
5
6
7
8
9
10
11
综合使用Optional
的高频方法
Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).filter(v->v==1).orElse(3);
# Date-Time API
对java.util.Date
的补充,解决了 Date 类的大部分痛点:
- 非线程安全
- 时区处理麻烦
- 各种格式化、和时间计算繁琐
- 设计有缺陷,Date 类同时包含日期和时间;还有一个 java.sql.Date,容易混淆。
# java.time 主要类
java.util.Date
既包含日期又包含时间,而 java.time
把它们进行了分离
LocalDateTime.class //日期+时间 format: yyyy-MM-ddTHH:mm:ss.SSS
LocalDate.class //日期 format: yyyy-MM-dd
LocalTime.class //时间 format: HH:mm:ss
2
3
# 格式化
Java8 之前:
public void oldFormat(){
Date now = new Date();
//format yyyy-MM-dd
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String date = sdf.format(now);
System.out.println(String.format("date format : %s", date));
//format HH:mm:ss
SimpleDateFormat sdft = new SimpleDateFormat("HH:mm:ss");
String time = sdft.format(now);
System.out.println(String.format("time format : %s", time));
//format yyyy-MM-dd HH:mm:ss
SimpleDateFormat sdfdt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String datetime = sdfdt.format(now);
System.out.println(String.format("dateTime format : %s", datetime));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Java 8 之后:
public void newFormat(){
//format yyyy-MM-dd
LocalDate date = LocalDate.now();
System.out.println(String.format("date format : %s", date));
//format HH:mm:ss
LocalTime time = LocalTime.now().withNano(0);
System.out.println(String.format("time format : %s", time));
//format yyyy-MM-dd HH:mm:ss
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String dateTimeStr = dateTime.format(dateTimeFormatter);
System.out.println(String.format("dateTime format : %s", dateTimeStr));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 字符串转日期格式
Java 8 之前:
//已弃用
Date date = new Date("2021-01-26");
//替换为
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date1 = sdf.parse("2021-01-26");
2
3
4
5
Java 8 之后:
LocalDate date = LocalDate.of(2021, 1, 26);
LocalDate.parse("2021-01-26");
LocalDateTime dateTime = LocalDateTime.of(2021, 1, 26, 12, 12, 22);
LocalDateTime.parse("2021-01-26 12:12:22");
LocalTime time = LocalTime.of(12, 12, 22);
LocalTime.parse("12:12:22");
2
3
4
5
6
7
8
Java 8 之前 转换都需要借助 SimpleDateFormat
类,而Java 8 之后只需要 LocalDate
、LocalTime
、LocalDateTime
的 of
或 parse
方法。
# 日期计算
下面仅以一周后日期为例。单位都在 java.time.temporal.ChronoUnit
枚举中定义。
Java 8 之前:
public void afterDay(){
//一周后的日期
SimpleDateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd");
Calendar ca = Calendar.getInstance();
ca.add(Calendar.DATE, 7);
Date d = ca.getTime();
String after = formatDate.format(d);
System.out.println("一周后日期:" + after);
//算两个日期间隔多少天,计算间隔多少年,多少月方法类似
String dates1 = "2021-12-23";
String dates2 = "2021-02-26";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date1 = format.parse(dates1);
Date date2 = format.parse(dates2);
int day = (int) ((date1.getTime() - date2.getTime()) / (1000 * 3600 * 24));
System.out.println(dates1 + "和" + dates2 + "相差" + day + "天");
//结果:2021-02-26和2021-12-23相差300天
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Java 8 之后:
public void pushWeek(){
//一周后的日期
LocalDate localDate = LocalDate.now();
//方法1
LocalDate after = localDate.plus(1, ChronoUnit.WEEKS);
//方法2
LocalDate after2 = localDate.plusWeeks(1);
System.out.println("一周后日期:" + after);
//算两个日期间隔多少天,计算间隔多少年,多少月
LocalDate date1 = LocalDate.parse("2021-02-26");
LocalDate date2 = LocalDate.parse("2021-12-23");
Period period = Period.between(date1, date2);
System.out.println("date1 到 date2 相隔:"
+ period.getYears() + "年"
+ period.getMonths() + "月"
+ period.getDays() + "天");
//打印结果是 “date1 到 date2 相隔:0年9月27天”
//这里period.getDays()得到的天是抛去年月以外的天数,并不是总天数
//如果要获取纯粹的总天数应该用下面的方法
long day = date2.toEpochDay() - date1.toEpochDay();
System.out.println(date1 + "和" + date2 + "相差" + day + "天");
//打印结果:2021-02-26和2021-12-23相差300天
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 获取指定日期
Java 8 之前:
public void getDay() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
//获取当前月第一天:
Calendar c = Calendar.getInstance();
c.set(Calendar.DAY_OF_MONTH, 1);
String first = format.format(c.getTime());
System.out.println("first day:" + first);
//获取当前月最后一天
Calendar ca = Calendar.getInstance();
ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));
String last = format.format(ca.getTime());
System.out.println("last day:" + last);
//当年最后一天
Calendar currCal = Calendar.getInstance();
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.set(Calendar.YEAR, currCal.get(Calendar.YEAR));
calendar.roll(Calendar.DAY_OF_YEAR, -1);
Date time = calendar.getTime();
System.out.println("last day:" + format.format(time));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24