JDK1.8 新特性
Java 8 新特性
Java8
新增了非常多的特性,我们主要讨论以下几个:
- Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
- 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
- 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Stream API −新添加的
Stream API(java.util.stream)
把真正的函数式编程风格引入到Java中。 - Date Time API − 加强对日期与时间的处理。
- Optional 类 −
Optional
类已经成为Java 8
类库的一部分,用来解决空指针异常。 - Nashorn, JavaScript 引擎 −
Java 8
提供了一个新的Nashorn javascript
引擎,它允许我们在JVM
上运行特定的javascript
应用。
1. Lambda 表达式
Lambda
表达式,也可称为闭包,它是推动 Java 8
发布的最重要新特性。
Lambda
允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda
表达式可以使代码变的更加简洁紧凑。
语法
lambda
表达式的语法格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
- 可选类型声明: 不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号: 一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号: 如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字: 如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
Lambda 表达式实例
public static void main(String[] args) {
// 普通写法
PersonCallBack p=new PersonCallBack() {
@Override
public void callback() {
System.out.println("写作业!");
}
};
new lamdabaTest3().test("小明",p);
}
public void test(String name,PersonCallBack callBack){
System.out.println(name);
callBack.callback();
}
}
// 函数式接口
@FunctionalInterface
interface PersonCallBack{
void callback();
}
很显然,这个并不是一个很简洁的写法,我们采用Java8
的Lambada
表达式来实现,那么如何简化呢?
整个过程:去掉修饰符(public
等)、去掉函数的名字(因为已经赋给变量,变量知道此方法名–往后知道抽象方法唯一,不需要方法名了)、去掉返回值类型(编译器可以推断)、去掉参数类型(编译器可以推断参数类型),最终的结果是下面的形式:
// Lambada 写法
PersonCallBack p1= () -> System.out.println("写作业!");
new lamdabaTest3().test("小明",p);
分析: 这样的最终结果就是把"一块代码赋给一个变量"。或者说是"这个被赋给一个变量的函数"就是一个Lambada表达式,由于Lambada可以直接赋给一个"变量",我们可以把Lambada(这里表示为变量)作为参数传递给函数。但是变量(Lambada表达式)的类型是什么呢?
// 函数式接口
@FunctionalInterface
interface PersonCallBack{
void callback();
}
说明:所有的Lambada的类型都是一个接口,而Lambada表达式本身(“那段代码”)就是一个接口的实现,这是理解Lambada
的一个关键所在,理解上可以这样认为:Lambada
表达式就是产生一个实现接口中唯一的抽象方法的子实现类的对象,因此最终结果:
// Lambada 写法
PersonCallBack p1= () -> System.out.println("写作业!");
函数式接口: 接口中只有一个需要被实现的抽象函数
说明:为了避免后来的人在接口中增加新的接口函数,导致其有多个接口函数需要被实现,变成非函数式接口,引入了一个新的Annotation
(注解):@FunctionalInterface
。可以把他它放在一个接口前,表示这个接口是一个函数式接口,加上它的接口不会被编译,如果加上此标记就不能再添加其他的抽象方法,否则会报错。它有点像@Override
,都是声明了一种使用意图,避免你把它用错。
总结: lambda
表达式本质是匿名方法
Lambda 表达式的结构
Lambada表达式的语法
(param1,param2,param3) -> { }
Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “ ->
”,该操作符被称为 Lambda
操作符或箭头操作符。它将 Lambda
分为两个部分:
- 左侧:指定了
Lambda
表达式需要的方法参数列表 - 右侧:指定了
Lambda
体,即Lambda
表达式要执行的功能
使用说明:
- 一个 Lambda 表达式可以有零个或多个参数,参数的类型既可以明确声明,也可以根据上下文来推断
- 圆括号内,方法参数列表之间用逗号相隔
- 当只有一个参数,且其类型可推导时,圆括号()可省略
Lambda
表达式的主体可包含零条或多条语句,如果Lambda
表达式的主体只有一条语句,花括号{}
可省略,如果有返回值,return
也可以省略,同时body
中的“;”也可以省略。匿名函数的返回类型与该主体表达式一致- 如果
Lambda
表达式的主体包含一条以上语句,则表达式必须包含在花括号{}
中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空
简单应用
对比匿名内部类做为参数传递和Lambda
表达式作为参数来传递–Runnable,Callable接口(具体看例子)
public class LamadaDemo {
public static void main(String[] args) {
//匿名内部类的形式开启一个线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我爱你!");
}
}).start();
//Lambada表达式创建匿名内部类开启一个线程
new Thread(() -> System.out.println("-------------")).start();
}
public class LamadaDemo1 {
public static void main(String[] args) {
//常见的函数式接口:Runnable、 Comparable--排序(是一个函数式接口吗?)
Comparable<Integer> comparable=new Comparable<Integer>() {
@Override
public int compareTo(Integer o) {
return 0;
}
};
//Lambada表达式的方法
Comparable<Integer> com=(a)->a;
int i = com.compareTo(3);
System.out.println(i);
}
}
2. 方法引用
概念: 方法引用其实是Lambda
表达式的另一种写法,当要传递给Lambda
体的操作已经有实现的方法了,可以使用方法引用
语法:使用操作符 “ ::
” 将方法名和对象或类的名字分隔开来
几种常见形式:
- 类名::静态方法
- 对象::实例方法
- 类名::实例方法
二.构造器引用
- ClassName::new
三 数组引用
- Type::new
注意:
- Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保存一致
- 若Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method;不管怎么说,实质还是抽象方法的实现
/** 一.方法引用:若Lambda体中的内容有方法已经实现了,我们可以使用"方法引用"
* (可以理解为方法引用是Lambda表达式的另外一种表现形式)
*
* 主要有三种语法格式:
* 对象::实例方法
* 类::静态方法名
* 类::实例方法名
* 注意:
* 1.Lambda 体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
* 2.若Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
* 二.构造器引用
*
* ClassName::new
*
* 三 数组引用
*
* Type::new
*
*
*
*/
public class TestMethodRef {
//数组引用
@Test
public void test7(){
Function<Integer,String[]> function = (x) -> new String[x];
String[] strings = function.apply(10);
System.out.println(strings);
Function<Integer,String[]> function2 = String[]::new;
System.out.println(function2.apply(10).length);
Supplier<ArrayList<Person>> supplier = () -> new ArrayList<>();
Supplier<ArrayList<Person>> supplier1 = ArrayList::new;
ArrayList<Person> people = supplier1.get();
}
//构造器引用
@Test
public void test5(){
Supplier<Person> supplier = () -> new Person();
Supplier<Person> supplier1 = Person::new;
System.out.println(supplier1.get());
}
@Test
public void test6(){
Function<Integer,Person> function = (x) -> new Person(x);
Function<Integer,Person> function1 = Person::new;
Person apply = function1.apply(15);
System.out.println(apply);
}
//对象::实例方法
@Test
public void test1(){
Consumer<String> con = (x) -> System.out.println(x);
PrintStream out = System.out;
Consumer<String> con1 = out::println;
con1.accept("asdfs");
BiFunction<String,Integer,Person> biFunction = (x,y) -> new Person(x,y);
BiFunction<String,Integer,Person> biFunction1 = Person::new;
System.out.println(biFunction1.apply("小名",19));
}
@Test
public void test2() {
Person person = new Person();
Supplier<String> supplier = () -> person.getUserName();
String s = supplier.get();
//对象::实例方法
Supplier<Integer> supplier1 = person::getAge;
System.out.println(supplier1.get());
System.out.println(s);
}
//类::静态方法名
@Test
public void test3(){
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
Comparator<Integer> com1 = Integer::compare;
}
//类::实例方法名
@Test
public void test4(){
BiPredicate<String,String> bp = (x,y) -> x.equals(y);
BiPredicate<String,String> bp1 = String::equals;
}
}
3. 函数式接口
函数式接口(Functional Interface
)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。在接口上添加@FunctionalInterface
注解声明为函数时接口
函数式接口可以被隐式转换为 lambda
表达式。
@FunctionalInterface
public interface MathOperation {
Integer add(Integer a,Integer b);
}
函数式接口可以对现有的函数友好地支持 lambda
。
JDK 1.8
新增加的函数接口:
java.util.function
java.util.function
它包含了很多类,用来支持 Java
的 函数式编程,该包中的函数式接口有:
序号 | 接口 & 描述 |
---|---|
1 | Function<T,R> method: R apply(T t); |
- | 接受一个输入参数,返回一个结果。 |
2 | Consumer method: void accept(T t); |
- | 代表了接受一个输入参数并且无返回的操作 |
3 | Predicate method: boolean test(T t); |
- | 接受一个输入参数,返回一个布尔值结果。 |
4 | Supplier method : T get(); |
- | 无参数,返回一个结果。 |
函数式接口实例
Predicate <T>
接口是一个函数式接口,它接受一个输入参数 T,返回一个布尔值结果。
该接口包含多种默认方法来将Predicate
组合成其他复杂的逻辑(比如:与,或,非)。
该接口用于测试对象是 true 或 false。
/*
Java 8 内置的四大核心函数式接口
Consumer<T> :消费型接口
void accept(T t);
Supplier<T> : 供给型接口
T get();
Function<T,R> :函数式接口
R apply(T t);
Predicate<T> :断言型接口
boolean test(T t);
*/
public class LambdaDemo6 {
// Consumer<T> :消费型接口
@Test
public void test1(){
happy(1000,(m)-> System.out.println("洗澡消费"+m+"元"));
}
public void happy(double money, Consumer<Double> con){
con.accept(money);
}
//Supplier<T> : 供给型接口
@Test
public void test2(){
getNumList(10,() -> (int)( Math.random()*100)).forEach((m)-> System.out.println(m));
}
//产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> supplier){
ArrayList<Integer> list = new ArrayList<>();
for (int i=0;i<num;i++){
Integer integer = supplier.get();
list.add(integer);
}
return list;
}
//Function<T,R> :函数式接口
@Test
public void test3(){
String newStr = strHandler("\t\t\t\t 我爱张柏芝",(a)-> a.trim());
System.out.println(newStr);
String subStr = strHandler("我喜欢你",(a)->a.substring(0,3));
System.out.println(subStr);
}
//需求:用于处理字符串
public String strHandler(String str, Function<String,String> function){
return function.apply(str);
}
//Predicate<T> :断言型接口
@Test
public void test4(){
List<String> lists = Arrays.asList("Hello","atagui","Lambda","www","ok");
filterStr(lists,(s) -> s.length()>3).forEach((a)-> System.out.println(a));
}
//将满足条件的字符串放入到集合中
public List<String> filterStr(List<String> list,Predicate<String> predicate) {
ArrayList<String> strList = new ArrayList<>();
for (String str : list) {
if (predicate.test(str)){
strList.add(str);
}
}
return strList;
}
}
4. Java 8 默认方法
Java 8
新增了接口的默认方法。简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
为什么要有这个特性?
首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8
之前的集合框架没有 foreach
方法,通常能想到的解决办法是在JDK
里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。
语法
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
}
多个默认方法
一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
}
public interface FourWheeler {
default void print(){
System.out.println("我是一辆四轮车!");
}
}
第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:
public class Car implements Vehicle, FourWheeler {
default void print(){
System.out.println("我是一辆四轮汽车!");
}
}
第二种解决方案可以使用 super 来调用指定接口的默认方法:
public class Car implements Vehicle, FourWheeler {
public void print(){
Vehicle.super.print();
}
}
静态默认方法
Java 8
的另一个特性是接口可以声明(并且可以提供实现)静态方法。例如:
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
// 静态方法
static void blowHorn(){
System.out.println("按喇叭!!!");
}
}
5. Java 8 Stream
Java 8 API
添加了一个新的抽象称为流Stream
,可以让你以一种声明的方式处理数据。
Stream
使用一种类似用 SQL
语句从数据库查询数据的直观方式来提供一种对Java
集合运算和表达的高阶抽象。
Stream API
可以极大提高Java
程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选,排序,聚合等。
元素流在管道中经过中间操作(intermediate operation
)的处理,最后由最终操作(terminal operation
)得到前面处理的结果。
+--------------------+ +------+ +------+ +---+ +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+
什么是 Stream?
Stream
(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。
Java
中的Stream
并不会存储元素,而是按需计算。 - 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似
SQL
语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection
操作不同, Stream
操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过
Iterator
或者For-Each
的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。Stream
提供了内部迭代的方式, 通过访问者模式(Visitor
实现。
生成流
在Java 8
中, 集合接口有两个方法来生成流:
- stream() − 为集合创建串行流。
- parallelStream() − 为集合创建并行流。
/**
* 一 Stream的三个步骤
* 1. 创建Stream流
* 2. 中间操作
* 3. 终止操作(终端操作)
*/
public class TestStreamAPI {
//创建Stream
@Test
public void test1(){
//1. 可以通过Collection系列集合提供的stream() 或 parallelSteam()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
//2.通过Arrays中的静态方法stream()获取数组流
Person[] persion = new Person[10];
Stream<Person> stream1 = Arrays.stream(persion);
//3. 通过Stream类中的静态方法of()
Stream<String> aaa = Stream.of("aaa", "bbb", "ccc");
//4. 创建无限流
//迭代
Stream<Integer> iterate = Stream.iterate(1, (x) -> x + 3);
iterate.limit(10).forEach((x)-> System.out.println(x));
//生成
Stream.generate(()->(int)(Math.random()*100)).limit(10).forEach(System.out::println);
}
}
Stream API
中间操作
filter(),limit(),shkip(),distinct()
/**
* 筛选与切片
* filter---接收Lambda,从流中排除某些元素;filter(能产生boolean结果的Lambda),如果参数Lambda产生了true值,则要元素;
* 如果产生了false,则不要这个元素。
* limit()---截断流,使其不超过给定数量
* skip()---跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
* distinct--筛选,通过流所生成元素的hashcode()和equals()去除重复元素
*/
@Test
public void test3(){
list.stream()
.filter((x)->x.getAge()>20)
.skip(2)
.distinct()
.forEach(System.out::println);
}
@Test
public void test2(){
list.stream()
.filter((e)-> {
System.out.println("短路");
return e.getAge()>20;
})
.limit(2)
.forEach(System.out::println);
}
//内部迭代:迭代操作有Stream API完成
@Test
public void test1(){
//中间操作
Stream<Person> personStream = list.stream()
.filter((e) -> {
System.out.println("Stream API的中间操作");
return e.getAge() > 22;
});
//终止操作:一次性执行全部内容,即"惰性求值"
personStream.forEach(System.out::println);
}
map()
/**
* 映射
* map---接收Lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
*flatMap--- 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
*/
@Test
public void test4(){
List<String> lists = Arrays.asList("aaa","bbb","ccc","ddd","eee");
lists.stream()
.map((x)-> x.toUpperCase())
.forEach(System.out::println);
System.out.println("----------------------");
list.stream()
.map(Person::getUserName)
.forEach(System.out::println);
System.out.println("-----------------------");
Stream<Stream<Character>> stream = lists.stream()
.map((x) -> TestStreamAPI2.filterCharacter(x));
stream.forEach((sm)->
sm.forEach(System.out::println)
);
System.out.println("-----------------------");
//flatMap
Stream<Character> characterStream = lists.stream()
.flatMap(TestStreamAPI2::filterCharacter);
characterStream.forEach(System.out::println);
}
public static Stream<Character> filterCharacter(String str){
List<Character> list1 = new ArrayList<>();
for (Character ch:str.toCharArray()) {
list1.add(ch);
}
return list1.stream();
}
peek()
peek入参是Consumer,没有返回值
Stream<T> peek(Consumer<? super T> action);
map入参是Function,是需要返回值的
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
peek接收一个没有返回值的λ表达式,可以做一些输出,外部处理等。map接收一个有返回值的λ表达式,之后Stream的泛型类型将转换为map参数λ表达式返回的类型
当我们对集合中的对象进行填充值时可以使用
map
List<OverallFund> fundList = overallFunds.stream().map(item -> {
long nextLong = RandomUtils.nextLong();
// 设置主键id
item.setId(nextLong);
// 设置区划
item.setAdmdivCode("430500");
// 设置创建时间
item.setCreateUser("430500");
item.setCreateTime(new Date());
return item;
}).collect(Collectors.toList());
peek()
List<OverallFund> fundList = overallFunds.stream().peek(item -> {
long nextLong = RandomUtils.nextLong();
// 设置主键id
item.setId(nextLong);
// 设置区划
item.setAdmdivCode("430500");
// 设置创建时间
item.setCreateUser("430500");
item.setCreateTime(new Date());
}).collect(Collectors.toList());
我们发现Function 比 Consumer 多了一个 return。这也就是peek 与 map的区别了。
sorted(),sorted(Comparator com)
/**
* 排序
* sorted()---自然排序 (Comparable)
* sorted(Comparator com) ---定制排序(Comparator)
*/
@Test
public void test6(){
List<String> lists = Arrays.asList("bbb","aaa","ddd","eee","ccc");
lists.stream()
.sorted()
.forEach(System.out::println);
System.out.println("------------------------------");
list.stream()
.sorted((e1,e2)-> e1.getAge().compareTo(e2.getAge())).forEach(System.out::println);
}
终止操作(终端操作)
/**
* 查找与匹配
* allMatch---检查是否匹配所有元素
* anyMatch---检查是否至少匹配一个元素
* noneMatch---检查是否没有匹配所有元素
* findFirst---返回第一个元素
* findAny---返回当前流中的任意元素
* count---放回流中元素的总个数
* max---返回流中最大值
* min---返回流中最小值
*/
@Test
public void test2(){
long count = list.stream()
.count();
System.out.println(count);
Optional<Person> max = list.stream()
.max((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
System.out.println(max.get());
Optional<Integer> min = list.stream()
.map(Person::getAge)
.min(Integer::compareTo);
System.out.println(min.get());
}
@Test
public void test1(){
boolean b = list.stream()
.allMatch((e) -> e.getStatus().equals(Status.BUSY));
System.out.println(b);
boolean b1 = list.stream()
.anyMatch((e) -> e.getStatus().equals(Status.BUSY));
System.out.println(b1);
boolean b2 = list.stream()
.noneMatch((e) -> e.getStatus().equals(Status.BUSY));
System.out.println(b2);
Optional<Person> op = list.stream()
.sorted((e1, e2) -> -Integer.compare(e1.getAge(), e2.getAge()))
.findFirst();
//System.out.println(op.get());
//多线程查找
Optional<Person> op1 = list.parallelStream()
.filter((e) -> e.getStatus().equals(Status.FREE))
.findAny();
System.out.println(op1.get());
}
reduce
/**归约:
* reduce(T identity, BinaryOperator<T> accumulator)
* reduce(BinaryOperator<T> accumulator)
* 可以将流中元素反复结合起来,得到一个值
*/
@Test
public void test3(){
List<Integer> list1 = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list1.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(sum);
System.out.println("---------------------");
Optional<Integer> reduce = list.stream()
.map(Person::getAge)
.reduce(Integer::sum);
System.out.println(reduce.get());
}
collect
/**
* 收集
* collect---将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
*/
@Test
public void test5(){
//总数
Long collect = list.stream()
.collect(Collectors.counting());
System.out.println("总数:"+collect);
//平均值
Double collect1 = list.stream()
.collect(Collectors.averagingLong(Person::getAge));
System.out.println("平均值:"+collect1);
//总和
Integer collect2 = list.stream()
.collect(Collectors.summingInt(Person::getAge));
System.out.println("总和:"+collect2);
//最大值
Optional<Person> collect3 = list.stream()
.collect(Collectors.maxBy((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())));
System.out.println("最大值:"+collect3.get());
//最小值
Optional<Person> collect4 = list.stream()
.collect(Collectors.minBy((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())));
System.out.println("最小值:"+collect4.get());
}
/**
* Collectors: 收集转换为其他集合类型
*/
@Test
public void test4(){
List<String> collect = list.stream()
.map(Person::getUserName)
.collect(Collectors.toList());
//collect.forEach(System.out::println);
Set<String> collect1 = list.stream()
.map(Person::getUserName)
.collect(Collectors.toSet());
collect1.forEach(System.out::println);
Set<String> collect2 = list.stream()
.map(Person::getUserName)
.collect(Collectors.toCollection(HashSet::new));
}
//分组
@Test
public void test6(){
Map<Status, List<Person>> map = list.stream()
.collect(Collectors.groupingBy(Person::getStatus));
System.out.println(map);
}
//多级分组
@Test
public void test7(){
Map<Status, Map<String, List<Person>>> collect = list.stream()
.collect(Collectors.groupingBy(Person::getStatus, Collectors.groupingBy(
(e) -> {
if (((Person) e).getAge() < 20) {
return "高中生";
} else if (((Person) e).getAge() > 20) {
return "大学生";
} else {
return "成年人";
}
}
)));
System.out.println(collect);
}
//分区 满足条件一个区,不满足条件一个区
@Test
public void test8(){
Map<Boolean, List<Person>> collect = list.stream()
.collect(Collectors.partitioningBy((e) -> e.getAge() > 22));
System.out.println(collect);
}
@Test
public void test10(){
String collect = list.stream()
.map(Person::getUserName)
.collect(Collectors.joining(",","===","==="));
System.out.println(collect);
}
@Test
public void test9(){
IntSummaryStatistics collect = list.stream()
.collect(Collectors.summarizingInt(Person::getAge));
System.out.println(collect.getMax());
System.out.println(collect.getSum());
System.out.println(collect.getMin());
}
分组:Collectors.groupingBy
// 1.按照年龄分组
Map<Integer, List<Person>> collect = list.stream().collect(Collectors.groupingBy(Person::getAge));
collect.forEach((k,v)->{
System.out.println(k+"="+v);
});
// 2.按照年龄和工资分组
list.stream().collect(Collectors.groupingBy(item->{
StringBuffer buffer = new StringBuffer();
// 这里是Map的key: age_salary
return buffer.append(item.getAge()).append("_").append(item.getSalary());
})).forEach((k,v)->{
System.out.println(k+"="+v);
});
// 3.按照条件分组 A组:20岁以下 B组:20到60岁 C组:60岁以上的
Map<String, List<Person>> collect2 = list.stream()
.collect(Collectors.groupingBy(p -> {
if(p.getAge() < 20){
// map的key是A
return "A";
}else if(p.getAge()>=20 && p.getAge()<=60){
// map的key是B
return "B";
}else {
// map的key是C
return "C";
}
}));
collect2.forEach((k,v)->{
System.out.println(k+"="+v);
});
//4.多级分组 先按照城市分组 在按照年龄分组
//要实现多级分组,可以使用一个由双参数版本的Collectors.groupingBy工厂方法创建的收集器,
// 它除了普通的分类函数之外,还可以接受collector类型的第二个参数。
// 那么要进行二级分组的话,我们可以把一个内层groupingBy传递给外层groupingBy,并定义一个为流中项目分类的二级标准。
Map<String, Map<String, List<Person>>> collect3 = list
.stream()
.collect(Collectors.groupingBy(Person::getUsername, // 一级分组:姓名
Collectors.groupingBy(p -> { // 二级分组:年龄
if (p.getAge() < 20) {
// map的key是A
return "A";
} else if (p.getAge() >= 20 && p.getAge() <= 60) {
// map的key是B
return "B";
} else {
// map的key是C
return "C";
}
})));
// 5.分组求每组数量 每个城市有几个人
Map<String, Long> collect4 = list
.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));
// 6.每个城市的人的工资总和
Map<String, Double> collect5 = list
.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.summingDouble(Person::getSalary)));
// 7.找到每个城市的员工姓名 联合其他的收集器使用
Map<String, List<String>> collect6 = list
.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.mapping(Person::getUsername, Collectors.toList())));
// Bigdecimal类型对象 分组求和
//不分组
BigDecimal intergralAmount = integralRecords.stream().map(UserIntegralRecord::getIntegral).reduce(BigDecimal.ZERO,BigDecimal::add);
//分组
//Map<uid,总积分>
Map<Integer, BigDecimal> integralMap = integralRecords.stream().collect(
Collectors.groupingBy(UserIntegralRecord::getUid,
Collectors.reducing(BigDecimal.ZERO,UserIntegralRecord::getIntegral,BigDecimal::add)));
}
Collectors.toMap
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("hepengju", 28, 20000.0));
list.add(new Person("lisi" , 44, 40000.0));
list.add(new Person("wangwu" , 55, 50000.0));
list.add(new Person("zhaoliu" , 66, 60000.0));
list.add(new Person("zhangsan", 33, 33333.0));
list.add(new Person("zhangsan", 23, 10000.0));
}
写法一、
// Function.identity() 为 Person对象
Map<String, Person> collect = list.stream().collect(Collectors.toMap(Person::getUsername, Function.identity()));
下面写法等同上面相同
Map<String, Person> collect1 = list.stream().collect(Collectors.toMap(Person::getUsername, v -> v));
当数组中有重复的username会报错,Duplicate key Person(username=zhangsan, age=33, salary=33333.0)
解决上面问题的写法
Map<String, Person> collect1 = list.stream().collect(Collectors.toMap(Person::getUsername, v -> v,(entity1, entity2) -> entity2));
或者使用,也创建其他类型的Map,ConcurrentHashMap::new / HashMap::new
Map<String, Person> collect2 = list.stream().collect(Collectors.toMap(Person::getUsername, Function.identity(),(entity1, entity2) -> entity2, ConcurrentHashMap::new));
collect1.forEach((k,v)->{ System.out.println(k+"="+v);});
(entity1, entity2) -> entity2)
里使用的箭头函数,也就是说当出现了重复key的数据时,会回调这个方法,可以在这个方法里处理重复Key数据问题。我这里取的是最后一个
(entity1, entity2) -> entity1)
: 如果数组中有多个,表示取第一个(entity1, entity2) -> entity2)
: 如果数组中有多个,表示取最后一个
判断集合中重复元素
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
private Integer age;
}
@Test
public void streamApi() {
User user1 = new User("王维", 24);
User user2 = new User("李白", 18);
User user3 = new User("杜甫", 18);
User user4 = new User("王之焕", 21);
User user5 = new User("欧阳修", 21);
List<User> users = Arrays.asList(user1, user2, user3, user4, user5);
// 分组统计重复的年龄
Map<Integer, Long> mapGroup = users.stream().collect(Collectors.groupingBy(User::getAge, Collectors.counting()));
// 获取重复的key值
List<Integer> list = mapGroup.entrySet().stream().
filter(item -> item.getValue() > 1).map(Map.Entry::getKey).collect(Collectors.toList());
}
forEach
Stream
提供了新的方法'forEach'
来迭代流中的每个数据。以下代码片段使用 forEach
输出了10个随机数:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
map
map
方法用于映射每个元素到对应的结果,以下代码片段使用 map
输出了元素对应的平方数:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
filter
filter
方法用于通过设置的条件过滤出元素。返回由该流中匹配给定元素的元素组成的流。
解释:filter(string -> string.isEmpty())
参数Lambda产生了true值,则保留元素;为false,则流中排除元素
以下代码片段使用 filter
方法过滤出空字符串:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();
limit
limit
方法用于获取指定数量的流。 以下代码片段使用 limit
方法打印出 10 条数据:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
sorted
sorted
方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
并行(parallel)程序
parallelStream
是流并行处理程序的代替方法。用多线程的方式处理stream流
List.of(1,2,3,4,5,6).stream().parallel().forEach(x->{
System.out.println(Thread.currentThread().getName()+"::"+x);
});
parallelStream并行流处理线程安全问题
用多线程并行流的方式往arrlist中添加1000个元素,list实际大小不等于1000
@Test
public void test1() {
ArrayList<Integer> list = new ArrayList<>();
IntStream.rangeClosed(1,1000).parallel().forEach(list::add);
System.out.println(list.size());
}
解决线程安全问题
synchronized
使用synchronized同步代码块
@Test
public void test1() {
ArrayList<Integer> list = new ArrayList<>();
Object o = new Object();
IntStream.rangeClosed(1,1000).parallel().forEach(
x->{
synchronized (o) {
list.add(x);
}
}
);
System.out.println(list.size());
}
Collections.synchronizedList
使用Collections集合工具类提供的集合安全转换方法
@Test
public void test2() {
ArrayList<Integer> list = new ArrayList<>();
List<Integer> list1 = Collections.synchronizedList(list);
IntStream.rangeClosed(1,1000).parallel().forEach(
list1::add
);
System.out.println(list.size());
}
Vector集合类
使用类型安全的集合类Vector
@Test
public void test3() {
Vector<Integer> list = new Vector<>();
IntStream.rangeClosed(1,1000).parallel().forEach(
list::add
);
System.out.println(list.size());
}
6. Optional类
Optional 类(java.util.Optional) 是一个容器类
,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
创建Optional类对象的方法
Optional.of(T t)
: 创建一个 Optional 实例,t必须非空;Optional.empty()
: 创建一个空的 Optional 实例Optional.ofNullable(T t)
:t可以为null
@Data
@AllArgsConstructor
@NoArgsConstructor
class Student {
private String name;
private Integer age;
}
public void test1() {
// 声明一个空Optional
Optional<Object> empty = Optional.empty();
// 依据一个非空值创建Optional
Student student = new Student();
Optional<Student> os1 = Optional.of(student);
// 可接受null的Optional
Student student1 = null;
Optional<Student> os2 = Optional.ofNullable(student1);
}
判断Optional容器中是否包含对象
boolean isPresent()
: 判断是否包含对象void ifPresent(Consumer<? super T> consumer)
:如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
public void test1() {
Student student = new Student();
Optional<Student> os1 = Optional.ofNullable(student);
boolean present = os1.isPresent();
System.out.println(present);
// 利用Optional的ifPresent方法做出如下:当student不为空的时候将name赋值为张三
Optional.ofNullable(student).ifPresent(p -> p.setName("张三"));
}
获取Optional容器的对象
T get()
: 如果调用对象包含值,返回该值,否则抛异常T orElse(T other)
:如果有值则将其返回,否则返回指定的other对象。T orElseGet(Supplier<? extends T> other)
:如果有值则将其返回,否则返回由Supplier接口实现提供的对象。T orElseThrow(Supplier<? extends X> exceptionSupplier)
:如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
public void test1() throws Exception {
Student student = null;
Optional<Student> os1 = Optional.ofNullable(student);
// 使用get一定要注意,假如student对象为空,get是会报错的
// java.util.NoSuchElementException: No value present
Student student1 = os1.get();
// 当student为空的时候,返回我们新建的这个对象,有点像三目运算的感觉
Student student2 = os1.orElse(new Student("张三", 18));
// orElseGet就是当student为空的时候,返回通过Supplier供应商函数创建的对象
Student student3 = os1.orElseGet(() -> new Student("张三", 18));
// orElseThrow就是当student为空的时候,可以抛出我们指定的异常
os1.orElseThrow(() -> new Exception());
}
过滤
Optional<T> filter(Predicate<? super <T> predicate)
:如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。
public void test1() {
Student student = new Student("李四", 3);
Optional<Student> os1 = Optional.ofNullable(student);
os1.filter(p -> p.getName().equals("张三")).ifPresent(x -> System.out.println("OK"));
}
映射
<U>Optional<U> map(Function<? super T,? extends U> mapper)
:如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
:如果值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象
public void test1() {
Student student = new Student("李四", 3);
Optional<Student> os1 = Optional.ofNullable(student);
// 如果student对象不为空,就加一岁
Optional<Student> emp = os1.map(e ->
{
e.setAge(e.getAge() + 1);
return e;
});
}
什么场景用Optional
1、场景一
PatientInfo patientInfo = patientInfoDao.getPatientInfoById(consultOrder.getPatientId());
if (patientInfo != null) {
consultInfoResp.setPatientHead(patientInfo.getHead());
}
// 使用Optional 和函数式编程,一行搞定,而且像说话一样
Optional.ofNullable(patientInfo).ifPresent(p -> consultInfoResp.setPatientHead(p.getHead()));
2、场景二
public void test1() throws Exception {
Student student = new Student(null, 3);
if (student == null || isEmpty(student.getName())) {
throw new Exception();
}
String name = student.getName();
// 业务省略...
}
public static boolean isEmpty(CharSequence str) {
return str == null || str.length() == 0;
}
// 使用Optional改造
Optional.ofNullable(student).filter(s -> !isEmpty(s.getName())).orElseThrow(() -> new Exception());
3、场景三
public static String getChampionName(Competition comp) throws IllegalArgumentException {
if (comp != null) {
CompResult result = comp.getResult();
if (result != null) {
User champion = result.getChampion();
if (champion != null) {
return champion.getName();
}
}
}
throw new IllegalArgumentException("The value of param comp isn't available.");
}
public static String getChampionName(Competition comp) throws IllegalArgumentException {
return Optional.ofNullable(comp)
.map(Competition::getResult) // 相当于c -> c.getResult(),下同
.map(CompResult::getChampion)
.map(User::getName)
.orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
}
4、场景四
int timeout = Optional.ofNullable(redisProperties.getTimeout())
.map(x -> Long.valueOf(x.toMillis()).intValue())
.orElse(10000);
public class TestOptional {
//用于解决空指针异常
//Optional 容器类的常用方法
/**Optional.of(T t) :创建一个optional实例
* Optional.empty() : 创建一个空的Optional实例
* Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
* isPresent(T t) : 判断是否包含值
* orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
* orElseGet(Supplier s) : 如果调用对象包含值,返回该值,否则返回s获取的值
* map(Function f) : 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
* flatMap(Function mapper) : 与map类似,要求返回值必须是Optional
*/
@Test
public void test5(){
Optional<Person> optionalPerson = Optional.ofNullable(new Person("韩信",21, Status.FREE));
Optional<String> s = optionalPerson.map(Person::getUserName);
System.out.println(s.get());
Optional<String> s1 = optionalPerson.flatMap((e) -> Optional.of(e.getUserName()));
System.out.println(s1);
}
@Test
public void test4(){
Optional<Person> optionalPerson = Optional.ofNullable(new Person());
if (optionalPerson.isPresent())
System.out.println(optionalPerson.get());
Person person = optionalPerson.orElseGet(() -> new Person());
System.out.println(person);
}
@Test
public void test3(){
Optional<Person> optionalPerson = Optional.ofNullable(new Person());
System.out.println(optionalPerson.get());
}
@Test
public void test2(){
Optional<Person> optionalPerson = Optional.empty();
System.out.println(optionalPerson.get());
}
@Test
public void test1(){
Optional<Person> person = Optional.of(new Person());
System.out.println(person.get());
}
}
Date Time API
public class TestLocalDateTime {
//DateTimeFormatter : 格式化时间/日期
@Test
public void test6(){
DateTimeFormatter isoDateTime = DateTimeFormatter.ISO_DATE;
LocalDateTime ldt = LocalDateTime.now();
String format = isoDateTime.format(ldt);
System.out.println(format);
System.out.println("---------------------------------");
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String format1 = dateTimeFormatter.format(ldt);
System.out.println(format1);
LocalDateTime parse = ldt.parse(format1,dateTimeFormatter);
System.out.println(parse);
}
//TemporalAdjuster : 时间校正器
@Test
public void test5(){
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
LocalDateTime ldt2 = ldt.withDayOfMonth(10);
System.out.println(ldt2);
LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
System.out.println(ldt3);
//自定义 : 下一个工作日
ldt3.with((x) -> {
LocalDateTime ldt4 = (LocalDateTime)x;
DayOfWeek dow = ldt4.getDayOfWeek();
if (dow.equals(DayOfWeek.FRIDAY)){
return ldt4.plusDays(3);
}else if (dow.equals(DayOfWeek.SATURDAY)){
return ldt4.plusDays(2);
}else {
return ldt4.plusDays(1);
}
});
}
//3. Duration : 计算两个时间之间的间隔
//Period : 计算两个日期之间的间隔
@Test
public void test4(){
LocalDate ld1 = LocalDate.of(2020, 1, 1);
LocalDate ld2 = LocalDate.now();
Period period = Period.between(ld1,ld2);
System.out.println(period);
System.out.println("年:"+period.getYears()+"--月:"+period.getMonths()+"--日:"+period.getDays()+"--年代学:"+period.getChronology()+"--\n" +
"\n" +
"单位 :"+period.getUnits());
}
@Test
public void test3(){
//Duration : 计算两个时间之间的间隔
Instant now = Instant.now();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant now1 = Instant.now();
Duration duration = Duration.between(now, now1);
System.out.println(duration.toMillis());
}
//2. Instant :时间戳(Unix 元年:1970 年1月1日 00:00:00 到某个时间之间的毫秒值)
@Test
public void test2(){
Instant now = Instant.now();
System.out.println(Instant.now());
OffsetDateTime offsetDateTime = Instant.now().atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
System.out.println(now.toEpochMilli());
}
//1. LocalTime LocalDate LocalDateTime
@Test
public void test1() {
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
LocalDateTime of = LocalDateTime.of(2020, 4, 17, 16, 40);
System.out.println(of);
}
}