JDK1.8 特性实践
个人喜欢看源码学习,但是随着JDK1.8趋于稳定,越来越多的架构迁移到JDK1.8上,所以对于之前不是很关注的1.8语言特性给源码阅读带来了很大的困扰,所以这里对新特性(其实已经很老了)通过代码进行实践。
每一种新特性都是为了解决问题而添加的,所以从问题出发进行实践学习。此部分只着重描述语言层次的新特性或其他特性的概述。更全的特性介绍请参见:
- 中文:https://www.jianshu.com/p/5b800057f2d8
- 英文:https://www.javacodegeeks.com/2014/05/java-8-features-tutorial.html
- 官方:http://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html
Lambda表达式
问题
匿名内部类编写时语法繁琐、以及其内部this关键字不易理解。同时提供Java不支持函数式编程。
概念
Lambda表达式是Java拥抱函数编程的开始,可以理解为替换匿名内部类的一种方式,但是被替换的匿名内部类实现的接口只能有一个未实现方法。表达式结构为:
(arg1,...,argN) -> {
//some process
return xxx;
}
即:
参数列表 -> 语句块
-
参数列表的中的参数用逗号隔开,括起参数列表的括号在只有一个参数时可以省略,无参数和多个参数时必须添加,例如
new Thread(() -> {System.out.println("aaa");}).start();
-
参数列表中的参数可以指定数据类型也可以不指定,例如:
LambdaFunctionCaller caller = new LambdaFunctionCaller(); caller.test((String e1,String e2) -> { if(e1.equals(e2)){ return true; }else{ return false; } });
-
语句块只有一条语句时可以省略花括号,多条语句不能省略。
-
语句块可以有返回值,是否有返回值由函数接口定义决定。
-
既然是匿名内部类,所以Lambda表达式既可以作为方法的参数传入,也可以作为方法的返回传出,例如:
public LambdaFunctionInterface generateLambda(){ return (String e1,String e2) -> { if(e1.equals(e2)){ return true; }else{ return false; } }; }
实现
Lambda表达式实质上是通过LambdaMetafactory(通过javap分析class文件得知)将Lambda表达式编译为一个函数接口的实现类,与匿名内部类不同的是Lambda生成的类不会序列化到磁盘,只会动态加载到JVM。
既然是将Lambda表达式编译为接口的实现类,那么为了简单接口只能有一个未实现方法(多个的话还需要处理Lambda表达式需要编译为哪个方法上),同时为了避免接口被修改为多个方法,添加了@FunctionalInterface注解来保证在编译阶段验证此接口只能有一个未实现方法。
其实上边一段话就是函数接口的由来,其定义为:
- 接口只能定义一个未实现方法(新引入的默认方法和静态方法不算未实现方法)
- 接口必须有@FunctionalInterface注解
- 请注意,接口是可以继承或多继承的。有@FunctionalInterface注解且多个未实现方法时,编译会报错。
更多的原理说明分析,请参见Java 8 动态类型语言Lambda表达式实现原理分析。
例子
//定义函数接口
package test;
@FunctionalInterface
public interface LambdaFunctionInterface {
boolean test(String s1,String s2);
}
package test;
//调用的例子
public class LambdaFunctionCaller {
public static void main(String[] args) {
LambdaFunctionCaller caller = new LambdaFunctionCaller();
caller.test((String e1,String e2) -> {
if(e1.equals(e2)){
return true;
}else{
return false;
}
});
}
public void test(LambdaFunctionInterface in){
System.out.println(in.test("aaa", "bbbb"));
}
public LambdaFunctionInterface generateLambda(){
return (String e1,String e2) -> {
if(e1.equals(e2)){
return true;
}else{
return false;
}
};
}
}
接口默认方法及接口静态方法
问题
维护老项目时,需要加入新特性我们经常会扩展原有的接口,这时为了避免对已有接口实现类的影响,我们可能会使用新接口继承老接口、抽象的方式来进行原有接口的扩展。同时,由于引入的了Lambda表达式,导致函数接口只能有一个未实现方法,这就会带来需要大量的接口造成接口膨胀。
另外,在某些情况下为了加入静态方法我们不得不在接口和实现类之间加入抽象类来作为静态方法的载体,调用静态方法时依赖于抽象类。这样做实质上违反了面向接口编程的思想。
概念简介
-
接口默认方法:
接口默认方法实质上是为接口方法提供了默认实现,接口实现类可以重写此方法也可以不重写,避免扩展接口时影响已有实现类。复用了default关键字,类似于操作符重载。
default void defaultMethod(){ System.out.println("I'm a defaultMethod ! In interface !"); }
-
接口静态方法:
接口静态方法为接口提供了静态方法支持,使得我们在构建接口实现类时不需要借助其他类,直接在接口静态方法就可以实现(还有其他使用场景)。
static MethodInterface staticMethod(String name){ System.out.println("I'm a simple create Method ! Create by " + name); switch (name) { case "one": return new MethodInterfaceOne(); case "two": return new MethodInterfaceTwo(); default: return new MethodInterfaceOne(); } }
实现
从javap看,字节码层面上没有特殊的内容(Lambda有明显的特殊内容)。说明接口默认方法和接口静态方法是字节码层面就支持的,相比Lambda要高效。
例子
package test;
import test.MethodInterfaceCaller.MethodInterfaceTwo;
import test.MethodInterfaceCaller.MethodInterfaceOne;
//接口
public interface MethodInterface {
default void defaultMethod(){
System.out.println("I'm a defaultMethod ! In interface !");
}
//这个方法叫staticCreate更好点
static MethodInterface staticMethod(String name){
System.out.println("I'm a simple create Method ! Create by " + name);
switch (name) {
case "one":
return new MethodInterfaceOne();
case "two":
return new MethodInterfaceTwo();
default:
return new MethodInterfaceOne();
}
}
}
package test;
//调用示例
public class MethodInterfaceCaller{
public static void main(String[] args) {
//The static method
MethodInterface one = MethodInterface.staticMethod("one");
MethodInterface two = MethodInterface.staticMethod("two");
//default method
one.defaultMethod();
two.defaultMethod();
}
public static class MethodInterfaceOne implements MethodInterface{
}
public static class MethodInterfaceTwo implements MethodInterface{
@Override
public void defaultMethod() {
System.out.println("I'm not a defaultMethod ! Not in interface !");
System.out.println("But I can call super defaultMethod : MethodInterface.super.defaultMethod() : ");
MethodInterface.super.defaultMethod();
}
}
}
方法引用
问题
本质上,方法引用本不是为了解决已有问题,而是Lambda表达式的一种扩展:为语句块内只调用一个已有方法且调用方法返回值与函数接口方法返回值一致的Lambda表达式提供一种简写方法。
概念
方法引用本质上是Lambda表达式的一种简写方式。有四种方式,具体请参见例子。
实现
同Lambda表达式。
例子
package test;
//定义函数接口
@FunctionalInterface
public interface MethodRefrencesInterface {
void test(String a,String b);
}
package test;
//定义方法载体类
public class MethodRefrences {
public MethodRefrences(String a,String b) {
System.out.println("MethodRefrences : "+a+","+b);
}
public MethodRefrences(){
}
public static boolean staticMethod(String a,String b){
System.out.println("staticMethod");
if(a.equals(b)){
return true;
}else{
return false;
}
}
public void normalMethod1(){
System.out.println("normalMethod1");
}
public boolean normalMethod2(String a,String b){
System.out.println("normalMethod2");
if(a.equals(b)){
return true;
}else{
return false;
}
}
}
package test;
import java.io.IOException;
//例子
public class MethodRefrenceseCaller {
public static void main(String[] args) throws IOException {
MethodRefrenceseCaller caller = new MethodRefrenceseCaller();
caller.test(MethodRefrences::new);
caller.test(MethodRefrences::staticMethod);
//这里测试不通过,从网上查找需要借用到Function或BiFunction函数接口,但是不是很明白为什么官方的例子里Comparator可以映射为Function,待后续补充
//caller.test(MethodRefrences::normalMethod1);
MethodRefrences refrences = new MethodRefrences();
caller.test(refrences::normalMethod2);
System.in.read();
}
public void test(MethodRefrencesInterface in){
in.test("aaa", "bbbb");
}
}
补充:
对于通过类直接调用示例方法的方法引用,官方描述的是Reference to an Instance Method of an Arbitrary Object of a Particular Type,网上的资料有,但是个人还是不太明白:
- https://stackoverflow.com/questions/23533345/what-does-an-arbitrary-object-of-a-particular-type-mean-in-java-8
- http://moandjiezana.com/blog/2014/understanding-method-references/
重复注解
问题
注解目前是Java使用面十分广的技术,在不考虑对外交互、可读方面的要求时,注解完全可以替代配置。但是之前的注解不支持重复注解,比如有一个注解表示别名,那么我们就只能通过此注解起一个别名,或者使用分隔符分开多个别名,在解析时进行特殊处理。
概念
重复注解就是允许某个注解可以在同一个位置多次注解。
实现
为了引入重复注解,引入了一个新的原生注解:@Repeatable,此注解作用于可以重复使用的注解,值为内部包含返回可重复使用注解数组value()方法的另一个注解,另一个注解作为可重复使用注解的容器,同时Class类加入了方法:getAnnotationsByType(),用于返回某个注解的所有实现。
例子
package test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//重复注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RepeatableMultiTest.class) //说明此注解可以在同一位置重复注解,且容器为RepeatableMultiTest
public @interface RepeatableTest {
String value();
}
package test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//容器注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatableMultiTest {
//返回值必须为可重复注解数组
RepeatableTest[] value();
}
package test;
@RepeatableMultiTest({@RepeatableTest("value1"),@RepeatableTest("value2")})
public class RepeatableCaller1 {
public static void main(String[] args) {
System.out.println("MultiRepeatableTest Annotation of RepeatableCaller1 count is " +
RepeatableCaller1.class.getAnnotationsByType(RepeatableMultiTest.class).length);
//MultiRepeatableTest Annotation of RepeatableCaller count is 1
System.out.println("RepeatableTest Annotation of RepeatableCaller1 count is " +
RepeatableCaller1.class.getAnnotationsByType(RepeatableTest.class).length);
//RepeatableTest Annotation of RepeatableCaller count is 2
System.out.println("MultiRepeatableTest Annotation of RepeatableCaller2 count is " +
RepeatableCaller2.class.getAnnotationsByType(RepeatableMultiTest.class).length);
//MultiRepeatableTest Annotation of RepeatableCaller2 count is 1
System.out.println("RepeatableTest Annotation of RepeatableCaller2 count is " +
RepeatableCaller2.class.getAnnotationsByType(RepeatableTest.class).length);
//RepeatableTest Annotation of RepeatableCaller2 count is 2
System.out.println("RepeatableMultiTest Annotation of RepeatableCaller3 count is " +
RepeatableCaller3.class.getAnnotationsByType(RepeatableMultiTest.class).length);
//RepeatableMultiTest Annotation of RepeatableCaller3 count is 0
System.out.println("RepeatableTest Annotation of RepeatableCaller3 count is " +
RepeatableCaller3.class.getAnnotationsByType(RepeatableTest.class).length);
//RepeatableTest Annotation of RepeatableCaller3 count is 1
System.out.println("RepeatableMultiTest Annotation of RepeatableCaller4 count is " +
RepeatableCaller4.class.getAnnotationsByType(RepeatableMultiTest.class).length);
//RepeatableMultiTest Annotation of RepeatableCaller3 count is 0
System.out.println("RepeatableTest Annotation of RepeatableCaller4 count is " +
RepeatableCaller4.class.getAnnotationsByType(RepeatableTest.class).length);
//RepeatableTest Annotation of RepeatableCaller3 count is 1
}
@RepeatableTest("value1")
@RepeatableTest("value2")
static class RepeatableCaller2{
}
@RepeatableTest("value1")
static class RepeatableCaller3{
}
@RepeatableTest("value1")
@RepeatableMultiTest({@RepeatableTest("value2"),@RepeatableTest("value2")})
static class RepeatableCaller4{
}
}
其他
###新类库
- Optional,结合Lambda表达式处理空指针异常
- stream类库(java.util.stream包),用于支持顺序、并行处理,不只是对集合处理,可以支持Stream接口相关的类和方法,例如ava.nio.file.Files.lines(Path, Charset)等。详细内容请参阅:Java 8 中的 Streams API 详解
- time类库(java.time包),参照Joda-Time对日期、时间、时区的设计,新的时间类库实现。
- nashorn.jar,通过SPI的方式加入Nashorn JavaScript处理引擎。服务名:JavaScript
- Base64,加入了Base64的原生支持,这个类貌似是个中国人写的哦(@author Xueming Shen)
JVM
- 删除了持久代(PermGen space),使用Metaspace代替。
- 可以支持硬件AES加密。
工具及其他
- jss,执行JS
- jdeps,类、jar依赖分析工具
- javac支持使用-parameters参数使class文件中保留方法参数名