Java8新特性

Java 8 新特性 - 菜鸟

Java 8 Tutorials

Java 8 Tutorials

包含一些原计划在 Java 7 中却延迟发布的功能。

简介

我们通常所说的接口的作用是用于定义一套标准、约束、规范等,接口中的方法只声明方法的签名,不提供相应的方法体,方法体由对应的实现类去实现。

在JDK1.8中打破了这样的认识,接口中的方法可以有方法体,但需要关键字static或者default来修饰,使用static来修饰的称之为静态方法,静态方法通过接口名来调用,使用default来修饰的称之为默认方法,默认方法通过实例对象来调用。

静态方法和默认方法的作用:

静态方法和默认方法都有自己的方法体,用于提供一套默认的实现,这样子类对于该方法就不需要强制来实现,可以选择使用默认的实现,也可以重写自己的实现。当为接口扩展方法时,只需要提供该方法的默认实现即可,至于对应的实现类可以重写也可以使用默认的实现,这样所有的实现类不会报语法错误:Xxx不是抽象的, 并且未覆盖Yxx中的抽象方法。

默认方法(default)

Java 8 默认方法 - 菜鸟

默认方法就是接口可以有实现方法,而且不需要实现类去实现

用法:

  • 使用 default 关键字修饰,访问权限默认且一定为 public。

  • 只能在接口中定义,且必须加 body 实现

  • 支持重载

  • 子类可重写:
        1、接口的 default 方法可以被 子接口 重写成 default 方法
        2、接口的 default 方法可以被 子接口 重写成 抽象方法
        3、接口的 default 方法可以被 子类 重写成 普通方法

  • JDK1.8 可以通过反射判断接口的某个方法是否为 default 方法

    1
    2
    Method m =Chinese.class.getMethod("speak");
    System.out.println("It is "+m.isDefault()+" that "+m.getName()+" is default method");

为什么要有这个特性?

首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

多个接口默认方法相同

一个类实现了多个接口,且这些接口有相同的默认方法,那么这个类就不知道继承哪个接口的默认方法,编译器会报错。解决方案如下:

1
2
3
4
5
6
7
8
9
10
11
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
}

public interface FourWheeler {
default void print(){
System.out.println("我是一辆四轮车!");
}
}

方案一 :重写接口的默认方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在interface中重写interface的方法,继续使用 `default` 修饰。
public interface Car extends Vehicle, FourWheeler {
@Override
default void print() {
System.out.println("我是一辆四轮汽车!");
}
}

// 在class中重写interface的方法,不能使用 `default` 修饰,必须有 public 访问权限。
public class Car implements Vehicle, FourWheeler {
@Override
public void print() {
System.out.println("我是一辆四轮汽车!");
}
}

方案二 :重写接口的默认方法,使用 super 来调用指定接口的默认方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Car extends Vehicle, FourWheeler {
@Override
default void print() {
Vehicle.super.print();
}
}

public class Car implements Vehicle, FourWheeler {
@Override
public void print() {
Vehicle.super.print();
}
}

静态方法(static)

接口可以定义静态方法。

用法:

  • 使用 static 关键字修饰,访问权限默认且一定为 public。
  • 可以在类、接口、枚举中定义 (注解类型除外),必须加 body 实现
  • 支持重载
  • 子类不可重写。

提示:静态方法属于类,无论在哪里定义,都必须有 body 实现。

1
2
3
4
5
6
7
8
9
10
public interface Vehicle {
default void print() {
System.out.println("我是一辆车!");
}

// 静态方法
static void blowHorn() {
System.out.println("按喇叭!!!");
}
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Java8Tester {
public static void main(String args[]) {
Vehicle vehicle = new Car();
vehicle.print();
}
}

interface Vehicle {
default void print() {
System.out.println("我是一辆车!");
}

static void blowHorn() {
System.out.println("按喇叭!!!");
}
}

interface FourWheeler {
default void print() {
System.out.println("我是一辆四轮车!");
}
}

class Car implements Vehicle, FourWheeler {
public void print() {
Vehicle.super.print();
FourWheeler.super.print();
Vehicle.blowHorn();
System.out.println("我是一辆汽车!");
}
}

函数式接口(@FunctionalInterface)

Java 8 函数式接口 - 菜鸟

函数式接口 (Functional Interface) 就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

函数式接口可以被隐式转换为 Lambda 表达式。

函数式接口用途

主要用在 Lambda 表达式和方法引用(实际上也可认为是 Lambda 表达式)上。Lambda 表达式为函数式接口提供实现。

如下定义了一个函数式接口:

1
2
3
4
5
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}

那么就可以使用 Lambda 表达式来表示该接口的一个实现 (注:JAVA 8 之前一般是用匿名对象实现):

1
GreetingService greetService1 = message -> System.out.println("Hello " + message);

关于 @FunctionalInterface 注解

Java 8 为函数式接口引入了一个新注解 @FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。

加不加 @FunctionalInterface 对于接口是不是函数式接口没有影响,该注解只是提醒编译器去检查该接口是否仅包含一个抽象方法。

允许定义默认方法

函数式接口里是可以包含默认方法,因为默认方法不是抽象方法,其有一个默认实现,所以是符合函数式接口的定义的;

允许定义静态方法

函数式接口里是可以包含静态方法,因为静态方法不能是抽象方法,是一个已经实现了的方法,所以是符合函数式接口的定义的;

允许重写java.lang.Object里的public方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {
InterfaceA i = null;
i.toString();
i.equals("");
i.hashCode();
}
}

@FunctionalInterface
interface InterfaceA {
@Override
String toString(); // 不算抽象方法

void finalize(); // 算抽象方法
}

    接口是否继承于Object类?    

推荐连接: 接口是否继承于Object类

Interface 并没有继承 Object 类,接口中的方法有三个来源(这不是 jdk 8 的特性) :

  • 接口声明的方法
  • 父接口声明的方法
  • 当接口没有直接的 SuperInterface 时,Java 接口的特殊处理会隐式地声明 Object 类里所有 public 方法。也可以手动在接口中显式声明 (即 override )。不算抽象方法
    调用这些隐式方法时会调 Object 类里的方法。
    Object 类里的 protected 方法(例如:finalize())不会被隐式的声明。也就意味着,如果在接口中定义 finalize() 方法,那它就算抽象方法

JDK中的函数式接口

函数式接口可以对现有的函数友好地支持 Lambda 。

JDK 1.8 之前已有的函数式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

JDK 1.8 新增的函数式接口:

  • java.util.function

函数式接口示例: Java 8 函数式接口 - 菜鸟

Lambda表达式

Java 8 Lambda 表达式 - 菜鸟

推荐链接: 学习Javascript闭包 (Closure) - 阮一峰匿名函数与闭包的区别

简介:

Lambda 表达式是一个 匿名函数 ,官方称为 lambda 表达式,非官方亦称 闭包 ,它是推动 Java 8 发布的最重要新特性。

Lambda 表达式允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

语法:

1
2
3
(parameters) -> expression

(parameters) ->{ statements; }

特征:

  • **可选类型声明:**不需要声明参数类型,编译器可以统一识别参数值。
  • **可选的参数圆括号:**一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • **可选的大括号:**如果主体包含了一个语句,就不需要使用大括号。
  • **可选的返回关键字:**如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

作用:

Lambda 表达式主要用来定义行内执行的方法类型接口。

Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但强大的 函数式编程 能力。

Lambda表达式与匿名对象相比:

相同:

可以直接访问外层环境的变量,但此变量不允许被后面的代码修改,无论是在内部还是外层,否则会编译错误(即:外层环境的变量,隐性的具有 final 语义)。

不同:

1、作用域

Lambda 表达式:基于词法作用域,也就是说 Lambda 表达式函数体里面的变量和它外层环境的变量具有相同的语义(也包括 Lambda 表达式的形式参数)。简言之,Lambda 表达式中不允许声明与外层环境的变量同名的形式参数或者局部变量。代码块亦是如此。

匿名对象:拥有新的作用域,允许声明与外层环境的变量同名的形式参数或者局部变量。

2、this关键字

Lambda 表达式:内部和外部也拥有相同的语义,所以其内部 this 代表外部对象的引用。代码块亦是如此。

匿名对象:其中的 this 代表该匿名对象的引用。

3、继承

Lambda 表达式:不会从超类(supertype)中继承任何变量。超类不允许有其他抽象方法 (函数式接口)。

匿名对象:会继承超类中任何变量和抽象方法,且必须实现全部抽象方法。

方法引用

Java 8 方法引用 - 菜鸟

Java8 中引入方法引用新特性,用于简化应用对象方法的调用,方法引用是用来直接访问类和实例已经存在的方法或构造方法。

方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。

当Lambda表达式只执行一个方法调用时,可替换成其等效的方法引用。

方法引用是一种更简洁易懂的 Lambda 表达式

方法引用使用一对冒号 ::

IPhone类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import java.util.Date;
import java.util.function.BiFunction;
import java.util.function.Supplier;

public class IPhone {
private Long id;
private String name;
private Long price;
private Date createDate;

public IPhone() {
}

public IPhone(Long id) {
this.id = id;
}

public IPhone(Long id, String name) {
this.id = id;
this.name = name;
}

public IPhone(Long id, String name, Long price) {
this.id = id;
this.name = name;
this.price = price;
}

public IPhone(Long id, String name, Long price, Date createDate) {
this.id = id;
this.name = name;
this.price = price;
this.createDate = createDate;
}

@Override
public String toString() {
return "IPhone{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
", createDate=" + createDate +
'}';
}

public static void info() {
System.out.println("Info " + "这是一部IPhone");
}

public static void info(Long id, String name) {
System.out.println("Info " + "id=" + id + ", name='" + name + '\'');
}

public static IPhone create(Long id, String name) {
BiFunction<Long, String, IPhone> factory = IPhone::new;
IPhone iPhone = factory.apply(id, name);
System.out.println("Creating " + iPhone.toString());
return iPhone;
}

public static IPhone create(final Supplier<IPhone> supplier) {
return supplier.get();
}

public static void recover(final IPhone IPhone) {
System.out.println("Recovering " + IPhone.toString());
}

public void copy(final IPhone another) {
System.out.println("Copying the " + another.toString());
}

public void repair() {
System.out.println("Repaired " + this.toString());
}
}

Test类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Test {
public static void main(String[] args) {

// 1、构造器引用,语法:Class::new 或 Class< T >::new
// Lambda表达式
IPhone iPhone1 = IPhone.create(() -> new IPhone());
// 等效方法引用
IPhone iPhone2 = IPhone.create(IPhone::new);
List<IPhone> iPhones = Arrays.asList(iPhone1, iPhone2);

// 2、静态方法引用,语法:Class::static_method
// Lambda表达式
iPhones.forEach(item -> IPhone.recover(item));
// 等效方法引用
iPhones.forEach(IPhone::recover);

// 3、特定对象的方法引用,语法:instance::method
// Lambda表达式
iPhones.forEach((item) -> IPhone.create(() -> new IPhone()).copy(item));
// 等效方法引用
iPhones.forEach(IPhone.create(IPhone::new)::copy);
// Lambda表达式
iPhones.forEach(item -> System.out.println(item));
// 等效方法引用
iPhones.forEach(System.out::println);

// 4、特定类的任意对象的方法引用,语法:Class::method
// Lambda表达式
iPhones.forEach(item -> item.repair());
// 等效方法引用
iPhones.forEach(IPhone::repair);
}
}

构造器引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 构造器引用,语法:Class::new 或 Class< T >::new
*/
public class Test1 {
public static void main(String[] args) {
/**
* 无参构造器
*/
// Lambda表达式
Supplier<IPhone> factory_01 = () -> new IPhone();
IPhone iPhone_01 = factory_01.get();
System.out.println(iPhone_01);
// 等效方法引用
Supplier<IPhone> factory_02 = IPhone::new;
IPhone iPhone_02 = factory_02.get();
System.out.println(iPhone_02);

/**
* 当构造器方法存在参数 参数个数为1个时
*/
// Lambda表达式
Function<Long, IPhone> factory_03 = (id) -> new IPhone(id);
IPhone iPhone_03 = factory_03.apply(2020L);
System.out.println(iPhone_03);
// 等效方法引用
Function<Long, IPhone> factory_04 = IPhone::new;
IPhone iPhone_04 = factory_04.apply(2020L);
System.out.println(iPhone_04);

/**
* 当构造器方法存在参数 参数个数为2个时
*/
// Lambda表达式
BiFunction<Long, String, IPhone> factory_05 = (id, name) -> new IPhone(id, name);
IPhone iPhone_05 = factory_05.apply(2020L, "IPhone 12");
System.out.println(iPhone_05);
// 等效方法引用
BiFunction<Long, String, IPhone> factory_06 = IPhone::new;
IPhone iPhone_06 = factory_06.apply(2020L, "IPhone 12");
System.out.println(iPhone_06);

/**
* 当构造器参数参过2个时怎么解决呢???
* 自定义函数式接口。
*/
// Lambda表达式
MyFunction<Long, String, Long, IPhone> factory_07 = (id, name, price) -> new IPhone(id, name, price);
IPhone iPhone_07 = factory_07.apply(2020L, "IPhone 12", 9999L);
System.out.println(iPhone_07);
// 等效方法引用
MyFunction<Long, String, Long, IPhone> factory_08 = IPhone::new;
IPhone iPhone_08 = factory_08.apply(2020L, "IPhone 12", 9999L);
System.out.println(iPhone_08);

// Java8的Function接口(compose和andThen)
Function<Long, Long> a = i -> i * 2;
Function<Long, Long> b = i -> i * i;
System.out.println(a.compose(b).apply(5L));
System.out.println(a.andThen(b).apply(5L));
System.out.println(Function.identity().compose(a).apply(5L));

}
}

@FunctionalInterface
interface MyFunction<T, U, V, R> {
R apply(T t, U u, V v);
}

静态方法引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 静态方法引用,语法:Class::static_method
*/
public class Test2 {
public static void main(String[] args) {
// Lambda表达式
PrintFunction pf_01 = () -> IPhone.info();
pf_01.print();
// 等效方法引用
PrintFunction pf_02 = IPhone::info;
pf_02.print();

// Lambda表达式
PrintFunction2<Long, String> pf_03 = (id, name) -> IPhone.info(id, name);
pf_03.print(2020L, "IPhone 12");
// 等效方法引用
PrintFunction2<Long, String> pf_04 = IPhone::info;
pf_04.print(2020L, "IPhone 12");

// Lambda表达式
PrintFunction3<Long, String, IPhone> pf_05 = (id, name) -> IPhone.create(id, name);
pf_05.print(2020L, "IPhone 12");
// 等效方法引用
PrintFunction3<Long, String, IPhone> pf_06 = IPhone::create;
IPhone iPhone = pf_06.print(2020L, "IPhone 12");

}
}

/**
* 定义函数式接口,不带参数不带返回值
*/
@FunctionalInterface
interface PrintFunction {
void print();
}

/**
* 定义函数式接口,带参数不带返回值
*/
@FunctionalInterface
interface PrintFunction2<T, U> {
void print(T t, U u);
}

/**
* 定义函数式接口,带参数带返回值
*/
@FunctionalInterface
interface PrintFunction3<T, U, R> {
R print(T t, U u);
}

特定对象的方法引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 特定对象的方法引用,语法:instance::method
*/
public class Test3 {
public static void main(String[] args) {
// Lambda表达式
BiFunction<Long, String, IPhone> factory_01 = (id, name) -> new IPhone(id, name);
IPhone iPhone_01 = factory_01.apply(2020L, "IPhone 12");
PrintFunction pf_01 = () -> iPhone_01.repair();
pf_01.print();
// 等效方法引用
BiFunction<Long, String, IPhone> factory_02 = IPhone::new;
IPhone iPhone_02 = factory_02.apply(2020L, "IPhone 12");
PrintFunction pf_02 = iPhone_02::repair;
pf_02.print();
}
}

类型注解和重复注解

类型注解

重复注解

Java8新特性-重复注解与类型注解

日期和时间 API

Java 8 日期时间 API - 菜鸟

【Java8新特性】关于Java8中的日期时间API,你需要掌握这些!!

在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:

  • 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:

  • Local(本地) − 简化了日期时间的处理,不存在时区问题。
  • Zoned(时区) − 通过制定的时区处理日期时间。

新的 java.time 包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。

主要方法:

  • now:静态方法,根据当前时间创建对象
  • of:静态方法,根据指定日期/时间创建对象
  • plusDays,plusWeeks,plusMonths,plusYears:向当前LocalDate 对象添加几天、几周、几个月、几年
  • minusDays,minusWeeks,minusMonths,minusYears:从当前LocalDate 对象减去几天、几周、几个月、几年
  • plus,minus:添加或减少一个Duration 或Period
  • withDayOfMonth,withDayOfYear,withMonth,withYear:将月份天数、年份天数、月份、年份修改为指定的值并返回新的LocalDate 对象
  • getDayOfYear:获得年份天数(1~366)
  • getDayOfWeek:获得星期几(返回一个DayOfWeek枚举值)
  • getMonth:获得月份, 返回一个Month 枚举值
  • getMonthValue:获得月份(1~12)
  • getYear:获得年份
  • until:获得两个日期之间的Period 对象,或者指定ChronoUnits 的数字
  • isBefore,isAfter:比较两个LocalDate
  • isLeapYear:判断是否是闰年

使用 LocalDate、 LocalTime、 LocalDateTime

LocalDate、 LocalTime、 LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。

注: ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法

方法 描述 示例
now() 静态方法,根据当前时间创建对象 LocalDate d = LocalDate.now();
LocalTime t = LocalTime.now();
LocalDateTime dt = LocalDateTime.now();
LocalDateTime dt = LocalDateTime.now(Clock.systemDefaultZone());
LocalDateTime dt = LocalDateTime.now(ZoneId.systemDefault());
of() 静态方法,根据指定日期/时间创建 对象 LocalDate d = LocalDate.of(2020, Month.DECEMBER, 12);
LocalDate d = LocalDate.of(2020, 12, 12);
LocalTime t = LocalTime.of(23, 59, 59, 999999999);
LocalDateTime dt = LocalDateTime.of(2020, Month.DECEMBER, 12, 23, 59, 59, 999999999);
LocalDateTime dt = LocalDateTime.of(2020, 12, 12, 23, 59, 59, 999999999);
plusDays, plusWeeks, plusMonths, plusYears 向当前 LocalDate 对象添加几天、 几周、 几个月、 几年
minusDays, minusWeeks, minusMonths, minusYears 从当前 LocalDate 对象减去几天、 几周、 几个月、 几年
plus, minus 添加或减少一个 Duration 或 Period LocalDateTime LocalDateTime = LocalDateTime.now().plus(Duration.ofDays(-1));
LocalDateTime LocalDateTime = LocalDateTime.now().plus(Period.ofDays(-1));
Duration.ofDays(-1).addTo(LocalDateTime.now());
withDayOfMonth, withDayOfYear, withMonth, withYear 将月份天数、 年份天数、 月份、 年 份 修 改 为 指 定 的 值 并 返 回 新 的 LocalDate 对象 LocalDateTime.now().withYear(2010).withMonth(9).withDayOfMonth(1);
getDayOfMonth 获得月份天数(1-31) LocalDateTime.now().getDayOfMonth();
getDayOfYear 获得年份天数(1-366)
getDayOfWeek 获得星期几(返回一个 DayOfWeek 枚举值)
getMonth 获得月份, 返回一个 Month 枚举值 LocalDateTime.now().getMonth().get(ChronoField.MONTH_OF_YEAR);
getMonthValue 获得月份(1-12) LocalDateTime.now().getMonthValue();
getYear 获得年份 LocalDateTime.now().getYear();
until 获得两个日期之间的 Period 对象, 或者指定 ChronoUnits 的数字
isBefore, isAfter 比较两个 LocalDate
isLeapYear 判断是否是闰年 LocalDateTime.now().toLocalDate().isLeapYear();

示例代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 获取当前系统时间
LocalDateTime localDateTime1 = LocalDateTime.now();
System.out.println(localDateTime1);
// 运行结果:2019-10-27T13:49:09.483

// 指定日期时间
LocalDateTime localDateTime2 = LocalDateTime.of(2019, 10, 27, 13, 45,10);
System.out.println(localDateTime2);
// 运行结果:2019-10-27T13:45:10

LocalDateTime localDateTime3 = localDateTime1
// 加三年
.plusYears(3)
// 减三个月
.minusMonths(3);
System.out.println(localDateTime3);
// 运行结果:2022-07-27T13:49:09.483

System.out.println(localDateTime1.getYear()); // 运行结果:2019
System.out.println(localDateTime1.getMonthValue()); // 运行结果:10
System.out.println(localDateTime1.getDayOfMonth()); // 运行结果:27
System.out.println(localDateTime1.getHour()); // 运行结果:13
System.out.println(localDateTime1.getMinute()); // 运行结果:52
System.out.println(localDateTime1.getSecond()); // 运行结果:6

LocalDateTime localDateTime4 = LocalDateTime.now();
System.out.println(localDateTime4); // 2019-10-27T14:19:56.884
LocalDateTime localDateTime5 = localDateTime4.withDayOfMonth(10);
System.out.println(localDateTime5); // 2019-10-10T14:19:56.884

Instant 时间戳

用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算 。

示例代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Instant instant1 = Instant.now();    // 默认获取UTC时区
System.out.println(instant1);
// 运行结果:2019-10-27T05:59:58.221Z

// 偏移量运算
OffsetDateTime offsetDateTime = instant1.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
// 运行结果:2019-10-27T13:59:58.221+08:00

// 获取时间戳
System.out.println(instant1.toEpochMilli());
// 运行结果:1572156145000

// 以Unix元年为起点,进行偏移量运算
Instant instant2 = Instant.ofEpochSecond(60);
System.out.println(instant2);
// 运行结果:1970-01-01T00:01:00Z

Duration 和 Period

详细介绍请看 Java 注释。

下面是 between 方法示例:

Duration.between:用于计算两个“时间”间隔。请看 Java 注释

Period.between:用于计算两个“日期”间隔 。请看 Java 注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Instant instant_1 = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant instant_2 = Instant.now();

Duration duration = Duration.between(instant_1, instant_2);
System.out.println(duration.toMillis());
// 运行结果:1000

LocalTime localTime_1 = LocalTime.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LocalTime localTime_2 = LocalTime.now();

System.out.println(Duration.between(localTime_1, localTime_2).toMillis());
// 运行结果:1000
LocalDate localDate_1 = LocalDate.of(2018,9, 9);
LocalDate localDate_2 = LocalDate.now();

Period period = Period.between(localDate_1, localDate_2);
System.out.println(period.getYears()); // 运行结果:1
System.out.println(period.getMonths()); // 运行结果:1
System.out.println(period.getDays()); // 运行结果:18

日期的操纵

TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。

TemporalAdjusters : 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。

例如获取下个周日,如下所示:

1
LocalDate nextSunday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));

完整的示例代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
LocalDateTime ldt = LocalDateTime.now();

System.out.println(ldt.withYear(2010).withMonth(9).withDayOfMonth(1));
// 2010-09-01T16:40:13.207

System.out.println("LocalDateTime: " + ldt.with(ChronoField.DAY_OF_YEAR, 2));
System.out.println("LocalDateTime: " + localDateTime.with(ChronoField.valueOf("DAY_OF_YEAR"), 2));

System.out.println("LocalDateTime: " + ldt.with(ChronoField.MONTH_OF_YEAR, 1));
System.out.println("LocalDateTime: " + localDateTime.with(ChronoField.valueOf("MONTH_OF_YEAR"), 1));

System.out.println("LocalDateTime: " + ldt.with(DayOfWeek.MONDAY));
System.out.println("LocalDateTime: " + ldt.with(DayOfWeek.of(1)));

System.out.println("LocalDateTime: " + ldt.with(Month.JANUARY));
System.out.println("LocalDateTime: " + ldt.with(Month.of(1)));

System.out.println("LocalDateTime: " + ldt.with(Year.parse("2012")));
System.out.println("LocalDateTime: " + ldt.with(Year.of(2012)));


LocalDateTime localDateTime1 = LocalDateTime.now();
System.out.println(localDateTime1); // 2019-10-27T14:19:56.884

// 获取这个月第一天的日期
System.out.println(localDateTime1.with(TemporalAdjusters.firstDayOfMonth())); // 2019-10-01T14:22:58.574
// 获取下个星期日的日期
System.out.println(localDateTime1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY))); // 2019-11-03T14:22:58.574

// 自定义:下一个工作日
LocalDateTime localDateTime2 = localDateTime1.with(l -> {
LocalDateTime localDateTime = (LocalDateTime) l;
DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();

if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
return localDateTime.plusDays(3);
} else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
return localDateTime.plusDays(2);
} else {
return localDateTime.plusDays(1);
}
});
System.out.println(localDateTime2);
// 运行结果:2019-10-28T14:30:17.400

解析与格式化

java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:

  • 预定义的标准格式
  • 语言环境相关的格式
  • 自定义的格式

示例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
LocalDateTime localDateTime = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.getDefault());

// Date -> String
String dateTimeToStr_1 = localDateTime.format(DateTimeFormatter.ISO_DATE); // 2020-09-08
String dateTimeToStr_2 = localDateTime.format(dateTimeFormatter); // 2020-09-08 17:15:27
String dateTimeToStr_3 = dateTimeFormatter.format(localDateTime); // 2020-09-08 17:15:27

// String -> Date
LocalDateTime strToDateTime_0 = LocalDateTime.parse("2008-08-08T08:00:00.999999999");
LocalDateTime strToDateTime_1 = LocalDateTime.parse(dateTimeToStr_1, dateTimeFormatter); // 2020-09-08T17:16:32
LocalDateTime strToDateTime_2 = LocalDateTime.parse("2008-08-08 08:00:00", dateTimeFormatter); // 2008-08-08T08:00

LocalDate date_0 = LocalDate.parse("2012-09-08"); // 2012-09-08
LocalDate date_1 = LocalDate.parse("2012-09-08T13:00:20.776", DateTimeFormatter.ISO_LOCAL_DATE_TIME); // 2012-09-08
LocalDate date_2 = LocalDate.parse("2012-09-08", DateTimeFormatter.ISO_LOCAL_DATE); // 2012-09-08
LocalDate date_3 = LocalDate.parse("2012-09-08", DateTimeFormatter.ISO_DATE); // 2012-09-08
LocalDate date_4 = LocalDate.parse("2012-252", DateTimeFormatter.ISO_ORDINAL_DATE); // 2012-09-08
LocalDate date_5 = LocalDate.parse("2012-W36-6", DateTimeFormatter.ISO_WEEK_DATE); // 2012-09-08
LocalDate date_6 = LocalDate.parse("20120908", DateTimeFormatter.BASIC_ISO_DATE); // 2012-09-08
LocalDate date_7 = LocalDate.parse("2012-09-08+08:00", DateTimeFormatter.ISO_OFFSET_DATE); // 2012-09-08

LocalTime time_0 = LocalTime.parse("08:00:00.999999999"); // 08:00:00.999999999
LocalTime time_1 = LocalTime.parse("2008-08-08 08:00:00", dateTimeFormatter); // 08:00

时区的处理

Java8 中加入了对时区的支持,带时区的时间为:ZonedDateTime。

其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式,例如 : Asia/Shanghai 等。

  • ZoneId:该类中包含了所有的时区信息
  • getAvailableZoneIds() : 可以获取所有时区时区信息
  • of(id) : 用指定的时区信息获取 ZoneId 对象

示例代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取所有的时区
Set<String> set = ZoneId.getAvailableZoneIds();
System.out.println(set);

// 通过时区构建LocalDateTime
LocalDateTime ldt1 = LocalDateTime.now(Clock.systemDefaultZone());
LocalDateTime ldt2 = LocalDateTime.now(ZoneId.systemDefault());
LocalDateTime ldt3 = LocalDateTime.now(ZoneId.of("Australia/Sydney"));
System.out.println(ldt3); // 2020-09-08T19:38:16.068

// 以时区格式显示时间
LocalDateTime ldt5 = LocalDateTime.now();
ZonedDateTime zdt1 = ldt5.atZone(ZoneId.of("Africa/Nairobi"));
System.out.println(zdt1); // 2020-09-08T17:39:09.538+03:00[Africa/Nairobi]

与传统日期处理的转换

img

Optional 类

什么是Optional类

Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

Optional 类的引入很好的解决空指针异常。

Optional类常用方法:

  • public static <T> Optional <T> empty() : 返回空的 Optional 实例。

  • public boolean isPresent() : 如果存在值,则返回 true,否则返回 false。

  • public void ifPresent(Consumer<? super T> consumer) : 如果值存在则使用该值调用 consumer , 否则不做任何事情。

  • public T get() : 如果此 Optional 中存在值,则返回该值,否则抛出 NoSuchElementException。

  • public T orElse(T other) : 返回值 (如果存在),否则返回 other。

  • public T orElseGet(Supplier<? extends T> other) : 返回值 (如果存在),否则调用 other 并返回该调用的结果。

  • public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X : 返回值 (如果存在),否则 supplier 抛出异常。

  • public static <T> Optional <T> of(T value) : 返回一个指定非null值的 Optional。

  • public static <T> Optional <T> ofNullable(T value) : 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。

  • public <U> Optional<U> map(Function<? super T, ? extends U> mapper) : 如果此 Optional 有值,则对其执行调用映射函数得到返回值,如果映射器 (mapper) 返回值不为 null,则创建包含映射器 (mapper) 返回值的新Optional作为map方法返回值,否则返回空Optional。如果此 Optional 无值,返回空Optional。

  • public <U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) : 与 map 类似,但要求 mapper 返回值必须是 Optional。如果值存在,返回基于映射器 (mapper) 的返回值,否则返回一个空的Optional。

  • public Optional<T> filter(Predicate<? super T> predicate) : 如果值存在,并且该值与给定 predicate 匹配,则返回描述该值的 Optional (当前 Optional 对象,即 this),否则返回空的 Optional。

重写 Object 的方法:

  • public boolean equals(Object obj) :
  • public int hashCode() :
  • public String toString() :

Optional类示例

创建Optional类

(1)使用empty()方法创建一个空的Optional对象:

1
Optional<String> empty = Optional.empty();

(2)使用of()方法创建Optional对象:

1
2
3
String name = "binghe";
Optional<String> opt = Optional.of(name);
assertEquals("Optional[binghe]", opt.toString());

传递给of()的值不可以为空,否则会抛出空指针异常。例如,下面的程序会抛出空指针异常。

1
2
String name = null;
Optional<String> opt = Optional.of(name);

如果我们需要传递一些空值,那我们可以使用下面的示例所示。

1
2
String name = null;
Optional<String> opt = Optional.ofNullable(name);

使用ofNullable()方法,则当传递进去一个空值时,不会抛出异常,而只是返回一个空的Optional对象,如同我们用Optional.empty()方法一样。

isPresent

我们可以使用这个isPresent()方法检查一个Optional对象中是否有值,只有值非空才返回true。

1
2
3
4
5
Optional<String> opt = Optional.of("binghe");
assertTrue(opt.isPresent());

opt = Optional.ofNullable(null);
assertFalse(opt.isPresent());

在Java8之前,我们一般使用如下方式来检查空值。

1
2
3
if(name != null){
System.out.println(name.length);
}

在Java8中,我们就可以使用如下方式来检查空值了。

1
2
Optional<String> opt = Optional.of("binghe");
opt.ifPresent(name -> System.out.println(name.length()));

get

get()方法表示从Optional对象中获取值。

1
2
3
Optional<String> opt = Optional.of("binghe");
String name = opt.get();
assertEquals("binghe", name);

使用get()方法也可以返回被包裹着的值。但是值必须存在。当值不存在时,会抛出一个NoSuchElementException异常。

1
2
Optional<String> opt = Optional.ofNullable(null);
String name = opt.get();

orElse和orElseGet

(1)orElse

orElse()方法用来返回Optional对象中的默认值,它被传入一个“默认参数‘。如果Optional对象中有值,则返回它,否则返回传入的“默认参数”。

1
2
3
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("binghe");
assertEquals("binghe", name);

(2)orElseGet:推荐使用

与orElse()方法类似,但是这个函数不接收一个“默认参数”,而是一个函数接口。

1
2
3
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(() -> "binghe");
assertEquals("binghe", name);

(3)二者有什么区别?

要想理解二者的区别,首先让我们创建一个无参且返回定值的方法。

1
2
3
4
public String getDefaultName() {
System.out.println("Getting Default Name");
return "binghe";
}

接下来,进行两个测试看看两个方法到底有什么区别。

1
2
3
4
5
6
7
8
String text;
System.out.println("Using orElseGet:");
String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultName);
assertEquals("binghe", defaultText);

System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(getDefaultName());
assertEquals("binghe", defaultText);

在这里示例中,我们的Optional对象中包含的都是一个空值,让我们看看程序执行结果:

1
2
3
4
Using orElseGet:
Getting default name...
Using orElse:
Getting default name...

两个Optional对象中都不存在value,因此执行结果相同。

那么,当Optional对象中存在数据会发生什么呢?我们一起来验证下。

1
2
3
4
5
6
7
8
9
String name = "binghe001";

System.out.println("Using orElseGet:");
String defaultName = Optional.ofNullable(name).orElseGet(this::getDefaultName);
assertEquals("binghe001", defaultName);

System.out.println("Using orElse:");
defaultName = Optional.ofNullable(name).orElse(getDefaultName());
assertEquals("binghe001", defaultName);

运行结果如下所示。

1
2
3
Using orElseGet:
Using orElse:
Getting default name...

可以看到,当使用orElseGet()方法时,getDefaultName()方法并不执行,因为Optional中含有值,而使用orElse时则照常执行。所以可以看到,当值存在时,orElse相比于orElseGet,多创建了一个对象。如果创建对象时,存在网络交互,那系统资源的开销就比较大了,这是需要我们注意的一个地方。

orElseThrow

orElseThrow()方法当遇到一个不存在的值的时候,并不返回一个默认值,而是抛出异常。

1
2
String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow(IllegalArgumentException::new);

map

如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()。

1
2
3
4
5
6
7
List<String> names = Arrays.asList("binghe001", "binghe002", "", "binghe003", "", "binghe004");
Optional<List<String>> listOptional = Optional.of(names);

int size = listOptional
.map(List::size)
.orElse(0);
assertEquals(6, size);

在这个例子中,我们使用一个List集合封装了一些字符串,然后再把这个List使用Optional封装起来,对其map(),获取List集合的长度。map()返回的结果也被封装在一个Optional对象中,这里当值不存在的时候,我们会默认返回0。如下我们获取一个字符串的长度。

1
2
3
4
5
6
7
String name = "binghe";
Optional<String> nameOptional = Optional.of(name);

int len = nameOptional
.map(String::length)
.orElse(0);
assertEquals(6, len);

我们也可以将map()方法与filter()方法结合使用,如下所示。

1
2
3
4
5
6
7
8
9
10
11
String password = " password ";
Optional<String> passOpt = Optional.of(password);
boolean correctPassword = passOpt.filter(
pass -> "password".equals(pass)).isPresent();
assertFalse(correctPassword);

correctPassword = passOpt
.map(String::trim)
.filter(pass -> "password".equals(pass)
.isPresent();
assertTrue(correctPassword);

上述代码的含义就是对密码进行验证,查看密码是否为指定的值。

flatMap

与 map 类似,要求返回值必须是Optional。

假设我们现在有一个Person类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
private int age;
private String password;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public Optional<String> getName() {
return Optional.ofNullable(name);
}

public Optional<Integer> getAge() {
return Optional.ofNullable(age);
}

public Optional<String> getPassword() {
return Optional.ofNullable(password);
}
}

接下来,我们可以将Person封装到Optional中,并进行测试,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Person person = new Person("binghe", 18);
Optional<Person> personOptional = Optional.of(person);

Optional<Optional<String>> nameOptionalWrapper = personOptional.map(Person::getName);
Optional<String> nameOptional = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
String name1 = nameOptional.orElse("");
assertEquals("binghe", name1);

// 前提:getName返回的是一个Optional对象
// 1、public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
// 由源码知,map返回一个包含Function返回值的Optional
// 2、public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
// 由源码知,flatMap返回Function返回的Optional
String name2 = personOptional.flatMap(Person::getName).orElse("");
assertEquals("binghe", name2);

filter

接收一个函数式接口,当匹配接口时,则返回当前Optional对象,否则返回一个空的Optional对象。

1
2
3
4
5
6
String name = "binghe";
Optional<String> nameOptional = Optional.of(name);
boolean isBinghe = nameOptional.filter(n -> "binghe".equals(name)).isPresent();
assertTrue(isBinghe);
boolean isBinghe001 = nameOptional.filter(n -> "binghe001".equals(name)).isPresent();
assertFalse(isBinghe001);

使用filter()方法会过滤掉我们不需要的元素。

接下来,我们再来看一例示例,例如目前有一个Person类,如下所示。

1
2
3
4
5
6
7
public class Person{
private int age;
public Person(int age){
this.age = age;
}
//省略get set方法
}

例如,我们需要过滤出年龄在25岁到35岁之前的人群,那在Java8之前我们需要创建一个如下的方法来检测每个人的年龄范围是否在25岁到35岁之前。

1
2
3
4
5
6
7
public boolean filterPerson(Peron person){
boolean isInRange = false;
if(person != null && person.getAge() >= 25 && person.getAge() <= 35){
isInRange = true;
}
return isInRange;
}

看上去就挺麻烦的,我们可以使用如下的方式进行测试。

1
2
3
4
5
assertTrue(filterPerson(new Peron(18)));
assertFalse(filterPerson(new Peron(29)));
assertFalse(filterPerson(new Peron(16)));
assertFalse(filterPerson(new Peron(34)));
assertFalse(filterPerson(null));

如果使用Optional,效果如何呢?

1
2
3
4
5
6
7
public boolean filterPersonByOptional(Peron person){
return Optional.ofNullable(person)
.map(Peron::getAge)
.filter(p -> p >= 25)
.filter(p -> p <= 35)
.isPresent();
}

使用Optional看上去就清爽多了,这里,map()仅仅是将一个值转换为另一个值,并且这个操作并不会改变原来的值。

Stream API

Java 8 Stream - 菜鸟

概述

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象

Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

什么是 Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器 generator 等。
  • 聚合操作 类似 SQL 语句一样的操作, 比如 filter, map, reduce, find, match, sorted 等。

和以前的 Collection 操作不同, Stream 操作还有两个基础的特征

  • 流水线 (Pipeline):中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代:以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

注意:

①Stream 自己不会存储元素。

②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream操作步骤

将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由终端操作(terminal operation)得到前面处理的结果。

1
2
3
+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+

步骤一、创建 Stream

一个数据源(如: 集合、数组), 获取一个流。

步骤二、中间操作

一个中间操作链,对数据源的数据进行处理。

步骤三、终止操作(终端操作)

一个终止操作,执行中间操作链,并产生结果 。

在这里插入图片描述

以上步骤转换为 Java 代码为:

1
2
3
4
5
6
List<Integer> transactionsIds = 
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();

如何创建Stream流?

Collection接口的两个default方法

在 Java 8 中, 单列集合Collection接口有两个方法来生成集合流。示例:

1
2
3
4
5
List<String> list = new ArrayList<>();
// 为集合创建串行流。
list.stream();
// 为集合创建并行流。
list.parallelStream();

Arrays工具类的静态方法stream()

Java8 中的 Arrays类的静态方法 stream() 可以获取数组流,还提供了多种形式的重载。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Test {
public static void main(String[] args) {
int[] arrayInt = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
IntStream intStream = Arrays.stream(arrayInt);

long[] arrayLong = new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
LongStream longStream = Arrays.stream(arrayLong);

double[] arrayDouble = new double[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
DoubleStream doubleStream = Arrays.stream(arrayDouble);

Object[] arrayObject = new Object[]{"1", "2", "3", "4", "5", "6", "7", "8", "9"};
Stream<Object> stream = Arrays.stream(arrayObject);

// 1、stream流转数组
arrayObject = Arrays.stream(arrayObject).toArray();
arrayObject = Arrays.stream(arrayObject).toArray(Object[]::new);
// 这里使用普通for循环遍历.因为增强的For-Each循环丢掉索引信息。
for (int i = 0; i < arrayObject.length; i++) {
if (i == 0) {
System.out.print("[");
}
if (i == arrayObject.length - 1) {
System.out.print(arrayObject[i]);
System.out.println("]");
// 在这里,continue与break结果一致,因为进入这里意味着最后一次循环
continue;
}
System.out.print(arrayObject[i] + ", ");
}

// 2、stream流转list集合
List list = Arrays.stream(arrayObject).collect(Collectors.toList());
System.out.println(list);

// 3、stream流转set集合
Set set = Arrays.stream(arrayObject).collect(Collectors.toSet());
System.out.println(set);
}
}



Stream类的静态方法of()

在Stream类中,提供了两个of()方法用以获取流,一个参数是泛型,另一个参数是可变泛型。

同理:IntStream.of、LongStream.of、DoubleStream.of

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) {
List<String> strList = Arrays.asList("a", "b", "c");
// arrayStream1只有一个元素,该元素是strList集合
Stream<List<String>> arrayStream1 = Stream.of(strList);
arrayStream1.forEach(System.out::println);

// arrayStream2有三个元素,分别是 "a", "b", "c"。
Stream<String> arrayStream2 = Stream.of("a", "b", "c");
arrayStream2.forEach(System.out::println);

// intStream有五个元素
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
// intStream.forEach(System.out::println);
// intStream.mapToObj(Integer::valueOf).forEach(System.out::println);
intStream.boxed().forEach(System.out::println);
}
}

Random类

更多重载方法见源码。

1
2
3
4
5
6
7
8
9
10
Random random = new Random();

IntStream intStream = random.ints(5, 1, 5);
intStream.forEach(System.out::println);

LongStream longStream = random.longs(5, 1, 5);
longStream.forEach(System.out::println);

DoubleStream doubleStream = random.doubles(5, 1.0, 5.0);
doubleStream.forEach(System.out::println);

创建无限流

Stream类

可以使用静态方法 Stream.iterate() 和Stream.generate(), 创建无限流。

1
2
3
4
5
6
7
// iterate()方法是使用“迭代”的方式生成无限流,需要指定初始值
Stream<Integer> stream1 = Stream.iterate(2, x -> x + 2);
stream1.limit(5).forEach(System.out::println);

// generate()方法是使用“生成”的方式生成无限流
Stream<Double> stream2 = Stream.generate(() -> Math.random());
stream2.limit(5).forEach(System.out::println);

Random类

1
2
Random random = new Random();
random.ints().forEach(System.out::println);

创建空流

1
2
3
4
Stream<String> empty1 = Stream.empty();
Stream<Integer> empty2 = Stream.empty();
Stream<Long> empty3 = Stream.empty();
Stream<Double> empty4 = Stream.empty();

并行流与串行流

什么是并行流?

简单来说,并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。

Stream API 可以通过 BaseStream 类的 parallel()sequential() 方法在并行流与串行流之间进行切换 。

1
2
3
4
List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
list.stream();
list.parallelStream();
list.stream().parallel().sequential().isParallel();

Fork/Join 框架

推荐链接: Java并发编程(07):Fork/Join框架机制详解

Fork/Join 框架: 就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总 。

在这里插入图片描述

Fork/Join 框架与传统线程池有啥区别?

采用 “工作窃取”模式(work-stealing):

当执行新的任务时它可以将其拆分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。

相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架的实现中,如果某个子任务由于等待另外一个子任务的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子任务来执行。这种方式减少了线程的等待时间,提高了程序的性能。

Fork/Join框架实例

了解了Fork/Join框架的原理之后,我们就来手动写一个使用Fork/Join框架实现累加和的示例程序,以帮助理解Fork/Join框架。好好体会下Fork/Join框架的强大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@Slf4j
public class ForkJoinTaskExample extends RecursiveTask<Long> {
public static final long startValue = 0;
public static final long endValue = 600000000L; // 分别设置值为 60000L、600000000L、

public static long threshold;
private long start;
private long end;

public ForkJoinTaskExample(long start, long end) {
this.start = start;
this.end = end;
// 阈值始终设置为endValue的六分之一,我的cpu是6核。
threshold = endValue / 6;
}

@Override
protected Long compute() {
long sum = 0;
//如果任务足够小就计算任务
boolean canCompute = (end - start) <= threshold;
if (canCompute) {
for (long i = start; i <= end; i++) {
sum += i;
}
} else {
// 如果任务大于阈值,就分裂成两个子任务计算
long middle = (start + end) / 2;

ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);

// 执行子任务
leftTask.fork();
rightTask.fork();

// 等待任务执行结束合并其结果
long leftResult = leftTask.join();
long rightResult = rightTask.join();

// 合并子任务
sum = leftResult + rightResult;
}
return sum;
}

public static void main(String[] args) {
long startTime = System.nanoTime();
ForkJoinPool forkjoinPool = new ForkJoinPool();
//生成一个计算任务
ForkJoinTaskExample task = new ForkJoinTaskExample(startValue, endValue);

//执行一个任务
Future<Long> result = forkjoinPool.submit(task);
try {
log.info("result:{}", result.get());
log.info("---- Fork/Join 框架耗时:\t" + (System.nanoTime() - startTime));
} catch (Exception e) {
log.error("exception", e);
}

long startTime2 = System.nanoTime();
long sum2 = 0;
for (long i = startValue; i <= endValue; i++) {
sum2 += i;
}
log.info("result:{}", sum2);
log.info("---- 普通 for 循环耗时:\t" + (System.nanoTime() - startTime2));


long startTime3 = System.nanoTime();
long sum3 = LongStream.rangeClosed(startValue, endValue).parallel().reduce(0, Long::sum);
log.info("result:{}", sum3);
log.info("---- stream 流耗时:\t\t" + (System.nanoTime() - startTime3));
}

}

统计

以下是已有的三个于收集统计信息(例如计数,最小值,最大值,总和和平均值)的摘要:

  • IntSummaryStatistics
  • LongSummaryStatistics
  • DoubleSummaryStatistics

可以由 IntStream、LongStream、DoubleStream 的 summaryStatistics() 方法获得。

也可以在使用 Stream.collect(Collector<? super T, A, R> collector) 方法时,传入 Collectors 的 summarizingInt 、summarizingLong、summingDouble 方法,以获取统计摘要。示例:

1
DoubleSummaryStatistics dss = listEmployees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));

完整示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
System.out.println("-----------------------------------------------");

System.out.println((ArrayList) numbers.stream().mapToInt(x -> x).collect(ArrayList::new, (list1, e) -> list1.add(e), (list1, list2) -> list1.addAll(list2)));
System.out.println(numbers.stream().mapToInt(x -> x).boxed().collect(Collectors.toList()));
System.out.println(numbers.stream().flatMapToDouble(x -> DoubleStream.of(x)).boxed().collect(Collectors.toList()));
System.out.println("-----------------------------------------------");

IntSummaryStatistics stats_1 = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats_1.getMax());
System.out.println("列表中最小的数 : " + stats_1.getMin());
System.out.println("所有数之和 : " + stats_1.getSum());
System.out.println("平均数 : " + stats_1.getAverage());
System.out.println("计数 : " + stats_1.getCount());
System.out.println("-----------------------------------------------");

IntSummaryStatistics stats_2 = numbers.stream().flatMapToInt(x -> IntStream.of(x)).summaryStatistics();
System.out.println("列表中最大的数 : " + stats_2.getMax());
System.out.println("列表中最小的数 : " + stats_2.getMin());
System.out.println("所有数之和 : " + stats_2.getSum());
System.out.println("平均数 : " + stats_2.getAverage());
System.out.println("计数 : " + stats_2.getCount());
System.out.println("-----------------------------------------------");

Stream的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值” 。 Stream的中间操作是不会有任何结果数据输出的。

Stream的中间操作在整体上可以分为:筛选与切片、映射、排序。

筛选与切片

筛选和切片相关操作:

方法 描述
filter(Predicate p) 接收Lambda表达式,从流中排除某些元素
distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去 除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素 不足 n 个,则返回一个空流。与 limit(n) 互补

接下来,我们列举几个简单的示例,以便加深理解。

为了更好的测试程序,先构造一个对象数组,如下。

1
2
3
4
5
6
7
protected static List<Employee> listEmployee = Arrays.asList(
new Employee("张三", 18, 9999.99),
new Employee("李四", 38, 5555.55),
new Employee("王五", 60, 6666.66),
new Employee("赵六", 8, 7777.77),
new Employee("田七", 58, 3333.33)
);

Employee类的定义如下。

1
2
3
4
5
6
7
8
9
10
11
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
class Employee implements Serializable {
private static final long serialVersionUID = -9079722457749166858L;
private String name;
private Integer age;
private Double salary;
}

filter

filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:

1
2
3
4
5
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取包含字符a的字符串
List<String> strings2 = strings.stream().filter(t -> t.contains("a")).collect(Collectors.toList());
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();

limit

limit 方法返回由该流的元素组成的流,其长度被截断。

1
2
3
4
5
6
Random random = new Random();
// 打印 10 条数据
random.ints().limit(10).forEach(System.out::println);

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> strings2 = strings.stream().limit(3).collect(Collectors.toList());

skip

skip 方法丢弃流的前 n 个元素,返回由其余元素组成的流。如果此流包含少于 n 个元素,则将返回空流。

1
2
//跳过前3个值
listEmployee.stream().skip(3).forEach(System.out::println);

distinct

distinct 方法通过流所生成元素的 hashCode()equals() 去除重复元素。

1
System.out.println(listEmployee.stream().distinct().collect(Collectors.toList()));

注意:distinct 需要实体中重写 hashCode() 和 equals() 方法才可以使用。

映射

映射相关操作:

方法 返回类型 描述
map(Function f) Stream 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 Stream。
mapToDouble(ToDoubleFunction f) DoubleStream 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f) IntStream 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f) LongStream 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f) Stream 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个 Stream。
flatMapToDouble(Function f) DoubleStream 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个 DoubleStream。
flatMapToInt(Function f) IntStream 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个 IntStream。
flatMapToLong(Function f) LongStream 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个 LongStream。

map

map 方法返回一个流,该流包括将给定函数应用于此流的元素的结果。

1
2
3
4
5
6
7
8
9
10
11
12
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());
System.out.println(squaresList);

//将流中每一个元素都映射到map的函数中,每个元素执行这个函数,再返回
List<String> listStr = Arrays.asList("aaa", "bbb", "ccc", "ddd");
listStr.stream().map((e) -> e.toUpperCase()).forEach(System.out::printf);

//获取Employee中的每一个人得名字name,再返回一个集合
List<String> names = listEmployee.stream().map(Employee::getName).collect(Collectors.toList());
System.out.println(names);

flatMap

返回一个流,将流中的 n 个元素,通过映射函数处理得到 n 个映射流,然后将每个映射流的内容放入该流,形成一个流并返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// <R> Stream<R>     map(Function<? super T, ? extends R> mapper);
// <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

public class Test {
public static void main(String[] args) {
List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
list.stream().flatMap(Test::filterCharacter).forEach(System.out::println);

// 如果使用map则需要这样写,
// 因为map返回的流是由多个映射流组成;而flatMap返回的流是由多个映射流的内容组成。
list.stream().map(Test::filterCharacter).forEach((e) -> {
e.forEach(System.out::println);
});
}

/**
* 将一个字符串转换为流
*/
public static Stream<String> filterCharacter(String str) {
List<String> list = new ArrayList<>();
list.add(str);
return list.stream();
}
}

其实 map 方法就相当于 Collection 的 add 方法,如果 add 的是个集合得话就会变成二维数组,而 flatMap 的话就相当于 Collections.addAll() 方法,参数如果是集合得话,只是将2个集合合并,而不是变成二维数组。

排序

排序相关操作:

方法 描述
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator comp) 产生一个新流,其中按比较器顺序排序

从上述表格可以看出:sorted有两种方法,一种是不传任何参数,叫自然排序,还有一种需要传Comparator 接口参数,叫做定制排序

sorted

sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Random random = new Random();
// 自然排序
List<Integer> list = random.ints().limit(10).sorted().boxed().collect(Collectors.toList());
list.forEach(System.out::println);

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "ba", "jkl");
// 定制排序
strings = strings.stream().sorted(String::compareTo).collect(Collectors.toList());

//定制排序
listEmployee.stream().sorted((e1, e2) -> {
if (e1.getAge().equals(e2.getAge())) {
return 0;
} else if (e1.getAge() > e2.getAge()) {
return 1;
} else {
return -1;
}
}).forEach((e) -> System.out.println(e.getAge()));

Stream的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如: List、 Integer、Double、String等等,甚至是 void 。

为了更好的测试程序,先构造一个对象数组,如下。

1
2
3
4
5
6
7
protected static List<Employee> listEmployees = Arrays.asList(
new Employee("张三", 18, 9999.99, Employee.Stauts.SLEEPING),
new Employee("李四", 38, 5555.55, Employee.Stauts.WORKING),
new Employee("王五", 60, 6666.66, Employee.Stauts.WORKING),
new Employee("赵六", 8, 7777.77, Employee.Stauts.SLEEPING),
new Employee("田七", 58, 8888.88, Employee.Stauts.VOCATION)
);

Employee类的定义如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
class Employee implements Serializable {
private static final long serialVersionUID = -9079722457749166858L;
private String name;
private Integer age;
private Double salary;
private Stauts stauts;

public enum Stauts {
WORKING,
SLEEPING,
VOCATION
}
}

forEach

方法 描述
forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反, Stream API 使用内部迭代)

为此流的每个元素执行一个动作 (内部迭代流中的每个数据)。

此操作的行为是明确不确定的。对于并行流管道,此操作不能保证遵守流遇到的顺序,保证顺序会牺牲并行的好处。如果操作访问共享状态(比如:线程安全、共享变量),请做好所需的同步, parallelStream线程安全问题

1
2
3
4
5
6
7
8
9
10
11
// 无返回值
void forEach(Consumer<? super T> action);

listEmployees.stream().forEach(System.out::println);

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
strings.stream().forEach(t -> System.out.println(t));
strings.stream().forEach(System.out::println);

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

查找与匹配

查找与匹配相关操作:

方法 描述
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值

allMatch

返回此流的所有元素是否与提供的 Predicate 匹配。如果流中的所有元素都与提供的 Predicate 匹配,或者流为空,返回 true。函数式接口 Predicate 中可选择要处理的元素。

注意:使用allMatch()方法时,只有所有的元素都匹配条件时 (即 Predicate 都返回true),allMatch()方法才会返回true。

1
2
3
4
5
6
7
8
boolean match = listEmployees.stream().allMatch((e) -> {
boolean boo = true;
if (e.getName().equals("李四")) {
boo = Employee.Stauts.SLEEPING.equals(e.getStauts()); // false
}
return boo;
});
System.out.println(match); // false

anyMatch

返回此流的任何元素是否与提供的 Predicate 匹配。如果流中无任何元素与提供的 Predicate 匹配,或者流为空,返回 false。函数式接口 Predicate 中可选择要处理的元素。

注意:使用anyMatch()方法时,只要有一个元素匹配条件 (即 Predicate 返回true),anyMatch()方法就会返回true。

1
2
3
4
5
6
7
8
boolean match = listEmployees.stream().anyMatch((e) -> {
boolean boo = false;
if (e.getName().equals("李四")) {
boo = Employee.Stauts.WORKING.equals(e.getStauts()); // true
}
return boo;
});
System.out.println(match); // true

noneMatch

返回此流中是否没有元素与提供的 Predicate 匹配。如果流中没有元素与提供的 Predicate 匹配,或者流为空,返回 true。函数式接口 Predicate 中可选择要处理的元素。

注意:使用noneMatch()方法时,只有所有的元素都不匹配条件时 (即 Predicate 都返回false),noneMatch()方法才会返回true。

1
2
3
4
5
6
7
8
boolean match = listEmployees.stream().noneMatch((e) -> {
boolean boo = false;
if (e.getName().equals("李四")) {
boo = Employee.Stauts.SLEEPING.equals(e.getStauts()); // false
}
return boo;
});
System.out.println(match); // true

findFirst

返回描述此流的第一个元素的 Optional,如果流为空,则返回空的 Optional。如果流没有遇到顺序,则可以返回任何元素。

1
2
3
Optional<Employee> opt = listEmployees.stream().findFirst();
System.out.println(opt.isPresent());
System.out.println(opt.get());

findAny

返回描述流中任意一个元素的 Optional,如果流为空,则返回空的{@code Optional}。

串行操作:与 findFirst() 返回值相同。

并行操作:此操作的行为是不确定的,可以自由选择流中的任何元素。这是为了在并行操作中获得最佳性能。代价是对同一源的多次调用可能不会返回相同的结果。(如果需要稳定的结果,请改用{@link #findFirst()}。)

1
2
3
4
5
6
7
8
9
10
11
12
13
Stream<Employee> stream = listEmployees.stream();
System.out.println("串行流findFirst: " + stream.findFirst().get());
try {
// java.lang.IllegalStateException: 流已被操作或关闭
System.out.println(stream.findAny().get());
} catch (IllegalStateException e) {
e.printStackTrace(System.out);
}
System.out.println("串行流findAny: " + listEmployees.stream().findAny().get());

System.out.println("并行流findFirst: " + listEmployees.parallelStream().findFirst());
System.out.println("并行流findAny: " + listEmployees.parallelStream().findAny());
System.out.println("并行流findAny: " + listEmployees.parallelStream().findAny());

执行结果:

image-20200921161008424

规约和收集

规约操作(reduction operation)又被称作折叠操作(fold),是通过某个连接动作将所有元素汇总成一个结果的过程。例如:

1、对流做聚合操作:对流中元素求和、求平均值、求最大值、最小值、求元素总个数。如果对数字操作,也可由统计摘要获取。

2、将流转换成集合:将所有元素转换成一个列表或集合。

Stream 类库有两个通用的规约操作 reduce()collect() ,也有一些为简化书写而设计的专用规约操作,比如 sum()max()min()count() 等。

最大最小值这类规约操作很好理解(至少方法语义上是这样),重点是有魔法的 reduce()collect()

count

返回流中元素总数。不可由统计摘要获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<Long> list1 = listEmployees.stream().mapToLong(e -> 1L).collect(new Supplier<List<Long>>() {
@Override
public List<Long> get() {
return new ArrayList<>();
}
}, new ObjLongConsumer<List<Long>>() {
@Override
public void accept(List<Long> dataList, long value) {
dataList.add(value);
}
}, (dataList1, dataList2) -> dataList1.addAll(dataList2));
System.out.println(list1);
List<Long> list2 = listEmployees.stream().mapToLong(e -> 1L).collect(ArrayList::new, (dataList, i) -> dataList.add(i), (dataList1, dataList2) -> dataList1.addAll(dataList2));
System.out.println(list2);

long count1 = listEmployees.stream().count();
System.out.println(count1);
long count2 = listEmployees.stream().mapToLong(e -> 1L).sum();
System.out.println(count2);

max和min

根据提供的 Comparator 返回此流最大或最小元素的 Optional。由统计摘要获取。

1
2
3
4
5
6
7
Integer maxAge = listEmployees.stream().map(e -> e.getAge()).max(Integer::compare).get();
Integer minAge = listEmployees.stream().map(e -> e.getAge()).min(Integer::compare).get();
System.out.println(maxAge);
System.out.println(minAge);

// 由统计摘要获取
Double maxAge2 = listEmployees.stream().mapToDouble(e -> e.getAge()).summaryStatistics().getMax();

reduce

方法 描述
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。 返回 T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。 返回 Optional

在Stream接口中的定义如下所示。

1
2
3
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);

示例:

1
2
3
4
5
6
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer result = list.stream().reduce(0, (x, y) -> x - y);
System.out.println(result);
System.out.println("----------------------------------------");
Optional<Double> op = listEmployees.stream().map(Employee::getSalary).reduce(Double::sum);
System.out.println(op.get());

我们也可以搜索 listEmployees 列表中“张”出现的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String[] args) {

Optional<Integer> sum = listEmployees.stream()
.map(Employee::getName)
.flatMap(Test::filterCharacter)
.map((ch) -> {
if (ch.equals('六')) {
return 1;
} else {
return 0;
}
}).reduce(Integer::sum);
System.out.println(sum.get());


}

/**
* 将一个字符串转换为流
*/
public static Stream<String> filterCharacter(String str) {
List<String> list = new ArrayList<>();
list.add(str);
return list.stream();
}

注意:上述例子使用了硬编码的方式来累加某个具体值,应用时需根据实际优化代码。

Collector和Collectors

java.util.stream.Collector:接口中方法的实现,决定了如何对流执行收集操作(如收集到 List、 Set、 Map)。

java.util.stream.Collectors:该工具类内部实现了 Collector(收集器) ;提供了很多静态方法 (各种有用的归约操作,例如将元素累积到集合中,根据各种标准对元素进行汇总等),可以方便地创建常见收集器实例。

Collectors介绍

部分方法与示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
public class Test {
protected static List<Employee> listEmployees = Arrays.asList(
new Employee("张三", 18, 9999.99, Employee.Stauts.SLEEPING),
new Employee("李四", 38, 5555.55, Employee.Stauts.WORKING),
new Employee("王五", 60, 6666.66, Employee.Stauts.WORKING),
new Employee("赵六", 8, 7777.77, Employee.Stauts.SLEEPING),
new Employee("田七", 58, 8888.88, Employee.Stauts.VOCATION)
);

public static void main(String[] args) {
String pattern = "%-25s %s %s";

// toList:把流中元素收集到List
List employees1 = listEmployees.stream().collect(Collectors.toList());
System.out.printf(pattern, "toList: ", employees1, "\n");

// toSet:把流中元素收集到Set
Set employees2 = listEmployees.stream().collect(Collectors.toSet());
System.out.printf(pattern, "toSet: ", employees2, "\n");

// toCollection:把流中元素收集到创建的集合
Collection employees3 = listEmployees.stream().collect(Collectors.toCollection(ArrayList::new));
System.out.printf(pattern, "toCollection: ", employees3, "\n");

// counting:计算流中元素的个数
long count = listEmployees.stream().collect(Collectors.counting());
System.out.printf(pattern, "counting: ", count, "\n");

// summingDouble:对流中元素的Double属性求和
double total1 = listEmployees.stream().collect(Collectors.summingDouble(Employee::getSalary));
System.out.printf(pattern, "summingDouble: ", total1, "\n");

// reducing:从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
double total2 = listEmployees.stream().collect(Collectors.reducing(0.0, Employee::getSalary, Double::sum));
System.out.printf(pattern, "reducing: ", total2, "\n");

// averagingDouble:计算流中元素Double属性的平均值
double avg = listEmployees.stream().collect(Collectors.averagingDouble(Employee::getSalary));
System.out.printf(pattern, "averagingDouble: ", avg, "\n");

// summarizingDouble:收集流中Double属性值, 返回用于收集统计信息的摘要 DoubleSummaryStatistics. 可用来计数, 最小值, 最大值, 总和, 平均值
DoubleSummaryStatistics dss = listEmployees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.printf(pattern, "summarizingDouble: ", dss.getMax(), "\n");
System.out.printf(pattern, "summarizingDouble: ", dss.getSum(), "\n");

// joining:连接流中每个字符串
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
System.out.printf(pattern, "joining: ", strings.stream().collect(Collectors.joining()), "\n");
System.out.printf(pattern, "joining: ", strings.stream().collect(Collectors.joining(",")), "\n");
System.out.printf(pattern, "joining: ", strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(",", "[", "]")), "\n");

// maxBy:根据比较器选择最大值
Optional max = listEmployees.stream().collect(Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary)));
System.out.printf(pattern, "maxBy: ", max.get(), "\n");

// minBy:根据比较器选择最小值
Optional min = listEmployees.stream().collect(Collectors.minBy(Comparator.comparingDouble(Employee::getSalary)));
System.out.printf(pattern, "minBy: ", min.get(), "\n");

// collectingAndThen:包裹另一个收集器, 对其结果转换函数
int how = listEmployees.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
System.out.printf(pattern, "collectingAndThen: ", how, "\n");

// groupingBy:返回Collector,根据某属性对流分组,Key是属性,Value是收集器收集的结果(类型由收集结果决定)
Map<Employee.Stauts, List<Employee>> map1 = listEmployees.stream().collect(Collectors.groupingBy(Employee::getStauts));
Map<Employee.Stauts, Set<Employee>> map2 = listEmployees.stream().collect(Collectors.groupingBy(Employee::getStauts, Collectors.toSet()));
TreeMap<Employee.Stauts, String> map3 = listEmployees.stream().collect(Collectors.groupingBy(Employee::getStauts, TreeMap::new, Collectors.mapping(Employee::getName, Collectors.joining(","))));
System.out.printf(pattern, "groupingBy: ", map1, "\n");
System.out.printf(pattern, "groupingBy: ", map2, "\n");
System.out.printf(pattern, "groupingBy: ", map3, "\n");

// groupingByConcurrent:返回并发的Collector,根据某属性对流分组,Key是属性,Value是收集器收集的结果(类型由收集结果决定)
ConcurrentMap<Employee.Stauts, String> concurrentMap = listEmployees.parallelStream().collect(Collectors.groupingByConcurrent(Employee::getStauts, ConcurrentHashMap::new, Collectors.mapping(Employee::getName, Collectors.joining(","))));
System.out.printf(pattern, "groupingByConcurrent: ", concurrentMap, "\n");

// partitioningBy:根据true或false进行分区
Map<Boolean, List<Employee>> vd = listEmployees.stream().collect(Collectors.partitioningBy(t -> t.getAge() > 50));
System.out.printf(pattern, "partitioningBy: ", vd, "\n");

// mapping:返回收集器,收集之前先对每个输入元素应用映射功能,以下两种写法等价
String nameStr1 = listEmployees.stream().collect(Collectors.mapping(Employee::getName, Collectors.joining(",", "[", "]")));
String nameStr2 = listEmployees.stream().map(Employee::getName).collect(Collectors.joining(",", "[", "]"));
System.out.printf(pattern, "mapping和map: ", nameStr1, "\n");
System.out.printf(pattern, "mapping和map: ", nameStr2, "\n");

// toMap:返回收集器,将元素收集到Map中,其key和value是两个映射函数作用于输入元素的结果。如果key已存在会抛异常,具体异常请查看源码。存放元素的Map默认是HashMap
Map<String, Integer> nameAgeMap = listEmployees.stream().collect(Collectors.toMap(e -> e.getName(), e -> e.getAge()));
// Map<String, Employee> m = listEmployees.stream().collect(Collectors.toMap(e -> e.getAge() < 40 ? "young" : "old", e -> e)); // 重复key异常
System.out.printf(pattern, "toMap: ", nameAgeMap, "\n");
// toMap:返回收集器,将元素收集到Map中,其key和value是两个映射函数作用于输入元素的结果。如果key已存在,则更新value。存放元素的Map默认是HashMap
Map<String, Employee> strObjMap1 = listEmployees.stream().collect(Collectors.toMap(e -> e.getAge() < 40 ? "young" : "old", e -> e, (e1, e2) -> e2));
System.out.printf(pattern, "toMap: ", strObjMap1, "\n");
// toMap:返回收集器,将元素收集到Map中,其key和value是两个映射函数作用于输入元素的结果。如果key已存在,则更新value。指定存放元素的Map为TreeMap
TreeMap<String, Employee> strObjMap2 = listEmployees.stream().collect(Collectors.toMap(e -> e.getAge() < 40 ? "young" : "old", e -> e, (e1, e2) -> e2, TreeMap::new));
System.out.printf(pattern, "toMap: ", strObjMap2, "\n");

// toConcurrentMap:返回并发的Collector,将元素收集到ConcurrentMap中,其key和value是两个映射函数作用于输入元素的结果。如果key已存在,则更新value。指定存放元素的ConcurrentMap为ConcurrentHashMap
System.out.println("--------------- toConcurrentMap ---------------");
ConcurrentMap<String, Integer> nameAgeConcurrentMap = listEmployees.parallelStream().collect(Collectors.toConcurrentMap(e -> {
// 由于没有找到保存元素加入顺序的ConcurrentMap,这里将顺序打印,大家可以手动实现一个ConcurrentMap来保存顺序
System.out.println(e.getName());
return e.getName();
}, e -> e.getAge(), (e1, e2) -> e2, ConcurrentHashMap::new));
System.out.printf(pattern, "toConcurrentMap: ", nameAgeConcurrentMap, "\n");
System.out.println("--------------- toConcurrentMap ---------------");
}
}

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
class Employee implements Serializable {
private static final long serialVersionUID = -9079722457749166858L;
private String name;
private Integer age;
private Double salary;
private Stauts stauts;

public enum Stauts {
WORKING,
SLEEPING,
VOCATION
}
}

collect

在Stream接口中的定义如下所示。

1
2
3
4
5
6
7
// 将流转换为其他形式。接收一个收集器(Collector接口)的实现,用于给Stream中元素做规约操作
<R, A> R collect(Collector<? super T, A, R> collector);

// 可以查看源码注释,并结合 Collectors.toList() 或 Collectors.toSet() 理解这三个对象
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
参数为 Collector 对象实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class Test {
public static void main(String[] args) {

List<String> dataList = new ArrayList<>();
dataList.add("john");
dataList.add("kain");
dataList.add("mike");
dataList.add("milk");
dataList.add("kav");

// 1. 接口参数为 Collector 对象实现

// 1.1 Supplier 对象负责创建容器对象,以便容纳 stream 中分隔后的数据,这里使用 List<Integer> 作为数据的容器对象
// 1.2 accumulator 对象负责将 stream 中分隔后的数据放入 Supplier 创建的容器中
// 1.3 combiner 对象负责将分隔的容器中的数据合并起来
// 1.4 finisher 对象负责将最终返回的结果进行最终的处理
// 1.5 characteristics 负责 Collector 执行效率的优化,具体要根据是否使用了 parallelStream 以及数据是否有顺序,不会影响最终的执行结果:
List<String> resultList = dataList.parallelStream().collect(new Collector<String, List<String>, List<String>>() {
@Override
public Supplier<List<String>> supplier() {
return () -> new ArrayList<>();
}

@Override
public BiConsumer<List<String>, String> accumulator() {
return (list, data) -> list.add(data.substring(0, 1).toUpperCase().concat(data.substring(1)));
}

@Override
public BinaryOperator<List<String>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
};
}

@Override
public Function<List<String>, List<String>> finisher() {
System.out.println("--执行--finisher");
return list -> list;
}

// Characteristics枚举类:用于指示 Collector 属性的特性,可用于优化归约实现。
@Override
public Set<Characteristics> characteristics() {
Set<Characteristics> characteristicsSet = new HashSet<>();

// 并行:如果使用 parallelStream,加上这个枚举。意味着Collector进行规约操作时,
// 支持从多个线程收集结果,而无需等到所有线程全部空闲后按输入元素(上文dataList)的顺序收集。
// 由 collect(Collector<? super T, A, R> collector) 方法的实现类可知:
// Characteristics.CONCURRENT && Characteristics.UNORDERED,即二者同时设置才有效果。
characteristicsSet.add(Characteristics.CONCURRENT);
// 无序:如果收集流中的数据不需要保留输入元素(上文dataList)的顺序,加上这个枚举.
characteristicsSet.add(Characteristics.UNORDERED);
// 身份完成:此枚举可以省略。如果设置,则不会执行finisher()方法,但必须保证该方法返回值类型Function<A, R>进行从A到R的强制转换是成功的。
characteristicsSet.add(Characteristics.IDENTITY_FINISH);
return characteristicsSet;
}
});

resultList.forEach(System.out::println);
}
}
参数为三个对象实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test {
public static void main(String[] args) {

List<String> dataList = new ArrayList<>();
dataList.add("john");
dataList.add("kain");
dataList.add("mike");
dataList.add("milk");
dataList.add("kav");

// 2. 接口参数为 Supplier, BiConsumer, BiConsumer 三个对象

// 2.1 Supplier 对象负责创建容器对象,以便容纳 stream 中分隔后的数据,这里使用 List<Integer> 作为数据的容器对象
// 2.2 BiConsumer 对象负责将 stream 中分隔后的数据放入上一步创建的容器中
// 2.3 BiConsumer 对象负责将分隔的容器中的数据合并起来
List<String> resultList2 = dataList.parallelStream().collect(
ArrayList::new,
(list, data) -> list.add(data.substring(0, 1).toUpperCase().concat(data.substring(1))),
(list1, list2) -> list1.addAll(list2));

resultList2.forEach(System.out::println);
}
}
parallelStream线程安全问题
  • 其中上面的两个例子中都使用了 parallelStream,并且使用了 collect 方法
  • parallelStream 可以提高数据处理效率,具体是通过将流中的数据分隔成若干端后再并行处理最后将结果合并
  • 如果不使用 collect 方法会发生什么情况,会不会出现线程安全问题?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Test {
private static Lock lock = new ReentrantLock();

public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
List<Integer> list3 = new ArrayList<>();

// 串行
IntStream.range(0, 10000).forEach(list1::add);

// 并行
IntStream.range(0, 10000).parallel().forEach(list2::add);

// 并行加锁
IntStream.range(0, 10000).parallel().forEach(i -> {
lock.lock();
try {
list3.add(i);
} finally {
lock.unlock();
}
});

System.out.println(String.format("串行执行的大小:%s", list1.size()));
System.out.println(String.format("有线程安全的并行问题大小:%s", list2.size()));
System.out.println(String.format("并行加锁:%s", list1.size()));
}
}

执行结果如下,可见在 parallelStream 中会出现线程安全问题,虽然通过锁可以避免线程安全问题,但是会降低程序处理效率,从而影响系统的吞吐率。

1
2
3
串行执行的大小:10000
有线程安全的并行问题大小:8370
并行加锁:10000

正确的解决的方法就是通过 collect 方法和 Collector 接口,具体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Test {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>();
// 串行
IntStream.range(0, 10000).forEach(list1::add);

// 通过 collect 接口解决并发问题
// 1. Supplier 对象负责创建容器对象,以便容纳 stream 中分隔后的数据,这里使用 List<Integer> 作为数据的容器对象
// 2. ObjIntConsumer 对象负责将 stream 中分隔后的数据放入上一步创建的容器中
// 3. BiConsumer 对象负责将分隔的容器中的数据合并起来
List<Integer> dataList = IntStream.range(0, 10000).parallel().collect(new Supplier<List<Integer>>() {
@Override
public List<Integer> get() {
System.out.println("new Supplier");
return new ArrayList<>();
}
}, new ObjIntConsumer<List<Integer>>() {
@Override
public void accept(List<Integer> dataList, int value) {
dataList.add(value);
}
}, new BiConsumer<List<Integer>, List<Integer>>() {
@Override
public void accept(List<Integer> dataList1, List<Integer> dataList2) {
System.out.println("merge result");
dataList1.addAll(dataList2);
}
});

List<Integer> dataList1 = IntStream.range(0, 10000).collect(ArrayList::new, (list, i) -> list.add(i), (lista, listb) -> lista.addAll(listb));
List<Integer> dataList2 = IntStream.range(0, 10000).parallel().collect(ArrayList::new, (list, i) -> list.add(i), (lista, listb) -> lista.addAll(listb));

System.out.println(String.format("串行执行的大小:%s", list1.size()));
System.out.println(String.format("线程安全的并行大小:%s", dataList.size()));
System.out.println(String.format("线程安全的并行大小:%s", dataList1.size()));
System.out.println(String.format("线程安全的并行大小:%s", dataList2.size()));
}
}

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
public class Java8Tester {
public static void main(String args[]) {
System.out.println("使用 Java 7: ");

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
System.out.println("列表: " + strings);

// 空字符串数量
long count = getCountEmptyStringUsingJava7(strings);
System.out.println("空字符串数量为: " + count);
count = strings.stream().filter(string -> string.isEmpty()).count();
System.out.println("空字符串数量为: " + count);

// 字符串长度为 3 的数量
count = getCountLength3UsingJava7(strings);
System.out.println("字符串长度为 3 的数量为: " + count);
count = strings.stream().filter(string -> string.length() == 3).count();
System.out.println("字符串长度为 3 的数量为: " + count);

// 删除空字符串
List<String> filtered = deleteEmptyStringsUsingJava7(strings);
System.out.println("筛选后的列表: " + filtered);
filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选后的列表: " + filtered);

// 删除空字符串,并用逗号合并
String mergedString = getMergedStringUsingJava7(strings, ", ");
System.out.println("合并字符串: " + mergedString);
mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
System.out.println("列表: " + numbers);

// 获取列表元素平方数
List<Integer> squaresList = getSquaresUsingJava7(numbers);
System.out.println("Squares List: " + squaresList);
squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
System.out.println("Squares List: " + squaresList);

List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19);
System.out.println("列表: " + integers);

System.out.println("列表中最大的数 : " + getMaxUsingJava7(integers));
System.out.println("列表中最小的数 : " + getMinUsingJava7(integers));
System.out.println("所有数之和 : " + getSumUsingJava7(integers));
System.out.println("平均数 : " + getAverageUsingJava7(integers));

IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());


System.out.println("随机数: ");

// 输出10个随机数
Random random = new Random();
for (int i = 0; i < 10; i++) {
System.out.println(random.nextInt());
}
random.ints().limit(10).sorted().forEach(System.out::println);

// 并行处理
count = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println("空字符串的数量为: " + count);
}

private static int getCountEmptyStringUsingJava7(List<String> strings) {
int count = 0;

for (String string : strings) {

if (string.isEmpty()) {
count++;
}
}
return count;
}

private static int getCountLength3UsingJava7(List<String> strings) {
int count = 0;

for (String string : strings) {

if (string.length() == 3) {
count++;
}
}
return count;
}

private static List<String> deleteEmptyStringsUsingJava7(List<String> strings) {
List<String> filteredList = new ArrayList<String>();

for (String string : strings) {

if (!string.isEmpty()) {
filteredList.add(string);
}
}
return filteredList;
}

private static String getMergedStringUsingJava7(List<String> strings, String separator) {
StringBuilder stringBuilder = new StringBuilder();

for (String string : strings) {

if (!string.isEmpty()) {
stringBuilder.append(string);
stringBuilder.append(separator);
}
}
String mergedString = stringBuilder.toString();
return mergedString.substring(0, mergedString.length() - 2);
}

private static List<Integer> getSquaresUsingJava7(List<Integer> numbers) {
List<Integer> squaresList = new ArrayList<Integer>();

for (Integer number : numbers) {
Integer square = new Integer(number.intValue() * number.intValue());

if (!squaresList.contains(square)) {
squaresList.add(square);
}
}
return squaresList;
}

private static int getMaxUsingJava7(List<Integer> numbers) {
int max = numbers.get(0);

for (int i = 1; i < numbers.size(); i++) {

Integer number = numbers.get(i);

if (number.intValue() > max) {
max = number.intValue();
}
}
return max;
}

private static int getMinUsingJava7(List<Integer> numbers) {
int min = numbers.get(0);

for (int i = 1; i < numbers.size(); i++) {
Integer number = numbers.get(i);

if (number.intValue() < min) {
min = number.intValue();
}
}
return min;
}

private static int getSumUsingJava7(List numbers) {
int sum = (int) (numbers.get(0));

for (int i = 1; i < numbers.size(); i++) {
sum += (int) numbers.get(i);
}
return sum;
}

private static int getAverageUsingJava7(List<Integer> numbers) {
return getSumUsingJava7(numbers) / numbers.size();
}
}

Base64

Java8 Base64 - 菜鸟