聊聊 JDK8 能提升开发效率的方式

聊聊 JDK8 能提升开发效率的方式

薛定谔的汪

前言

Java 自1.0 版本发布以来,java8 可以说是变化最大的一次。基于函数式编程思想,它本身并没有删除原有的东西,所以就算升级了 java8,好多人的编码方式还是按照以前的来,并没有使用到 java8 的新功能,所以也没意识到 java8 带给我们开发人员的便利。

也有一些人,在了解了 java8 的新特性后,认为其改变了一直以来的编码习惯,对其产生了抵触心理,并未在项目中使用。

但是 java8 提供的新特性还是很强大的,通过这些新特性,可以帮助我们写出更简洁,更清晰的代码

Java8 新特性

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

Java8 允许在接口中定义默认方法和静态方法,对于这两种方法,可以直接在接口中实现,无需在实现类中实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface MyInterface {
// 抽象方法
String abstractMethod();

// 默认方法
default void method() {
System.out.println("MyInterface method invoke...");
}

// 静态方法
static void staticMethod() {
System.out.println("MyInterface staticMethod invoke...");
}
}

默认方法

扩展方法,在方法前需要用default关键字修饰,不能通过接口调用,必须通过接口实现类的实例对象进行方法调用

1
2
MyInterfaceImpl myInterface = new MyInterfaceImpl();
myInterface.defaultMethod();

静态方法

在接口中,用 static 关键字修饰的方法,可以通过接口直接调用

1
MyInterface.staticMethod();

方法重写

接口的实现类,对于普通的抽象方法,必须要重写,这个不用多说;

对于默认方法,可以选择重写,也可以不重写;

接口的静态方法无法被重写;

注意点

一个接口 A 有一个默认方法 method,一个父类B也有个方法 method,如果有个子类既实现了接口 A,又继承了父类 B,那么在 A 执行 method 时,调用的是接口的呢还是 父类的呢?

答案是: 父类的(类优先原则)

Lambda 表达式

Lambda表达式是Java8中非常重要的一个新特性,其基于函数式编程的思想,支持将代码作为方法参数进行使 用。可以把Lambda表达式理解为通过一种更加简洁的方式表示可传递的匿名函数。

它本身没有名称,而且不像方法那样属于某一个类,但是可以有参数列表、代码体、返回值。使用了Lambda表达 式之后就不需要再去编写匿名类了。

Lambda 基础格式

1
(参数列表) ‐> { 方法体 }

函数式接口

在Java8中为了让现在有的函数能够更加友好的使用Lambda表达式,因此引入了函数式接口这个概念。其是一个 仅有一个抽象方法的普通接口。如果声明多个抽象方法则会报错。但是默认方法和静态方法在此接口中可以定义多 个。

要想自定义一个函数式接口的话,需要在接口上添加 @FunctionalInterface

常见应用

在Java8的类库设计中,已经引入了几个函数式接口:Predicate、Consumer、Function、Supplier

Predicate 用于判断操作,抽象方法 test

Consumer 用于获取数据后对数据进行某些操作,抽象方法 accept

Function 用于类型转换,抽象方法 apply

Supplier 生成数据,抽象方法 get

类型检查&推断

Lambda表达式中对于数据类型是可以不用声明的,lambda直接就能进行 正确的运算,那么对于这一点,Lambda表达式内部又是如何来进行实现的呢?

其实对于Lambda表达式的类型推断,它是对Java之前版本中的目标类型推断进行的再次扩展。

如:

1
2
3
Map<String,String> map = new HashMap<String, String>();

Map<String,String> map1 = new HashMap<>();

那么在Java8中对于这种使用方式又进一步扩展,可以省略Lambda表达式中的所有参数类型。在编译时根据 Lambda表达式的上下文信息推断出参数的正确类型。这就是所谓的类型推断。

1
2
3
4
5
(int x,int y) ‐>{ 
System.out.println(x);
System.out.println(x);
return x+y;
}
1
2
3
4
5
(x,y) ‐>{ 
System.out.println(x);
System.out.println(x);
return x+y;
}

方法引用

类名或实例名::方法名

Stream流使用

可以参考:https://mp.weixin.qq.com/s/WmG781nHGZdvcOYbHM2gIw

数据并行化

stream() 串行流

parallelStream() 并行流

当处理一个流时,如果处理的逻辑不要求先后顺序的话,可以使用并行流

并行流原理

对于并行流,其在底层实现中,是沿用了Java7提供的fork/join分解合并框架进行实现。fork根据 cpu 核数进行数据分块,join 对各个fork 进行合并

使用注意事项

对于并行流,不一定就比串行快,影响并行速度的主要是五个因素:

  1. 数据大小,因为在并行内部实现中涉及到了fork/join操作,它 本身就存在性能上的开销。因此只有当数据量很大,使用并行处理才有意义。

  2. 源数据结构,fork时会对源数据进行分割,数据源的特性直接影响了fork的性能。

    ArrayList、数组或IntStream.range,可分解性最佳,因为他们都支持随机读取,因此可以被任意分割。

    HashSet、TreeSet,可分解性一般,其虽然可被分解,但因为其内部数据结构,很难被平均分解。

    LinkedList、Streams.iterate、BufferedReader.lines,可分解性极差,不好确定在哪里进行 分割。

  3. 装箱拆箱, 尽量使用基本数据类型,避免装箱拆箱。

  4. CPU核数 , fork的产生数量是与可用CPU核数相关,可用的核数越多,获取的性能提升就会越大。

  5. 单元处理开销,花在流中每个元素的时间越长,并行操作带来的性能提升就会越明显。

异步编程 CompleTableFuture

在互联网开发中,经常要求设计出性能更加优异的系统,就上上面提到的数据并行化,就上充分利用多核处理器结合并行操作来让代码执行效率会更加优异。

比如在微博里里,点击某条微博详情,需要查询该条微博的点赞数、转发数和评论数,假设点赞、转发、评论是分属三个不同系统中的业务,那么就需要在聚合层调用三个基础服务获取这三个数据,但其实在这个业务中,查询点赞数、转发数、评论数这三者是没有先后逻辑关系的,各自完成自己的事情,那么就会考虑开启多个线程,让他们能够在各自的子 线程中完成自己的任务。

在开发中对于松耦合服务,需要让他们尽量的并行执行,避免阻塞出现,从而最大化程序的吞吐量。

同步API

同步API就是传统的方法调用,以串行的形式实现。比方当调用多个方法时,第一个方法执行时,其他方法会产生 等待,当第一个方法执行完之后,取得返回结果后,后续的方法在继续逐一执行。对于这种调用方式,一般称之为阻塞式调用。

异步API

异步API采用并行的形式完成方法执行,当第一个方法在执行并且未执行完时,开始另外的线程去执行其他的方法,这种方式称之为 非阻塞式调用。值得注意的是,每一个任务线程会将自己的结果返回给调用方, 方式有两种,要么是回调,要么通过调用方再次执行一个“等待->结束”的方法。

StampedLock锁

StampedLock类是在JDK8引入的一把新锁,其是对原有ReentrantReadWriteLock读写锁的增强,增加了一个乐 观读模式,内部提供了相关API不仅优化了读锁、写锁的访问,也可以让读锁与写锁间可以互相转换,从而更细粒 度的控制并发。

Oracle 官方示例:https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/StampedLock.html

  • Title: 聊聊 JDK8 能提升开发效率的方式
  • Author: 薛定谔的汪
  • Created at : 2019-06-30 18:01:54
  • Updated at : 2023-11-17 19:37:37
  • Link: https://www.zhengyk.cn/2019/06/30/java/jdk8-new-feature/
  • License: This work is licensed under CC BY-NC-SA 4.0.