之前在网上看了很多很多的新特性相关的东西,每次看完都深深的感觉
JDK
变强了,一定要用上,但是没有用到的特性过一段时间又会完美的忘掉,所以打算总结一下,定期过来补充补充,供自己今后复习使用。
JDK8
的新特性
Oracle
公司于 2014
年 3
月 18
日发布 Java 8
,其中包含了一部分JavaFX
的增强,不列入文章。
函数式接口
JDK8
提供了函数式接口,用@FunctionalInterface
注解来标明,标明为函数式接口后,接口中就只能写一个抽象方法,并且可以使用lambda
表达式来创建这个接口的实现类。这也就意味着lambda
其实是匿名内部类的简写形式。
JDK
提供了四种函数式接口,这四种是(注意是四种,不是四个):
// 消费型接口,只吃不吐
Consumer<String> consumer = x -> System.out.println(x);
// 补给型接口,只吐不吃
Supplier<String> supplier = () -> "Hello";
// 函数型接口,又吃又吐
Function<String, Integer> function = x -> x.length();
// 断言型接口,只做裁判
Predicate<String> predicate = x -> "Hello".equals(x);
Lambda
表达式
Lambda
允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
// 完整的使用方式
(String x) -> {
System.out.println(x);
}
// 括号中的参数数据类型可以不写
(x) -> {
System.out.println(x);
}
// 括号中只有一个参数可以省略括号
x -> {
System.out.println(x);
}
// 函数体只有一个语句,可以省略{},但是要注意{}, return,分号必须一起省略
x -> System.out.println(x)
接口的默认方法和静态方法
默认方法就是一个在接口里面用default
修饰的方法,静态方法和普通的静态方法一样。
区别:
-
默认方法通过实例调用,静态方法通过接口名调用(好像是理所应当)
-
默认方法可以被继承,实现类可以直接使用接口默认方法,也可以覆写接口的默认方法。
-
静态方法不能被继承,实现类不能覆写接口的静态方法,只能使用接口名调用。
为什么要产生默认方法和静态方法,看了看网上的解释,记录一下吧:
- 默认方法的产生是为了避免突然产生了一个需求,并且每个子类都实现一样的功能,连代码都一样,工作量大,并且子类自己去实现也不能保证每个子类都能实现正确。不过我感觉增加了默认方法,这倒解决了Java中抽象类不能多继承的问题。
- 静态方法,常用于做静态工厂方法。
方法引用
方法引用提供了非常有用的语法,可以直接引用已有Java
类或对象(实例)的方法或构造器。与lambda
联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用的几种形式:
// 基本的四种方式
对象名::成员方法名 // containingObject::instanceMethodName
类名::静态方法名 // ContainingClass::staticMethodName
类名::实例方法名 // ContainingType::methodName
类名::new // ClassName::new
// 四种方式衍生的用法
数组::new
super::父类成员方法名
this::本类成员方法名
重复注解
重复注解在开发中十分常用,而且也迫切的使用到,比如说要为一个表建立索引,写成下面的形式:
@Table(name = "ccb_user_info",
indexes = {
@Index(name = "longitudeIndex", columnList = "longitude"),
@Index(name = "latitudeIndex", columnList = "latitude")
}
)
期望中想写成下面的形式:
@Table(name = "ccb_user_info")
@Index(name = "longitudeIndex", columnList = "longitude")
@Index(name = "latitudeIndex", columnList = "latitude")
Java8
提供了这样的支持,使用方法如下:
public class Test {
public static void main(String[] args) {
// 使用新增的反射方法来获取可重复注解
Index[] indexs = UseRepeatableAnnotation.class.getAnnotationsByType(Index.class);
for (Index index : indexs) {
System.out.println(index);
}
}
}
/**
* 使用可重复注解
*/
@Index("第一个索引")
@Index("第二个索引")
class UseRepeatableAnnotation {
}
/**
* 定义重复注解的容器
*/
@Retention(RetentionPolicy.RUNTIME)
@interface Indexs {
Index[] value();
}
/**
* 要重复使用的注解
*/
@Repeatable(Indexs.class) // 使用容器
@Retention(RetentionPolicy.RUNTIME)
@interface Index {
String value();
}
运行结果:
@com.cy.lambda.Index(value="第一个索引")
@com.cy.lambda.Index(value="第二个索引")
对泛型进行类型推断
用一行代码形容,就是下面的样子:
Map<String, String> map = new HashMap<>();
后面的泛型可以不写了。
拓宽注解/@Target
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
ElementType.TYPE_USE
和ElementType.TYPE_PARAMETER
是Java 8新增的两个注解,用于描述注解的使用场景。Java 语言也做了对应的改变,以识别这些新增的注解。
ElementType.TYPE_USE
:可以在任何的类型前面使用,尽管是int
这样的基础数据类型
ElementType.TYPE_PARAMETER
:可以在泛型前面使用的注解
可以获取参数名称
在反射中可以直接获取参数的名称了:
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class Test {
public static void main(String[] args) throws NoSuchMethodException {
Method method = Test.class.getMethod("method", String.class, String.class);
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter.getName());
}
}
public static void method(String happy, String newYear) {
}
}
这是不够的,因为这个特性默认是关闭的,所以需要编译的时候加入-parameters
参数
javac -parameters Test.java
java Test
运行结果:
happy
newYear
Optional
类
Optional
类已经成为 Java 8 类库的一部分,用来解决空指针异常,这部分内容过于简单了,直接看源码上手更快
Stream API
新添加的Stream API
(java.util.stream
) 把真正的函数式编程风格引入到Java
中。
Stream
的使用方法很简单,写出来的代码也很简单,但是完成的功能是非常复杂的。
它包含了三种操作:创建操作,中间操作,终止操作
创建操作:可以用两种方式来判断操作是不是创建操作
- Stream中的静态方法,返回Stream对象
- 其他对象的某个方法,返回Stream对象
中间操作:Stream对象执行操作后依然返回Stream对象,属于中间操作
终止操作:Stream对象执行操作后,返回其他类型数据,或者void,这个操作就是终止操作
知道了怎么区分这些操作,就可以通过代码试探里面的某些API
如何去使用了。
列举常用的一些方法:
forEach
,count
,filter
,limit
,skip
,map
,sorted
,distinct
,allMatch
,anyMatch
,noneMatch
,findFirst
,findAny
,max
,min
,reduce
,concat
,mapToInt
,collect
等
parallel
可以将Stream
转换成并行流,它用Fork/Join框架,但是如果不正确的使用,会造成线程不安全的问题,造成的结果就是数据不准确,还有会十分耗费CPU资源,如果不是什么特别重要的操作,不建议使用parallel
,因为会占用CPU
,影响其他操作的执行。I/O密集型的操作不要使用Fork/Join
,比如说下载。如果十分注重顺序,不要使用parallel
。
通过collect
方法结合Collectors
工具类可以做到以下操作(不止)
- 转集合:
toList
,toSet
,toCollection
,toArray
- 聚合计算:
maxBy
,minBy
,summingInt
,averagingInt
,counting
等 - 分组:
groupingBy
可以做到一到多级分组 - 分区:
partitioningBy
可以做到分区,也就是true
和false
两种区域 - 拼接:
joining
可以做到拼接
Date Time API
旧版的Date Time API
设计很差(很多包都有Date),线程不安全(例如:SimpleDateFormat),时区处理麻烦
新版的API
位于java.time
LocalDate
:包含年月日
LocalTime
:包含时分秒毫秒纳秒
LocalDateTime
:包含年月日时分秒毫秒
DateTimeFormatter
:日期时间格式化
Instant
:时间戳,一个特定的时间瞬间
Duration
:用于计算两个时间的距离
Period
:用于计算两个日期的距离
TemporalAdjuster
:时间调整器,例如下个月的第一天,TemporalAdjusters
提供了很多调整方式
ZoneDateTime
:包含时区
引用《阿里巴巴Java开发手册》上的一句话
说明:如果是JDK8的应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormatter,官方给出的解释:simple beautiful strong immutable thread-safe。
SpringMVC
,SpringDataJPA
也可以使用这些日期时间API
,只需要做类型转换就可以了
新的编译工具,如:Nashorn
引擎 jjs
、 类依赖分析器jdeps
。
Nashorn, JavaScript
引擎
Java 8提供了一个新的Nashorn
Javascript引擎,它允许我们在JVM上运行特定的Javascript应用。
但是JDK11
发布后,看到了JEP335,将要废弃Nashorn
,因为难以维护。
想到了JavaFX中的WebView就是用来加载网页的,当然就要执行JavaScript,同时可以使用Java执行JS,也可以JS执行Java,才能让Java和WebView中的数据进行交互,这里不具体谈这个WebView。
【参考文章】介绍 Nashorn —— Java 8 JavaScript 引擎
Oracle
实验室提供了一个全栈虚拟机GraalVM
也可以用Java代码来运行JavaScript
代码
Base64
Base64.getEncoder()
用来获取编码成Base64
的对象
Base64.getDecoder()
用来获取将Base64
解码成明文的对象