JDK8
1 JavaSE的发展历史
1.1 Java语言的介绍
- SUN公司在1991年成立了一个称为绿色计划(Green Project)的项目,由James Gosling(高斯林)博士领导,绿色计划的目的是开发一种能够在各种消费性电子产品(机顶盒、冰箱、收音机等)上运行的程序架构。这个项目的产品就是Java语言的前身:Oak(橡树)。Oak当时在消费品市场上并不算成功,但是随着1995年互联网潮流的兴起,Oak迅速找到了最适合自己发展的市场定位。
1.2 JDK的发展历史
- JDK Beta - 1995。
- JDK 1.0 - 1996年1月 (真正第一个稳定的版本JDK 1.0.2,被称为Java1)
- JDK 1.1 - 1997年2月。
- J2SE 1.2 - 1998年12月。
- J2SE(Java 2 Standard Edition,Java 2平台的标准版),应用于桌面环境。
- J2ME(Java 2 Micro Edition,Java 2平台的微型版),应用于移动、无线及有限资源的环境。
- J2EE(Java 2 Enterprise Edition,Java 2平台的企业版),应用于基于Java的应用服务器。
- J2SE 1.3 - 2000年5月。
- J2SE 1.4 - 2002年2月。
J2SE 5.0 - 2004年9月。
- Java SE 6 - 2006年12月。
- Java SE 7 - 2011年7月。
Java SE 8 (LTS) - 2014年3月。
- Java SE 9 - 2017年9月。
- Java SE 10 (18.3) - 2018年3月。
Java SE 11 (18.9 LTS) - 2018年9月。
- Java SE 12 (19.3) - 2019年3月。
- Java SE 13 (19.9) - 2019年9月。
- ……
2 了解Open JDK和Oracle JDK
2.1 Open JDK的来源
- Java是由SUN公司发明,Open JDK是SUN在2006年末把Java开源而形成的项目。也就是说Open JDK是Java SE平台版的开源和免费实现,它是由SUN和Java社区提供支持,2009年Oracle收购了SUN公司,自此Java的维护方之一的SUN也变成了Oracle。
2.2 Open JDK和Oracle JDK的关系
- 大多数的JDK都是在Open JDK的基础上进一步编写实现的,比如IBM J9、Oracle JDK等。
- Oracle JDK完全是由Oracle公司开发,Oracle JDK是基于Open JDK源代码的商业版本。此外,它包含闭源组件。
- Oracle JDK根据二进制代码许可协议获取许可,在没有商业许可的情况下,在2019年1月之后发布的Oracle JDK的公开更新将无法用于商业或生产用途。
- Open JDK是完全开源的,可以自由使用。
2.3 Open JDK官网介绍
- Open JDK官网。
- JDK Enhancement Proposals(JDK增强建议)。通俗的讲JEP就是JDK的新特性。
2.4 总结
- Oracle JDK是基于Open JDK源代码的商业版本。
- 我们要学习Java新技术可以去Open JDK官网学习。
3 JDK8新特性
3.1 Lambda表达式的介绍
3.1.1 使用匿名内部类存在的问题
- 当需要启动一个线程去完成任务的时候,通常会通过Runnable接口来定义任务内容,并使用Thread类来启动该线程。
- 传统写法,代码如下:
package com.sunxaiping.jdk8;
public class LambdaDemo1 {
public static void main(String[] args) {
//使用匿名内部类存在的问题
//匿名内部类做了哪些事情
//①定义了一个没有名字的类
//②这个类实现了Runnable接口
//③创建了这个类的对象
//其实我们最关注的是run方法和里面要执行的代码。
//所以使用匿名内部类语法是很冗余的。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行啦(*^▽^*)");
}
}).start();
}
}
- 由于面向对象的语法要求,首先创建一个Runnable接口的匿名类对象来指定线程要执行的任务内容,再将其交给一个线程来启动。
- 代码分析:对于Runnable的匿名内部类用法,可以分析出以下几点内容:
- 1️⃣Thread类需要Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心。
- 2️⃣为了指定run的方法体,不得不需要Runnable接口的实现类。
- 3️⃣为了省去定义个Runnable实现类的麻烦,不得不使用匿名内部类。
- 4️⃣必须覆盖重写run方法,所以方法名称,方法参数、方法返回值不得不重写一遍,且不能写错。
- 5️⃣实际上,似乎只有run方法体才是关键所在。
3.1.2 体验Lambda
- Lambda是一个匿名函数,可以理解为一段可以传递的代码。
- Lambda表达式写法,代码如下:
package com.sunxaiping.jdk8;
public class LambdaDemo2 {
public static void main(String[] args) {
//Lambda体现的是函数式编程思想,只需要将要执行的代码放到函数中(函数就是类中的方法)
//Lambda就是一个匿名函数,我们只需要将要执行的代码放到Lambda表达式中即可
//Lambda表达式的好处:可以简化匿名内部类,让代码更加精简。
new Thread(() -> {
System.out.println("线程执行啦(*^▽^*)");
}).start();
}
}
- 这段代码和使用内名内部类的执行效果是完全一样的,可以在JDK8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
3.1.3 Lambda的优点
- 简化匿名内部类的使用,语法更加简单。
3.2 Lambda的标准格式
3.2.1 Lambda的标准格式
- Lambda省去了面向对象的条条框框,Lambda的标准格式由3个部分组成:
(参数类型 参数名称) -> {
代码体;
}
- 格式说明:
- (参数类型 参数名称):参数列表。
- {代码体;}:方法体。
- ->:箭头,没有实际含义,起到连接的作用。
3.2.2 无参数无返回值的Lambda
- 示例:
package com.sunxaiping.jdk8;
@FunctionalInterface
public interface Swimmable {
/**
* 游泳
*/
void swimming();
}
package com.sunxaiping.jdk8;
/**
* Lambda表达式的标准格式:
* (参数列表)->{}
* (参数列表):参数列表
* {}:方法体
* ->:没有实际含义,起到连接的作用
*/
public class LambdaDemo3 {
public static void main(String[] args) {
goSwimming(new Swimmable() {
@Override
public void swimming() {
System.out.println("我是匿名内部类的游泳");
}
});
goSwimming(() -> {
System.out.println("我是Lambda的游泳");
});
}
/**
* 无参数无返回值的Lambda
*
* @param swimmable
*/
public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}
3.2.3 有参数有返回值的Lambda
- 示例:
package com.sunxaiping.jdk8;
public interface Smokeable {
int smoking(String name);
}
package com.sunxaiping.jdk8;
/**
* Lambda表达式的标准格式:
* (参数列表)->{}
* (参数列表):参数列表
* {}:方法体
* ->:没有实际含义,起到连接的作用
*/
public class LambdaDemo4 {
public static void main(String[] args) {
goSmoking(new Smokeable() {
@Override
public int smoking(String name) {
System.out.println("匿名内部类:抽了" + name + "的烟");
return 0;
}
});
goSmoking((String name) -> {
System.out.println("Lambda:抽了" + name + "的烟");
return 0;
});
}
public static void goSmoking(Smokeable smokeable) {
smokeable.smoking("中华");
}
}
3.2.4 总结
- Lambda表达式的标准格式:
(参数列表) -> {}
- 以后我们看到调用的方法其参数是接口就可以考虑使用Lambda表达式来替代匿名内部类,但是不是所有的匿名内部类都能使用Lambda表达式来替代,Lambda表达式相当于对接口的抽象方法的重写。。
3.3 了解Lambda的实现原理
- 示例:
package com.sunxaiping.jdk8;
@FunctionalInterface
public interface Swimmable {
/**
* 游泳
*/
void swimming();
}
package com.sunxaiping.jdk8;
/**
* Lambda表达式的标准格式:
* (参数列表)->{}
* (参数列表):参数列表
* {}:方法体
* ->:没有实际含义,起到连接的作用
*/
public class LambdaDemo3 {
public static void main(String[] args) {
goSwimming(new Swimmable() {
@Override
public void swimming() {
System.out.println("我是匿名内部类的游泳");
}
});
}
/**
* 无参数无返回值的Lambda
*
* @param swimmable
*/
public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}
- 我们可以看到匿名内部类会在编译后产生一个类:LambdaDemo3$1.class。
- 使用XJad反编译这个类,得到如下的代码:
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
// Source File Name: LambdaDemo3.java
package com.sunxaiping.jdk8;
import java.io.PrintStream;
// Referenced classes of package com.sunxaiping.jdk8:
// Swimmable, LambdaDemo3
static class LambdaDemo3$1
implements Swimmable
{
public void swimming()
{
System.out.println("我是匿名内部类的游泳");
}
LambdaDemo3$1()
{
}
}
- 我们再看Lambda的效果,修改代码如下:
package com.sunxaiping.jdk8;
/**
* Lambda表达式的标准格式:
* (参数列表)->{}
* (参数列表):参数列表
* {}:方法体
* ->:没有实际含义,起到连接的作用
*/
public class LambdaDemo3 {
public static void main(String[] args) {
goSwimming(()->{
System.out.println("Lambda的游泳");
});
}
/**
* 无参数无返回值的Lambda
*
* @param swimmable
*/
public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}
- 运行程序,控制台得到预期的结果,但是并没有出现一个新的类,也就是说Lambda并没有在编译的时候产生新的类。使用XJad对这个类进行反编译,发现XJad报错。使用了Lambda后XJad反编译工具无法反编译。我们使用JDK自带的一个工具:javap,对字节码进行反汇编,查看字节码指令。
javap -c -p 文件名.class
-c:表示对代码进行反编译
-p:显示所有类和成员
- 反编译的效果如下:
- 可以看到在类中多了一个私有的静态方法
lambda$main$0
。这个方法里面存放的是什么内容,我们可以通过断点调试来看看:
- 可以确认
lambda$main$0
里面放的就是Lambda中的内容,那么我们可以这么理解lambda$main$0
方法:
package com.sunxaiping.jdk8;
public class LambdaDemo3 {
public static void main(String[] args) {
...
}
private static void lambda$main$0(){
System.out.println("Lambda的游泳");
}
}
- 关于这个方法
lambda$main$0
的命名:以lambda开头,因为是在main函数里面使用了Lambda表达式,所以带有$main
表示,因为是第一个,所以$0
。 - 如果调用这个方法?其实Lambda在运行的时候会生成一个内部类,为了验证是否生成内部类,可以在运行的时候加上
-Djdk.internal.lambda.dumpProxyClasses
,加上这个参数后,运行时会将生成的内部class码输出到一个文件中。其命令如下:
java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名
- 根据上面的格式,在命令行输入以下的命令:
java -Djdk.internal.lambda.dumpProxyClasses com.sunxaiping.jdk8.LambdaDemo3
- 执行完毕,可以看到生成一个新的类,效果如下:
- 反编译
LambdaDemo3$$Lambda$1.class
这个字节码文件,内容如下:
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
package com.sunxaiping.jdk8;
// Referenced classes of package com.sunxaiping.jdk8:
// Swimmable, LambdaDemo3
final class LambdaDemo3$$Lambda$1
implements Swimmable
{
public void swimming()
{
LambdaDemo3.lambda$main$0();
}
private LambdaDemo3$$Lambda$1()
{
}
}
- 可以看到这个匿名内部类实现了Swimmable接口,并且重写了swimming方法,swimming方法调用
LambdaDemo3.lambda$main$0()
,也就是调用Lambda中的内容。最后可以将Lambda理解为:
package com.sunxaiping.jdk8;
public class LambdaDemo3 {
public static void main(String[] args) {
goSwimming(new Swimmable() {
@Override
public void swimming() {
LambdaDemo3.lambda$main$0();
}
});
}
private static void lambda$main$0(){
System.out.println("Lambda的游泳");
}
}
3.4 Lambda的省略格式
-
在Lambda标准格式的基础上,使用省略写法的规则是:
-
1️⃣小括号内的参数的类型可以省略。
-
2️⃣如果小括号内有且仅有一个参数,则小括号可以省略。
-
3️⃣如果大括号内有且仅有一个语句,则可以同时省略大括号、return关键字以及语句的分号。
-
示例:
-
Lambda标准格式:
(int a ) -> {
return new Person();
}
- Lambda表达式省略后:
a -> new Person()
3.5 Lambda的前提条件
-
Lambda的语法非常简洁,但是Lambda表达式不是随随便便就能使用的,使用时需要注意以下几个条件:
-
1️⃣方法的参数或局部变量类型必须为接口才能使用Lambda。
-
2️⃣接口中有且仅有一个抽象方法。
-
示例:
package com.sunxaiping.jdk8;
/**
* 只有一个抽象方法的接口称为函数函数式接口。
* @FunctionalInterface 注解用来检测这个接口是不是只有一个抽象方法
*/
@FunctionalInterface
public interface Flyable {
void fly();
}
package com.sunxaiping.jdk8;
public class LambdaCondition {
public static void main(String[] args) {
test(() -> {
System.out.println("飞");
});
//局部变量类型是接口,可以使用Lambda
Flyable flyable = () -> {
System.out.println("飞");
};
}
/**
* 方法的参数类型,可以使用Lambda
*
* @param flyable
*/
public static void test(Flyable flyable) {
flyable.fly();
}
}
3.6 函数式接口
- 函数式接口在Java中是指有且仅有一个抽象方法的接口。
- 函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利的进行推导。
- Java8中专门为函数式接口引入了一个新的注解:@FunctionalInterface。该注解用于接口的定义上。
package com.sunxaiping.jdk8;
/**
* 只有一个抽象方法的接口称为函数函数式接口。
* @FunctionalInterface 注解用来检测这个接口是不是只有一个抽象方法
*/
@FunctionalInterface
public interface Flyable {
void fly();
}
- 一旦使用该注解来定义接口,编译器将会强制检查是否确实仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
3.7 Lambda和匿名内部类的对比
- 所需的类型不一样:
- 匿名内部类,需要的类型可以是类、抽象类、接口。
- Lambda,需要的类型必须是接口。
- 抽象方法数量不一样:
- 匿名内部类所需接口中的抽象方法的数量随意。
- Lambda所需接口中的抽象方法的数量有且仅有一个。
- 实现原理不同:
- 匿名内部类是在编译后会形成class文件。
- Lambda是在程序运行的时候动态生成class文件。
3.8 JDK8接口新增的两种方法
3.8.1 概述
- 在JDK8以前的接口:
interface 接口名{
静态常量;
抽象方法;
}
- JDK8对接口进行了增强,接口还可以有默认方法和静态方法。
interface 接口名{
静态常量;
抽象方法;
默认方法;
静态方法;
}
3.8.2 接口引入默认方法的背景
- 在JDK8以前接口中只能有抽象方法。存在如下问题:
- 如果给接口中新增抽象方法,所有实现类都必须重写这个抽象方法。不利于接口的扩展。
interface A{
void test1();
//接口新增抽象方法,所有实现类都需要去重写这个方法,非常不利于接口的扩展
void test2();
}
interface B implements A{
@Override
public void test1(){
System.out.println("B test1");
}
//接口新增抽象方法,所有实现类都需要去重写这个方法
@Override
public void test2(){
System.out.println("B test2");
}
}
- 例如,JDK8的时候,Map接口新增了forEach方法:
public interface Map<K,V> {
...
void forEach(BiConsumer<? super K, ? super V> action) {}
}
- 通过API可以查询到Map接口的实现类:
- 如果在Map接口中增加一个抽象方法,所有的实现类都需要去实现这个方法,那么工程量是巨大的。
- 因此,在JDK8为接口新增了默认方法,效果如下:
public interface Map<K,V> {
...
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
}
- 接口中的默认方法实现类不必重写,可以直接使用,实现类也可以根据需要重写,这样就方便接口的扩展。
3.8.3 接口默认方法的定义格式
interface 接口名{
default 返回值类型 方法名(){
//代码;
}
}
3.8.4 接口的默认方法的使用
- 1️⃣实现类可以直接使用接口的默认方法。
package com.sunxaiping.jdk8;
public class DefaultFunction {
public static void main(String[] args) {
BB bb = new BB();
bb.show();
}
}
interface AA{
default void show(){
System.out.println("我是AA接口中的默认方法");
}
}
//默认方法的使用方式一:实现类可以直接使用
class BB implements AA{
}
- 2️⃣实现类重写接口的默认方法。
package com.sunxaiping.jdk8;
public class DefaultFunction {
public static void main(String[] args) {
BB bb = new BB();
bb.show();
CC cc = new CC();
cc.show();
}
}
interface AA{
default void show(){
System.out.println("我是AA接口中的默认方法");
}
}
//默认方法的使用方式一:实现类可以直接使用
class BB implements AA{
}
//默认方法的使用方式二:实现类可以重写默认方法
class CC implements AA{
@Override
public void show() {
System.out.println("我是CC类重写的默认方法");
}
}
3.8.5 接口静态方法的定义格式
interface 接口名{
static 返回值类型 方法名(){
//代码
}
}
3.8.6 接口静态方法的使用
- 直接使用接口名调用即可:接口名.静态方法名();
- 常用接口中的静态方法不能被继承,也不能被重写。
package com.sunxaiping.jdk8;
public class DefaultFunction {
public static void main(String[] args) {
AA.show();
}
}
interface AA{
static void show(){
System.out.println("我是接口的静态方法");
}
}
3.8.7 接口中默认方法和静态方法的区别
- 1️⃣接口中的默认方法是通过实例调用,而接口中的静态方法是通过接口名调用。
- 2️⃣接口中的默认方法可以被继承,实现类可以直接使用接口中的默认方法,也可以重写接口中的默认方法。
- 3️⃣接口中的静态方法不能被继承,实现类也不能重写接口中的静态方法,只能使用接口名调用。
3.9 常用内置函数式接口
3.9.1 内置函数式接口的由来
- 我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名、抽象方法名,只关系抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。
package com.sunxaiping.jdk8;
public class LambdaDemo5 {
public static void main(String[] args) {
test((a1, a2) -> {
System.out.println(a1 + a2);
});
}
public static void test(Operation operation) {
operation.getSum(1, 2);
}
}
interface Operation {
void getSum(int a, int b);
}
3.9.2 常用的函数式接口介绍
- 它们主要在java.util.function包中。下面是最常用的几个接口:
- 1️⃣Supplier接口:供给型接口
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
- 2️⃣Consumer接口:消费型接口。
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
- 3️⃣Function:函数型接口。
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
/**
* Returns a composed function that first applies the {@code before}
* function to its input, and then applies this function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of input to the {@code before} function, and to the
* composed function
* @param before the function to apply before this function is applied
* @return a composed function that first applies the {@code before}
* function and then applies this function
* @throws NullPointerException if before is null
*
* @see #andThen(Function)
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*
* @see #compose(Function)
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
- 4️⃣Predicate:断言型接口。
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
/**
* Returns a composed predicate that represents a short-circuiting logical
* AND of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code false}, then the {@code other}
* predicate is not evaluated.
*
* <p>Any exceptions thrown during evaluation of either predicate are relayed
* to the caller; if evaluation of this predicate throws an exception, the
* {@code other} predicate will not be evaluated.
*
* @param other a predicate that will be logically-ANDed with this
* predicate
* @return a composed predicate that represents the short-circuiting logical
* AND of this predicate and the {@code other} predicate
* @throws NullPointerException if other is null
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
* Returns a predicate that represents the logical negation of this
* predicate.
*
* @return a predicate that represents the logical negation of this
* predicate
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}
/**
* Returns a composed predicate that represents a short-circuiting logical
* OR of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code true}, then the {@code other}
* predicate is not evaluated.
*
* <p>Any exceptions thrown during evaluation of either predicate are relayed
* to the caller; if evaluation of this predicate throws an exception, the
* {@code other} predicate will not be evaluated.
*
* @param other a predicate that will be logically-ORed with this
* predicate
* @return a composed predicate that represents the short-circuiting logical
* OR of this predicate and the {@code other} predicate
* @throws NullPointerException if other is null
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/**
* Returns a predicate that tests if two arguments are equal according
* to {@link Objects#equals(Object, Object)}.
*
* @param <T> the type of arguments to the predicate
* @param targetRef the object reference with which to compare for equality,
* which may be {@code null}
* @return a predicate that tests if two arguments are equal according
* to {@link Objects#equals(Object, Object)}
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
3.9.3 Supplier接口
java.util.function.Supplier<T>
接口,它意味着“供给”,对应的Lambda表达式需要对外提供一个符合泛型类型的对象数据。
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
-
供给型接口,通过Supplier接口中的get方法可以得到一个值,无参有返回值的接口。
-
示例:使用Lambda表达式返回数组元素的最大值。
package com.sunxaiping.jdk8;
import java.util.Arrays;
import java.util.function.Supplier;
public class LambdaDemo6 {
public static void main(String[] args) {
printMax(() -> {
int[] arr = {11, 99, 100, -7};
Arrays.sort(arr);
return arr[arr.length-1];
});
}
public static void printMax(Supplier<Integer> supplier) {
Integer max = supplier.get();
System.out.println("max = " + max);
}
}
3.9.4 Consumer接口
java.util.function.Consumer<T>
接口正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
- 示例:使用Lambda表达式将一个字符串转成大写和小写字符串。
package com.sunxaiping.jdk8;
import java.util.function.Consumer;
public class LambdaDemo7 {
public static void main(String[] args) {
test((s)->{
String lowerCase = s.toLowerCase();
System.out.println(lowerCase);
String upperCase = s.toUpperCase();
System.out.println(upperCase);
});
}
public static void test(Consumer<String> consumer){
consumer.accept("hello world");
}
}
- 示例:使用Lambda表达式将一个字符串先转成小写然后在转成大写。
package com.sunxaiping.jdk8;
import java.util.function.Consumer;
public class LambdaDemo8 {
public static void main(String[] args) {
//使用Lambda表达式先将一个字符串转成小写,再转成大写
test((s) -> {
System.out.println(s.toLowerCase());
}, (s) -> {
System.out.println(s.toUpperCase());
});
}
public static void test(Consumer<String> c1, Consumer<String> c2) {
c1.andThen(c2).accept("hello world");
}
}
3.9.5 Function接口
java.util.function.Function<T, R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有参数有返回值。
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
/**
* Returns a composed function that first applies the {@code before}
* function to its input, and then applies this function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of input to the {@code before} function, and to the
* composed function
* @param before the function to apply before this function is applied
* @return a composed function that first applies the {@code before}
* function and then applies this function
* @throws NullPointerException if before is null
*
* @see #andThen(Function)
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*
* @see #compose(Function)
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.function.Function;
public class LambdaDemo1 {
//使用Lambda表达式将字符串转成数字
public static void main(String[] args) {
getNumber((s) -> {
int num = Integer.parseInt(s);
return num;
});
}
public static void getNumber(Function<String, Integer> function) {
Integer num = function.apply("10");
System.out.println("num = " + num);
}
}
3.9.6 Predicate接口
- 有时我们需要对某种类型的数据进行判断,从而得到一个boolean值的结果,这时可以使用
java.util.function.Predicate<T>
接口。
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
/**
* Returns a composed predicate that represents a short-circuiting logical
* AND of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code false}, then the {@code other}
* predicate is not evaluated.
*
* <p>Any exceptions thrown during evaluation of either predicate are relayed
* to the caller; if evaluation of this predicate throws an exception, the
* {@code other} predicate will not be evaluated.
*
* @param other a predicate that will be logically-ANDed with this
* predicate
* @return a composed predicate that represents the short-circuiting logical
* AND of this predicate and the {@code other} predicate
* @throws NullPointerException if other is null
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
* Returns a predicate that represents the logical negation of this
* predicate.
*
* @return a predicate that represents the logical negation of this
* predicate
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}
/**
* Returns a composed predicate that represents a short-circuiting logical
* OR of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code true}, then the {@code other}
* predicate is not evaluated.
*
* <p>Any exceptions thrown during evaluation of either predicate are relayed
* to the caller; if evaluation of this predicate throws an exception, the
* {@code other} predicate will not be evaluated.
*
* @param other a predicate that will be logically-ORed with this
* predicate
* @return a composed predicate that represents the short-circuiting logical
* OR of this predicate and the {@code other} predicate
* @throws NullPointerException if other is null
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/**
* Returns a predicate that tests if two arguments are equal according
* to {@link Objects#equals(Object, Object)}.
*
* @param <T> the type of arguments to the predicate
* @param targetRef the object reference with which to compare for equality,
* which may be {@code null}
* @return a predicate that tests if two arguments are equal according
* to {@link Objects#equals(Object, Object)}
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
- 示例:使用Lambda判断一个名称,如果名称超过3个字就认为是很长的名称。
package com.sunxiaping.jdk8;
import java.util.function.Predicate;
public class LambdaDemo2 {
//使用Lambda判断一个名称,如果名称超过3个字就认为是很长的名称
public static void main(String[] args) {
test((name) -> {
return name.length() > 3;
}, "迪丽热巴");
}
public static void test(Predicate<String> predicate, String name) {
boolean test = predicate.test(name);
if (test) {
System.out.println(name + "的名称很长");
} else {
System.out.println(name + "的名称不长");
}
}
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.function.Predicate;
public class LambdaDemo3 {
public static void main(String[] args) {
//使用Lambda表达式判断一个字符串即包含W也包含H
testAnd(s1 -> s1.contains("W"), s2 -> s2.contains("H"), "Hello World");
//使用Lambda表达式判断一个字符串包含W或包含H
testOr(s1 -> s1.contains("W"), s2 -> s2.contains("H"), "Hello World");
//使用Lambda表达式判断一个字符串不包含W
testNegate(s1 -> s1.contains("W"), "Hello world");
}
public static void testAnd(Predicate<String> p1, Predicate<String> p2, String str) {
boolean test = p1.and(p2).test(str);
if (test) {
System.out.println(str + "既包含W也包含H");
}
}
public static void testOr(Predicate<String> p1, Predicate<String> p2, String str) {
boolean test = p1.or(p2).test(str);
if (test) {
System.out.println(str + "包含W或包含H");
}
}
public static void testNegate(Predicate<String> p1, String str) {
boolean test = p1.negate().test(str);
if (test) {
System.out.println(str + "不包含W");
}
}
}
3.10 方法引用
3.10.1 介绍方法引用
- 示例:使用Lambda表达式求一个数组的和
package com.sunxiaping.jdk8;
import java.util.function.Consumer;
public class LambdaDemo4 {
/**
* 获取一个数组的和
*
* @param arr
*/
public static void getSum(int[] arr) {
int sum = 0;
for (int i : arr) {
sum += i;
}
System.out.println("sum = " + sum);
}
public static void main(String[] args) {
int[] arrs = {10, 19, -10, 0};
//使用方法引用:让这个指定的方法去重写接口的抽象方法,到时候调用接口的抽象方法就是调用传递过去的这个方法
printSum(LambdaDemo4::getSum,arrs);
Consumer<int[]> consumer = LambdaDemo4::getSum;
consumer.accept(arrs);
}
public static void printSum(Consumer<int[]> consumer, int[] arr) {
consumer.accept(arr);
}
}
- 注意其中的双冒号
::
写法,这被称为"方法引用",是一种新的语法。
方法引用的前提:
- 1️⃣方法引用所引用的方法的参数列表必须要和函数式接口中抽象方法的参数列表相同(完全一致)。
- 2️⃣方法引用所引用的方法的的返回值类型必须要和函数式接口中抽象方法的返回值类型相同(完全一致)。
3.10.2 方法引用的格式
- 符号表示:
::
。 - 符号说明:双冒号为方法引用运算符,而它所在的表达式被称为
方法引用
。 - 应用场景:如果Lambda所要实现的方案,已经有了其他方法存在相同方法,那么就可以使用方法引用。
3.10.3 常用的引用方式
- 方法引用在JDK8中使用方式非常灵活,有以下几种形式。
- 1️⃣实例名::成员方法名。
- 2️⃣类名::静态方法名。
- 3️⃣类名::方法名。
- 4️⃣类名::new。
- 5️⃣数据类型[]::new。
3.10.4 实例名::成员方法名
- 如果一个类中已经存在了一个成员方法,则可以通过实例名引用成员方法。
package com.sunxiaping.jdk8;
import java.util.Date;
import java.util.function.Supplier;
/**
* 方法引用: 实例名::方法名
*/
public class LambdaDemo5 {
public static void main(String[] args) {
//使用Lambda表达式获取当前秒数
Date date = new Date();
Supplier<Long> supplier = () -> {
return date.getTime();
};
System.out.println(supplier.get());
//使用方法引用简化上述代码
supplier = date::getTime;
System.out.println(supplier.get());
}
}
3.10.5 类名::静态方法名
- 由于在java.lang.System类中已经存在了许多静态方法,比如currentTimeMillis()方法,所以当我们需要通过Lambda来调用这些静态方法的时候,可以使用类名::静态方法名。
package com.sunxiaping.jdk8;
import java.util.function.Supplier;
/**
* 方法引用: 类名::静态方法名
*/
public class LambdaDemo6 {
public static void main(String[] args) {
//使用Lambda表达式获取当前的毫秒值
Supplier<Long> supplier = ()->{
return System.currentTimeMillis();
};
System.out.println("supplier = " + supplier.get());
//使用方法引用简化上面的代码
supplier = System::currentTimeMillis;
System.out.println("supplier = " + supplier.get());
}
}
3.10.6 类名::方法名
- Java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者。
package com.sunxiaping.jdk8;
import java.util.function.Function;
/**
* 方法引用 类名::方法名
*/
public class LambdaDemo7 {
public static void main(String[] args) {
//使用Lambda表达式将字符串转换为Long类型
Function<String, Long> function = (s) -> {
return Long.parseLong(s);
};
Long apply = function.apply("5");
System.out.println("apply = " + apply);
//使用Lambda简化上面的代码
function = Long::parseLong;
apply = function.apply("5");
System.out.println("apply = " + apply);
}
}
3.10.7 类名::new
- 由于构造器的名称和类名完全一样,所以构造器引用使用
类名::new
的格式表示。
package com.sunxiaping.jdk8;
import java.util.function.BiFunction;
import java.util.function.Supplier;
/**
* 方法引用 类名::new
*/
public class LambdaDemo8 {
public static void main(String[] args) {
//使用Lambda表达式获取Person类对象
Supplier<Person> supplier = () -> {
return new Person();
};
Person person = supplier.get();
System.out.println("person = " + person);
supplier = Person::new;
person = supplier.get();
System.out.println("person = " + person);
BiFunction<String, Integer, Person> function = (name, age) -> {
return new Person(name, age);
};
person = function.apply("李四", 20);
System.out.println("person = " + person);
function = Person::new;
person = function.apply("张三", 25);
System.out.println("person = " + person);
}
}
3.10 数据类型[]::new
- 数组也是Object的子类,所以具有同样的构造器。
package com.sunxiaping.jdk8;
import java.util.function.Function;
/**
* 方法引用 数据类型[]::new
*/
public class LambdaDemo9 {
public static void main(String[] args) {
//使用Lambda表达式创建指定长度的String数组
Function<Integer, String[]> function = (length) -> {
return new String[length];
};
String[] str = function.apply(2);
System.out.println("str.length = " + str.length);
//使用方法引用简化上面的代码
function = String[]::new;
str = function.apply(5);
System.out.println("str.length = " + str.length);
}
}
3.10.11 总结
- 方法引用是对Lambda表达式符合特定情况下的一种缩写,它使得我们的Lambda表达式更加的精简,也可以理解为Lambda表达式的缩写形式,不过要注意的是方法引用只能
引用
已经存在的方法。
3.11 Stream API
3.11.1 集合处理数据的弊端
- 当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合的遍历。我们来体验集合操作数据的弊端,需求如下:
一个ArrayList集合中存储有以下数据:张无忌、周芷若、赵敏、张强、张三丰
需求:①拿到所有姓张的 ②拿到名字长度为3个字的 ③打印这些数据
- 代码如下:
package com.sunxiaping.jdk8;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class StreamIntroDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
//拿到所有姓张的
List<String> zhangList = new ArrayList<>();
for (String s : list) {
if (s.startsWith("张")) {
zhangList.add(s);
}
}
//拿到名字长度=3的
List<String> threeList = new ArrayList<>();
for (String s : zhangList) {
if (s.length() == 3) {
threeList.add(s);
}
}
//对结果进行打印
for (String name : threeList) {
System.out.println(name);
}
}
}
- 循环遍历的弊端:
- 上面的代码中包含三个循环,每一个作用不同:
- 1️⃣首先筛选所有姓张的人。
- 2️⃣然后筛选名字中有三个字的人。
- 3️⃣最后对结果进行打印输出。
- 每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环,这样太麻烦了。
- 上面的代码中包含三个循环,每一个作用不同:
- Stream的优雅的写法:
package com.sunxiaping.jdk8;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class LambdaDemo10 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
list.stream().filter(name->name.startsWith("张")).filter((name)->name.length()==3).forEach(System.out::println);
}
}
3.11.2 Stream流式思想概述
- Stream流式思想类似于工厂车间的"生产流水线",Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
- Stream API能让我们快速完成许多重复的操作,如筛选,切片、映射、查找、去除重复、统计、匹配和归约。
3.11.3 获取Stream流的方式
- 1️⃣java.util.Collection接口中加入了default方法stream()用来获取流,所以其所有的实现类均可获取Stream流。
public interface Collection<E> extends Iterable<E> {
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
//略
}
package com.sunxiaping.jdk8;
import java.util.Arrays;
import java.util.List;
/**
* 获取Stream流的方式
*/
public class LambdaDemo11 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3);
list.stream().forEach(System.out::println);
}
}
- 2️⃣Stream接口的静态方法of可以获取数组对应的Stream流。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
/**
* Returns a sequential {@code Stream} containing a single element.
*
* @param t the single element
* @param <T> the type of stream elements
* @return a singleton sequential stream
*/
public static<T> Stream<T> of(T t) {
return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}
/**
* Returns a sequential ordered stream whose elements are the specified values.
*
* @param <T> the type of stream elements
* @param values the elements of the new stream
* @return the new stream
*/
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
//略
}
package com.sunxiaping.jdk8;
import java.util.stream.Stream;
/**
* 获取Stream流的方式
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
stream.forEach(System.out::println);
}
}
- 3️⃣java.util.Arrays的stream()方法获取数组对应的Stream流。
public class Arrays {
public static <T> Stream<T> stream(T[] array) {
return stream(array, 0, array.length);
}
/**
* Returns a sequential {@link Stream} with the specified range of the
* specified array as its source.
*
* @param <T> the type of the array elements
* @param array the array, assumed to be unmodified during use
* @param startInclusive the first index to cover, inclusive
* @param endExclusive index immediately past the last index to cover
* @return a {@code Stream} for the array range
* @throws ArrayIndexOutOfBoundsException if {@code startInclusive} is
* negative, {@code endExclusive} is less than
* {@code startInclusive}, or {@code endExclusive} is greater than
* the array size
* @since 1.8
*/
public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
}
/**
* Returns a sequential {@link IntStream} with the specified array as its
* source.
*
* @param array the array, assumed to be unmodified during use
* @return an {@code IntStream} for the array
* @since 1.8
*/
public static IntStream stream(int[] array) {
return stream(array, 0, array.length);
}
/**
* Returns a sequential {@link IntStream} with the specified range of the
* specified array as its source.
*
* @param array the array, assumed to be unmodified during use
* @param startInclusive the first index to cover, inclusive
* @param endExclusive index immediately past the last index to cover
* @return an {@code IntStream} for the array range
* @throws ArrayIndexOutOfBoundsException if {@code startInclusive} is
* negative, {@code endExclusive} is less than
* {@code startInclusive}, or {@code endExclusive} is greater than
* the array size
* @since 1.8
*/
public static IntStream stream(int[] array, int startInclusive, int endExclusive) {
return StreamSupport.intStream(spliterator(array, startInclusive, endExclusive), false);
}
/**
* Returns a sequential {@link LongStream} with the specified array as its
* source.
*
* @param array the array, assumed to be unmodified during use
* @return a {@code LongStream} for the array
* @since 1.8
*/
public static LongStream stream(long[] array) {
return stream(array, 0, array.length);
}
/**
* Returns a sequential {@link LongStream} with the specified range of the
* specified array as its source.
*
* @param array the array, assumed to be unmodified during use
* @param startInclusive the first index to cover, inclusive
* @param endExclusive index immediately past the last index to cover
* @return a {@code LongStream} for the array range
* @throws ArrayIndexOutOfBoundsException if {@code startInclusive} is
* negative, {@code endExclusive} is less than
* {@code startInclusive}, or {@code endExclusive} is greater than
* the array size
* @since 1.8
*/
public static LongStream stream(long[] array, int startInclusive, int endExclusive) {
return StreamSupport.longStream(spliterator(array, startInclusive, endExclusive), false);
}
/**
* Returns a sequential {@link DoubleStream} with the specified array as its
* source.
*
* @param array the array, assumed to be unmodified during use
* @return a {@code DoubleStream} for the array
* @since 1.8
*/
public static DoubleStream stream(double[] array) {
return stream(array, 0, array.length);
}
/**
* Returns a sequential {@link DoubleStream} with the specified range of the
* specified array as its source.
*
* @param array the array, assumed to be unmodified during use
* @param startInclusive the first index to cover, inclusive
* @param endExclusive index immediately past the last index to cover
* @return a {@code DoubleStream} for the array range
* @throws ArrayIndexOutOfBoundsException if {@code startInclusive} is
* negative, {@code endExclusive} is less than
* {@code startInclusive}, or {@code endExclusive} is greater than
* the array size
* @since 1.8
*/
public static DoubleStream stream(double[] array, int startInclusive, int endExclusive) {
return StreamSupport.doubleStream(spliterator(array, startInclusive, endExclusive), false);
}
//略
}
package com.sunxiaping.jdk8;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* 获取Stream流的方式
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<Integer> stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5});
stream.forEach(System.out::println);
}
}
3.11.4 Stream常用方法
- Stream流模型的操作非常丰富,这里介绍一些常用的API。
方法名 | 方法作用 | 返回值类型 | 方法种类 |
---|---|---|---|
count | 统计个数 | long | 终结 |
forEach | 逐一处理 | void | 终结 |
filter | 过滤 | Stream | 函数拼接 |
limit | 取前几个 | Stream | 函数拼接 |
skip | 跳过前几个 | Stream | 函数拼接 |
map | 映射 | Stream | 函数拼接 |
concat | 组合 | Stream | 函数拼接 |
- 终结方法:返回值类型不是Stream类型的方法,不再支持链式调用。
- 非终结方法:返回值类型是Stream类型的方式,支持链式调用。
3.11.5 Stream注意事项
- 1️⃣Stream只能操作一次。
- 2️⃣Stream方法返回的是新的流。
- 3️⃣Stream不调用终结方法,中间的操作不会执行。
3.11.6 Stream流的forEach方法
- Stream中的forEach方法用来遍历流中的数据,该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理
public interface Stream<T> extends BaseStream<T, Stream<T>> {
void forEach(Consumer<? super T> action);
//略
}
- Map中的forEach方法用来遍历Map中的每个Map.Entry类型的元素,该方法接受一个BiConsumer接口函数,会将Map.Entry类型元素的key和value交给该函数进行处理。
public interface Map<K,V> {
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
//略
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* forEach
*/
public class LambdaDemo11 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四", "王五");
list.stream().forEach(System.out::println);
}
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.HashMap;
import java.util.Map;
/**
* forEach
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Map<String,Object> map = new HashMap<>();
map.put("aa","aaa");
map.put("bb","bbb");
map.forEach((k,v)->{
System.out.println("k = " + k);
System.out.println("v = " + v);
});
}
}
3.11.7 Stream流的count方法
- Stream流提供count方法来统计其中的元素个数:
public interface Stream<T> extends BaseStream<T, Stream<T>> {
long count();
//略
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.stream.Stream;
/**
* count
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
long count = stream.count();
System.out.println("count = " + count);
}
}
3.11.8 Stream流的filter方法
- Stream流中的filter方法用于过滤数据,返回符合条件的数据。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
Stream<T> filter(Predicate<? super T> predicate);
//略
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.stream.Stream;
/**
* filter
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七", "王八");
stream.filter(s -> s.contains("张")).forEach(System.out::println);
}
}
3.11.9 Stream流中的limit方法
- Stream流中的limit方法可以对流进行截取,只取前n个:参数是一个long类型,如果集合当前长度大于参数则进行截取,否则不进行任何操作。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
Stream<T> limit(long maxSize);
//略
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.stream.Stream;
/**
* limit
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七", "王八");
stream.limit(3).forEach(System.out::println);
}
}
3.11.10 Stream流中的skip方法
- 如果希望跳过前n个元素,可以使用skip方法获取一个截取之后的新流:
public interface Stream<T> extends BaseStream<T, Stream<T>> {
Stream<T> skip(long n);
//略
}
- 如果流当前的长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
- 示例:
package com.sunxiaping.jdk8;
import java.util.stream.Stream;
/**
* skip
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七", "王八");
stream.skip(3).forEach(System.out::println);
}
}
3.11.11 Stream流中的map方法
- 如果需要将流中的元素映射到另一个流中,可以使用map方法。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
//略
}
-
该接口需要一个Function函数式接口参数,可以将当前流中的T类型转换为另一种R类型的流。
-
使用场景:
- 1️⃣转换流中的数据格式。
- 2️⃣提取流中的对象属性。
-
示例:
package com.sunxiaping.jdk8;
import java.util.stream.Stream;
/**
* map
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1", "2", "3", "4", "5", "6");
stream.map(Integer::parseInt).forEach(System.out::println);
}
}
3.11.12 Stream流中的flatMap方法
- Stream流中的flatMap方法是一个维度升降的方法。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
//略
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* flatMap
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Hello", "World");
stream.map(s -> s.split("")).flatMap(Arrays::stream).forEach(System.out::println);
}
}
3.11.13 Stream流中的sorted方法
- 如果需要将数据排序,可以使用sorted方法。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
//略
}
- 示例:
package com.sunxaiping.jdk8;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class LambdaDemo11 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, -1, 10, 0, 100, 55);
//自然排序
list.stream().sorted().forEach(System.out::println);
//逆序
list.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);
}
}
3.11.14 Stream流中的distinct方法
- 如果需要去除重复数据,可以使用distinct方法。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
Stream<T> distinct();
//略
}
- 示例:
package com.sunxaiping.jdk8;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class LambdaDemo11 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, -1, 10, 0, 10, 55);
list.stream().distinct().forEach(System.out::println);
}
}
3.11.15 Stream流中的match方法
- 如果需要判断数据是否匹配指定的条件,可以使用match相关方法。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
//元素是否全部满足条件
boolean anyMatch(Predicate<? super T> predicate);
//元素是否满足任意一个条件
boolean allMatch(Predicate<? super T> predicate);
//元素是否全部不满足条件
boolean noneMatch(Predicate<? super T> predicate);
//略
}
- 示例:
package com.sunxaiping.jdk8;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class LambdaDemo11 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, -1, 10, 0, 10, 55);
boolean b = list.stream().allMatch((e) -> e > 5);
if (b) {
System.out.println("元素是否满足都>5");
}
b = list.stream().anyMatch((e) -> e > 5);
if (b) {
System.out.println("元素是否满足任意元素>5");
}
b = list.stream().noneMatch(e -> e > 5);
if (b) {
System.out.println("元素是否全部不小于5");
}
}
}
3.11.16 Stream流中的find方法
- 如果需要找到某些数据,可以使用find相关方法:
public interface Stream<T> extends BaseStream<T, Stream<T>> {
//获取Stream流中的第一个元素
Optional<T> findFirst();
//获取Stream流中的第一个元素
Optional<T> findAny();
//略
}
- 示例:
package com.sunxaiping.jdk8;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public class LambdaDemo11 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, -1, 10, 0, 10, 55);
Optional<Integer> first = list.stream().findFirst();
System.out.println("first = " + first.get());
Optional<Integer> any = list.stream().findAny();
System.out.println("any = " + any.get());
}
}
3.11.17 Stream流中的max和min方法
- 如果需要获取最大值和最小值,可以使用max和min方法。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
//获取Stream流中的最大值
Optional<T> max(Comparator<? super T> comparator);
//获取Stream流中的最小值
Optional<T> min(Comparator<? super T> comparator);
//略
}
- 示例:
package com.sunxaiping.jdk8;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public class LambdaDemo11 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, -1, 10, 0, 10, 55);
Optional<Integer> max = list.stream().max(Integer::compareTo);
System.out.println("max = " + max.get());
Optional<Integer> min = list.stream().min(Integer::compareTo);
System.out.println("min = " + min.get());
}
}
3.11.18 Stream流中的reduce方法
- 如果需要将所有数据归纳到一个数据,可以使用reduce方法。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
//获取Stream流中的最大值
T reduce(T identity, BinaryOperator<T> accumulator);
//获取Stream流中的最小值
Optional<T> reduce(BinaryOperator<T> accumulator);
//略
}
- 示例:
package com.sunxaiping.jdk8;
import java.util.stream.Stream;
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, -5, 9, 100, 0);
//第一个参数是默认值
//第二个参数:对数据进行处理的方式
Integer reduce = stream.reduce(0, Integer::sum);
System.out.println("reduce = " + reduce);
}
}
3.11.19 Stream流中的mapToInt方法
- 如果需要将
Stream<Integer>
中的Integer类型转成int类型,可以使用mapToInt方法。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
//获取Stream流中的最大值
IntStream mapToInt(ToIntFunction<? super T> mapper);
//略
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* mapToInt
*/
public class LambdaDemo11 {
public static void main(String[] args) {
//Integer占用的内存比int多,在Stream流操作中会自动装箱和拆箱
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
//操作
stream.filter(n -> n > 3).forEach(System.out::println);
//mapToInt将Stream流中的Integer变为int
//IntStream:内部操作的是int类型的数据,节省内存,减少自动装箱和拆箱
IntStream intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(Integer::intValue);
intStream.filter(n -> n > 3).forEach(System.out::println);
}
}
3.11.20 Stream流中的concat方法
- 如果有两个流,希望合并成一个流,那么就可以使用Stream接口中的静态方法concat。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
//将两个流合并成一个流
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
//略
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.stream.Stream;
/**
* concat
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<Integer> stream1 = Stream.of(1, 2, 3);
Stream<Integer> stream2 = Stream.of(4, 5, 6);
//两个流合并之后,不能操作之前的流
Stream<Integer> concat = Stream.concat(stream1, stream2);
concat.forEach(System.out::println);
}
}
3.12 收集Stream结果
3.12.1 Stream流中的结果保存到集合中
- Stream流提供collect方法,其参数需要一个
java.util.stream.Collector<T, A, R>
接口对象来指定收集到那种集合中。java.util.stream.Collectors类提供一些方法,可以作为Collector接口的实例。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
//Stream流中的结果保存到集合中
<R, A> R collect(Collector<? super T, A, R> collector);
//略
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Stream流中的结果保存到集合中
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
//将Stream流中的结果保存到List集合中
List<Integer> list = stream1.collect(Collectors.toList());
list.forEach(System.out::println);
//将Stream流中的结果保存到Set集合中
Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Set<Integer> set = stream2.collect(Collectors.toSet());
set.forEach(System.out::println);
//将Stream流中的结果保存到ArrayList集合中
Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
ArrayList<Integer> arrayList = stream3.collect(Collectors.toCollection(ArrayList::new));
arrayList.forEach(System.out::println);
//将Stream流中的结果保存到HashSet集合中
Stream<Integer> stream4 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
HashSet<Integer> hashSet = stream4.collect(Collectors.toCollection(HashSet::new));
hashSet.forEach(System.out::println);
}
}
3.12.2 Stream流中的结果保存到数组中
- Stream流提供toArray方法,以便将Stream流中的结果保存到数组中。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
//Stream流中的结果保存到数组中
Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);
//略
}
- 示例:
package com.sunxiaping.jdk8;
import java.util.Arrays;
import java.util.stream.Stream;
public class LambdaDemo11 {
public static void main(String[] args) {
//Stream流中的数据转换成Object数组
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Object[] array = stream1.toArray();
System.out.println("Stream流转换为Object数组:" + Arrays.toString(array));
//Stream流中的数据转换成String数组
Stream<String> stream2 = Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9", "0");
String[] strings = stream2.toArray(String[]::new);
System.out.println("Stream流转换为String数组 = " + Arrays.asList(strings));
}
}
3.12.3 对流中数据进行聚合计算
-
当我们使用Stream流处理数据的时候,可以像数据库的聚合函数一样对某个字段进行操作。比如获取最大值、获取最小值、求和、平均值、统计数量。
-
示例:
package com.sunxiaping.jdk8;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Stream流一旦调用终止方法,就不可以再操作。
*/
public class LambdaDemo11 {
public static void main(String[] args) {
//获取Stream流中数据的总数
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
long count = stream.count();
System.out.println("count = " + count);
//获取Stream流中的最大值
stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Optional<Integer> max = stream.max(Integer::compareTo);
System.out.println("max = " + max.get());
//获取Stream流中的最小值
stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Optional<Integer> min = stream.min(Integer::compareTo);
System.out.println("min = " + min.get());
System.out.println("-----------------------------");
stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Optional<Integer> maxOptional = stream.collect(Collectors.maxBy(Integer::compareTo));
System.out.println("max = " + maxOptional.get());
stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Optional<Integer> minOptional = stream.collect(Collectors.minBy(Integer::compareTo));
System.out.println("min = " + minOptional.get());
stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
count = stream.collect(Collectors.counting());
System.out.println("count = " + count);
stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Double avg = stream.collect(Collectors.averagingInt(Integer::intValue));
System.out.println("avg = " + avg);
stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Integer sum = stream.collect(Collectors.summingInt(Integer::intValue));
System.out.println("sum = " + sum);
}
}
3.12.4 对流中的数据进行分组
-
当我们使用Stream流处理数据后,可以根据某个属性进行数据分组。
-
示例:
package com.sunxiaping.jdk8;
public class Person {
private String name;
private Integer age;
private Integer score;
public Person() {
}
public Person(String name, Integer age, Integer score) {
this.name = name;
this.age = age;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
package com.sunxiaping.jdk8;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Stream流一旦调用终止方法,就不可以再操作。
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<Person> stream = Stream.of(
new Person("赵丽颖", 52, 95),
new Person("杨颖", 56, 86),
new Person("迪丽热巴", 56, 99),
new Person("柳岩", 52, 77));
//根据年龄分组
Map<Integer, List<Person>> map = stream.collect(Collectors.groupingBy(Person::getAge));
map.forEach((k, v) -> {
System.out.println("年龄 = " + k);
List<Person> personList = v;
System.out.println("person = " + personList);
});
}
}
3.12.5 对流中的数据进行多级分组
-
还可以对流中的数据进行多级分组。
-
示例:
package com.sunxiaping.jdk8;
public class Person {
private String name;
private Integer age;
private Integer score;
private String sex;
public Person() {
}
public Person(String name, Integer age, Integer score, String sex) {
this.name = name;
this.age = age;
this.score = score;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
", sex='" + sex + '\'' +
'}';
}
}
package com.sunxiaping.jdk8;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Stream流一旦调用终止方法,就不可以再操作。
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<Person> stream = Stream.of(
new Person("赵丽颖", 52, 95, "女"),
new Person("杨颖", 56, 86, "女"),
new Person("迪丽热巴", 56, 99, "女"),
new Person("黄晓明", 52, 77, "男"));
//先根据性别分组,性别相同再按照年龄分组
Map<String, Map<Integer, List<Person>>> map = stream.collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getAge)));
map.forEach((sex, v1) -> {
System.out.println("sex = " + sex);
Map<Integer, List<Person>> map1 = v1;
map1.forEach((age, v2) -> {
System.out.print(age + "---->");
System.out.println("personList = " + v2);
});
});
}
}
3.12.6 对流中的数据进行分区
- Collectors的partitioningBy方法会根据值是否为true,把流中的数据分为两个部分,一个是true的部分,一个是false的部分。
- 示例:
package com.sunxiaping.jdk8;
public class Person {
private String name;
private Integer age;
private Integer score;
public Person() {
}
public Person(String name, Integer age, Integer score) {
this.name = name;
this.age = age;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
package com.sunxiaping.jdk8;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Stream流一旦调用终止方法,就不可以再操作。
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<Person> stream = Stream.of(
new Person("赵丽颖", 52, 95),
new Person("杨颖", 56, 86),
new Person("迪丽热巴", 56, 99),
new Person("黄晓明", 52, 77));
Map<Boolean, List<Person>> map = stream.collect(Collectors.partitioningBy(p -> p.getScore() > 90));
map.forEach((k,v)->{
System.out.println("k = " + k);
System.out.println("v = " + v);
});
}
}
3.12.7 对流中的数据进行拼接
-
Collectors的joining方法会根据指定的连接符,将所有元素连接成一个字符串。
-
示例:
package com.sunxiaping.jdk8;
public class Person {
private String name;
private Integer age;
private Integer score;
public Person() {
}
public Person(String name, Integer age, Integer score) {
this.name = name;
this.age = age;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
package com.sunxiaping.jdk8;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Stream流一旦调用终止方法,就不可以再操作。
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<Person> stream = Stream.of(
new Person("赵丽颖", 52, 95),
new Person("杨颖", 56, 86),
new Person("迪丽热巴", 56, 99),
new Person("黄晓明", 52, 77));
String str = stream.map(Person::getName).collect(Collectors.joining("><", "(#^.^#)", "^_^"));
System.out.println("str = " + str);
}
}
3.13 并行的Stream流
3.13.1 了解串行的Stream流
- 目前使用的Stream流是串行的,就是在一个线程上执行。
package com.sunxiaping.jdk8;
import java.util.stream.Stream;
/**
* Stream流一旦调用终止方法,就不可以再操作。
*/
public class LambdaDemo11 {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
long count = stream.filter(s -> {
System.out.println(Thread.currentThread().getName() + "---" + s);
return true;
}).count();
System.out.println("count = " + count);
}
}
3.13.2 获取并行的Stream流的两种方式
- 直接获取并行的Stream流。
package com.sunxiaping.jdk8;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* Stream流一旦调用终止方法,就不可以再操作。
*/
public class LambdaDemo11 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//直接获取并行的Stream流
Stream<String> parallelStream = list.parallelStream();
}
}
- 将串行流转成并行流。
package com.sunxiaping.jdk8;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* Stream流一旦调用终止方法,就不可以再操作。
*/
public class LambdaDemo11 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//串行流转成并行流
Stream<String> parallel = list.stream().parallel();
}
}
3.14 Optional类的使用
3.14.1 以前对null的处理
package com.sunxiaping.jdk8;
public class OptionalDemo {
public static void main(String[] args) {
String username = null;
if(null != username){
System.out.println("username = " + username);
}
}
}
3.14.2 Optional类的介绍
- Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用就是为了解决避免NULL检查,防止NullPointerException。
- Optional类的创建方式:
//创建一个Optional实例
public static <T> Optional<T> of(T value);
//创建一个空的Optional实例
public static<T> Optional<T> empty();
//如果value不为null,则创建Optional实例,否则创建空的Optional实例
public static <T> Optional<T> ofNullable(T value);
- Optional类的常用方法:
//判断是否包含值,如果包含值返回true,否则返回false
public boolean isPresent();
//如果Optional有值则将其抛出NoSuchElementException异常
public T get();
//如果存在包含值,返回包含值,否则返回参数other
public T orElse(T other) ;
//如果存在包含值,返回包含值,否则返回other获取的值
public T orElseGet(Supplier<? extends T> other);
//如果存在包含值,对其处理,并返回处理后的Optional,否则返回Optional.empty()
public<U> Optional<U> map(Function<? super T, ? extends U> mapper);
3.14.3 Optional类的基本使用
- 示例:
package com.sunxiaping.jdk8;
import org.junit.Test;
import java.util.Optional;
public class OptionalDemo {
/**
* 创建Optional实例
* Optional.of(T value):不能传null,否则报错
*/
@Test
public void testCreateOptional1() {
Optional<String> optional = Optional.of("凤姐");
System.out.println("optional.get() = " + optional.get());
}
/**
* 创建Optional实例
* Optional.empty() 创建一个空的Optional
*/
@Test
public void testCreateOptional2() {
Optional<Object> empty = Optional.empty();
System.out.println("empty = " + empty);
}
/**
* 创建Optional实例
* Optional.ofNullable(T value):如果value是null,则返回Optional.empty();如果value有值,则返回Optional.of(T value)
*/
@Test
public void testCreateOptional3() {
Optional<Object> optional = Optional.ofNullable(null);
System.out.println("optional = " + optional);
optional = Optional.ofNullable("你好啊");
System.out.println("optional = " + optional);
}
}
- 示例:
package com.sunxiaping.jdk8;
import org.junit.Test;
import java.util.Optional;
public class OptionalDemo {
/**
* 判断Optional类中是否有值
*/
@Test
public void test() {
Optional<Object> empty = Optional.empty();
if (empty.isPresent()) {
System.out.println("Optional类中有值");
} else {
System.out.println("Optional类中没有值");
}
}
}
- 示例:
package com.sunxiaping.jdk8;
import org.junit.Test;
import java.util.Optional;
public class OptionalDemo {
/**
* 获取Optional类中的值
*/
@Test
public void test() {
Optional<String> optional = Optional.ofNullable("你好,世界");
if (optional.isPresent()) {
//get()方法,可以用来获取Optional类中的值,如果有值就返回具体值,否则就报错。
//一般get()方法配置isPresent()方法使用
String str = optional.get();
System.out.println("str = " + str);
}
}
}
3.14.4 Optional类的高级使用
- 示例:
package com.sunxiaping.jdk8;
import org.junit.Test;
import java.util.Optional;
public class OptionalDemo {
/**
* 获取Optional类中的值
*/
@Test
public void test() {
Optional<Object> optional = Optional.empty();
//orElse:如果Optional中有值,就返回Optional中的值。否则返回orElse方法中参数指定的值
Object obj = optional.orElse("如花");
System.out.println("obj = " + obj);
}
}
- 示例:
package com.sunxiaping.jdk8;
import org.junit.Test;
import java.util.Optional;
public class OptionalDemo {
/**
* 获取Optional类中的值
*/
@Test
public void test() {
Optional<String> optional = Optional.of("凤姐");
//ifPresent:如果有值,就调用参数
optional.ifPresent((s) -> {
System.out.println("有值:" + s);
});
//ifPresentOrElse在JDK9以后才有
//ifPresentOrElse第一个参数表示如果有值,做什么
//ifPresentOrElse第二个参数表示如果没有值,做什么
optional.ifPresentOrElse(s -> {
System.out.println("s = " + s);
}, () -> {
System.out.println("没有值");
});
}
}
- 示例:
package com.sunxiaping.jdk8;
import org.junit.Test;
import java.util.Optional;
public class OptionalDemo {
/**
* 获取Optional类中的值
*/
@Test
public void test() {
//将Person中的用户名转成大写返回
Person person = null;
person = new Person("hello world", 18, 99);
String name = getTraditionUpperName(person);
System.out.println("name = " + name);
name = getOptionalUpperName(person);
System.out.println("name = " + name);
}
public String getOptionalUpperName(Person person) {
return Optional.ofNullable(person).map(Person::getName).map(String::toUpperCase).orElse(null);
}
/**
* 传统方式 实现需求
*
* @param person
* @return
*/
public String getTraditionUpperName(Person person) {
if (null != person) {
String name = person.getName();
if (null != name) {
return name.toUpperCase();
}
}
return null;
}
}
3.15 JDK8新的时间和日期API
3.15.1 旧版日期时间API存在的问题
- 1️⃣设计很差,在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期。此外用于格式化和解析的类在java.text包中定义。
- 2️⃣非线程安全:java.util.Date是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
- 3️⃣时区处理麻烦:日期类并不提供国际化,没有时区支持,因此引入了java.util.Calendar和java.util.TimeZone类,单它们同样存在上述所有问题。
3.15.2 新日期时间API介绍
-
JDK8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。
-
新的日期和时间API位于java.time包中,下面是一些管件类。
- 1️⃣LocalDate:表示日期,包含年、月、日,格式为2011-11-11。
- 2️⃣LocalTIme:表示时间,包含时、分、秒,格式为14:28:40.426000700。
- 3️⃣LocalDateTime:表示日期时间,包含年、月、日、时、分、秒,格式为2020-09-08T14:29:37.546506。
- 4️⃣DateTimeFormatter:日期时间格式化类。
- 5️⃣Instant:时间戳,表示一个特定的时间瞬间。
- 6️⃣Duration:用于计算2个时间(LocalTime,Instant等)的距离。
- 7️⃣Period:用于计算2个日期(LocalDate)的距离。
- 8️⃣ZoneDateTime:包含时区的时间。
-
Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年有366天。此外Java8还提供了4条其他历法,分别是:
- 1️⃣ThaiBuddhistDate:泰国佛教历法。
- 2️⃣MinguoDate:中华民国历法。
- 3️⃣JapaneseDate:日本历法。
- 4️⃣HijrahDate:伊斯兰历法。
3.15.3 JDK8的日期和时间类
-
LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示ISO 8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含和时区相关的信息。
-
示例:
package com.sunxiaping.jdk8;
import org.junit.Test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class DateTimeDemo {
@Test
public void testLocalDate() {
//LocalDate:表示日期,有年、月、日
LocalDate now = LocalDate.now();
System.out.println("now = " + now);
LocalDate date = LocalDate.of(2011, 11, 11);
System.out.println("date = " + date);
int year = now.getYear();
System.out.println("year = " + year);
int month = now.getMonth().getValue();
System.out.println("month = " + month);
month = now.getMonthValue();
System.out.println("month = " + month);
int day = now.getDayOfMonth();
System.out.println("day = " + day);
}
@Test
public void testLocalTime() {
//LocalTime:表示时间,有时、分、秒
LocalTime now = LocalTime.now();
System.out.println("now = " + now);
LocalTime time = LocalTime.of(15, 30, 11);
System.out.println("time = " + time);
int hour = now.getHour();
System.out.println("hour = " + hour);
int minute = now.getMinute();
System.out.println("minute = " + minute);
int second = now.getSecond();
System.out.println("second = " + second);
int nano = now.getNano();
System.out.println("nano = " + nano);
}
@Test
public void testLocalDateTime() {
//LocalDateTime:表示日期时间,有年、月、日、时、分、秒
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);
LocalDateTime dateTime = LocalDateTime.of(2011, 11, 11, 11, 11, 11);
System.out.println("dateTime = " + dateTime);
int year = now.getYear();
System.out.println("year = " + year);
int month = now.getMonthValue();
System.out.println("month = " + month);
month = now.getMonth().getValue();
System.out.println("month = " + month);
int day = now.getDayOfMonth();
System.out.println("day = " + day);
int hour = now.getHour();
System.out.println("hour = " + hour);
int minute = now.getMinute();
System.out.println("minute = " + minute);
int second = now.getSecond();
System.out.println("second = " + second);
int nano = now.getNano();
System.out.println("nano = " + nano);
}
/**
* 修改时间
*/
@Test
public void testLocalDateTime2() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime localDateTime = now.withYear(2021).withMinute(5);
System.out.println("now = " + now);
System.out.println("localDateTime = " + localDateTime);
LocalDateTime localDateTime1 = now.plusYears(1);
System.out.println("localDateTime1 = " + localDateTime1);
}
/**
* 比较时间
*/
@Test
public void testLocalDateTime3() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime localDateTime = LocalDateTime.now().plusYears(1);
boolean before = now.isBefore(localDateTime);
System.out.println("before = " + before);
}
}
3.15.4 JDK8的时间格式化和解析
-
通过
java.time.format.DateTimeFormatter
类可以进行日期时间解析和格式化。 -
示例:
package com.sunxiaping.jdk8;
import org.junit.Test;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateTimeFormatterDemo {
@Test
public void test() {
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
//格式化 调用LocalDateTime
String format = now.format(df);
System.out.println("format = " + format);
//解析 调用LocalDateTime
LocalDateTime parse = LocalDateTime.parse(format, df);
System.out.println("parse = " + parse);
//格式化
format = df.format(now);
System.out.println("format = " + format);
//解析
parse = df.parse(format, LocalDateTime::from);
System.out.println("parse = " + parse);
}
}
3.15.5 JDK8的Instant类
-
Instant时间戳/时间线,内部保存了从1970年1月1日 00:00:00 以来的秒和纳秒。
-
示例:
package com.sunxaiping.jdk8;
import org.junit.Test;
import java.time.Instant;
public class DateTimeDemo {
@Test
public void test() {
//Instance内部保存了秒和纳秒,一般不是给用户使用的,而是方便我们程序做一些统计
Instant now = Instant.now();
System.out.println("now = " + now);
Instant instant = now.plusSeconds(20);
System.out.println("instant = " + instant);
long epochSecond = now.getEpochSecond();
System.out.println("epochSecond = " + epochSecond);
int nano = now.getNano();
System.out.println("nano = " + nano);
}
}
3.15.6 JDK8的计算日期时间差类
-
Duration:用于计算2个时间(LocalTime,Instant等)的距离。
-
Period:用于计算2个日期(LocalDate)的距离。
-
示例:
package com.sunxaiping.jdk8;
import org.junit.Test;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;
public class DateTimeDemo {
@Test
public void test() {
Duration duration = Duration.between(LocalTime.now(), LocalTime.now().plusSeconds(60));
System.out.println("相差天 = " + duration.toDays());
System.out.println("相差小时 = " + duration.toHours());
System.out.println("相差分钟 = " + duration.toMinutes());
System.out.println("相差毫秒 = " + duration.toNanos());
System.out.println("相差秒 = " + duration.getSeconds());
Period period = Period.between(LocalDate.now(), LocalDate.now().plusDays(2).plusMonths(2));
System.out.println("相差年 = " + period.getYears());
System.out.println("相差月 = " + period.getMonths());
System.out.println("相差日 = " + period.getDays());
}
}
3.15.7 JDK8的时间校正器
-
有时我们可能需要获取例如:将日期调整到
下一个月的第一天
等操作。可以通过时间校正器来进行。 -
TemporalAdjuster:时间校正器。
-
TemporalAdjusters:该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
-
示例:
package com.sunxaiping.jdk8;
import org.junit.Test;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjusters;
public class DateTimeDemo {
@Test
public void test() {
LocalDateTime now = LocalDateTime.now();
//将日期和时间调整到"下一个月的第一天"
LocalDateTime localDateTime = now.with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println("localDateTime = " + localDateTime);
}
}
3.15.8 JDK8设置日期时间的时区
-
JDK8中加入了对时区的支持,LocalDate、LocalTime、LocalDateTIme是不带时区的,带时区的日期时间类分别是ZonedDate、ZonedTime、ZonedDateTime。
-
其中每个时区都对应的ID,ID的格式是“区域/城市”,比如:Asia/Shanghai等。
-
ZoneId:该类中包含了所有的时区信息。
-
示例:
package com.sunxaiping.jdk8;
import org.junit.Test;
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class DateTimeDemo {
@Test
public void test() {
//获取所有的时区信息
ZoneId.getAvailableZoneIds().forEach(System.out::println);
//获取当前默认的时区
String id = ZoneId.systemDefault().getId();
System.out.println("默认的时区id = " + id);
//不带时区,获取计算机的当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println("不带时区的当前时间 = " + now);
//操作带时区的类
ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
System.out.println("世界标准时间 = " + bz);
ZonedDateTime time = ZonedDateTime.now(ZoneId.systemDefault());
System.out.println("带时区的当前时间 = " + time);
//修改时区
ZonedDateTime dateTime = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
//withZoneSameInstant既改时区,也改时间
ZonedDateTime zonedDateTime = dateTime.withZoneSameInstant(ZoneId.systemDefault());
//withZoneSameLocal只改时区
ZonedDateTime zonedDateTime1 = dateTime.withZoneSameLocal(ZoneId.systemDefault());
System.out.println("修改时区和时间 = " + zonedDateTime);
System.out.println("只改时区 = " + zonedDateTime1);
}
}
3.16 重复注解
-
自从JDK5中引入注解依赖,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK8引入重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK8中使用@Repeatable注解定义重复注解。
-
重复注解的使用步骤:
-
1️⃣定义重复注解的容器注解:
package com.sunxaiping.jdk8;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 定义重复注解的容器
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTests {
MyTest[] value();
}
- 2️⃣定义一个可以重复的注解:
package com.sunxaiping.jdk8;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 定义重复注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
public @interface MyTest {
String value();
}
- 3️⃣配置多个重复的注解:
package com.sunxaiping.jdk8;
import java.io.Serializable;
@MyTest("aa")
@MyTest("bb")
@MyTest("cc")
public class Person implements Serializable {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 4️⃣解析得到重复注解:
package com.sunxaiping.jdk8;
import java.util.Arrays;
public class RepeatableAnnotationDemo {
public static void main(String[] args) {
//getAnnotationsByType是新增的API,用于获取重复的注解
MyTest[] myTests = Person.class.getAnnotationsByType(MyTest.class);
Arrays.stream(myTests).map(MyTest::value).forEach(System.out::println);
}
}
3.17 类型注解
-
JDK8为@Target元注解新增了两种类型:TYPE_PARAMETER和TYPE_USE。
-
TYPE_PARAMETER:表示该注解能写在类型参数的声明语句中。
-
TYPE_USE:表示注解可以在任何用到类型的地方使用。
-
示例:
package com.sunxaiping.jdk8;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.TYPE_PARAMETER)
public @interface TypeParam {
}
package com.sunxaiping.jdk8;
public class Demo02<@TypeParam T> {
public <@TypeParam E> void test(){
}
}
- 示例:
package com.sunxaiping.jdk8;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.TYPE_USE)
public @interface TypeUse {
}
package com.sunxaiping.jdk8;
public class Demo02<@TypeUse T> {
public <@TypeUse E> void test(){
}
}