Java基础知识-Java8新特性

Lambda表达式,函数式接口,方法引用与构造器引用,Stream API,Optional类

1. 介绍

Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本

Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以 来最具革命性的版本。Java 8为Java语言、编译器、类库、开发 工具与JVM带来了大量新特性

  • 速度更快

  • 代码更少(增加了新的语法:Lambda 表达式)

  • 强大的 Stream API:

    并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率

    parallel() 与 sequential() 在并行流与顺序流之间进行切换

  • 便于并行

  • 最大化减少空指针异常:Optional

  • Nashorn引擎,允许在JVM上运行JS应用

2. Lambda表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

在Java 8中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符 或箭头操作符。它将 Lambda 分为两个部分:

  • 左侧:指定了 Lambda 表达式需要的参数列表
  • 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能。

2.1. Lambda语法

  1. 无参,无返回值

    1
    Runnable ri = ()->{System.out.println("Hello Lambda!");};
  2. 一个参数,但是没有返回值

    1
    Consumer<String> con = (String str)->{System.out.println(str);};
  3. 数据类型可以省略 - 类型推断

    Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断

    1
    Consumer<String> con = (str)->{System.out.println(str);};
  4. 需要一个参数时,参数的小括号可以省略

    1
    Consumer<String> con = str->{System.out.println(str);};
  5. 需要两个或以上的参数,多条执行语句,并且可以有返回值

    1
    2
    3
    4
    Comparator<Integer> com = (x,y)->{
    System.out.println("实现函数式接口方法!");
    return Integer.compare(x,y);
    }
  6. 体只有一条语句时,return 与大括号若有,都可以省略

    1
    Comparator<Integer> com = (x,y)->Integer.compare(x,y);

2.2. Lambda的延迟执行

有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。

2.2.1. 性能浪费的日志案例

日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。 一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public class Demo01Logger {
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}

public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, msgA + msgB + msgC);
}
}

这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。

SLF4J是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进 行字符串拼接。

如: LOGGER.debug(“变量{}的取值为{}。”, “os”, “macOS”) ,其中的大括号 {} 为占位 符。如果满足日志级别要求,则会将“os”和“macOS”两个字符串依次拼接到大括号的位置;否则不会进行字 符串拼接。这也是一种可行解决方案,但Lambda可以做到更好

使用Lambda必然需要一个函数式接口:

1
2
3
4
@FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}

然后对 log 方法进行改造:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo02LoggerLambda {
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, () ‐> msgA + msgB + msgC );
}
}

这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。

证明Lambda的延迟,下面的代码可以通过结果进行验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo03LoggerDelay {
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}

public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(2, () ‐> {
System.out.println("Lambda执行!"); return msgA + msgB + msgC;
});
}
}

从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。

实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法来完成。而是否调用其所在方法是在条件判断之后才执行的

2.3. 使用Lambda作为参数和返回值

如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数 式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式 接口作为方法参数。

例如 java.lang.Runnable 接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就 可以使用Lambda进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别。

1
2
3
4
5
6
7
8
9
public class Demo04Runnable {
private static void startThread(Runnable task) {
new Thread(task).start();
}

public static void main(String[] args) {
startThread(() ‐> System.out.println("线程任务执行!"));
}
}

类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一 个方法来获取一个 java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Arrays; 
import java.util.Comparator;

public class Demo06Comparator {
private static Comparator<String> newComparator() {
return (a, b) ‐> b.length() ‐ a.length();
}
public static void main(String[] args) {
String[] array = { "abc", "ab", "abcd" };
System.out.println(Arrays.toString(array));
Arrays.sort(array, newComparator());
System.out.println(Arrays.toString(array));
}
}

其中直接return一个Lambda表达式即可。

3. 函数式接口概念

3.1. 如何理解函数式接口

  • Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP) 编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)
  • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在 Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型—函数式接口。
  • 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是 Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
  • 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。

3.2. 什么是函数式(Functional)接口

函数式接口 : 有且仅有一个抽象方法的接口。

即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda

语法糖

指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。

只要确保接口中有且仅有一个抽象方法即可

1
2
3
4
修饰符 interface 接口名称 {
// 其他非抽象方法内容
public abstract 返回值类型 方法名称(可选参数信息);
}

由于接口当中抽象方法的 public abstract 是可以省略的,

所以定义一个函数式接口很简单

1
2
3
public interface MyFunctionalInterface {
void myMethod();
}

3.3. @FunctionalInterface注解

与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注

解可用于一个接口的定义上:

1
2
3
4
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}

一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注 意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

3.4. 常用函数式接口

JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供。 下面是最简单的几个接口及使用示例。

3.4.1. 核心接口

函数式接口 参数类型 返回类型 抽象方法 描述
Consumer<T> T void void accpet(T t) 处理一个T类型的值
Supplier<T> T T get() 提供一个T类型的值
Function<T,R> T R R apply(T t) T类型对象应用操作,结果是R类
Predicate<T> T boolean boolean test(T t) 判断T类型是否满足某约束

3.4.2. 其他接口

函数式接口 参数类型 返回类型 方法 用途
BiFunction<T,U,R> T, U R R apply(T t, U u) 对类型为T, U参数应用操作,返回R类型的结果
UnaryOperator<T>
(Function子接口)
T T T apply(T t) 对类型为T的对象进行一元运算,并返回T类型的结果
BinaryOperator<T>
(BiFunction 子接口)
T, T T T apply(T t1, T t2) 对类型为T的对象进行二元运算,并返回T类型的结果
BiConsumer<T,U> T, U void void accept(T t, U u) 对类型为T, U参数应用操作
BiPredicate<T,U> T,U boolean boolean test(T t,U u) 判断T,U类型是否满足某约束
ToIntFunction<T>,
ToLongFunction<T>,
ToDoubleFunction<T>
T int,long double int applyAsInt(T value)
long applyAsLong(T value)
double applyAsDouble(T value)
分别计算int、long、double值的函数
IntFunction<R>
LongFunction<R>
DoubleFunction<R>
int,long double R R apply(int value)
R apply(long value)
R apply(double value)
参数分别为int、long、double 类型的函数

3.4.3. Supplier接口 - T get()

java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get()

用来获取一个泛型参数指定类型的对 象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象 数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.function.Supplier;

public class Demo08Supplier {
private static String getString(Supplier<String> function) {
return function.get();
}

public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
System.out.println(getString(() ‐> msgA + msgB));

}
}

3.4.3.1. 求数组元素最大值

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 Demo02Test { 
//定一个方法,方法的参数传递Supplier,泛型使用Integer
public static int getMax(Supplier<Integer> sup){
return sup.get();
}

public static void main(String[] args) {
int arr[] = {2,3,4,52,333,23};
//调用getMax方法,参数传递Lambda
int maxNum = getMax(()‐>{
//计算数组的最大值
int max = arr[0];
for(int i : arr){
if(i>max){
max = i;
}
}
return max;
});

System.out.println(maxNum);
}
}

3.4.4. Consumer接口

java.util.function.Consumer<T> 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据, 其数据类型由泛型决定。

3.4.4.1. void accept(T t)

Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。

基本使用如:

1
2
3
4
5
6
7
8
9
import java.util.function.Consumer; 
public class Demo09Consumer {
private static void consumeString(Consumer<String> function) {
function.accept("Hello");
}
public static void main(String[] args) {
consumeString(s ‐> System.out.println(s));
}
}

当然,更好的写法是使用方法引用。

3.4.4.2. Consumer andThen(Consumer)

如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。

1
2
3
4
5
6
7
default Consumer<T> andThen(Consumer<? super T> after) { 
Objects.requireNonNull(after);
return (T t) ‐> {
accept(t);
after.accept(t);
};
}

java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出 NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。

要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。

例如两个步骤组 合的情况:

1
2
3
4
5
6
7
8
9
10
import java.util.function.Consumer; 								
public class Demo10ConsumerAndThen {
private static void consumeString(Consumer<String> one, Consumer<String> two) { one.andThen(two).accept("Hello");
}
public static void main(String[] args) {
consumeString(
s ‐> System.out.println(s.toUpperCase()),
s ‐> System.out.println(s.toLowerCase()));
}
}

运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。当然,通过链式写法可以实现更多步骤的 组合。

3.4.4.3. 格式化打印信息

下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。 ”的格式将信息打印出来。要求将打印姓 名的动作作为第一个 Consumer 接口的Lambda实例,将打印性别的动作作为第二个 Consumer 接口的Lambda实 例,将两个 Consumer 接口按照顺序“拼接”到一起。

1
2
3
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
}

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.function.Consumer;
public class DemoConsumer {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
printInfo(
s ‐> System.out.print("姓名:" + s.split(",")[0]),
s ‐> System.out.println("。性别:" + s.split(",")[1] + "。"),
array);
}

private static void printInfo(Consumer<String> one,
Consumer<String> two, String[] array) {
for (String info : array) {
one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。
}
}
}

3.4.5. Predicate接口

有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用

java.util.function.Predicate<T> 接口。

3.4.5.1. boolean test(T t)

用于条件判断的场景

1
2
3
4
5
6
7
8
9
10
import java.util.function.Predicate;  
public class Demo15PredicateTest {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() > 5);
}
}

条件判断的标准是传入的Lambda表达式逻辑,只要字符串长度大于5则认为很长。

3.4.5.2. Predicate and(Predicate)

既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实 现“并且”的效果时,可以使用default方法 and

1
2
3
4
default Predicate<T> and(Predicate<? super T> other) { 
Objects.requireNonNull(other);
return (t) ‐> test(t) && other.test(t);
}

如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么: import java.util.function.Predicate;

1
2
3
4
5
6
7
8
9
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.and(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"));
}
}

3.4.5.3. Predicate or(Predicate)“或”

or 实现逻辑关系中的“或”

1
2
3
4
default Predicate<T> or(Predicate<? super T> other) { 
Objects.requireNonNull(other);
return (t) ‐> test(t) || other.test(t);
}

如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.function.Predicate;
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.or(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}

public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"));

}
}

3.4.5.4. Predicate negate(Predicate)“非”

1
2
3
default Predicate<T> negate() { 
return (t) ‐> !test(t);
}

从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前

调用 negate 方法,正如 and 和 or 方法一样:

1
2
3
4
5
6
7
8
9
10
11
import java.util.function.Predicate;  
public class Demo17PredicateNegate {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.negate().test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}

public static void main(String[] args) { method(s ‐> s.length() < 5);

}
}

3.4.5.5. 集合信息筛选

数组当中有多条“姓名+性别”的信息如下,请通过 Predicate 接口的拼装将符合要求的字符串筛选到集合 ArrayList 中,需要同时满足两个条件: 1. 必须为女生; 2. 姓名为4个字。

1
2
3
4
5
public class DemoPredicate {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
}
}

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class DemoPredicate {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
List<String> list = filter(array,
s ‐> "女".equals(s.split(",")[1]),s ‐> s.split(",")[0].length() == 4);
System.out.println(list);
}
private static List<String> filter(String[] array, Predicate<String> one,
Predicate<String> two) {
List<String> list = new ArrayList<>(); for (String info : array) {
if (one.and(two).test(info)) {
list.add(info);
}
}
return list;
}
}

3.4.6. Function接口

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件

3.4.6.1. R apply(T t)

根据类型T的参数获取类型R的结果。 使用的场景例如:将 String 类型转换为 Integer 类型。

1
2
3
4
5
6
7
8
9
10
import java.util.function.Function;
public class Demo11FunctionApply {
private static void method(Function<String, Integer> function) {
int num = function.apply("10");
System.out.println(num + 20);
}
public static void main(String[] args) {
method(s ‐> Integer.parseInt(s));
}
}

当然,最好是通过方法引用的写法。

3.4.6.2. Function andThen(Function)

1
2
3
4
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) ‐> after.apply(apply(t));
}

该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.function.Function;  
public class Demo12FunctionAndThen {
private static void method(Function<String, Integer> one,
Function<Integer, Integer> two) {
int num = one.andThen(two).apply("10");
System.out.println(num + 20);
}

public static void main(String[] args) {
method(str‐>Integer.parseInt(str)+10, i ‐> i *= 10);
}
}

第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一 起。

请注意,Function的前置条件泛型和后置条件泛型可以相同。

3.4.6.3. 自定义函数模型拼接

请使用 Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为: String str = “赵丽颖,20”;

  1. 将字符串截取数字年龄部分,得到字符串;
  2. 将上一步的字符串转换成为int类型的数字;
  3. 将上一步的int数字累加100,得到结果int数字。

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.function.Function;
public class DemoFunction {
public static void main(String[] args) {
String str = "赵丽颖,20";
int age = getAgeNum(str,s ‐> s.split(",")[1],
s ‐>Integer.parseInt(s),
n ‐> n += 100); System.out.println(age);
}
private static int getAgeNum(String str, Function<String, String> one,
Function<String, Integer> two,
Function<Integer, Integer> three) {
return one.andThen(two).andThen(three).apply(str);
}
}

4. 方法引用与构造器引用

4.1. 方法引用

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
  • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向 一个方法,可以认为是Lambda表达式的一个语法糖。
  • 实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致
  • 格式:使用操作符 “::” 将类(或对象) 与方法名分隔开来。
  • 如下三种主要使用情况:
    • 对象::实例方法名
    • 类::静态方法名
    • 类::实例方法名

4.1.1. 类::实例方法名

1
2
3
4
5
6
7
8
9
10
Comparator<Integer> com = (x,y)->Integer.compare(x,y);
int value = com.compare(12,32);
//-1
System.out.println(value);

//=========等价于===========
Comparator<Integer> com2 = Integer::compare;
value = com.compare(12,32);
//-1
System.out.println(value);

当函数式接口方法的第一个参数是需要引用方法的调用者,且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName

1
2
3
4
5
6
7
8
9
10
11
12
@Test
void testSystem(){
BiPredicate<String,String> bp = (x,y)->x.equals(y);
boolean test = bp.test("abc", "abb");
//false
System.out.println(test);

bp = String::equals;
boolean test1 = bp.test("abc", "abc");
//true
System.out.println(test1);
}

4.2. 构造器引用 class::new

与函数式接口相结合,自动与函数式接口中方法兼容。 可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致,且方法的返回值即为构造器对应类的对象。

n->new MyClass(n) ->MyClass::new

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
class MyClass{
public String name;

public MyClass(String name) {
this.name = name;
}
}
@FunctionalInterface
interface MyInterface{
MyClass getClass(String name);
}


@Test
void testSystem(){

MyInterface my = n->new MyClass(n);
MyClass myClass = my.getClass("AAA");
//AAA
System.out.println(myClass.name);


my = MyClass::new;
MyClass abc = my.getClass("ABC");
//ABC
System.out.println(abc.name);

}

4.3. 数组引用 type[]::new

n->new Integer[n] -> Integer[]::new

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@FunctionalInterface
interface MyInterface{
Integer[] getInteger(int n);
}

public Integer[] Method(int n,MyInterface face){
return face.getInteger(n);
}

@Test
void testSystem(){

Integer[] integers = Method(10,n->new Integer[n]);
//10
System.out.println(integers.length);

integers = Method(20,Integer[]::new);
//20
System.out.println(integers.length);

}

5. Stream

5.1. 定义

Stream(流)是一个来自数据源的元素队列

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算
  • 数据源 流的来源。 可以是集合,数组 等

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

1. Pipelining

中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。

2. 内部迭代

以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。

当使用一个流的时候,通常包括三个基本步骤:

  1. 获取一个数据源(source)

  2. 数据转换

  3. 执行操作获取想要的结果

每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

注意:

1. Stream 自己不会存储元素。
2. Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
3. Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

5.2. 获取Stream方法

5.2.1. 通过集合Collection.stream()

  • default Stream<E> stream() : 返回一个顺序流
  • default Stream<E> parallelStream() : 返回一个并行流
1
2
3
4
5
6
7
8
import java.util.stream;

List<String> list = new ArrayList<>();
list.add("BA");
list.add("AB");

//Stream<T> filter(Predicate<? super T> p)
list.stream().filter((name)->name.endsWith("A")).forEach((i)-> System.out.println(i));

5.2.2. 通过数组Arrays.stream(T[])

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

public static <T> Stream<T> stream(T[] array)

public static IntStream stream(int[] array, int startInclusive, int endExclusive)

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)
1
2
3
4
5
6
7
8
@Test
void testSystem(){

int[] arr = new int[]{1,5,3,2,4};
IntStream stream = Arrays.stream(arr);
//5
stream.filter(i->i>4).forEach(i-> System.out.println(i));
}

5.2.3. 通过Stream的of()

可以调用Stream类静态方法 of(), 通过显示值创建一个 流。它可以接收任意数量的参数。

public static<T> Stream<T> of(T... values) : 返回一个流

1
2
Stream<Integer> s=Stream.of(1,2,3,4,5);
s.filter((i)->i>2).forEach((i)-> System.out.println(i));

5.2.4. 创建无限流

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

  • 迭代
    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

    它的值包含种子,在种子上调用f产生的值,在前一个元素上调用f产生的值

  • 生成
    public static<T> Stream<T> generate(Supplier<T> s)

    它的值通过反复调用函数s而构建

1
2
3
4
5
6
7
8
9
10
11
12
@Test
void testSystem(){

// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
stream.limit(10).forEach(System.out::println);
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream<Double> stream1 = Stream.generate(Math::random);
stream1.limit(10).forEach(System.out::println);
}

5.2.5. 正则表达式中的流

splitAsStream() 按照则表达式来分割一个CharSequence对象,分割为一个个单词

1
2
3
Stream<String> stream = Pattern.compile("\\D").splitAsStream("1,2,3,4");
//分割为单个的1,2,3,4
stream.forEach(i-> System.out.println(i));

5.3. Stream 的中间操作

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

5.3.1. 筛选与切片

方法 描述
filter(Predicate p) 接收 Lambda , 从流中排除某些元素
distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一 个空流。与 limit(n) 互补
peek(Consumer) 它与当前流的元素相同,在获取每个元素时,会将其传递给action

5.3.2. 映射

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

代码:

1
2
3
4
5
6
7
@Test
void testSystem(){

int[] arr = new int[]{2,5,3,1};
IntStream stream = Arrays.stream(arr);
stream.map(i->i+2).forEach(System.out::println);
}

5.3.3. 排序

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

5.4. Stream 的终止操作

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

流进行了终止操作后,不能再次使用。

5.4.1. 匹配与查找

方法 描述 返回值
allMatch(Predicate p) 检查是否匹配所有元素 boolean
anyMatch(Predicate p) 检查是否至少匹配一个元素 boolean
noneMatch(Predicate p) 检查是否没有匹配所有元素 ` boolean
findFirst() 返回第一个元素 Optional
findAny() 返回当前流中的任意元素 Optional
count() 返回流中元素总数 long
max(Comparator c) 返回流中最大值 Optional
min(Comparator c) 返回流中最小值 Optional
forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代 称为外部迭代。相反,Stream API 使用内部迭 代——它帮你把迭代做了) void

5.4.2. 归约

方法 描述
Optional< T> reduce(BinaryOperator< T> accumulator); 可以将流中元素通过BinaryOperator结合起来,返回Optional< T>
T reduce(T identity, BinaryOperator< T> accumulator); 可以将流中元素从identity开始通过BinaryOperator结合起来,得到一个值。返回T类型

备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

BinaryOperator接口有抽象方法R apply(T t, U u);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}

public interface BinaryOperator<T> extends BiFunction<T,T,T> {
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

//1+2+3
Optional<Integer> reduce = list.stream().reduce(Integer::sum);
Integer integer = reduce.get();
//6
System.out.println(integer);

//2+1+2+3
integer = list.stream().reduce(2, Integer::sum);
//8
System.out.println(integer);

5.4.3. 收集

方法 描述
collect(Collector c) 将流转换为其他形式。接收一个 Collector 接口的实现,用于给Stream中元素做汇总 的方法

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

另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例, 具体方法与实例如下表:

5.4.3.1. Collectors 实用类静态方法

方法 返回类型 作用
toList List<T> 把流中元素收集到List
List<Employee> emps= list.stream().collect(Collectors.toList());
toSet Set<T> 把流中元素收集到Set
Set<Employee> emps= list.stream().collect(Collectors.toSet());
toCollection Collection<T> 把流中元素收集到创建的集合
Collection<Employee> emps= list.stream().collect(Collectors.toCollection(ArrayList::new));
counting Long 计算流中元素的个数
long count = list.stream().collect(Collectors.counting());
summingInt Integer 对流中元素的整数属性求和
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt Double 计算流中元素Integer属性的平均值
double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值。如:平均值
int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joining String 连接流中每个字符串
String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy Optional<T> 根据比较器选择最大值
Optionalmax= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy Optional<T> 根据比较器选择最小值
Optional min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing 归约产生的类型 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结果转 换函数
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy Map<K, List<T>> 根据某属性值对流分组,属性为K 结果为V
Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy Map<Boolean, List<T>> 根据true或false进行分区
Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));
## 基本类型流

IntStream,LongStream,DoubleStream

基本数据类型 基本数据流
byte IntStream
short IntStream
int IntStream
long LongStream
float DoubleStream
double DoubleStream
char IntStream
boolean IntStream

5.4.4. 创建IntStream流

5.4.4.1. IntStream of(int… values)

1
2
IntStream stream = IntStream.of(1, 2, 3, 4);
stream.forEach(SYstem.out::println);

5.4.4.2. Arrays.stream(int[] array)

1
2
int[] value = new int[]{1,2,3,4};
IntStream intStream = Arrays.stream(value);

5.4.4.3. IntStream range(from,to)

生成0-9,10个元素

1
2
IntStream range = IntStream.range(0, 10);
range.forEach(System.out::print);

5.4.4.4. IntStream rangeClosed(from,to)

生成0-10,11个元素

1
2
IntStream intStream = IntStream.rangeClosed(0, 10);
intStream.forEach(System.out::print);

5.4.4.5. mapToXXX

对象流转为基本数据流

1
2
3
4
5
public interface Stream<T> extends BaseStream<T, Stream<T>> {
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
}

案例:取字符流中的字符长度作为整数流

1
2
3
4
5
6
7
8
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("AB");
Stream<String> stream = list.stream();
//对字符串的长度进行处理
IntStream toInt = stream.mapToInt(String::length);
toInt.forEach(System.out::println);

5.4.5. IntStream基本方法

5.4.5.1. int[] toArray()

返回基本数据类型

1
int[] ints = IntStream.rangeClosed(0, 10).toArray();

5.4.5.2. OptionalInt findFirst()

返回第一个元素

1
2
3
4
5
6
7
8
9
10
11
12
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("AB");
Stream<String> stream = list.stream();
IntStream toInt = stream.mapToInt(String::length);

//the first element of this stream
OptionalInt any = toInt.findFirst();
//returns the value
int asInt = any.getAsInt();
System.out.println(asInt);

5.4.5.3. OptionalInt max()

返回最大值

1
2
3
4
5
6
7
8
9
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("AB");
Stream<String> stream = list.stream();
IntStream toInt = stream.mapToInt(String::length);

//describing the maximum element of this stream
OptionalInt any = toInt.max();

5.4.5.4. OptionalInt min()

返回最小值

1
2
3
4
5
6
7
8
9
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("AB");
Stream<String> stream = list.stream();
IntStream toInt = stream.mapToInt(String::length);

//describing the minimum element of this stream
OptionalInt any = toInt.min();

5.4.5.5. OptionalDouble average()

平均值

1
2
3
4
5
6
7
8
9
10
11
12
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("AB");
Stream<String> stream = list.stream();
IntStream toInt = stream.mapToInt(String::length);

//平均值
OptionalDouble average = toInt.average();
double asDouble = average.getAsDouble();
System.out.println(asDouble);

5.4.5.6. summaryStatistics

可同时返回最大值max,最小值min,平均值average,总和sum,总数count

1
2
3
4
5
6
7
IntSummaryStatistics statistics = toInt.summaryStatistics();
double average = statistics.getAverage();
long count = statistics.getCount();
int max = statistics.getMax();
int min = statistics.getMin();
long sum = statistics.getSum();
String s = statistics.toString();

5.4.5.7. Stream< Integer> boxed()

转化为包装器对象流

1
Stream<Integer> boxed = toInt.boxed();

5.5. 并行流

只要在终结方法执行时,流处于并行模式,所有中间结果流操作都将被并行化

并行运行时,要让其返回结果与顺序执行时返回的结果相同

1
2
3
4
//用Collection.parallelStream()获取并行流
Stream<String> stream = list.parallelStream();
//顺序流转换为并行流
Stream<String> parallel = stream.parallel();

6. Optional类

  • 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。 以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类, Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代 码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
  • Optional<T> 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
  • Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在 则isPresent()方法会返回true,调用get()方法会返回该对象。

6.1. 常用方法

Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

6.1.1. 创建Optional类对象的方法

6.1.1.1. Optional.of(T value)

创建一个 Optional 实例,value必须非空; value为null,会抛出NullPointerException对象

1
2
3
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}

6.1.1.2. Optional.empty()

创建一个空的 Optional 实例

1
2
3
4
5
6
7
private static final Optional<?> EMPTY = new Optional<>();

public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}

6.1.1.3. Optional.ofNullable(T value)

value可以为null

1
2
3
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}

6.1.2. 判断Optional容器中是否包含对象

6.1.2.1. boolean isPresent()

判断是否包含对象

1
2
3
4
public boolean isPresent() {
return value != null;
}

6.1.2.2. void ifPresent(Consumer<? super T> consumer)

如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。

1
2
3
4
5
6
7
8
9
10
11
12
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}

public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}

//=============
list.stream().filter(i -> i.startsWith("c")).findAny().ifPresent(System.out::println);

6.1.3. 获取Optional容器的对象

6.1.3.1. T get()

如果调用对象包含值,返回该值,否则抛异常NoSuchElementException

1
2
3
4
5
6
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}

6.1.3.2. T orElse(T other)

如果有值则将其返回,否则返回指定的other对象。

1
2
3
public T orElse(T other) {
return value != null ? value : other;
}

6.1.3.3. T orElseGet(Supplier<? extends T> other)

如果有值则将其返回,否则返回由Supplier接口实现提供的对象。

1
2
3
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}

6.1.3.4. T orElseThrow(Supplier<? extends X> exceptionSupplier)

如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。

1
2
3
4
5
6
7
8
public <X extends Throwable> T orElseThrow(
Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}

6.2. 案例

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
@Test
void testSystem(){
Boy boy = new Boy("Amy");
//ofNullable(T t):t可以为null
Optional<Girl> girl = Optional.ofNullable(boy.getGirl());
//存在对象就打印对象信息
girl.ifPresent(System.out::println);

boy = new Boy();
Optional<Girl> girl1 = Optional.ofNullable(boy.getGirl());
//有对象就输出,没有则使用该对象
Girl eura = girl1.orElse(new Girl("Eura"));
//Eura
System.out.println(eura.getName());
boolean present = girl1.isPresent();
//false
System.out.println(present);
}

class Boy{
private Girl girl;

public Girl getGirl() {
return girl;
}

public void setGirl(Girl girl) {
this.girl = girl;
}

public Boy(String name) {
this.girl = new Girl(name);
}

public Boy() {
}
}
class Girl{
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Girl(String name) {
this.name = name;
}

@Override
public String toString() {
return "Girl{" +
"name='" + name + '\'' +
'}';
}
}
本文结束  感谢您的阅读