Java8的新特性
Java8的新特性
1. 简介。1
Java 8于2014年3月19日发布正式版,是自Java5以来最具革命性的版本,在语言、编译器、类库、开发工具以及Java虚拟机等方面都带来了不少新特性。
2. Java语言的新特性
2.1 函数式接口
如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。这样的接口可以被隐式转换为lambda表达式。java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的两个例子。在实际使用过程中,如有某个人在接口定义中增加了另一个方法,这时,这个接口就不再是函数式的了,并且编译过程也会失败。为了克服函数式接口的这种问题,Java8增加了一种特殊的注解@FunctionalInterface(Java8中所有类库的已有接口都添加了@FunctionalInterface注解)。
@FunctionalInterface //通过该注解声明该接口为函数式接口
public interface Functional {
void method();
}
2.2 Lambda表达式
Lambda允许把函数作为一个方法的参数.在最简单的形式中,一个lambda可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。
lambda表达式的结构
- 参数可以是零个或多个
- 参数类型可指定,可省略(根据表达式上下文推断)
- 参数包含在圆括号中,用逗号分隔
- 表达式主体可以是零条或多条语句,包含在花括号中
- 表达式主体只有一条语句时,花括号可省略
- 表达式主体有一条以上语句时,表达式的返回类型与代码块的返回类型一致
- 表达式只有一条语句时,表达式的返回类型与该语句的返回类型一致
通常情况下,我们要设置一个监听事件,需要使用匿名内部类的写法如下:
button.addActionListener(new ActionListener) {
public void actionPerformed(ActionEvent e) {
ui.dazzle(e.getModifiers());
}
}
使用lambda表达式
//lambda表达式设置事件监听
button.setOnClickListener((e) -> System.out.print("aaa"));
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
参数e的类型可以省略,由编译器推测出来的。
如果函数有多行代码,可以将函数体放在花括号中。
Lambda可以引用类的成员变量与局部变量(如果这些变量不是final的话,它们会被隐含的转为final,这样效率更高)
编译器负责推导lambda表达式的类型,检查lambda表达式的类型和目标类型的方法签名(method signature)是否一致。当且仅当下面所有条件均满足时,lambda表达式才可以被赋给目标类型T:
- T是一个函数式接口
- lambda表达式的参数和T的方法参数在数量和类型上一一对应
- lambda表达式的返回值和T的方法返回值相兼容(Compatible)
- lambda表达式内所抛出的异常和T的方法throws类型相兼容
2.3 方法引用
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
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 );
//静态方法引用,它的语法是Class::static_method
cars.forEach( Car::collide );
//特定类的任意对象的方法引用,它的语法是Class::method
cars.forEach( Car::repair );
//特定对象的方法引用,它的语法是instance::method
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
再举个栗子:
interface StringFunc {
String func(String n);
}
class MyStringOps {
//静态方法: 反转字符串
public static String strReverse(String str) {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) {
result += str.charAt(i);
}
return result;
}
}
public class MethodRefDemo {
public static String stringOp(StringFunc sf, String s) {
return sf.func(s);
}
public static void main(String[] args) {
String inStr = "lambda add power to Java";
//一般写法
String outStr1 = stringOp(new StringFunc() {
@Override
public String func(String n) {
return MyStringOps.strReverse(n);
}
}, inStr);
//使用lambda表达式
String outStr2 = stringOp(n -> MyStringOps.strReverse(n), inStr);
//MyStringOps::strReverse 相当于实现了接口方法func(),strReverse参数个数和类型需要和stringOp保持一致
String outStr3 = stringOp(MyStringOps::strReverse, inStr);
}
}
2.4 默认方法和静态方法
默认方法让我们能给我们的软件库的接口增加新的方法,并且能保证对使用这个接口的老版本代码的兼容性。此外在Java8的接口中,不光能写默认方法,还能写静态方法.
public interface TimeClient {
void setTime(int hour, int minute, int second);
void setDate(int day, int month, int year);
//静态方法
static public ZoneId getZoneId (String zoneString) {
try {
return ZoneId.of(zoneString);
} catch (DateTimeException e) {
System.err.println("Invalid time zone: " + zoneString +
"; using default time zone instead.");
return ZoneId.systemDefault();
}
}
//默认方法
default ZonedDateTime getZonedDateTime(String zoneString) {
return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
}
默认方法关键字为default,以往我们只能在接口中定义只有声明没有实现的方法。有了默认方法,我们就不需要修改继承接口的实现类,就给接口添加了新的方法实现。
继承或实现一个含有默认方法的接口一般有以下三种情况:
- 继承的接口直接继承默认方法
- 重新声明默认方法,这样会使得这个方法变成抽象方法
- 重新定义默认方法,这样会使得方法被重写
2.5 扩展注解
Java8引入了类型注解和重复注解机制.
2.5.1 类型注解
在java 8之前,注解只能是在声明的地方所使用,java8开始,注解可以应用在任何地方.
public class Annotations {
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
public @interface NonEmpty {
}
public static class Holder< @NonEmpty T > extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
}
}
@SuppressWarnings( "unused" )
public static void main(String[] args) {
final Holder< String > holder = new @NonEmpty Holder< String >();
@NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();
}
}
类型注解被用来支持在Java的程序中做强类型检查。配合第三方插件工具可以在编译的时候检测出运行时异常。
ElementType.TYPE_USE和ElementType.TYPE_PARAMETER是两个新添加的用于描述适当的注解上下文的元素类型。在Java语言中,注解处理API也有小的改动来识别新增的类型注解。
2.4.2 重复注解
相同的注解可以在同一地方使用多次。Java8以前的版本使用注解有一个限制是相同的注解在同一位置只能使用一次。
@interface MyHints {
Hint[] value();
}
@Repeatable(MyHints.class)
@interface Hint {
String value();
}
//使用包装类当容器来存多个注解(旧版本方法),但可读性不是很好
@MyHints({@Hint("hint1"), @Hint("hint2")})
class Person {}
//多重注解
@Hint("hint1")
@Hint("hint2")
class Person {}
for (Hint hint : MyHints.class.getAnnotationsByType(Hint.class)) {
System.out.println(hint.value());
}
3. JavaScript引擎Nashorn
Nashorn,一个新的JavaScript引擎随着Java8一起公诸于世,它允许在JVM上开发运行某些JavaScript应用。允许Java与JavaScript相互调用。下面看一个例子:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );
System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
//输出:
//jdk.nashorn.api.scripting.NashornScriptEngine
//Result: 2
在 JavaScript 端调用 Java 方法
//先定义一个Java类
static String fun1(String name) {
System.out.format("Hi there from Java, %s", name);
return "greetings from java";
}
//JS中通过Java.type API来引用Java类
var MyJavaClass = Java.type('my.package.MyJavaClass');
var result = MyJavaClass.fun1('John Doe');
print(result);
// Hi there from Java, John Doe
// greetings from java
4. Java 类库的新特性
Java 8 通过增加大量新类,扩展已有类的功能的方式来改善对并发编程、函数式编程、日期/时间相关操作以及其他更多方面的支持。
4.1 Optional
Optional是一个容器对象,可以用它来封装可能为空的引用。Optional对象通过缺失值代表null。这个类有许多实用的方法来促使代码能够处理一些像可用或者不可用的值,而不是检查那些空值(null)。
Optional的优点
- 显式的提醒你需要关注null的情况,对程序员是一种字面上的约束
- 将平时的一些显式的防御性检测给标准化了,并提供一些可串联操作
- 解决null会导致疑惑的概念
Optional类的用法:
public class NewFeaturesTester {
public static void main(String args[]){
NewFeaturesTester tester = new NewFeaturesTester();
Integer value1 = null;
Integer value2 = new Integer(5);
// ofNullable 允许传参时给出 null
Optional<Integer> a = Optional.ofNullable(value1);
// 如果传递的参数为null,那么 of 将抛出空指针异常(NullPointerException)
Optional<Integer> b = Optional.of(value2);
System.out.println(tester.sum(a,b));
}
public Integer sum(Optional<Integer> a, Optional<Integer> b){
// isPresent 用于检查值是否存在
System.out.println("First parameter is present: " + a.isPresent());
System.out.println("Second parameter is present: " + b.isPresent());
// 如果a为null,则返回传入的参数值
Integer value1 = a.orElse(new Integer(0));
// get 用于获得值,条件是这个值必须存在
Integer value2 = b.get();
return value1 + value2;
}
}
Optional.ofNullable()
//将一个对象放入Optional容器。传 null 进到就得到 Optional.empty(), 非 null 就调用 Optional.of(obj)
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
Optional.of()
//将一个对象放入Optional容器。要求传入的 obj 不能是 null 值的,否则NullPointerException
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
Optional.isPresent()
//返回一个值是否存在
public boolean isPresent() {
return value != null;
}
Optional.orElse()
//返回Optional容器中的值,如果为空则返回传入的默认参数值
public T orElse(T other) {
return value != null ? value : other;
}
Optional.get()
//如果一个值存在于当前Optional中,则返回这个值;否则将抛出一个NoSuchElementException异常
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
4.2 Stream
Java8添加的Stream API(java.util.stream)把真正的函数式编程风格引入到Java中,是 Java 8用来补充集合类。允许我们去表达我们想要完成什么而不是要怎样做。
4.2.1 Stream一般语法
4.2.2 创建Stream
- 通过Stream接口的静态工厂方法
Stream.of(1, 2, 3, 5);
Stream.generate(Math::random);
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
- 通过Collection接口的默认方法
new ArrayList<>().stream();
Arrays.stream(Object[])
4.2.3 转换Stream
转换Stream其实就是把一个Stream通过某些行为转换成一个新的Stream。它不会修改原始的数据源,而且是由在终点操作开始的时候才真正开始执行。Stream接口中定义了几个常用的转换方法:
- distinct: 对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素。该方法依赖于toString方法
- filter: 对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素
- limit:对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素
- peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数
int[] number = {1,2,3,4,5};
System.out.print("和为:" +Arrays.stream(number).filter(num -> num > 0).distinct().peek(System.out::println).skip(2).sum());
结果:
1
2
3
4
5
和为:12
性能问题:
转换操作都是lazy的,多个转换操作只会在汇聚操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。
stream提供了parallelStream使用多线程进行操作,加大了运算效率.
4.2.4 汇聚/归纳(Reduce)Stream
汇聚操作(也称为折叠)接受一个元素序列为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。比如查找一个数字列表的总和或者最大值,或者把这些数字累积成一个List对象。
常用操作:
collect:把Stream中的元素收集到一个结果容器中
List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
collect(Collectors.toList());
forEach:接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式
Arrays.asList(1,2,3,4,5,6).stream().forEach(i -> System.out.println(i + "") );
4.3 Date/Time API (JSR 310)
Java8新的日期和时间库很好的解决了以前日期和时间类的很多弊端。并且也借鉴了第三方日期库joda很多的优点。
Java8新增的time包中的是类是不可变且线程安全的。新的时间及日期API位于java.time中,下面是一些关键类
Instant——它代表的是时间戳
LocalDate——不包含具体时间的日期,比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等
LocalTime——它代表的是不含日期的时间
LocalDateTime——它包含了日期及时间,不过还是没有偏移信息或者说时区。
ZonedDateTime——这是一个包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。
//java8自带了Clock类,用来获取某个时区下的瞬时时间、日期。
final Clock clock = Clock.systemUTC();
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
//通过of方法将日期格式化输出
//LocalDateTime相当于LocalDate和LocalTime的结合
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );
final Duration duration = Duration.between( from, to );
//获取当前时间戳。可与Date类互相转换
Instant.now()
//使用自定义的格式器来解析日期
String holiday = "0713 2017";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM dd YYYY");
LocalDate formateDate = LocalDate.parse(holiday, formatter);
输出结果:
16:24:42.742
08:24:42.742
2017-07-13T16:24:42.742
2017-07-13T08:24:42.742
Duration in days: 365
Duration in hours: 8783
4.5 Base64
Base64就是用来将非ASCII字符的数据转换成ASCII字符的一种方法。适合在http,mime协议下快速传输数据。
示例:
String str = Base64.getEncoder().encodeToString("Hello world!".getBytes("utf-8"));
System.out.println(str);
byte[] bytes=Base64.getDecoder().decode(str);
System.out.println(new String(bytes,"utf-8"));
6. 其他新特性
更好的类型推测机制:Java8在类型推测方面有了很大的提高,这就使代码更整洁,不需要太多的强制类型转换了。
编译器优化:Java 8将方法的参数名加入了字节码中,这样在运行时通过反射就能获取到参数名,只需要在编译时使用-parameters参数。
并行(parallel)数组:支持对数组进行并行处理,主要是parallelSort()方法,它可以在多核机器上极大提高数组排序的速度。
内存模型换成红黑树,有利于gc,减少内存泄露java内存分代进行了改进,取消了永久代,变成了metaSpace
concurrentHashMap变成了cas无锁模式,只有在扩容的时候才会用sync进行锁,cas的无锁模式使得concurrentHashMap在吞吐量上有了一定等级的提升
hashMap的优化.在hash冲突的时候,链表长度大于默认因子数8的时候,会变更为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,用以提高效率.
7. Android中会用Java8
Android N(SDK25)开始支持Java8的部分特性。
如果不是Android N,需要在gradle中添加如下设置
android {
...
defaultConfig {
...
jackOptions {
enabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}