Java8新特性

Lambda表达式


Lambda 表达式将函数式编程引入了 Java,Lambda 允许把函数作为一个方法的参数。
函数式编程:Java中,万物皆对象,编写程序时站在有什么对象的角度考虑对象能做什么事。而最终做的事情才是我们最关心的,至于什么对象来做倒是无所谓了,只要事情做好就行,所以在 Java8 中重新思考了某些特定情况下面向函数编程的好处。

函数式编程就是让我们由原来的传递对象变为传递一种行为。

面向对象的束缚:在面向对象的世界中,如果要传递一种行为,必须把这个行为封装到某各类中,然后创建类的对象,再把对象传递出去。

1
2
3
4
5
6
@Test
public void testLanmda() {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
}

语法

  1. 传递的函数必须是 @FunctionalInterface(函数式接口) 注解的接口的方法
  2. 一定要覆盖接口中的唯一的抽象方法
  3. 方法中固定的形参

特殊情况:该方法的形参只有1个时,可以省略圆括号,该方法只有一行代码时,可以省略花括号和 return 关键字。

需求:对集合中的整数对象排序

1
2
3
4
5
6
7
8
public void testSort() {
// 需求:对集合中的整数对象排序
List<Integer> list = Arrays.asList(7, 4, 9, 5, 2, 6);

// 自然排序
Collections.sort(list, (o1, o2) -> o1.compareTo(o2));
list.forEach(System.out::println);
}

内置的函数式接口

java.util.function

函数式接口 参数类型 返回类型 说明
Consumer消费型接口 T void 对类型为 T 的对象操作,方法:void accept(T t)
Supplier供给型接口 T 返回类型为 T 的对象,方法 T get(); 可用作工厂
Function<T, R> 函数处理接口 T R 对类型为 T 的对象操作,并返回结果是 R 类型的对象,方法 R apply(T t)
Predicate 断言型接口 T boolean 判断类型为 T 的对象是否满足条件,并返回 boolean 值,方法 boolean test(T t)
  • 消费型函数接口:Consumer 有参数无返回
  • 供给型函数接口:Supplier 无参数有返回
  • 函数处理型接口:Function<T, R> 传入 T 型对象,返回 R 类型对象
  • 断言型接口: Predicate 对 T 类型对象断言,返回 boolean 类型结果

接口的默认方法与静态方法


使用 default 关键字,提供默认的实现,所有实现这个接口的类都会接受默认方法的实现,除非子类提供自己的实现。

Java8 之前,如果发现接口中的功能不够,需要在接口中增加新的方法,会出现重大问题,接口中增加一个抽象方法,所有实现类都要跟着一起改动,这样对 Java 整个体系牵连较大。 Java8 提出了接口中可以有非抽象的默认方法,表示抽象方法的默认实现,跟注解中的属性给予默认值是一样的。

这项特性是为了兼容之前接口的特征,在升级时避免改动所有的实现类。

当实现类实现了2个接口,且两个接口中有相同的默认方法时,实现类必须覆盖该方法。

接口中的静态方法,取代工具类。直接在接口中使用 default 静态方法。

1
2
3
4
5
6
7
public interface Stream {
public static<T> Stream<T> empty() {
...
}
}

Stream.empty();

方法引用


foreach 实现 接受一个 @FunctionalInterface 函数式接口匿名对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}

Consumer<String> con = new Consumer<String>() {
public void accept(String t) {
...
}
}

List<String> list1 = Array.asList("1", "2", "3");
list1.forEach(t -> {
System.out.println(t);
});

List<String> list1 = Array.asList("1", "2", "3");
list1.forEach(t -> System.out.println(t));

// 如果传入的对象和方法中的对象一样时,省略要传入的对象
// 形参和调用方法的实参一致可以写成如下
list1.forEach( p::print );
  1. 对象的引用::实例方法名 (System.out::println)
  2. 类名 :: 静态方法名 (Supplier s = Math::random;)
  3. 类名 :: 实例方法名 (Function<String, Integer> f = String::length;)
  4. 类名 :: new (构造器引用) (Supplier sp = Date::new;)
  5. 类型[] :: new (数组引用) (Function<Integer, String[]) f2 = String[]::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
29
30
public void testMethodRef() {
// 1.对象 :: 实例方法名 有参数,无返回值
// Arrays.asList(1, 2, 3).forEach(i -> System.out.println(i));
// 参数跟方法实参一致,省略
Arrays.asList(1, 2, 3).forEach(System.out::println);

// 2.类名 :: 静态方法名 无参数,有返回值
Supplier<Double> s1 = () -> { return Math.random(); };
Supplier<Double> s = Math::random;

// 3.类名 :: 实例方法名
// 传入一个 String,返回一个 Integer
// Function<String, Integer> f = (ss) -> { return ss.length(); };
// Function<String, Integer> f = ss -> ss.length();
Function<String, Integer> f = String::length;
Integer len = f.apply("李朝");
System.out.println(len);

// 4. 类名::new 无参数有返回值
// Supplier<Date> sp = () -> { return new Date(); };
// Supplier<Date> sp = () -> new Date();
Supplier<Date> sp = Date::new;
Date d = sp.get();

// 5.类型[]::new
// Function<Integer, String[]> f2 = (len) -> { return new String[len]; };
Function<Integer, String[]> f2 = String[]::new;
String[] strings = f2.apply(5);
System.out.println(strings.length);
}

记住一个点:但凡方法引用,分为几种类型,有参/无参,有返回值/无返回值,简写规则:该方法的形参只有1个时,可以省略圆括号,该方法只有一行代码时,可以省略花括号和 return 关键字。如果方法形参和方法内的实参一致时,可以写成 xx::方法名

Stream API


Java8 的 Stream 是对数据处理的一种抽象描述,就像工厂中的流水生产。在程序中,通过 Stream 提供的操作,对数据进行加工处理,如:过滤、查找、排序、统计等。

Stream 是一套工具,提供了对数据的各种便捷操作,提高操作数据的效率(并行流)

1
2
3
4
5
6
7
8
9
public void testStream() {
List<String> list = Arrays.asList("马云", "马化腾", "李彦宏", "雷军", "刘强东", "马大哈");
// 需求 1 找出所有姓马的
// 需求 2 找出3个字的姓马的
Stream<String> stream = list.stream();
stream.filter(s -> s.startsWith("马")).filter(s -> s.length() == 3).forEach(System.out::println);
// 马化腾
// 马大哈
}

Stream -> 数据处理操作的源生成的元素序列。

  • 元素序列 就像集合一样,可以访问特定元素类型的一组有序值,因为集合是数据结构,以特定的时间/控件复杂度存储和访问元素(ArrayList、linkList)。流的目的在于计算,比如 filter、sorted 和 map,集合 -> 存储, 流 -> 计算。
  • ,流会使用一个提供数据的源,如集合、数组或者输入/输出资源。有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
  • 数据处理操作,类似数据库的操作,流的数据处理操作:filter、map、reduce、find、match、sort等,流操作可以顺序执行也可以并行执行。

流操作有两个特点

  • 流水线,很多流本身会返回一个流,这样多个操作就可以链接起来
  • 内部迭代,与使用迭代器外部迭代的集合不同,流的迭代操作是背后进行的

所有功能一起执行,在一次迭代中,流水线上会执行全部功能。在执行终端操作的时候,功能才会运行起来。

Stream 的三种迭代方式


1
2
3
4
5
6
7
8
9
10
@Test
public void testCreatStream() {
// 1. Collection 接口中的 stream 方法
List<String> list = Arrays.asList("马云", "马化腾", "李彦宏", "雷军", "刘强东", "马大哈");
Stream<String> stream1 = list.stream();
// 2. Arrays 工具类中的 stream 方法
IntStream stream2 = Arrays.stream(new int[]{1, 2, 3});
// 3. Stream 接口中的 of 方法
Stream<String> stream3 = Stream.of("a", "b", "c");
}

Stream 的操作分类:

2019-08-22 at 7.55 P

  • 中间操作:拼接流水线,返回值为 Stream 的大都是中间操作,中间操作支持链式调用,并且 Lambda 代码会延迟执行
  • 终端操作(结束操作 terminal operations) 返回值不为 Stream 的为终端操作(立即求值),终端操作不支持链式调用,会实际触发计算后流会关闭,不能继续使用。

Stream 并发和并行


并发,多个任务共享时间段(CPU 切换执行),
并行,多个任务发生在同一时刻(必须多核 CPU),同时执行。

任务可以并行化处理,数据也可以并行化处理。数据并行化处理是指将数据分成快,为每块数据分配单独的处理单元,也就是并行流。Hadoop 最常用的方式,让数据同时在多台机器上做处理。最后把结果汇总。

Java8 以前,对数据(集合、数组)中的若干元素做并发操作,十分繁琐。 Stream 做了很好的支持,其 API 中可以声明通过 parallel() 与 sequential() 在并行流与串行流之间切换。

并行流 CPU 使用率72,串行流39

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 需求:Integer 的最小值累加到 Integer 的最大值
* 顺序流 CPU 使用率 39
*/
@Test
public void testStreamAPI1() {
Long s = System.currentTimeMillis(); // 获取当前系统时间
LongStream.rangeClosed(Integer.MIN_VALUE, Integer.MAX_VALUE).sum();
Long e = System.currentTimeMillis();
System.out.println(e - s); // 2650
}

/**
* 并行流 CPU 使用率 72
*/
@Test
public void testStreamAPI2() {
Long s = System.currentTimeMillis(); // 获取当前系统时间
LongStream.rangeClosed(Integer.MIN_VALUE, Integer.MAX_VALUE).parallel().sum();
Long e = System.currentTimeMillis();
System.out.println(e - s); // 1560
}

Optional


针对空指针异常,推出的技术。
Optional 类是一个可以为 null 的容器对象,如果值存在则 isPresent() 方法会返回 true,调用 get() 方法返回该对象。

Optional 是个容器:保存类型 T 的值,或者保存 null,Optional 可以使得代码不用显式的进行控制检测。
Optional 引入很好的解决空指针异常,也就是说我们在写业务代码时,只要专注于处理业务的逻辑,不需要关心对象是否为 null。

用法

获取 Optional 的静态方法:
static<T> Optional<T> ofNullable(T value) 如果为空,返回Optional 描述的指定值,否则返回空的 Optional

操作 Optional 的实例方法:

  • void ifPresent(Consumer<? super T> comsumer) 如果值存在则使用该值调用 consumer,否则不做任何事情
  • boolean isPresent() 如果值妇女在,方法返回 true,否则 false
  • T get() 返回值,否则爆出异常
  • T orElse(T other) 如果存在该值,返回值,否则返回 other
  • <U> Optional<U> map(Function<? super T, ? extends U> mapper) 如果存在该值,提供映射方法,如果返回非 null ,返回一个 Optional 描述结果(把一种类型的 Optional 转换成另一种类型的 Optional,为空就不会变)
  • <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) 如果该值存在,返回包含的值,否则爆出由 Supplier 继承的异常。
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
@Test
public void testOptional() throws Exception {
String s1 = getString1();
String s2 = getString2();

// 获取 Optional 对象
Optional<String> o1 = Optional.ofNullable(s1);
Optional<String> o2 = Optional.ofNullable(s2);

// 如果值存在,则使用该值调用 consumer,否则不做任何事情
o1.ifPresent(System.out::println); // 不执行
o2.ifPresent(System.out::println); // 123

// 如果值存在则方法会返回 true,否则返回 false
System.out.println(o1.isPresent()); // false
System.out.println(o2.isPresent()); // true

// 如果 Optional 包含值,返回包含的值否则抛出异常
// System.out.println(o1.get()); // 报错
System.out.println(o2.get());

// 如果存在该值,返回值,否则返回 other
System.out.println(o1.orElse("111")); // 111
System.out.println(o2.orElse("111")); // 123

// 如果存在值,提供映射方法,返回一个指定类型的 Optional 对象,否则返回 Optional.Empty
System.out.println(o1.map(s -> true)); // Optional.Empty
System.out.println(o2.map(s -> true)); // Optional[true]

// 如果存在值 返回值,否则抛出 由 Supplier 继承的异常
// o1.orElseThrow(Exception::new); // 报错
o2.orElseThrow(Exception::new);
}

private String getString1() {
return null;
}

private String getString2() {
return "123";
}

工具类设计

Optional 完成 StringUtil

StringUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 判空
* @param str
* @return
*/
public static boolean hasLength(String str) {
return Optional.ofNullable(str)
.filter(s -> s.trim().length() > 0)
.isPresent();
}

/**
* 如果不满足要求:为 null 或者是空字符串
* 返回 null,否则返回本身
* @param str
* @return
*/
public static String empty2Null(String str) {
return Optional.ofNullable(str)
.filter(s -> s.trim().length() > 0)
.orElse(null);
}

疑问,为何空串可以被 null 置换?为何 s.trim() 不会报空指针异常?

看源码:Optional.filter 方法:
Optional.filter 方法底层如下,会做 isPresent() 处理,因此无需担心 predicate 使用 value 时会有空指针异常。而空串为何会被 null 置换,因为 filter 过滤不合格的值,会直接返回 empty()。empty() 就是存了 null 的 Optional。

1
2
3
4
5
6
7
public Optional<T> filter(Predicate<? super T> predicate) { 
Objects.requireNonNull(predicate);
if (!isPresent()) // 判断是否有值
return this; // 没有值直接返回 this
else
return predicate.test(value) ? this : empty(); // 有值但不满足条件 也会返回 empty()
}

Base64 加密算法


Base64 算法 用于传输二进制数据。可以把二进制数据转换为字符数据,可以用于在 HTTP 环境下传递较长的标识信息。

1
2
Base64.Encoder.encode(byte[] src);
Base64.Decoder.decode(String src);
文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2019/08/22/Java8%E6%96%B0%E7%89%B9%E6%80%A7/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论