前言:越来越多的项目已经使用 Java 8了,毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和 JVM 等方面的十多个新特性。Java 8是一次变化巨大的更新,耗费了工程师大量的时间,还借鉴了很多其它语言和类库。很多语言从一开始就支持了 Lambda 表达式,像函数式思维语言 Groovy,Scala 等。Java8最大的卖点就是加入了Lambda表达式,以便支持函数式编程。
JDK8新特性:
1.Lambda表达式和函数式接口
2.接口的默认方法与静态方法
3.引入Optional
4.新增方法引用格式
5. 注解相关的改变
6.新增Stream类
7.新的日期API
8.使用Base64
9.支持并行(parallel)数组
10.对并发类(Concurrency)的扩展
11.JavaScript引擎Nashorn
一、Lambda表达式和函数式接口
Java7及以前,我们一直都是通过匿名内部类把方法或函数当做参数传递,传统匿名内部类缺点是代码臃肿难以阅读。
Java 8中加入Lambda表达式是一个令人兴奋的新特性,虽然这个新特性来得比较迟了,Java算是主流开发语言中最后一个支持函数式编程的语言。
1、Lambad表达式简介
Lambda 表达式也可称为闭包,是推动 Java 8 发布的最重要新特性,它将函数式编程引入了Java。
Lambda表达式本质上是一个匿名方法。Lambda表达式允许把函数作为一个方法的参数(函数作为参数传递进方法中)或者把代码看成数据,使用 Lambda 表达式可以使代码变的更加简洁紧凑。
Lambda语法格式:
- 用逗号分隔的参数列表
->
符号- 函数体
在最简单的形式中,一个lambda表达式可以由:用逗号分隔的参数列表、–>符号、函数体三部分表示,在某些情况下Lambda的函数体会更加复杂,这时可以把函数体放到在一对花括号中,就像在Java中定义普通函数一样。Lambda可以引用类的成员变量与局部变量(如果这些变量不是final的话,它们会被隐含的转为final,这样效率更高)。Lambda 的返回值和参数类型由编译器推理得出,不需要显示定义,如果只有一行代码可以不写 return 语句
首先看看在老版本的Java中是如何排序字符串的:
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return bpareTo(a);
}
});
只需要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。
在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:
Collections.sort(names, (String a, String b) -> {
return bpareTo(a);
});
看到了吧,代码变得更短且更具有可读性,但是实际上还可以写得更短,对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字:
Collections.sort(names, (String a, String b) -> bpareTo(a));
但是你还可以写得更短点,Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。
Collections.sort(names, (a, b) -> bpareTo(a));
2 、Lambd表达式接口使用场景
①使用lambda表达式替换内部匿名类
而实现Runnable接口是内部匿名类的最好示例,通过() -> {}代码块替代了整个匿名类。
Java 8之前:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Before Java8, too much code for too little to do");
}
}).start();
Java 8方式:
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();
Lambda 表达式免去了使用匿名对象的麻烦,并且给予Java简单但是强大的函数化的编程能力。
②lambda表达式允许把代码看作数据
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
等价于
List<String> list = Arrays.asList( "a", "b", "d" );
for(String e:list){
System.out.println(e);
}
如果语句块比较复杂,使用 {} 包起来
Arrays.asList( "a", "b", "d" ).forEach( e -> {
String m = "9420 " e;
System.out.print( m );
});
3、函数式接口
为了使现有Java友好的支持Lambda表达式,Java 8引入了函数式接口的概念。函数式接口就是接口里面必须有且只有一个抽象方法的普通接口,像这样的函数式接口在的地方可以用Lambda表达式代替,使节约代码更简洁易读。
在可以使用lambda表达式的地方,方法声明时必须包含一个函数式的接口。 任何函数式接口都可以使用lambda表达式替换,例如:ActionListener、Comparator、java.lang.Runnable与java.util.concurrent.Callable是函数式接口比较典型的例子。
函数式接口是容易出错的:如有某个人在接口定义中增加了另一个抽象方法,这时,这个接口就不再是函数式的了,并且在lambda代替它的编译过程也会失败。为了克服函数式接口的这种脆弱性并且能够明确声明接口作为函数式接口的意图,Java 8增加了一种特殊的注解@FunctionalInterface,但是接口的默认方法与静态方法并不影响函数式接口的契约,可以任意使用。
@FunctionalInterface
public interface Functional {
void method();
}
解释:
Lambda表达式是如何在Java的类型系统中表示的呢?每一个Lambda表达式都对应一个函数式接口。而函数式接口是指仅仅只包含一个抽象方法的接口,每一个该类型的Lambda表达式都会被匹配到这个抽象方法。因为 默认方法和静态方法不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
我们可以将lambda表达式当作任意只包含一个抽象方法的函数式接口,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
二、接口的默认方法与静态方法
Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。
我们可以在接口中定义默认方法,使用default关键字,并提供默认的实现。所有实现这个接口的类都会接受默认方法的实现,除非子类覆盖重写接口的默认方法。
public interface Vehicle
{
default void print(){
System.out.println("我是一辆车!");
}
}
我们还可以在接口中定义静态方法,使用static关键字,也可以提供实现。例如:
public interface StaticFunctionInterface {
static String staticFunction() {
return "static function";
}
}
接口的默认方法和静态方法的引入,以后我们再也不用在每个实现类中都写重复的代码了。
三、Optional
Java 8 引入一个很有趣的特性是 Optional 类,Optional 类主要解决的问题是臭名昭著的空指针异(NullPointerException)
Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现。
Java 8引入Optional类来防止空指针异常,Optional类最先是由Google的Guava项目引入的。Optional类实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional 类的引入很好的解决空指针异常,Optional提供很多有用的方法,这样我们就不用显式进行null值判断(null的防御性检查)。
我们来看一段代码:
public static String getGender(Student student)
{
if(null == student)
{
return "Unkown";
}
return student.getGender();
}
这是一个获取学生性别的方法,方法入参为一个Student对象,为了防止student对象为null, 做了防御性检查:如果值为null,返回"Unkown"。
再看使用Optional优化后的方法:
public static String getGender(Student student)
{
return Optional.ofNullable(student).map(u -> u.getGender()).orElse("Unkown");
}
可以看到,Optional类结合lambda表达式的使用能够让开发出的代码更简洁和优雅
Optional 类相关API
尽量避免在程序中直接调用Optional对象的get()和isPresent()方法,避免使用Optional类型声明实体类的属性。
(1)Optional.of(T t) : 创建一个 Optional 实例
(2)Optional.empty() : 创建一个空的 Optional 实例
(3)Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
(4)isPresent() : 判断是否包含值 orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
(5)orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
(6)map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
(7)flatMap(Function mapper):与 map 类似,要求返回值必须是Optional
1.创建optional对象,一般用ofNullable()而不用of():
(1)empty() :用于创建一个没有值的Optional对象:Optional<String> emptyOpt = Optional.empty();
(2)of() :使用一个非空的值创建Optional对象:Optional<String> notNullOpt = Optional.of(str);
(3)ofNullable() :接收一个可以为null的值:Optional<String> nullableOpt = Optional.ofNullable(str);
2.判断Null:
(1)isPresent():如果创建的对象实例为非空值的话,isPresent()返回true,调用get()方法会返回该对象,如果没有值,调用isPresent()方法会返回false,调用get()方法抛出NullPointerException异常。
3.获取对象:
(1)get()
4.使用map提取对象的值,如果我们要获取User对象中的roleId属性值,常见的方式是先判断是否为null然后直接获取,但使用Optional中提供的map()方法可以以更简单的方式实现
5.使用orElse方法设置默认值,Optional类还包含其他方法用于获取值,这些方法分别为:
(1)orElse():如果有值就返回,否则返回一个给定的值作为默认值;
(2)orElseGet():与orElse()方法作用类似,区别在于生成默认值的方式不同。该方法接受一个Supplier<? extends T>函数式接口参数,用于生成默认值;
(3)orElseThrow():与前面介绍的get()方法类似,当值为null时调用这两个方法都会抛出NullPointerException异常,区别在于该方法可以指定抛出的异常类型。
6.使用filter()方法过滤,filter()方法可用于判断Optional对象是否满足给定条件,一般用于条件过滤,在代码中,如果filter()方法中的Lambda表达式成立,filter()方法会返回当前Optional对象值,否则,返回一个值为空的Optional对象。
想知道更多用法的看看这个博客,感谢前辈:【Java8新特性】Optional详解
四、方法引用
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象的方法或构造器。方法引用通常与Lambda表达式联合使用,可以使语言的构造更紧凑简洁,减少冗余代码。
举例:
定义了4个方法的Car这个类作为例子,区分Java中支持的4种不同的方法引用。
public static class Car {
public static Car create( final Supplier< Car > supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
第一种方法引用是构造器引用,它的语法是Class::new,或者更一般的Class< T >::new。请注意构造器没有参数。
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
第二种方法引用是静态方法引用,它的语法是Class::static_method。请注意这个方法接受一个Car类型的参数
cars.forEach( Car::collide );
第三种方法引用是特定类的任意对象的方法引用,它的语法是Class::method。请注意,这个方法没有参数。
cars.forEach( Car::repair );
第四种方法引用是特定对象的方法引用,它的语法是instance::method。请注意,这个方法接受一个Car类型的参数
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
五、注解相关改变
1、引入重复注解@Repeatable
自从Java 5引入了注解机制,这一特性就变得非常流行并且广为使用。但是在Java 5中使用注解有一个限制,即相同的注解在同一位置只能声明一次。Java 8引入重复注解,这样相同的注解在同一地方也可以声明多次。
重复注解机制本身必须用@Repeatable注解。事实上,这并不是语言层面上的改变,更多的是编译器的技巧,Java 8在编译器层做了优化,相同注解会以集合的方式保存,因此底层的原理并没有变化。
2、扩展注解的支持
Java 8扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。
六、Stream API
Java 8 API添加了一个新的抽象称为流。Stream把真正的函数式编程风格引入到Java中,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API极大简化了集合框架的处理,这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
Stream API是把真正的函数式编程风格引入到Java中。其实简单来说可以把Stream理解为MapReduce,当然Google的MapReduce的灵感也是来自函数式编程。她其实是一连串支持连续、并行聚集操作的元素。从语法上看,也很像linux的管道、或者链式编程,代码写起来简洁明了。
Stream详解参考这位前辈:【Java8新特性】Stream API详解
七、Date/Time API (JSR 310)
Java 8新的Date-Time API (JSR 310)受开源的Joda-Time库的影响,提供了新的java.time包,可以用来替代 java.util.Date和java.util.Calendar。一般会用到Clock、LocaleDate、LocalTime、LocaleDateTime、ZonedDateTime、Duration这些类,对于时间日期的改进还是非常不错的。
时间大致可以分为三个部分:日期、时间、时区
其中日期又细分为年、月、日;时间又细分为时、分、秒
一般机器时间用从 1970-01-01T00:00 到现在的秒数来表示时间; 这里纠正大部分人犯的一个错误概念,时间戳指的是秒数,而不是毫秒数。
几乎所有的时间对象都实现了 Temporal 接口,所以接口参数一般都是 Temporal
Instant: 表示时间线上的一个点,参考点是标准的Java纪元(epoch),即1970-01-01T00:00:00Z(1970年1月1日00:00 GMT)
LocalDate: 日期值对象如 2019-09-22
LocalTime: 时间值对象如 21:25:36
LocalDateTime: 日期 时间值对象
ZoneId: 时区
ZonedDateTime: 日期 时间 时区值对象
DateTimeFormatter: 用于日期时间的格式化
Period: 用于计算日期间隔
Duration: 用于计算时间间隔
八、Base64
在Java 8中,Base64编码成为了Java类库的标准。Base64类同时还提供了对URL、MIME友好的编码器与解码器。
对于 Base64 终于不用引用第三方包了,使用 java 库就可以完成
// 编码
final String encoded = Base64.getEncoder().encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
// 解码
final String decoded = new String( Base64.getDecoder().decode( encoded ),StandardCharsets.UTF_8 );
九、并行(parallel)数组
并行(parallel)数组:支持对数组进行并行处理,主要是parallelSort()方法,它可以在多核机器上极大提高数组排序的速度。
Java 8增加了大量的新方法来对数组进行并行处理。可以说,最重要的是parallelSort()方法,因为它可以在多核机器上极大提高数组排序的速度。下面的例子展示了新方法(parallelXxx)的使用。
上面的代码片段使用了parallelSetAll()方法来对一个有20000个元素的数组进行随机赋值。然后,调用parallelSort方法。这个程序首先打印出前10个元素的值,之后对整个数组排序。这个程序在控制台上的输出如下(请注意数组元素是随机生产的):
十、并发(Concurrency)
并发(Concurrency):在新增Stream机制与Lambda的基础之上,加入了一些新方法来支持聚集操作。
在新增Stream机制与lambda的基础之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法来支持聚集操作。同时也在java.util.concurrent.ForkJoinPool类中加入了一些新方法来支持共有资源池(common pool)(请查看我们关于Java 并发的免费课程)。
新增的java.util.concurrent.locks.StampedLock类提供一直基于容量的锁,这种锁有三个模型来控制读写操作(它被认为是不太有名的java.util.concurrent.locks.ReadWriteLock类的替代者)。
在java.util.concurrent.atomic包中还增加了下面这些类:
1.DoubleAccumulator
2.DoubleAdder
3.LongAccumulator
4.LongAdder
十一、JavaScript引擎Nashorn
Nashorn引擎jjs:基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。
Nashorn允许在JVM上开发运行JavaScript应用,允许Java与JavaScript相互调用。
补充:
除了这十大新特性之外,还有另外的一些新特性:
更好的类型推测机制:Java 8在类型推测方面有了很大的提高,这就使代码更整洁,不需要太多的强制类型转换了。
编译器优化:Java 8将方法的参数名加入了字节码中,这样在运行时通过反射就能获取到参数名,只需要在编译时使用-parameters参数。
类依赖分析器jdeps:可以显示Java类的包级别或类级别的依赖。
JVM的PermGen空间被移除:取代它的是Metaspace(JEP 122)。
当前2020年6月19日:Java最新版本为Java14,于2020年3月17日正式发布。
如果想要了解Java8以后的版本,可以参考以下两篇博客:
聊聊 Java8 以后各个版本的新特性
Java14发布,16大新特性,代码更加简洁明快
参考链接:
Java版本:JDK8的十大新特性介绍
JAVA8十大新特性详解(精编)
更多推荐
Java 8版本的十大新特性
发布评论