JDK8新特性
Lambda表达式
1. 使用匿名内部类存在的问题
当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。
传统写法,代码如下:
public class LambdaIntro {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程任务执行!");
}
}).start();
}
}
由于面向对象的语法要求,首先创建一个 Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,再将其交
给一个线程来启动。
代码分析:
对于 Runnable 的匿名内部类用法,可以分析出几点内容:
- Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心
- 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类
- 为了省去定义一个 Runnable 实现类的麻烦,不得不使用匿名内部类
- 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错
- 而实际上,似乎只有方法体才是关键所在。
2. Lambda体验
Lambda是一个匿名函数,可以理解为一段可以传递的代码。
Lambda表达式写法,代码如下:
借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到相同的效果
new Thread(() -> System.out.println("新线程任务执行!")).start(); // 启动线程
这段代码和刚才的执行效果是完全一样的,可以在JDK 8或更高的编译级别下通过。从代码的语义中可以看出:我们
启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象。
// 使用匿名内部类存在的问题
// public Thread(Runnable target)
// 匿名内部类做了哪些事情
// 1.定义了一个没有名字的类
// 2.这个类实现了Runnable接口
// 3.创建了这个类的对象
// 使用匿名内部类语法是很冗余的
// 其实我们最关注的是run方法和里面要执行的代码.
// Lambda表达式体现的是函数式编程思想,只需要将要执行的代码放到函数中(函数就是类中的方法)
// Lambda就是一个匿名函数, 我们只需要将要执行的代码放到Lambda表达式中即可
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程执行代码啦");
}
}).start();
// 体验Lambda表达式
new Thread(() -> {
System.out.println("Lambda表达式执行啦");
}).start();
// Lambda表达式的好处: 可以简化匿名内部类,让代码更加精简
3. Lambda的优点
简化匿名内部类的使用,语法更加简单。
小结:
了解了匿名内部类语法冗余,体验了Lambda表达式的使用,发现Lmabda是简化匿名内部类的简写。
4. Lambda的标准格式
Lambda省去面向对象的条条框框,Lambda的标准格式格式由3个部分
组成:
(参数类型 参数名称)->
{ 代码体; }
格式说明:
- (参数类型 参数名称):参数列表
- {代码体;}:方法体
- -> :箭头,分隔参数列表和方法体
Lambda与方法的对比
匿名内部类
public void run() {
System.out.println("aa");
}
Lambda
() -> System.out.println("bb!")
5. 练习无参数无返回值的Lambda
掌握了Lambda的语法,我们来通过一个案例熟悉Lambda的使用。
interface Swimmable {
public abstract void swimming();
}
/**
* Lambda的标准格式:
* (int a) -> {
* 要执行的代码
* }
*/
public class LambdaUse {
public static void main(String[] args) {
goSwimming(new com.itheima.demo01lambda.Swimmable() {
@Override
public void swimming() {
System.out.println("匿名内部类的游泳");
}
});
// 小结:Lambda表达式相当于是对接口抽象方法的重写
goSwimming(() -> {
System.out.println("Lambda表达式的游泳");
});
}
// 练习无参数无返回值的Lambda
public static void goSwimming(Swimmable s) {
s.swimming();
}
public static void test(int a) {
}
}
6. 练习有参数有返回值的Lambda
下面举例演示 java.util.Comparator 接口的使用场景代码,其中的抽象方法定义为:
- public abstract int compare(T o1, T o2);
当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator 接口实例来指定排序的规则。
如果使用传统的代码对 ArrayList 集合进行排序,写法如下:
public class Person {
private String name;
private int age;
private int height;
// 省略其他
}
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("刘德华", 58, 174));
persons.add(new Person("张学友", 58, 176));
persons.add(new Person("刘德华", 54, 171));
persons.add(new Person("黎明", 53, 178));
// 对集合中的数据进行排序
/*Collections.sort(persons, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge(); // 升序排序
}
});*/
// Lambda表达式进行排序
Collections.sort(persons, (Person o1, Person o2) -> {
return o2.getAge() - o1.getAge(); // 降序
});
System.out.println("-----------");
//更简洁的写法
Collections.sort(persons, (o1,o2) -> o2.getAge() - o1.getAge());
for (Person person : persons) {
System.out.println(person);
}
System.out.println("-----------");
persons.forEach((t) -> {
System.out.println(t);
});
这种做法在面向对象的思想中,似乎也是“理所当然”的。其中 Comparator 接口的实例(使用了匿名内部类)代表
了“按照年龄从小到大”的排序规则。
小结
首先学习了Lambda表达式的标准格式
(参数列表) -> {
方法体;
}
以后我们调用方法时,看到参数是接口就可以考虑使用Lambda表达式,Lambda表达式相当于是对接口中抽象方法的重
写
7. 了解Lambda的实现原理
匿名内部类在编译的时候会一个class文件
Lambda在程序运行的时候形成一个类:
- 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
- 还会形成一个匿名内部类,实现接口,重写抽象方法
- 在接口的重写方法中会调用新生成的方法
8. Lambda省略格式
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
(int a) -> {
return new Person();
}
a -> new Person()
9. Lambda的前提条件
Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
- 方法的参数或局部变量类型必须为接口才能使用Lambda
- 接口中有且仅有一个抽象方法
public interface Flyable {
public abstract void flying();
}
public static void main(String[] args) {
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("aaaa", 58, 174));
persons.add(new Person("bbb", 58, 176));
persons.add(new Person("ccc", 54, 171));
persons.add(new Person("ddd", 53, 178));
Collections.sort(persons, (o1, o2) -> o2.getAge() - o1.getAge());
persons.forEach(t -> System.out.println(t));
}
小结
Lambda表达式的前提条件:
- 方法的参数或变量的类型是接口
- 这个接口中只能有一个抽象方法
10. 函数式接口
函数式接口在Java中是指:有且仅有一个抽象方法的接口
。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以
适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
FunctionalInterface注解
与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注
解可用于一个接口的定义上:
@FunctionalInterface
public interface Operator {
void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即
使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
11. Lambda和匿名内部类对比
了解Lambda和匿名内部类在使用上的区别
- 所需的类型不一样
匿名内部类,需要的类型可以是类,抽象类,接口
Lambda表达式,需要的类型必须是接口 - 抽象方法的数量不一样
匿名内部类所需的接口中抽象方法的数量随意
Lambda表达式所需的接口只能有一个抽象方法 - 实现原理不同
匿名内部类是在编译后会形成class
Lambda表达式是在程序运行的时候动态生成class
小结
当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类
常用内置函数式接口
目标
了解内置函数式接口由来
了解常用内置函数式接口
1. 内置函数式接口来由来
我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽
象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。
import java.util.List;
public class Demo01UserFunctionalInterface {
public static void main(String[] args) {
// 调用函数式接口中的方法
method((arr) -> {
int sum = 0;
for (int n : arr) {
sum += n;
}
return sum;
});
}
// 使用自定义的函数式接口作为方法参数
public static void method(Operator op) {
int[] arr = {1, 2, 3, 4};
int sum = op.getSum(arr);
System.out.println("sum = " + sum);
}
}
@FunctionalInterface
interface Operator {
public abstract int getSum(int[] arr);
}
2. 常用内置函数式接口介绍
它们主要在 java.util.function 包中。下面是最常用的几个接口。
2.1. Supplier接口
java.util.function.Supplier 接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类
型的对象数据。
@FunctionalInterface
public interface Supplier<T> {
public abstract T get();
}
供给型接口,通过Supplier接口中的get方法可以得到一个值,无参有返回的接口。
使用Lambda表达式返回数组元素最大值
使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用
java.lang.Integer 类。
代码示例:
import java.util.Arrays;
public class Demo05Supplier {
public static void main(String[] args) {
printMax(() -> {
int[] arr = {10, 20, 100, 30, 40, 50};
// 先排序,最后就是最大的
Arrays.sort(arr);
return arr[arr.length - 1]; // 最后就是最大的
});
}
private static void printMax(Supplier<Integer> supplier) {
int max = supplier.get();
System.out.println("max = " + max);
}
}
2.2. Consumer接口
java.util.function.Consumer 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛
型参数决定。
@FunctionalInterface
public interface Consumer<T> {
public abstract void accept(T t);
}
使用Lambda表达式将一个字符串转成大写和小写的字符串
Consumer消费型接口,可以拿到accept方法参数传递过来的数据进行处理, 有参无返回的接口。基本使用如:
import java.util.function.Consumer;
public class Demo06Consumer {
public static void main(String[] args) {
// Lambda表达式
test((String s) -> {
System.out.println(s.toLowerCase());
});
}
public static void test(Consumer<String> consumer) {
consumer.accept("HelloWorld");
}
}
默认方法:andThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费一个数据的时候,首先做一个操
作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。下面是JDK的源代
码:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
备注: java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出
NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组合
的情况:
public class Demo07ConsumerAndThen {
public static void main(String[] args) {
// Lambda表达式
test((String s) -> {
System.out.println(s.toLowerCase());
}, (String s) -> {
System.out.println(s.toUpperCase());
});
// Lambda表达式简写
test(s -> System.out.println(s.toLowerCase()), s ->
System.out.println(s.toUpperCase()));
}
public static void test(Consumer<String> c1, Consumer<String> c2) {
String str = "Hello World";
// c1.accept(str); // 转小写
// c2.accept(str); // 转大写
// c1.andThen(c2).accept(str);
c2.andThen(c1).accept(str);
}
}
运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。当然,通过链式写法可以实现更多步骤的组
合。
3.3. Function接口
java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,
后者称为后置条件。有参数有返回值。
Interface Function<T,R> 可以用作lambda表达式或方法引用的赋值对象。
参数类型
T - 函数输入的类型
R - 函数的结果类型
类型 | 方法说明 |
---|---|
default Function<T,V> | andThen(Function<? super R,? extends V> after) 返回一个组合函数,首先将该函数应用于其输入,然后将 after函数应用于结果。 |
R | apply(T t) 将此函数应用于给定的参数。 |
default Function<V,R> | compose(Function<? super V,? extends T> before) 返回一个组合函数,首先将 before函数应用于其输入,然后将此函数应用于结果。 |
static Function<T,T> | identity() 返回一个总是返回其输入参数的函数。 |
@FunctionalInterface
public interface Function<T, R> {
public abstract R apply(T t);
}
使用Lambda表达式将字符串转成数字
Function转换型接口,对apply方法传入的T类型数据进行处理,返回R类型的结果,有参有返回的接口。使用的场景
例如:将 String 类型转换为 Integer 类型。
public class Demo08Function {
public static void main(String[] args) {
// Lambda表达式
test((String s) -> {
return Integer.parseInt(s); // 10
});
}
public static void test(Function<String, Integer> function) {
Integer in = function.apply("10");
System.out.println("in: " + (in + 5));
}
}
默认方法:andThen
Function 接口中有一个默认的 andThen 方法,用来进行组合操作。JDK源代码如:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:
public class Demo09FunctionAndThen {
public static void main(String[] args) {
// Lambda表达式
test((String s) -> {
return Integer.parseInt(s);
}, (Integer i) -> {
return i * 10;
});
}
public static void test(Function<String, Integer> f1, Function<Integer, Integer> f2) {
// Integer in = f1.apply("66"); // 将字符串解析成为int数字
// Integer in2 = f2.apply(in);// 将上一步的int数字乘以10
Integer in3 = f1.andThen(f2).apply("66");
System.out.println("in3: " + in3); // 660
}
}
第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一
起。
请注意,Function的前置条件泛型和后置条件泛型可以相同。
以lambda表达式作为传参
manurePriceMonitorStatisticsVO.setRetailPrice(getAvg(v,a -> a.getRetailPrice().compareTo(BigDecimal.ZERO) > 0, ManurePriceMonitorStatisticsVO::getRetailPrice));
manurePriceMonitorStatisticsVO.setTradePrice(getAvg(v,a -> a.getTradePrice().compareTo(BigDecimal.ZERO) > 0, ManurePriceMonitorStatisticsVO::getTradePrice));
manurePriceMonitorStatisticsVO.setLeaveFactoryPrice(getAvg(v,a -> a.getLeaveFactoryPrice().compareTo(BigDecimal.ZERO) > 0, ManurePriceMonitorStatisticsVO::getLeaveFactoryPrice));
// 计算平均值
private BigDecimal getAvg(List<ManurePriceMonitorStatisticsVO> v, Function<ManurePriceMonitorStatisticsVO,Boolean> function,Function<ManurePriceMonitorStatisticsVO,BigDecimal> function2) {
List<BigDecimal> price = v.stream().filter(a->function.apply(a)).map(function2).collect(Collectors.toList());
BigDecimal avg = price.stream().reduce(BigDecimal.ZERO, BigDecimal::add).divide(new BigDecimal(price.size()));
return avg;
}
3.4. Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用
java.util.function.Predicate 接口。
@FunctionalInterface
public interface Predicate<T> {
public abstract boolean test(T t);
}
Predicate接口用于做判断,返回boolean类型的值
使用Lambda判断一个人名如果超过3个字就认为是很长的名字
对test方法的参数T进行判断,返回boolean类型的结果。用于条件判断的场景:
public class Demo10Predicate {
public static void main(String[] args) {
test(s -> s.length() > 3, "啊啊啊啊");
}
private static void test(Predicate<String> predicate, String str) {
boolean veryLong = predicate.test(str);
System.out.println("名字很长吗:" + veryLong);
}
}
条件判断的标准是传入的Lambda表达式逻辑,只要名称长度大于3则认为很长。
默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实
现“并且”的效果时,可以使用default方法 and 。其JDK源码为:
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
使用Lambda表达式判断一个字符串中即包含W,也包含H
使用Lambda表达式判断一个字符串中包含W或者包含H
使用Lambda表达式判断一个字符串中即不包含W
如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:
public class Demo10Predicate_And_Or_Negate {
public static void main(String[] args) {
// Lambda表达式
test((String s) -> {
return s.contains("H");
}, (String s) -> {
return s.contains("W");
});
}
public static void test(Predicate<String> p1, Predicate<String> p2) {
String str = "HelloWorld";
boolean b1 = p1.test(str); // 判断包含大写“H”
boolean b2 = p2.test(str); // 判断包含大写“W”
// if (b1 && b2) {
// System.out.println("即包含W,也包含H");
// }
boolean bb = p1.and(p2).test(str);
if (bb) {
System.out.println("即包含W,也包含H");
}
}
}
默认方法:or
使用Lambda表达式判断一个字符串中包含W或者包含H
与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”。JDK源码为:
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可,其他都不变:
public class Demo10Predicate_And_Or_Negate {
public static void main(String[] args) {
// Lambda表达式
test((String s) -> {
return s.contains("H");
}, (String s) -> {
return s.contains("W");
});
}
public static void test(Predicate<String> p1, Predicate<String> p2) {
String str = "HelloWorld";
boolean b1 = p1.test(str); // 判断包含大写“H”
boolean b2 = p2.test(str); // 判断包含大写“W”
// if (b1 || b2) {
// System.out.println("有H,或者W");
// }
boolean bbb = p1.or(p2).test(str);
if (bbb) {
System.out.println("有H,或者W");
}
}
}
默认方法:negate
使用Lambda表达式判断一个字符串中即不包含W
“与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法 negate 的JDK源代码为:
default Predicate<T> negate() {
return (t) -> !test(t);
}
从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前调
用 negate 方法,正如 and 和 or 方法一样:
import java.util.function.Predicate;
public class Demo10Predicate_And_Or_Negate {
public static void main(String[] args) {
// Lambda表达式
test((String s) -> {
return s.contains("H");
}, (String s) -> {
return s.contains("W");
});
}
public static void test(Predicate<String> p1, Predicate<String> p2) {
String str = "HelloWorld";
boolean b1 = p1.test(str); // 判断包含大写“H”
boolean b2 = p2.test(str); // 判断包含大写“W”
// 没有H,就打印
// if (!b1) {
// System.out.println("没有H");
// }
boolean test = p1.negate().test(str);
if (test) {
System.out.println("没有H");
}
}
}
介绍方法引用"::"
目标
了解Lambda的冗余场景
掌握方法引用的格式
了解常见的方法引用方式
1. Lambda的冗余场景
使用Lambda表达式求一个数组的和
public class Demo11MethodRefIntro {
public static void getMax(int[] arr) {
int sum = 0;
for (int n : arr) {
sum += n;
}
System.out.println(sum);
}
public static void main(String[] args) {
printMax((int[] arr) -> {
int sum = 0;
for (int n : arr) {
sum += n;
}
System.out.println(sum);
});
}
private static void printMax(Consumer<int[]> consumer) {
int[] arr = {10, 20, 30, 40, 50};
consumer.accept(arr);
}
}
2. 体验方法引用简化Lambda
如果我们在Lambda中所指定的功能,已经有其他方法存在相同方案,那是否还有必要再写重复逻辑?可以直接“引
用”过去就好了:
public class DemoPrintRef {
public static void getMax(int[] arr) {
int sum = 0;
for (int n : arr) {
sum += n;
}
System.out.println(sum);
}
public static void main(String[] args) {
printMax(Demo11MethodRefIntro::getMax);
}
private static void printMax(Consumer<int[]> consumer) {
int[] arr = {10, 20, 30, 40, 50};
consumer.accept(arr);
}
}
请注意其中的双冒号 :: 写法,这被称为“方法引用”,是一种新的语法。
4.方法引用的格式
符号表示: ::
符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用。
常见引用方式
方法引用在JDK 8中使用方式相当灵活,有以下几种形式:
- instanceName::methodName 对象::方法名
- ClassName::staticMethodName 类名::静态方法
- ClassName::methodName 类名::普通方法
- ClassName::new 类名::new 调用的构造器
- TypeName[]::new String[]::new 调用数组的构造器
5. 对象名::引用成员方法
这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法,代
码为:
// 对象::实例方法
@Test
public void test01() {
Date now = new Date();
Supplier<Long> supp = () -> {
return now.getTime();
};
System.out.println(supp.get());
Supplier<Long> supp2 = now::getTime;
System.out.println(supp2.get());
}
方法引用的注意事项
-
被引用的方法,参数要和接口中抽象方法的参数一样
-
当接口抽象方法有返回值时,被引用的方法也必须有返回值
6. 类名::引用静态方法
由于在 java.lang.System 类中已经存在了静态方法 currentTimeMillis ,所以当我们需要通过Lambda来调用该
方法时,可以使用方法引用 , 写法是:
// 类名::静态方法
@Test
public void test02() {
Supplier<Long> supp = () -> {
return System.currentTimeMillis();
};
System.out.println(supp.get());
Supplier<Long> supp2 = System::currentTimeMillis;
System.out.println(supp2.get());
}
7. 类名::引用实例方法
Java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用
者。
// 类名::实例方法
@Test
public void test03() {
Function<String, Integer> f1 = (s) -> {
return s.length();
};
System.out.println(f1.apply("abc"));
Function<String, Integer> f2 = String::length;
System.out.println(f2.apply("abc"));
BiFunction<String, Integer, String> bif = String::substring;
String hello = bif.apply("hello", 2);
System.out.println("hello = " + hello);
}
8. 类名::new引用构造器
由于构造器的名称与类名完全一样。所以构造器引用使用 类名称::new 的格式表示。首先是一个简单的 Person 类:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
要使用这个函数式接口,可以通过方法引用传递:
// 类名::new
@Test
public void test04() {
Supplier<Person> sup = () -> {
return new Person();
};
System.out.println(sup.get());
Supplier<Person> sup2 = Person::new;
System.out.println(sup2.get());
BiFunction<String, Integer, Person> fun2 = Person::new;
System.out.println(fun2.apply("张三", 18));
}
9. 数组::new 引用数组构造器
数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。
// 类型[]::new
@Test
public void test05() {
Function<Integer, String[]> fun = (len) -> {
return new String[len];
};
String[] arr1 = fun.apply(10);
System.out.println(arr1 + ", " + arr1.length);
Function<Integer, String[]> fun2 = String[]::new;
String[] arr2 = fun.apply(5);
System.out.println(arr2 + ", " + arr2.length);
}
小结
方法引用是对Lambda表达式符合特定情况下的一种缩写,它使得我们的Lambda表达式更加的精简,也可以理解为Lambda表达式的缩写形式 , 不过要注意的是方法引用只能"引用"已经存在的方法!
JDK8的Stream流
1. 什么是Stream
Stream是JDK8中引入,Stream是一个来自数据源的元素序列并支持聚合操作。可以让你以一种声明的方式处理数据,Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
2. Stream特点
- 元素:是特定类型的对象,形成一个序列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源:流的来源可以是集合,数组,I/O channel等。
- 过滤、聚合、排序等操作:类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等
- Pipelining(流水线/管道): 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式。
- 只能遍历一次:数据流的从一头获取数据源,在流水线上依次对元素进行操作,当元素通过流水线,便无法再对其进行操作,也就是执行了结束或者终止操作以后将不能再进行操作,如果再执行操作报错:
java.lang.IllegalStateException: stream has already been operated upon or closed
一个stream是由三部分组成的。数据源,零个或一个或多个中间操作,一个或零个终止操作。
中间操作是对数据的加工,注意:中间操作是lazy操作,并不会立马启动,需要等待终止操作才会执行。
终止操作是stream的启动操作,只有加上终止操作,stream才会真正的开始执行。
小结:
Stream API能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复,统计,匹配和归约。
首先我们了解了集合操作数据的弊端,每次都需要循环遍历,还要创建新集合,很麻烦
Stream是流式思想,相当于工厂的流水线,对集合中的数据进行加工处理
3. Stream入门案例
//要求把list1中的空字符串过滤掉,并把结果保存在列表中
public class Test {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("ab", "", "cd", "ef", "mm","", "hh");
System.out.println(list1);//[ab, , cd, ef, mm, , hh]
List<String> result = list1.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
System.out.println(result);//[ab, cd, ef, mm, hh]
}
}
上面这个例子可以看出list1是一个字符串的列表,其中有两个空字符串,在stream的操作过程中,我们使用了stream()、filter()、collect()等方法,在filter()过程中,我们引入了Lambda表达式s->!s.isEmpty(),结果是把两个空字符串过滤掉后,形成了一个新的列表result。
上面这个需求如果我们使用传统的代码完成如下:
public class Test {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("ab", "", "cd", "ef", "mm","", "hh");
List<String> result = new ArrayList<>();
for (String str : list1) {
if(str.isEmpty()){
continue;
}
result.add(str);
}
System.out.println(result);
}
}
比较两段代码,我们可以发现在第二段代码中我们自己创建了一个字符串对象列表,开启一个for循环遍历字符串对象列表,在for循环中判断是否当前的字符串是空串,如果不是,加到结果列表中。而在第一段程序中,我们并不需要自己开启for循环遍历,stream会在内部做迭代,我们只需要传入我们的过滤条件就可以了,最后这个字符串列表也是代码自动创建出来的,并且把结果放入了列表中,可以看出,第一段代码简洁优雅。
//传统写法
// 开始时间
long startTime1 = System.currentTimeMillis();
List<String> list1 = Arrays.asList("ab", "", "cd", "ef", "mm","", "hh");
List<String> result1 = new ArrayList<>();
for (String str : list1) {
if(str.isEmpty()){
continue;
}
result1.add(str);
}
System.out.println(result1);
// 结束时间
long endTime1 = System.currentTimeMillis();
// 计算执行时间
System.out.println("传统写法执行时长 "+(endTime1 - startTime1)+"毫秒.");
System.out.println("----------");
//Stream流写法
// 开始时间
long startTime2 = System.currentTimeMillis();
List<String> list2 = Arrays.asList("ab", "", "cd", "ef", "mm","", "hh");
//[ab, , cd, ef, mm, , hh]
List<String> result2 =
// 创建一个流(stream)
list1.stream().
// 过滤条件
filter(s -> !s.isEmpty())
.collect(Collectors.toList());
//[ab, cd, ef, mm, hh]
System.out.println(result2);
// 结束时间
long endTime2 = System.currentTimeMillis();
// 计算执行时间
System.out.println("Stream写法执行时长 "+(endTime2 - startTime2)+"毫秒.");
可以看出作为jdk8新特性竟然比使用传统写法使用的时间还要久,原因:
-
在少低数据量的处理场景中(size<=1000),stream 的处理效率是不如传统的 iterator 外部迭代器处理速度快的,但是实际上这些处理任务本身运行时间都低于毫秒,这点效率的差距对普通业务几乎没有影响,反而 stream 可以使得代码更加简洁;
-
在大数据量(szie>10000)时,stream 的处理效率会高于 iterator,特别是使用了并行流,在cpu恰好将线程分配到多个核心的条件下(当然parallel stream 底层使用的是 JVM 的 ForkJoinPool,这东西分配线程本身就很玄学),可以达到一个很高的运行效率,然而实际普通业务一般不会有需要迭代高于10000次的计算;
-
Parallel Stream(并行流) 受引 CPU 环境影响很大,当没分配到多个cpu核心时,加上引用 forkJoinPool 的开销,运行效率可能还不如普通的 Stream;
4. Stream操作分类和常用方法
4.1 Stream操作分类
- 无状态:指元素的处理不受之前元素的影响;
- 有状态:指该操作只有拿到所有元素之后才能继续下去。
- 非短路操作:指必须处理所有元素才能得到最终结果;
- 短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。
4.2 Stream中间操作常用方法
Stream流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
方法名 | 方法作用 | 返回值类型 | 方法种类 |
---|---|---|---|
count | 统计个数 | long | 终结 |
forEach | 逐一处理 | void | 终结 |
filter | 过滤 | Stream | 函数拼接 |
limit | 取用前几个 | Stream | 函数拼接 |
skip | 跳过前几个 | Stream | 函数拼接 |
map | 映射 | Stream | 函数拼接 |
concat | 组合 | Stream | 函数拼接 |
sorted | Stream | 对stream中所有的元素按照指定规则进行排序 | |
distinct | Stream | 对Stream中所有元素进行去重 |
终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。终结方法包括 count 和
forEach 方法。
非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结
方法。)
备注:本文章之外的更多方法,请自行参考API文档。
Stream注意事项(重要)
-
Stream只能操作一次
-
Stream方法返回的是新的流
-
Stream不调用终结方法,中间的操作不会执行
小结
我们学习了Stream的常用方法,我们知道Stream这些常用方法可以分成两类,终结方法,函数拼接方法
Stream的3个注意事项:
-
Stream只能操作一次
-
Stream方法返回的是新的流
-
Stream不调用终结方法,中间的操作不会执行
4.3 常用方法的使用
1. Stream流的forEach方法
forEach 用来遍历流中的数据
void forEach(Consumer<? super T> action);
该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。例如:
@Test
public void testForEach() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东");
/*one.stream().forEach((String s) -> {
System.out.println(s);
});*/
// 简写
// one.stream().forEach(s -> System.out.println(s));
one.stream().forEach(System.out::println);
}
2. Stream流的count方法
Stream流提供 count 方法来统计其中的元素个数:
long count();
该方法返回一个long值代表元素个数。基本使用:
@Test
public void testCount() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东");
System.out.println(one.stream().count());
}
3. Stream流的filter方法
filter用于过滤数据,返回符合过滤条件的数据
可以通过 filter 方法将一个流转换成另一个子集流。方法声明:
Stream<T> filter(Predicate<? super T> predicate);
该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
Stream流中的 filter 方法基本使用的代码如:
@Test
public void testFilter() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东");
one.stream().filter(s -> s.length() == 2).forEach(System.out::println);
}
在这里通过Lambda表达式来指定了筛选的条件:姓名长度为2个字。
4. Stream流的limit方法
limit 方法可以对流进行截取,只取用前n个。方法签名:
Stream<T> limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取。否则不进行操作。基本使用:
@Test
public void testLimit() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东");
one.stream().limit(3).forEach(System.out::println);
}
5. Stream流的skip方法
如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
Stream skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:
@Test
public void testSkip() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东");
one.stream().skip(2).forEach(System.out::println);
}
6. Stream流的map方法
如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
Stream流中的 map 方法基本使用的代码如:
@Test
public void testMap() {
Stream<String> original = Stream.of("11", "22", "33");
Stream<Integer> result = original.map(Integer::parseInt);
result.forEach(s -> System.out.println(s + 10));
}
这段代码中, map 方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为 Integer 类对象)。
7. Stream流的sorted方法
如果需要将数据排序,可以使用 sorted 方法。方法签名:
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
基本使用
Stream流中的 sorted 方法基本使用的代码如:
@Test
public void testSorted() {
// sorted(): 根据元素的自然顺序排序
// sorted(Comparator<? super T> comparator): 根据比较器指定的规则排序
Stream.of(33, 22, 11, 55)
.sorted()
.sorted((o1, o2) -> o2 - o1)
.forEach(System.out::println);
List<Person> one = Arrays.asList(
new Person("萧炎",18),
new Person("美杜莎",92),
new Person("萧薰儿",19),
new Person("药老",120),
new Person("云韵",30),
new Person("海波东",60),
new Person("纳然嫣然",18));
sort(one).stream().forEach(System.out::println);
System.out.println("------------");
sortDesc(one).stream().forEach(System.out::println);
}
public static List<Person> sort(List<Person> one) {
// 按照年龄自然排序
List<Person> collect = one.stream().sorted(Comparator.comparing(Person::getAge)).collect(Collectors.toList());
return collect;
}
public static List<Person> sortDesc(List<Person> one) {
// 按照年龄逆序排序
List<Person> collect = one.stream().sorted(Comparator.comparing(Person::getAge).reversed()).collect(Collectors.toList());
return collect;
}
}
这段代码中, sorted 方法根据元素的自然顺序排序,也可以指定比较器排序。
8. Stream流的distinct方法
如果需要去除重复数据,可以使用 distinct 方法。方法签名:
Stream<T> distinct();
基本使用
Stream流中的 distinct 方法基本使用的代码如:
@Test
public void testDistinct() {
Stream.of(22, 33, 22, 11, 33)
.distinct()
.forEach(System.out::println);
}
如果是自定义类型如何是否也能去除重复的数据呢?
@Test
public void testDistinct2() {
Stream.of(
new Person("萧炎", 58),
new Person("美杜莎", 56),
new Person("萧薰儿", 56),
new Person("云韵", 52))
.distinct()
.forEach(System.out::println);
}
public class Person {
private String name;
private int age;
// 省略其他
}
自定义类型是根据对象的hashCode和equals来去除重复元素的。
9. Stream流的match方法
如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法。方法签名:
boolean allMatch(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
基本使用
Stream流中的 Match 相关方法基本使用的代码如:
@Test
public void testMatch() {
boolean b = Stream.of(5, 3, 6, 1)
// .allMatch(e -> e > 0); // allMatch: 元素是否全部满足条件
// .anyMatch(e -> e > 5); // anyMatch: 元素是否任意有一个满足条件
.noneMatch(e -> e < 0); // noneMatch: 元素是否全部不满足条件
System.out.println("b = " + b);
}
10. Stream流的find方法
如果需要找到某些数据,可以使用 find 相关方法。方法签名:
Optional<T> findFirst();
Optional<T> findAny();
基本使用
Stream流中的 find 相关方法基本使用的代码如:
@Test
public void testFind() {
Optional<Integer> first = Stream.of(5, 3, 6, 1).findFirst();
System.out.println("first = " + first.get());
Optional<Integer> any = Stream.of(5, 3, 6, 1).findAny();
System.out.println("any = " + any.get());
}
11. Stream流的max和min方法
如果需要获取最大和最小值,可以使用 max 和 min 方法。方法签名:
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
基本使用
Stream流中的 max 和 min 相关方法基本使用的代码如:
@Test
public void testMax_Min() {
Optional<Integer> max = Stream.of(5, 3, 6, 1).max((o1, o2) -> o1 - o2);
System.out.println("first = " + max.get());
Optional<Integer> min = Stream.of(5, 3, 6, 1).min((o1, o2) -> o1 - o2);
System.out.println("any = " + min.get());
}
12. Stream流的reduce方法
如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法。方法签名:
T reduce(T identity, BinaryOperator<T> accumulator);
基本使用
Stream流中的 reduce 相关方法基本使用的代码如:
@Test
public void testReduce() {
int reduce = Stream.of(4, 5, 3, 9)
.reduce(0, (a, b) -> {
System.out.println("a = " + a + ", b = " + b);
return a + b;
});
// reduce:
// 第一次将默认做赋值给x, 取出第一个元素赋值给y,进行操作
// 第二次,将第一次的结果赋值给x, 取出二个元素赋值给y,进行操作
// 第三次,将第二次的结果赋值给x, 取出三个元素赋值给y,进行操作
// 第四次,将第三次的结果赋值给x, 取出四个元素赋值给y,进行操作
System.out.println("reduce = " + reduce);
int reduce2 = Stream.of(4, 5, 3, 9)
.reduce(0, (x, y) -> {
return Integer.sum(x, y);
});
int reduce3 = Stream.of(4, 5, 3, 9).reduce(0, Integer::sum);
int max = Stream.of(4, 5, 3, 9)
.reduce(0, (x, y) -> {
return x > y ? x : y;
});
System.out.println("max = " + max);
}
13. Stream流的map和reduce组合使用
@Test
public void testMapReduce() {
// 求出所有年龄的总和
int totalAge = Stream.of(
new Person("刘德华", 58),
new Person("张学友", 56),
new Person("郭富城", 54),
new Person("黎明", 52))
.map((p) -> p.getAge())
.reduce(0, (x, y) -> x + y);
System.out.println("totalAge = "+totalAge);
// 找出最大年龄
int maxAge = Stream.of(
new Person("刘德华", 58),
new Person("张学友", 56),
new Person("郭富城", 54),
new Person("黎明", 52))
.map((p) -> p.getAge())
.reduce(0, (x, y) -> x > y ? x : y);
System.out.println("maxAge = "+maxAge);
// 统计 数字2 出现的次数
int count = Stream.of(1, 2, 2, 1, 3, 2)
.map(i -> {
if (i == 2) {
return 1;
} else {
return 0;
}
})
.reduce(0, Integer::sum);
System.out.println("count = "+count);
}
14. Stream流的mapToInt
如果需要将Stream中的Integer类型数据转成int类型,可以使用 mapToInt 方法。方法签名:
IntStream mapToInt(ToIntFunction<? super T> mapper);
基本使用
Stream流中的 mapToInt 相关方法基本使用的代码如:
@Test
public void test1() {
// Integer占用的内存比int多,在Stream流操作中会自动装箱和拆箱
Stream<Integer> stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5});
// 把大于3的和打印出来
// Integer result = stream
// .filter(i -> i.intValue() > 3)
// .reduce(0, Integer::sum);
// System.out.println(result);
// 先将流中的Integer数据转成int,后续都是操作int类型
IntStream intStream = stream.mapToInt(Integer::intValue);
int reduce = intStream
.filter(i -> i > 3)
.reduce(0, Integer::sum);
System.out.println(reduce);
// 将IntStream转化为Stream<Integer>
IntStream intStream1 = IntStream.rangeClosed(1, 10);
Stream<Integer> boxed = intStream1.boxed();
boxed.forEach(s ->System.out.println(s.getClass()+", "+s));
}
15. Stream流的concat方法
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
static Stream concat(Stream<? extends T> a, Stream<? extends T> b)
备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。
该方法的基本使用代码如:
@Test
public void testContact() {
Stream<String> streamA = Stream.of("萧炎");
Stream<String> streamB = Stream.of("美杜莎");
Stream<String> result = Stream.concat(streamA, streamB);
result.forEach(System.out::println);
}
5. Stream综合案例
现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下
若干操作步骤:
- 第一个队伍只要名字为3个字的成员姓名;
- 第一个队伍筛选之后只要前3个人;
- 第二个队伍只要姓张的成员姓名;
- 第二个队伍筛选之后不要前2个人;
- 将两个队伍合并为一个队伍;
- 根据姓名创建 Person 对象;
- 打印整个队伍的Person对象信息。
注意:只有java9以上才支持List.of,可修改为Arrays.asList
两个队伍(集合)的代码如下:
public class DemoArrayListNames {
public static void main(String[] args) {
List<String> one = List.of("萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东", "纳然嫣然");
List<String> two = List.of("唐三", "小舞", "戴沐白", "奥斯卡", "马红俊", "宁荣荣", "朱竹清");
// ....
}
}
而 Person 类的代码为:
public class Person {
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{name='" + name + "'}";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1. 传统方式
使用for循环 , 示例代码:
package com.example.springjpaquery.hand;
import com.example.springjpaquery.pojo.Person;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DemoArrayListNames {
public static void main(String[] args) {
List<String> one = Arrays.asList("萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东", "纳然嫣然");
List<String> two = Arrays.asList("唐三", "唐舞", "戴沐白", "奥斯卡", "唐红俊", "唐荣荣", "唐竹清");
// 第一个队伍只要名字为3个字的成员姓名;美杜莎,萧薰儿,海波东
List<String> oneA = new ArrayList<>();
for (String name : one) {
if (name.length() == 3) {
oneA.add(name);
}
}
// 第一个队伍筛选之后只要前3个人;美杜莎,萧薰儿,海波东
List<String> oneB = new ArrayList<>();
for (int i = 0; i < 3; i++) {
oneB.add(oneA.get(i));
}
// 第二个队伍只要姓唐的成员姓名;唐三,唐舞,唐红俊,唐荣荣,唐竹清
List<String> twoA = new ArrayList<>();
for (String name : two) {
if (name.startsWith("唐")) {
twoA.add(name);
}
}
// 第二个队伍筛选之后不要前2个人;唐红俊,唐荣荣,唐竹清
List<String> twoB = new ArrayList<>();
for (int i = 2; i < twoA.size(); i++) {
twoB.add(twoA.get(i));
}
// 将两个队伍合并为一个队伍;美杜莎,萧薰儿,海波东,唐红俊,唐荣荣,唐竹清
List<String> totalNames = new ArrayList<>();
totalNames.addAll(oneB);
totalNames.addAll(twoB);
// 根据姓名创建Person对象;
List<Person> totalPersonList = new ArrayList<>();
for (String name : totalNames) {
totalPersonList.add(new Person(name));
}
// 打印整个队伍的Person对象信息。
for (Person person : totalPersonList) {
System.out.println(person);
}
}
}
2. Stream方式
等效的Stream流式处理代码为:
package com.example.springjpaquery.hand;
import com.example.springjpaquery.pojo.Person;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class DemoArrayListNames {
public static void main(String[] args) {
List<String> one = Arrays.asList("萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东", "纳然嫣然");
List<String> two = Arrays.asList("唐三", "唐舞", "戴沐白", "奥斯卡", "唐红俊", "唐荣荣", "唐竹清");
// 第一个队伍只要名字为3个字的成员姓名;
// 第一个队伍筛选之后只要前3个人;
Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);
// 第二个队伍只要姓张的成员姓名;
// 第二个队伍筛选之后不要前2个人;
Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("唐")).skip(2);
// 将两个队伍合并为一个队伍;
// 根据姓名创建Person对象;
// 打印整个队伍的Person对象信息。
Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println);
}
}
3. 综合案例2
//
List<AuthBO> auth = memberDO.getMemberAuth().getAuth();
List<String> urlList = new ArrayList<>();
if (!CollectionUtils.isEmpty(memberUserDO.getUserAuth().getAuth())) {
List<AuthBO> platformAuthList = memberUserDO.getUserAuth().getAuth().stream().filter(authBO -> authBO.getSource().equals(UserLoginSourceEnum.BUSINESS_MANAGEMENT_PLATFORM.getCode())).collect(Collectors.toList());
Set<Long> platformAuthParentAuthSet =
platformAuthList.stream().map(t -> t.getParentId()).collect(Collectors.toSet());
Set<String> parentUrlSet = auth.stream().filter(t -> platformAuthParentAuthSet.contains(t.getId())).map(t -> t.getUrl()).collect(Collectors.toSet());
urlList = platformAuthList.stream().map(AuthBO::getUrl).collect(Collectors.toList());
urlList.addAll(parentUrlSet);
}
4. 综合案例3
需求:从给定句子中返回单词长度大于5的单词列表,按长度倒序输出,最多返回3个
/**
* 【常规方式】
* 从给定句子中返回单词长度大于5的单词列表,按长度倒序输出,最多返回3个
*
* @param sentence 给定的句子,约定非空,且单词之间仅由一个空格分隔
* @return 倒序输出符合条件的单词列表
*/
public List<String> sortGetTop3LongWords(@NotNull String sentence) {
// 先切割句子,获取具体的单词信息
String[] words = sentence.split(" ");
List<String> wordList = new ArrayList<>();
// 循环判断单词的长度,先过滤出符合长度要求的单词
for (String word : words) {
if (word.length() > 5) {
wordList.add(word);
}
}
// 对符合条件的列表按照长度进行排序
wordList.sort((o1, o2) -> o2.length() - o1.length());
// 判断list结果长度,如果大于3则截取前三个数据的子list返回
if (wordList.size() > 3) {
wordList = wordList.subList(0, 3);
}
return wordList;
}
/**
* 【Stream方式】
* 从给定句子中返回单词长度大于5的单词列表,按长度倒序输出,最多返回3个
*
* @param sentence 给定的句子,约定非空,且单词之间仅由一个空格分隔
* @return 倒序输出符合条件的单词列表
*/
private static List<String> sortGetTop3LongWordsByStream(@NotNull String sentence) {
return Stream.of(sentence.split(" "))
.filter(s -> s.length() > 5)
.sorted((o1, o2) -> o2.length() - o1.length())
.limit(3)
.collect(Collectors.toList());
}
5. 综合案例4
6. 比较两个集合是否匹配
List<String> list = Arrays.asList("business_license","company_name","legal_name","legal_id_card","contact_address","unified_code","registered_capital","establishment_date","company_address","legal_person_phone");
List<String> strings = Arrays.asList("business_license","company_name","legal_name","legal_id_card","contact_address","unified_code","registered_capital","establishment_date","company_address","legal_person_phone");
boolean b = strings.containsAll(list);//不匹配则返回false
System.out.println("b = " + b);
7. 使用Stream流累加BigDecimal
BigDecimal districtSum = reportDOS.stream().map(OrderDistrictCityReportDO::getTotalAmountSum).reduce(BigDecimal.ZERO, BigDecimal::add);
8. 分组,方法引用综合使用
User sourceUser = new User();
sourceUser.setName("Tom");
sourceUser.setAge(25);
sourceUser.setId(2L);
sourceUser.setAddress(null);
User sourceUser1 = new User();
sourceUser1.setName("Tom1");
sourceUser1.setAge(25);
sourceUser1.setId(5L);
sourceUser1.setAddress(null);
User sourceUser2 = new User();
sourceUser2.setName("Tom2");
sourceUser2.setAge(25);
sourceUser2.setId(25L);
sourceUser2.setAddress(null);
List<User> arrayList = new ArrayList<>();
arrayList.add(sourceUser);
arrayList.add(sourceUser1);
arrayList.add(sourceUser2);
// 将user.getId()作为map的key分组
Map<Long, List<User>> assetGroupMap = arrayList.stream().collect(Collectors.groupingBy(User::getId));
System.out.println("assetGroupMap = " + assetGroupMap);
List<Integer> aa = new ArrayList<>();
aa.add(2);
aa.add(3);
aa.add(4);
// 遍历aa通过CommodityStatusEnum的toTypeName(通过code获取message的静态方法)获取message
List<String> serviceTypeNames = aa.stream().map(CommodityStatusEnum::toTypeName).collect(Collectors.toList());
System.out.println("serviceTypeNames = " + serviceTypeNames);
// 获取map中key为25的List<User>,没有则为空集合
List<User> orDefault = assetGroupMap.getOrDefault(25L, new ArrayList<>());
System.out.println("orDefault = " + orDefault);
5. Stream使用案例
5.1. 创建流
5.1.1.使用Collection下的 stream() 和 parallelStream() 方法
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个串行流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
}
}
5.1.2.使用Arrays 中的 stream() 方法,将数组转成流
public class Test {
public static void main(String[] args) {
Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums).boxed();
//Arrays.stream(array)获取的是一个IntStream对象,boxed 方法用于将目前 Stream 中的基本类型装箱
}
}
5.1.3.使用Stream中的静态方法:of()、iterate()、generate()
public class Test {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
stream.forEach(System.out::print);//1 2 3 4 5 6
System.out.println("==========");
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
stream2.forEach(System.out::print); // 0 2 4 6 8 10
System.out.println("==========");
Stream<Double> stream3 = Stream.generate(Math::random).limit(2);
stream3.forEach(System.out::print);//随机产生两个小数
}
}
5.1.4.使用 BufferedReader.lines() 方法,将每行内容转成流
public class Test {
public static void main(String[] args) throws FileNotFoundException {
BufferedReader reader = new BufferedReader(new FileReader("d:\\study\\demo\\test_stream.txt"));
Stream<String> lineStream = reader.lines();
lineStream.forEach(System.out::println);
}
}
5.1.5.使用 Pattern.splitAsStream() 方法,将字符串分隔成流
public class Test {
public static void main(String[] args) {
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("tom,jack,jerry,john");
stringStream.forEach(System.out::println);
}
}
5.1.6 直接通过值获取
Stream<String> stream = Stream.of("are","you","ok");
5.1.7 对list分组并排序
// 1. 商品通过店铺id分组,利用TreeMap来自然排序
Map<Long, List<CartCommodityResponse>> cartCommodityMap = collect.stream().collect(Collectors.groupingBy(CartCommodityResponse::getStoreId,TreeMap::new,Collectors.toList()));
// 2. 商品通过店铺id分组,利用TreeMap来反序
Map<Long, List<CartCommodityResponse>> cartCommodityMap = collect.stream().collect(Collectors.groupingBy(CartCommodityResponse::getStoreId,TreeMap::new,Collectors.toList())).descendingMap();
// 3. 前面两个方法不能控制哪个字段来排序,换个思路:分组时使用LinkedHashMap,这样可以保留原来的排序,将排序放在分组前完成
Map<Long, List<CartCommodityResponse>> cartCommodityMap = collect.stream().collect(Collectors.groupingBy(CartCommodityResponse::getStoreId,LinkedHashMap::new,Collectors.toList()));
5.2. 中间操作
5.2.1.筛选与切片
- filter:过滤流中的某些元素
- limit(n):获取n个元素
- skip(n):跳过n元素,配合limit(n)可实现分页
- distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
//filter 测试
public class Test {
public static void main(String[] args) {
List<String> list = Arrays.asList("aaa", "ff", "dddd","eeeee","hhhhhhh");
//把字符串长度大于3的过滤掉
Stream<String> stringStream = list.stream().filter(s -> s.length() <= 3);
stringStream.forEach(System.out::println);
System.out.println("===================");
//验证整个流只遍历一次
//stream只有遇到终止操作才会触发流启动,中间操作都是lazy
Stream.of(1, 2, 3, 4, 5)
.filter(i -> {
System.out.println("filter1的元素:" + i);
return i > 0;
}).filter(i -> {
System.out.println("filter2的元素:" + i);
return i == 5;
}).forEach(i-> System.out.println("最后结果:"+i));
}
}
//limit 测试
public class Test {
public static void main(String[] args) {
List<String> list = Arrays.asList("aaa", "ff", "dddd","eeeee","hhhhhhh");
//取三个元素
List<String> result = list.stream().limit(3).collect(Collectors.toList());
System.out.println(result);
}
}
//limit 和 skip 测试
public class Test {
public static void main(String[] args) {
List<String> list = Arrays.asList("11", "22", "33","44","55","66","77","88","99");
//演示skip:跳过前三条记录
list.stream().skip(3).forEach(System.out::println);
//模拟翻页,每页3条记录
//第一页
List<String> page1= list.stream().skip(0).limit(3).collect(Collectors.toList());
System.out.println(page1);
//第二页
List<String> page2= list.stream().skip(3).limit(3).collect(Collectors.toList());
System.out.println(page2);
//第三页
List<String> page3= list.stream().skip(6).limit(3).collect(Collectors.toList());
System.out.println(page3);
//limit和skip顺序换一下
//可以看出,最终的结果会收到执行顺序的影响
List<String> page4= list.stream().limit(3).skip(1).collect(Collectors.toList());
System.out.println(page4);
}
}
//distinct去重测试
//注意:当我们自己重写hashcode和equals的方法的时候,要遵循一个原则:
//如果两个对象的hashcode相等,那么用equals比较不一定相等;反之,如果两个对象用equals比较相等,那么他们的hashcode也一定相等
public class Student {
private Integer id;
private String name;
public Student(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return getId().equals(student.getId()) &&
getName().equals(student.getName());
}
@Override
public int hashCode() {
return Objects.hash(getId(), getName());
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
//去掉重复的student
//1.Student类的hashcode和equals包含了id和name
//2.Student类的hashcode和equals中只包含name
public class Test {
public static void main(String[] args) {
List<Student> studentList = Arrays.asList(
new Student(1, "zhangsan"),
new Student(6, "zhangsan"),
new Student(2, "lisi"),
new Student(5, "lisi"),
new Student(3, "wangwu"));
//1.学生对象去重
List<Student> result = studentList.stream().distinct().collect(Collectors.toList());
System.out.println(result);
//2.普通字符串去重
Stream<String> stringStream = Stream.of("a", "a", "b", "c", "d");
List<String> stringList = stringStream.distinct().collect(Collectors.toList());
System.out.println(stringList);
}
}
//移除不存在的
memberRoleDO.getAuth().removeIf(auth -> !updateVO.getMenuIds().contains(auth.getId()));
5.2.2.映射(map和flatMap)
public class Test {
public static void main(String[] args) {
//第一个例子对比
List<String> list = Arrays.asList("a,b,c", "1,2,3");
//将每个元素转成一个新的且不带逗号的元素
//注意:这里元素是值在list中的元素,一共有两个,分别是"a,b,c" 和"1,2,3"
//map函数传入的lambda表达式就是我们的转换逻辑,需要返回一个转换之后的元素
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
s1.forEach(System.out::println); // abc 123
System.out.println("===============");
List<Integer> integerList = Arrays.asList(1, 2, 3);
integerList.stream().map(i->i*2).forEach(System.out::println);
System.out.println("===============");
//将每个元素转换成一个stream
//注意:flatMap跟上面的map函数对比
//两者传入的lambda都是转换逻辑,但是map中的lambda返回的是一个转换后的新元素,
//flatMap可以把每一个元素进一步处理:例如"a,b,c"进一步分隔成a b c三个元素
//返回的是这三个元素形成的三个stream,最终把这些单独的stream合并成一个stream返回
//总结:可以看出,flatMap相比于map,它可以把每一个元素再进一步拆分成更多的元素,
// 最后,拆分出来的元素个数会多于最初输入的列表中的元素个数
//就这个例子而言,最初输入两个元素"a,b,c" 和"1,2,3",结果是6个元素 a b c 1 2 3
Stream<String> s3 = list.stream().flatMap(s -> {
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
});
s3.forEach(System.out::println); // a b c 1 2 3
System.out.println("===============");
//第二个例子(嵌套的list)[["a","b","c"],["d","e","f"],["h","k"]]
//输出结果要求是:["A","B","C","D","E","F","G","H"]
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("a","b","c"),
Arrays.asList("d","e","f"),
Arrays.asList("h","k")
);
Stream<String> s4 = nestedList.stream()
.flatMap(Collection::stream)
.map(s -> s.toUpperCase());
s4.forEach(System.out::print);
}
}
5.2.3.排序sorted
- sorted():自然排序,流中元素需实现Comparable接口
- sorted(Comparator com):定制排序,自定义Comparator排序器
//字符串排序
public class Test {
public static void main(String[] args) {
List<String> list = Arrays.asList("aaa", "ff", "dddd");
//String 类自身已实现Compareable接口,可以按照字符的自然顺序【升序】排序
list.stream().sorted().forEach(System.out::println);// aaa dddd ff
System.out.println("=====");
//给sorted函数传入一个lambda表达式
//1.自定义排序规则,按照字符串的长度【升序】排序,也就是字符串长度最短的排在最前面
list.stream().sorted((s1,s2)->s1.length()-s2.length()).forEach(System.out::println);//ff aaa dddd
System.out.println("=====");
//2.自定义排序规则,按照字符串的长度【降序】排序,也就是字符串长度最长的排在最前面
list.stream().sorted((s1,s2)->s2.length()-s1.length()).forEach(System.out::println);//dddd aaa ff
}
}
//对象排序
public class Employee {
private String name;
private Integer salary;
public Employee(String name,Integer salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getSalary() {
return salary;
}
public void setSalary(Integer salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
//测试类
public class Test {
public static void main(String[] args) {
List<Employee> list = Arrays.asList(
new Employee("Tom",1000),
new Employee("Jack",900),
new Employee("John",1300),
new Employee("Jack",2000)
);
//自定义排序规则,先按照名称【升序】,如果名称相同,再按照工资【降序】
list.stream().sorted((e1,e2)->{
if(e1.getName().equals(e2.getName())){
return e2.getSalary()-e1.getSalary();
}else{
return e1.getName().compareTo(e2.getName());
}
}).forEach(System.out::println);
//输出结果:
// Employee{name='Jack', salary=2000}
// Employee{name='Jack', salary=900}
// Employee{name='John', salary=1300}
// Employee{name='Tom', salary=1000}
//打印原始列表,看看是否被改变,注意我们通过stream进行排序操作,原始的列表元素顺序没有变化,也就是说我们没有修改原始的list
System.out.println(list);
//Stream排序和集合本身的排序方法对比
//我们使用List接口本身的sort方法再来排序一下看看
list.sort((e1,e2)->{
if(e1.getName().equals(e2.getName())){
return e2.getSalary()-e1.getSalary();
}else{
return e1.getName().compareTo(e2.getName());
}
});
//排序后再次打印一下list本身,可以发现,list本身元素的顺序被修改过了
System.out.println(list);
}
}
5.2.4.消费peek
peek:如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。
//为Tom增加500工资
public class Test {
public static void main(String[] args) {
List<Employee> list = Arrays.asList(
new Employee("Tom",1000),
new Employee("John",1300),
new Employee("Jack",2000)
);
//如果是Tom,工资增加500
list.stream().peek(e->{
if("Tom".equals(e.getName())){
e.setSalary(500+e.getSalary());
}
}).forEach(System.out::println);
//输出结果
// Employee{name='Tom', salary=1500}
// Employee{name='John', salary=1300}
// Employee{name='Jack', salary=2000}
}
}
5.3. 终止操作
5.3.1.匹配
- allMatch:流中所有的元素都匹配,返回true,否则返回false
- noneMatch:流中没有任何的元素匹配,返回true,否则返回false
- anyMatch:流中只要有任何一个元素匹配,返回true,否则返回false
- findFirst:返回流的第一个元素
- findAny:返回流中的任意元素
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(2, 1, 3, 4, 5);
//流中所有的元素都匹配,返回true,否则返回false
boolean allMatch = list.stream().allMatch(e -> {
System.out.println(e);
return e > 10;
}); //false
System.out.println("allMatch:"+allMatch);
//流中没有任何的元素匹配,返回true,否则返回false
boolean noneMatch = list.stream().noneMatch(e -> {
System.out.println(e);
return e > 10;
}); //true
System.out.println("noneMatch:"+noneMatch);
//流中只要有任何一个元素匹配,返回true,否则返回false
boolean anyMatch = list.stream().anyMatch(e -> {
System.out.println(e);
return e > 1;
}); //true
System.out.println("anyMatch:"+anyMatch);
//返回流的第一个元素
Integer findFirst = list.stream().findFirst().get(); //2
System.out.println("findFirst"+findFirst);
//返回流中的任意元素,因为是串行流stream()所以返回的都是2
Integer findAny = list.stream().findAny().get(); //2
System.out.println("findAny:"+findAny);
//返回流中的任意元素,这里使用并行流
Integer findAny2 = list.parallelStream().findAny().get();
System.out.println("findAny2:"+findAny2);
}
}
5.3.2.聚合
- count:计算元素总的数量
- max:找出最大的元素
- min:找出最小元素
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//计算元素总的数量
long count = list.stream().count(); //5
System.out.println(count);
//找出最大的元素(需要传入Lambda比较器)
Integer max = list.stream().max(Integer::compareTo).get(); //5
System.out.println(max);
//找出最小元素(需要传入Lambda比较器)
Integer min = list.stream().min(Integer::compareTo).get(); //1
System.out.println(min);
}
}
5.3.3.归约
在java.util.stream.Stream接口中,reduce有下面三个重载的方法
/**
第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。
*/
Optional<T> reduce(BinaryOperator<T> accumulator);
/**
流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。
*/
T reduce(T identity, BinaryOperator<T> accumulator);
/**
在串行流(stream)中,该方法跟第二个方法一样,即第三个参数combiner不会起作用。
在并行流(parallelStream)中,我们知道流被fork join创建出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,accumulator)一样,而第三个参数combiner函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流程进行归约。
*/
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
归约应用举例
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer v = list.stream().reduce((a1, a2) -> a1 + a2).get();
System.out.println("reduce计算v="+v); // 55
Integer v1 = list.stream().reduce(10, (a1, a2) -> a1 + a2);
System.out.println("reduce计算v1="+v1); //65
Integer v2 = list.stream().reduce(0,
(a1, a2) -> {
return a1 + a2;
},
(a1, a2) -> {
return 1000; //第二个表达式在串行流中无效,这里返回1000测试
});
System.out.println("reduce计算v2="+v2);
//并行流reduce传三个参数
Integer v3 = list.parallelStream().reduce(0,
(a1, a2) -> {
System.out.println(Thread.currentThread().getName()+":parallelStream accumulator: a1:" + a1 + " a2:" + a2);
return a1 + a2;
},
(a1, a2) -> {
System.out.println(Thread.currentThread().getName()+":parallelStream combiner: a1:" + a1 + " a2:" + a2);
return a1 + a2;
});
System.out.println("并行流reduce计算v3=:"+v3);
}
}
5.3.4.收集collect
collect:接收一个Collector实例,将流中元素收集成另外一个数据结构
Collectors提供的收集器
方法 | 含义说明 |
---|---|
toList | 将流中的元素收集到一个List中 |
toSet | 将流中的元素收集到一个Set中 |
toCollection | 将流中的元素收集到一个Collection中 |
toMap | 将流中的元素映射收集到一个Map中 |
counting | 统计流中的元素个数 |
summingInt | 计算流中指定int字段的累加总和。针对不同类型的数字类型,有不同的方法,比如summingDouble等 |
averagingInt | 计算流中指定int字段的平均值。针对不同类型的数字类型,有不同的方法,比如averagingLong等 |
joining | 将流中所有元素(或者元素的指定字段)字符串值进行拼接,可以指定拼接连接符,或者首尾拼接字符 |
maxBy | 根据给定的比较器,选择出值最大的元素 |
minBy | 根据给定的比较器,选择出值最小的元素 |
groupingBy | 根据给定的分组函数的值进行分组,输出一个Map对象 |
partitioningBy | 根据给定的分区函数的值进行分区,输出一个Map对象,且key始终为布尔值类型 |
collectingAndThen | 包裹另一个收集器,对其结果进行二次加工转换 |
reducing | 从给定的初始值开始,将元素进行逐个的处理,最终将所有元素计算为最终的1个值输出 |
<R, A> R collect(Collector<? super T, A, R> collector);
应用举例:
//创建一个Person类
public class Person {
private String name;
private String sex;
private Integer age;
public Person(String name, String sex, Integer age) {
this.name = name;
this.sex = sex;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
//1.collect(Collectors.toList()) 把流转换成一个列表(允许重复值)
Stream<String> stringStream = Stream.of("aa","bb","dd","ee","bb");
List<String> listResult = stringStream.collect(Collectors.toList());
System.out.println(listResult);//[aa, bb, dd, ee, bb]
//2.collect(Collectors.toSet()) 把流转换成一个集合(去重)
Stream<String> stringStream1 = Stream.of("aa","bb","dd","ee","bb");
Set<String> setResult = stringStream1.collect(Collectors.toSet());
System.out.println(setResult);//[aa, bb, dd, ee]
//3.collect(Collectors.toCollection(LinkedList::new)) 把流转换成一个指定的集合类型(LinkedList)
Stream<String> stringStream2 = Stream.of("aa","bb","dd","ee","bb");
LinkedList<String> linkedListResult = stringStream2.collect(Collectors.toCollection(LinkedList::new));
System.out.println(linkedListResult);//[aa, bb, dd, ee, bb]
//4.collect(Collectors.toCollection(ArrayList::new)) 把流转换成一个指定的集合类型(ArrayList)
Stream<String> stringStream3 = Stream.of("aa","bb","dd","ee","bb");
ArrayList<String> arrayListResult = stringStream3.collect(Collectors.toCollection(ArrayList::new));
System.out.println(arrayListResult);//[aa, bb, dd, ee, bb]
//5.collect(Collectors.toCollection(TreeSet::new)) 把流转换成一个指定的集合类型(TreeSet)
Stream<String> stringStream4 = Stream.of("aa","bb","dd","ee","bb");
TreeSet<String> treeSetResult = stringStream4.collect(Collectors.toCollection(TreeSet::new));
System.out.println(treeSetResult);//[aa, bb, dd, ee]
//6.collect(Collectors.joining()) 使用joining拼接流中的元素
Stream<String> stringStream5 = Stream.of("A","B","C","D","E");
String result5 = stringStream5.collect(Collectors.joining());
System.out.println(result5);//ABCDE
//7.collect(Collectors.joining("-")) 使用joining拼接流中的元素并指定分隔符
Stream<String> stringStream6 = Stream.of("A","B","C","D","E");
String result6 = stringStream6.collect(Collectors.joining("-"));
System.out.println(result6);//A-B-C-D-E
//7.collect(Collectors.joining("-","<",">")) 使用joining拼接流中的元素并指定分隔符
Stream<String> stringStream7 = Stream.of("A","B","C","D","E");
String result7 = stringStream7.collect(Collectors.joining("-","<",">"));
System.out.println(result7);//<A-B-C-D-E>
//8.collect(Collectors.groupingBy(Person::getSex) 对person流按照性别进行分组
Stream<Person> stringStream8 = Stream.of(
new Person("zhangsan", "男", 10),
new Person("lisi", "女", 11),
new Person("wangwu", "男", 15),
new Person("zhaoliu", "男", 12),
new Person("xiaoming", "女", 13)
);
Map<String, List<Person>> resultMap1 = stringStream8.collect(Collectors.groupingBy(Person::getSex));
System.out.println(resultMap1.toString());//{女=[Person{name='lisi', sex='女', age=11}, Person{name='xiaoming', sex='女', age=13}], 男=[Person{name='zhangsan', sex='男', age=10}, Person{name='wangwu', sex='男', age=15}, Person{name='zhaoliu', sex='男', age=12}]}
//9.collect(Collectors.groupingBy(Person::getSex, Collectors.mapping(Person::getName, Collectors.toList())))
// 对person流按照性别进行分组,并且把每一组对象流中人员的姓名转成列表
Stream<Person> stringStream9 = Stream.of(
new Person("zhangsan", "男", 10),
new Person("lisi", "女", 11),
new Person("wangwu", "男", 15),
new Person("zhaoliu", "男", 12),
new Person("xiaoming", "女", 13)
);
Map<String, List<String>> listMap = stringStream9.collect(
Collectors.groupingBy(Person::getSex, Collectors.mapping(Person::getName, Collectors.toList()))
);
System.out.println(listMap.toString());//{女=[lisi, xiaoming], 男=[zhangsan, wangwu, zhaoliu]}
//10.collect(Collectors.groupingBy(Person::getSex, Collectors.mapping(Person::getAge, Collectors.maxBy(Integer::compareTo))))
// 对person流按照性别进行分组,并统计每一组中年龄最大的人的年龄
Stream<Person> stringStream10 = Stream.of(
new Person("zhangsan", "男", 10),
new Person("lisi", "女", 11),
new Person("wangwu", "男", 15),
new Person("zhaoliu", "男", 12),
new Person("xiaoming", "女", 13)
);
Map<String, Optional<Integer>> listMap1 = stringStream10.collect(
Collectors.groupingBy(Person::getSex, Collectors.mapping(Person::getAge, Collectors.maxBy(Integer::compareTo)))
);
System.out.println(listMap1.toString());//{女=Optional[13], 男=Optional[15]}
//11.collect(
// Collectors.groupingBy(Person::getName,
// Collectors.reducing(BinaryOperator.maxBy(Comparator.comparingInt(Person::getAge)))
// )
//对person流按照性别进行分组,并统计每一组中年龄最大的人
//这个案例使用了groupingBy和reducing组合
Stream<Person> stringStream111 = Stream.of(
new Person("zhangsan", "男", 10),
new Person("lisi", "女", 11),
new Person("zhangsan", "男", 15),
new Person("zhaoliu", "男", 12),
new Person("lisi", "女", 13)
);
Map<String, Optional<Person>> resultMap111 = stringStream111.collect(
Collectors.groupingBy(Person::getSex,
Collectors.reducing(BinaryOperator.maxBy(Comparator.comparingInt(Person::getAge)))
)
);
System.out.println(resultMap111.toString());//{女=Optional[Person{name='lisi', sex='女', age=13}], 男=Optional[Person{name='zhangsan', sex='男', age=15}]}
//12.collect(Collectors.groupingBy(Person::getSex,
// Collectors.reducing(0,Person::getAge,(x,y)->x+y)
// )
// )
//对person流按照性别进行分组,并统计每一组人员年龄和
Stream<Person> stringStream121 = Stream.of(
new Person("zhangsan", "男", 10),
new Person("lisi", "女", 11),
new Person("zhangsan", "男", 15),
new Person("zhaoliu", "男", 12),
new Person("lisi", "女", 13)
);
Map<String, Integer> resultMap121 = stringStream121.collect(
Collectors.groupingBy(Person::getSex,
Collectors.reducing(0,Person::getAge,(x,y)->x+y)
)
);
/*上面这段如果不使用reducing,还可以用下面这中方式完成
Map<String, Integer> resultMap121 = stringStream121.collect(
Collectors.groupingBy(Person::getSex, Collectors.summingInt(Person::getAge))
);*/
System.out.println(resultMap121.toString());//{女=24, 男=37}
//12.collect(Collectors.groupingBy(Person::getName, TreeMap::new, Collectors.toList()))
// 对person流按照name进行分组,结果转成TreeMap,key是name,value是这个组的对象列表
//groupingBy的第一个参数就是获取分组的属性,第二个参数指定返回类型,第三个是把每个分组里面的对象元素转成一个列表
Stream<Person> stringStream11 = Stream.of(
new Person("zhangsan", "男", 10),
new Person("lisi", "女", 11),
new Person("zhangsan", "男", 15),
new Person("lisi", "男", 12),
new Person("xiaoming", "女", 13)
);
TreeMap<String, List<Person>> listMap2 = stringStream11.collect(
Collectors.groupingBy(Person::getName, TreeMap::new, Collectors.toList())
);
System.out.println(listMap2.toString());//{lisi=[Person{name='lisi', sex='女', age=11}, Person{name='lisi', sex='男', age=12}], xiaoming=[Person{name='xiaoming', sex='女', age=13}], zhangsan=[Person{name='zhangsan', sex='男', age=10}, Person{name='zhangsan', sex='男', age=15}]}
//13.collect(Collectors.collectingAndThen(
// Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getName))),
// ArrayList::new))
//对Person流先通过TreeSet去重,去重的比较属性是name,然后在把这个TreeSet中的元素转换成ArrayList
Stream<Person> stringStream12 = Stream.of(
new Person("lisi", "女", 11),
new Person("lisi", "女", 11),
new Person("zhangsan", "男", 15),
new Person("zhangsan", "男", 15),
new Person("xiaoming", "女", 13)
);
List<Person> list = stringStream12.collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getName))),
ArrayList::new));//这里的ArrayList::new等同于pset->new ArrayList(pset),是把前面生成的TreeSet赋值给ArrayList构造函数
System.out.println(list);//[Person{name='lisi', sex='女', age=11}, Person{name='xiaoming', sex='女', age=13}, Person{name='zhangsan', sex='男', age=15}]
//14.collect(Collectors.groupingBy(Person::getName, Collectors.summingInt(Person::getAge)))
// 对person流按照姓名进行分组,并对每一个组内的人员的年龄求和
Stream<Person> stringStream13 = Stream.of(
new Person("zhangsan", "男", 10),
new Person("zhangsan", "女", 11),
new Person("lisi", "男", 15),
new Person("zhaoliu", "男", 12),
new Person("lisi", "女", 13)
);
Map<String, Integer> resultMap2 = stringStream13.collect(Collectors.groupingBy(Person::getName, Collectors.summingInt(Person::getAge)));
System.out.println(resultMap2.toString());//{lisi=28, zhaoliu=12, zhangsan=21}
//15.collect(Collectors.groupingBy(Person::getName, Collectors.averagingInt(Person::getAge)))
// 对person流按照姓名进行分组,并对每一个组内的人员的年龄求平均值
Stream<Person> stringStream14 = Stream.of(
new Person("zhangsan", "男", 10),
new Person("zhangsan", "女", 11),
new Person("lisi", "男", 15),
new Person("zhaoliu", "男", 12),
new Person("lisi", "女", 13)
);
Map<String, Double> resultMap3 = stringStream14.collect(Collectors.groupingBy(Person::getName, Collectors.averagingInt(Person::getAge)));
System.out.println(resultMap3.toString());//{lisi=14.0, zhaoliu=12.0, zhangsan=10.5}
//16.parallel().collect(
// Collectors.groupingByConcurrent(Person::getSex, Collectors.summingInt(Person::getAge))
// )
//使用并行流,把人员按照性别分组,计算每一组中的年龄和,返回的类型是ConcurrentMap,保证线程安全
Stream<Person> stringStream16 = Stream.of(
new Person("zhangsan", "男", 10),
new Person("zhangsan", "女", 11),
new Person("lisi", "男", 15),
new Person("zhaoliu", "男", 12),
new Person("zhaoliu", "男", 16),
new Person("zhaoliu", "男", 17),
new Person("lisi", "女", 13));
ConcurrentMap<String, Integer> resultMap4 = stringStream16.parallel().collect(
Collectors.groupingByConcurrent(Person::getSex, Collectors.summingInt(Person::getAge))
);
System.out.println(resultMap4.toString());//{女=24, 男=70}
//17.collect(Collectors.partitioningBy(p -> p.getAge() > 12))
//把流中元素根据年龄是否大于12分成两组,保存在Map中,key是true或者false,value是对象列表
Stream<Person> stringStream17 = Stream.of(
new Person("zhangsan", "女", 11),
new Person("wangwu", "男", 10),
new Person("lisi", "男", 15),
new Person("zhaoliu", "女", 13));
Map<Boolean, List<Person>> resultMap5 = stringStream17.collect(Collectors.partitioningBy(p -> p.getAge() > 12));
System.out.println(resultMap5.toString());//{false=[Person{name='zhangsan', sex='女', age=11}], true=[Person{name='lisi', sex='男', age=15}, Person{name='lisi', sex='女', age=13}]}
//18.collect(Collectors.partitioningBy(p -> p.getAge() > 12,Collectors.summingInt(Person::getAge)))
//把流中元素根据年龄是否大于12分成两组,保存在Map中,key是true或者false,每一组的年龄的和
Stream<Person> stringStream18 = Stream.of(
new Person("zhangsan", "女", 11),
new Person("wangwu", "男", 10),
new Person("lisi", "男", 15),
new Person("zhaoliu", "女", 13));
Map<Boolean, Integer> resultMap6 = stringStream18.collect(Collectors.partitioningBy(p -> p.getAge() > 12,Collectors.summingInt(Person::getAge)));
System.out.println(resultMap6.toString());//{false=21, true=28}
//19.Collectors.toMap:有两个参数的toMap方法,流中对象的key是不允许存在相同的,否则报错
//toMap的第二个参数需要创建一个列表,并且key对应的元素对象放入列表
Stream<Person> stringStream19 = Stream.of(
new Person("zhangsan", "女", 11),
new Person("zhaoliu", "女", 13));
Map<String, List<Person>> resultMap7 = stringStream19.collect(Collectors.toMap(Person::getName, p -> {
List<Person> personList = new ArrayList<>();
personList.add(p);
return personList;
}));
System.out.println(resultMap7.toString());//{zhaoliu=[Person{name='zhaoliu', sex='女', age=13}], zhangsan=[Person{name='zhangsan', sex='女', age=11}]}
//20.Collectors.toMap:有两个参数的toMap方法,流中对象的key是不允许存在相同的,否则报错
//toMap的第二个参数直接使用流中的对象作为key所对应的value
Stream<Person> stringStream20 = Stream.of(
new Person("zhangsan", "女", 11),
new Person("zhaoliu", "女", 13));
Map<String, Person> resultMap8 = stringStream20.collect(Collectors.toMap(Person::getName, p -> p));
System.out.println(resultMap8.toString());//{zhaoliu=Person{name='zhaoliu', sex='女', age=13}, zhangsan=Person{name='zhangsan', sex='女', age=11}}
//21.Collectors.toMap:有三个参数的toMap方法,流中对象的key是允许存在相同的,
// 第三个参数表示key重复的处理方式(这里是把重复的key对应的value用新的替换老的)
//toMap的第二个参数直接使用流中的对象作为key所对应的value
Stream<Person> stringStream21 = Stream.of(
new Person("zhangsan", "女", 11),
new Person("zhangsan", "男", 12),
new Person("zhaoliu", "男", 13)
);
Map<String, Person> resultMap9 = stringStream21.collect(
Collectors.toMap(Person::getName,
p -> p,
(oldPerson,newPerson)->newPerson
)
);
System.out.println(resultMap9.toString());//{zhaoliu=[Person{name='zhaoliu', sex='男', age=13}], zhangsan=[Person{name='zhangsan', sex='女', age=11}, Person{name='zhangsan', sex='女', age=11}]}
//22.Collectors.toMap:有三个参数的toMap方法,流中对象的key是允许存在相同的,第三个参数表示key重复的处理方式(这里是把重复的key对应的value放入列表)
//toMap的第二个参数直接使用流中的对象作为key所对应的value
Stream<Person> stringStream22 = Stream.of(
new Person("zhangsan", "女", 11),
new Person("zhangsan", "女", 11),
new Person("zhaoliu", "男", 13)
);
Map<String, List<Person>> resultMap10 = stringStream22.collect(
Collectors.toMap(Person::getName,
p -> {
List<Person> personList = new ArrayList<>();
personList.add(p);
return personList;
},
(oldList,newList)->{
oldList.addAll(newList);
return oldList;
})
);
System.out.println(resultMap10.toString());//{zhaoliu=[Person{name='zhaoliu', sex='男', age=13}], zhangsan=[Person{name='zhangsan', sex='女', age=11}, Person{name='zhangsan', sex='女', age=11}]}
//23.Collectors.toMap:有四个参数的toMap方法,流中对象的key是允许存在相同的,
//toMap的第二个参数直接使用流中的对象作为key所对应的value
//第三个参数表示key重复的处理方式(这里是把重复的key对应的value放入列表)
//第四个参数可以指定一个返回的Map具体类型
Stream<Person> stringStream23 = Stream.of(
new Person("zhangsan", "女", 11),
new Person("zhangsan", "女", 11),
new Person("zhaoliu", "男", 13)
);
Map<String, List<Person>> resultMap11 = stringStream23.collect(
Collectors.toMap(Person::getName,
p -> {
List<Person> personList = new ArrayList<>();
personList.add(p);
return personList;
},
(oldList,newList)->{
oldList.addAll(newList);
return oldList;
},
LinkedHashMap::new
)
);
System.out.println(resultMap11.toString());//{zhangsan=[Person{name='zhangsan', sex='女', age=11}, Person{name='zhangsan', sex='女', age=11}], zhaoliu=[Person{name='zhaoliu', sex='男', age=13}]}
//24.Collectors.summarizingInt((a -> a.getAge()))
//针对Integer类型的元素进行汇总计算
//得到1、元素数量 2、元素的和 3、元素的最大值 4、元素的最小值 5、平均值
Stream<Person> personStream24=Stream.of(
new Person("zhangsan", "女", 11),
new Person("zhangsan", "女", 25),
new Person("zhaoliu", "男", 13)
);
IntSummaryStatistics intSummaryStatistics = personStream24.collect(Collectors.summarizingInt((a -> a.getAge())));
System.out.println(intSummaryStatistics);//IntSummaryStatistics{count=3, sum=49, min=11, average=16.333333, max=25}
}
}
1. 累加summingLong的用法
// 采购数量
Long totalNum = orderDO.getProducts().stream().map(s -> s.getQuantity()).collect(Collectors.summingLong(i->i.longValue()));
6. 补充方法:
JDK 8新增的Optional类
1. 以前对null的处理方式
@Test
public void test01() {
String userName = "凤姐";
// String userName = null;
if (userName != null) {
System.out.println("用户名为:" + userName);
} else {
System.out.println("用户名不存在");
}
}
2. Optional类介绍
Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检
查,防止NullPointerException。
3. Optional的基本使用
Optional类的创建方式:
Optional.of(T t) : 创建一个 Optional 实例
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
Optional类的常用方法:
isPresent() : 判断是否包含值,包含值返回true,不包含值返回false
get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException
orElse(T t) : 如果调用对象包含值,返回该值,否则返回参数t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
@Test
public void test02() {
// Optional<String> userNameO = Optional.of("凤姐");
// Optional<String> userNameO = Optional.of(null);
// Optional<String> userNameO = Optional.ofNullable(null);
Optional<String> userNameO = Optional.empty();
// isPresent() : 判断是否包含值,包含值返回true,不包含值返回false。
if (userNameO.isPresent()) {
// get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException。
String userName = userNameO.get();
System.out.println("用户名为:" + userName);
} else {
System.out.println("用户名不存在");
}
}
4. Optional的高级使用
@Test
public void test03() {
Optional<String> userNameO = Optional.of("凤姐");
// Optional<String> userNameO = Optional.empty();
// 存在做的什么
// userNameO.ifPresent(s -> System.out.println("用户名为" + s));
// 存在做的什么,不存在做点什么
userNameO.ifPresentOrElse(s -> System.out.println("用户名为" + s), () -> System.out.println("用户名不存在"));
}
@Test
public void test04() {
// Optional<String> userNameO = Optional.of("凤姐");
Optional<String> userNameO = Optional.empty();
// 如果调用对象包含值,返回该值,否则返回参数t
System.out.println("用户名为" + userNameO.orElse("null"));
// 如果调用对象包含值,返回该值,否则返回参数Supplier得到的值
String s1 = userNameO.orElseGet(() -> {return "未知用户名";});
System.out.println("s1 = " + s1);
}
@Test
public void test05() {
// User u = new User("凤姐", 18);
// User u = new User(null, 18);
// User u = null;
// System.out.println(getUpperCaseUserName1(u));
// 我们将可能会为null的变量构造成Optional类型
// User u = new User("凤姐", 18);
User u = new User(null, 18);
Optional<User> uO = Optional.of(u);
System.out.println(getUpperCaseUserName2(uO));
}
public String getUpperCaseUserName2(Optional<User> uO) {
return uO.map(u -> u.getUserName())
.map(name -> name.toUpperCase())
.orElse("null");
}
/*public String getUpperCaseUserName1(User u) {
if (u != null) {
String userName = u.getUserName();
if (userName != null) {
return userName;
} else {
return null;
}
} else {
return null;
}
}*/
小结
Optional是一个可以为null的容器对象。orElse,ifPresent,ifPresentOrElse,map等方法避免对null的判断,写出更加优雅的代码。
复习一下java中的时间
Data
Date类的两个常用方法(getTime和toString)
Date(long date),即Date类的形参是long类型的date日期参数
(1)getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以此 Date 对象表示的毫秒数。
(2)toString():把Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。
pattern格式的写法总结:
yyyy:年
MM:月
dd:日
hh:1~12小时制(1-12)
HH:24小时制(0-23)
mm:分
ss:秒
S:毫秒
E:星期几
D:一年中的第几天
F:一月中的第几个星期(会把这个月总共过的天数除以7)
w:一年中的第几个星期
W:一月中的第几星期(会根据实际情况来算)
a:上下午标识
k:和HH差不多,表示一天24小时制(1-24)。
K:和hh差不多,表示一天12小时制(0-11)。
z:表示时区
SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm");
String createTime = document.getElementsByClass("article-meta-time").eq(0).text().trim();
Long createTimeM = sdf.parse(createTime).getTime();
String time = sdf.format(date);
LocalDateTime beginDate = LocalDateTime.now();
// beginDate = beginDate.minus(1, ChronoUnit.MONTHS);
beginDate = beginDate.withDayOfMonth(1);
BigDecimal monthPriceIndex = orderRepository.getMonthSellIndexs();
BigDecimal priceIndex = orderRepository.getSellIndexs(beginDate);
BigDecimal subtract = priceIndex.subtract(monthPriceIndex);
BigDecimal divide = subtract.divide(priceIndex,BigDecimal.ROUND_CEILING);
// 判断已完成的订单创建是否已经超过30天,超过则不显示售后按钮
if ((order.getOuterStatus().equals(OrderOuterStatusEnum.ACCOMPLISHED.getCode()) && order.getBuyerInnerStatus().equals(BuyerInnerStatusEnum.ACCOMPLISHED.getCode()))) {
LocalDateTime startTime = LocalDateTime.now();
Duration duration = Duration.between(order.getSubmitTime(), startTime);
Long days = duration.toDays();
Long toDay = 30L;
if (days > toDay) {
queryVO.setShowAfterSales(false);
}
}
private static final ZoneId ZONE_ID = ZoneOffset.systemDefault();
/**
* 8小时的秒数
*/
private static final int OFFSET = 8 * 60 * 60;
/**
* LocalDateTime -> 秒
*
* @param localDateTime localDateTime
* @return 秒
*/
public static long toSeconds(LocalDateTime localDateTime) {
return localDateTime.atZone(ZONE_ID).toEpochSecond();
}
/**
* LocalDateTime -> 毫秒
* 竟然加了8小时
*
* @param localDateTime localDateTime
* @return 毫秒
*/
public static long toMilliSecond(LocalDateTime localDateTime) {
// 比标准实际慢8小时,就是当前的时间了。
return localDateTime.toInstant(ZoneOffset.ofTotalSeconds(OFFSET)).toEpochMilli();
}
/**
* LocalDateTime 转秒和毫秒
*/
@Test
public void toSecondsAndMilliSecond() {
LocalDateTime now = LocalDateTime.now();
System.out.println(TimeUtils.toSeconds(now));
System.out.println(TimeUtils.toMilliSecond(now));
}
判断json对象是Map还是List
JSONObject object = JSONObject.parseObject(res);
if (object instanceof List) {
System.out.println("是list集合");
}
if (object instanceof Map) {
System.out.println("是Map集合");
}
hutool
各种工具DateUtil,strUtil等挺好用
BeanUtil.copyProperties(c, commodityDetailResponse);//复制bean对象属性(源对象,目标对象)
JDK 8新的日期和时间 API
1. 了解旧版日期时间 API 存在的问题
- 设计很差: 在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包
含日期。此外用于格式化和解析的类在java.text包中定义。 - 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
- 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
2. 新日期时间 API介绍
DK 8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于 java.time 包中,下面是一些关键类。
- LocalDate :表示日期,包含年月日,格式为 2019-10-16
- LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
- LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
- DateTimeFormatter :日期时间格式化类。
- Instant:时间戳,表示一个特定的时间瞬间。
- Duration:用于计算2个时间(LocalTime,时分秒)的距离
- Period:用于计算2个日期(LocalDate,年月日)的距离
- ZonedDateTime :包含时区的时间
Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。此外Java 8还提供了4套其他历法,分别是:
- ThaiBuddhistDate:泰国佛教历
- MinguoDate:中华民国历
- JapaneseDate:日本历
- HijrahDate:伊斯兰历
3. JDK 8的日期和时间类
LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用 ISO-8601 日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
3.1 LocalDate转换为LocalDatetime
LocalDate localDate = LocalDate.now();
LocalDateTime localDateTime = localDate.atStartOfDay();
这样就可以将当前的LocalDate对象转换为对应的LocalDateTime对象了。在转换过程中,会将LocalDate的日期部分置为时间的0点,即00:00:00。
3.2 LocalDateTime转LocalDate
// LocalDateTime转LocalDate
LocalDate data = LocalDateTime.now().toLocalDate()
// LocalDate:获取日期时间的信息。格式为 2022-09-29
@Test
public void test01() {
// 创建指定日期
LocalDate fj = LocalDate.of(1985, 9, 23);
System.out.println("fj = " + fj); // 1985-09-23
// 得到当前日期
LocalDate nowDate = LocalDate.now();
System.out.println("nowDate = " + nowDate); // 2022-09-29
// 获取日期信息
System.out.println("年: " + nowDate.getYear());
System.out.println("月: " + nowDate.getMonthValue());
System.out.println("日: " + nowDate.getDayOfMonth());
System.out.println("星期: " + nowDate.getDayOfWeek());
}
// LocalTime类: 获取时间信息。格式为 18:03:50.492
@Test
public void test02() {
// 得到指定的时间
LocalTime time = LocalTime.of(12, 15, 28, 129_900_000);
System.out.println("time = " + time);
// 得到当前时间
LocalTime nowTime = LocalTime.now();
System.out.println("nowTime = " + nowTime);
// 获取时间信息
System.out.println("小时: " + nowTime.getHour());
System.out.println("分钟: " + nowTime.getMinute());
System.out.println("秒: " + nowTime.getSecond());
System.out.println("纳秒: " + nowTime.getNano());
}
// LocalDateTime类: 获取日期时间信息。格式为 2022-09-29T18:05:19.360
@Test
public void test03() {
LocalDateTime fj = LocalDateTime.of(1985, 9, 23, 9, 10, 20);
System.out.println("fj = " + fj); // 1985-09-23T09:10:20
// 得到当前日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); // 2022-09-29T18:05:19.360
System.out.println(now.getYear());
System.out.println(now.getMonthValue());
System.out.println(now.getDayOfMonth());
System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
System.out.println(now.getNano());
}
对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象,他们不会影响原来的对象。
// LocalDateTime类: 对日期时间的修改
@Test
public void test05() {
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);
// 修改日期时间
LocalDateTime setYear = now.withYear(2078);
System.out.println("修改年份: " + setYear);
System.out.println("now == setYear: " + (now == setYear));
System.out.println("修改月份: " + now.withMonth(6));
System.out.println("修改小时: " + now.withHour(9));
System.out.println("修改分钟: " + now.withMinute(11));
// 再当前对象的基础上加上或减去指定的时间
LocalDateTime localDateTime = now.plusDays(5);
System.out.println("5天后: " + localDateTime);
System.out.println("now == localDateTime: " + (now == localDateTime));
System.out.println("10年后: " + now.plusYears(10));
System.out.println("20月后: " + now.plusMonths(20));
System.out.println("20年前: " + now.minusYears(20));
System.out.println("5月前: " + now.minusMonths(5));
System.out.println("100天前: " + now.minusDays(100));
}
// LocalDateTime类: 对日期时间的判断,isBefore()和isAfter()
@Test
public void test06() {
LocalDateTime dt1 = LocalDateTime.parse("2025-12-05T12:45:30");
System.out.println(dt1);
LocalDateTime dt2 = LocalDateTime.parse("2022-12-05T12:45:30");
System.out.println(dt2);
System.out.println(dt1.isAfter(dt2));//true
System.out.println(dt1.isBefore(dt2));//false
}
3.3 LocalDate转为时间戳
普通方法
LocalDate localDate = LocalDate.now();
long timestamp = localDate.atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli();
4. JDK 8的时间格式化与解析
通过 java.time.format.DateTimeFormatter 类可以进行日期时间解析与格式化。
// 日期格式化
@Test
public void test04() {
// 得到当前日期时间
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 将日期时间格式化为字符串
String format = now.format(formatter);
System.out.println("format = " + format);
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
System.out.println("keySuffix = " + keySuffix );
// 将字符串解析为日期时间
LocalDateTime parse = LocalDateTime.parse("1985-09-23 10:12:22", formatter);
System.out.println("parse = " + parse);
}
5. JDK 8的Instant时间戳
Instant 时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。
// 时间戳
@Test
public void test07() {
Instant now = Instant.now();
System.out.println("当前时间戳 = " + now);
// 获取从1970年1月1日 00:00:00的秒
System.out.println(now.getNano());
System.out.println(now.getEpochSecond());
System.out.println(now.toEpochMilli());
System.out.println(System.currentTimeMillis());
Instant instant = Instant.ofEpochSecond(5);
System.out.println(instant);
}
6. JDK 8的计算日期时间差类
Duration/Period类: 计算日期时间差。
- Duration:用于计算2个时间(LocalTime,时分秒)的距离
- Period:用于计算2个日期(LocalDate,年月日)的距离
- Temporal :可用于计算两个时间差(可精准到到任何时间单位,推荐使用)
// Duration/Period类: 计算日期时间差
@Test
public void test08() {
// Duration计算时间的距离
LocalTime now = LocalTime.now();
LocalTime time = LocalTime.of(14, 15, 20);
Duration duration = Duration.between(time, now);
System.out.println("相差的天数:" + duration.toDays());
System.out.println("相差的小时数:" + duration.toHours());
System.out.println("相差的分钟数:" + duration.toMinutes());
// System.out.println("相差的秒数:" + duration.toSeconds());
System.out.println("相差的毫秒秒数:" + duration.toMillis());
// Period计算日期的距离
LocalDate nowDate = LocalDate.now();
LocalDate date = LocalDate.of(1998, 8, 8);
// 让后面的时间减去前面的时间
Period period = Period.between(date, nowDate);
System.out.println("相差的年:" + period.getYears());
System.out.println("相差的月:" + period.getMonths());
System.out.println("相差的天:" + period.getDays());
}
Temporal start = LocalDate.of(2022, 3, 9);;
Temporal end = LocalDate.now();
// 方法返回为相差月份
long l = ChronoUnit.MONTHS.between(start, end);
System.out.println(l);
Temporal start1 = LocalDateTime.of(2022, 3, 9,5,12);
Temporal end2 = LocalDateTime.now();
// 方法返回为相差小时
long l1 = ChronoUnit.HOURS.between(start1, end2);
System.out.println(l1);
7. JDK 8设置日期时间的时区
Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime。其中每个时区都对应着 ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。
ZoneId:该类中包含了所有的时区信息
// 设置日期时间的时区
@Test
public void test10() {
// 1.获取所有的时区ID
// ZoneId.getAvailableZoneIds().forEach(System.out::println);
// 不带时间,获取计算机的当前时间
LocalDateTime now = LocalDateTime.now(); // 中国使用的东八区的时区.比标准时间早8个小时
System.out.println("now = " + now);
// 2.操作带时区的类
// now(Clock.systemUTC()): 创建世界标准时间
ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
System.out.println("bz = " + bz);
// now(): 使用计算机的默认的时区,创建日期时间
ZonedDateTime now1 = ZonedDateTime.now();
System.out.println("now1 = " + now1); // 2019-10-19T16:19:44.007153500+08:00[Asia/Shanghai]
// 使用指定的时区创建日期时间
ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Vancouver"));
System.out.println("now2 = " + now2); // 2019-10-19T01:21:44.248794200-07:00[America/Vancouver]
}
8. JDK 8的时间校正器
有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。
- TemporalAdjuster : 时间校正器。
- TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
// TemporalAdjuster类:自定义调整时间
@Test
public void test09() {
LocalDateTime now = LocalDateTime.now();
// 得到下一个月的第一天
TemporalAdjuster firsWeekDayOfNextMonth = temporal -> {
LocalDateTime dateTime = (LocalDateTime) temporal;
LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);
System.out.println("nextMonth = " + nextMonth);
return nextMonth;
};
LocalDateTime nextMonth = now.with(firsWeekDayOfNextMonth);
System.out.println("nextMonth = " + nextMonth);
}
9. 小结
详细学习了新的日期是时间相关类,LocalDate表示日期,包含年月日,LocalTime表示时间,包含时分秒,LocalDateTime = LocalDate + LocalTime,时间的格式化和解析,通过DateTimeFormatter类型进行.
学习了Instant类,方便操作秒和纳秒,一般是给程序使用的.学习Duration/Period计算日期或时间的距离,还使用时间调整器方便的调整时间,学习了带时区的3个类ZoneDate/ZoneTime/ZoneDateTime
JDK 8新的日期和时间 API的优势:
- 新版的日期和时间API中,日期和时间对象是不可变的。操纵的日期不会影响老值,而是新生成一个实例。
- 新的API提供了两种不同的时间表示方式,有效地区分了人和机器的不同需求。
- TemporalAdjuster可以更精确的操纵日期,还可以自定义日期调整器。
- 是线程安全的
复习正则表达式(非新特性)
过滤特殊字符
String regEx = "\\pP|\\pS|\\s+";
title = Pattern.compile(regEx).matcher(title).replaceAll("").trim();
System.out.println("title = " + title);
Pattern类用于创建一个正则表达式,也可以说创建一个匹配模式,它的构造方法是私有的,不可以直接创建,但可以通过Pattern.complie(String regex)简单工厂方法创建一个正则表达式
Pattern p=Pattern.compile("\\w+");
p.pattern();// 返回 \w+
// pattern() 返回正则表达式的字符串形式,其实就是返回Pattern.complile(String regex)的regex参数
注意事项:
\s+是空格一个或者多个,不管在那个位置都能匹配
\pP 其中的小写 p 是 property 的意思,表示 Unicode 属性,用于 Unicode 正表达式的前缀。
大写 P 表示 Unicode 字符集七个字符属性之一:标点字符。
其他六个是
L:字母;
M:标记符号(一般不会单独出现);
Z:分隔符(比如空格、换行等);
S:符号(比如数学符号、货币符号等);
N:数字(比如阿拉伯数字、罗马数字等);
C:其他字符
上面这七个是属性,七个属性下还有若干个子属性,用于更进一步地进行细分。
Java 中用于 Unicode 的正则表达式数据都是由 Unicode 组织提供的。
Unicode 正则表达式标准(可以找到所有的子属性):http://www.unicode.org/reports/tr18/
各 Unicode 字符属性的定义,可以用一看看某个字符具有什么属性:http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
这个文本文档一行是一个字符,第一列是 Unicode 编码,第二列是字符名,第三列是 Unicode 属性, 以及其他一些字符信息。