1. 背景
Java8发行版是自Java5以来最具革命性的版本,Java8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。
2. Java语言的新特性
2.1 Lambda表达式与Functional接口
Lambda表达式(称为闭包)是整个Java8发行版中最受期待的在Java语言层面上的改变。
Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)或者把代码看成数据。
在最简单的形式中,一个lambda可以由逗号分隔的参数列表、->符号与函数体三部分表示。
String sep = ",";
Arrays.asList("a", "b", "c").forEach((String e) -> { System.out.print(e + sep); });
Lambda可以引用类的成员变量与局部变量(如果这些变量不是final的话,它们会被隐含的转维final,这样效率更高),如上面代码中的变量sep。
Lambda可能会返回一个值,返回值的类型也是有编译器推测出来的。如果Lambda的函数体只有一行的话,那么没有必要显式使用return语句。下面两个代码是等价的:
Arrays.asList("a", "b", "c").sort((e1, e2) -> e1.compareTo(e2)); Arrays.asList("a", "b", "c").sort((e1, e2) -> { int result = e1.compareTo(e2); return result; });
语言设计者思考如何使现有的函数友好地支持Lambda。最终采取的方法是:增加函数式接口的概念。
函数式接口就是一个具有一个方法的普通接口。像这样的接口,可以被隐式转换为Lambda表达式。
为了解决函数式接口的脆弱性,Java8增加了一种特殊的注解@FunctionalInterface(Java8中所有类库的已有接口都添加了@FunctionalInterface注解)
@FunctionalInterface interface Functional { void method(); }
注意:默认方法和静态方法并不影响函数式接口的契约。
Lambda是Java8最大的卖点,它具有吸引越来越多程序员到Java平台的潜力,并且能够在纯Java语言环境中提供一种优雅的方式来支持函数式编程。
2.2 接口的默认方法与静态方法
Java8用默认方法与静态方法这两个新概念来扩展接口的声明。
默认方法使得接口可以包含实现代码。但与传统的接口又有些不一样,它允许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。
默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是美容恩方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)。
Java8带来一个新特性是接口可以声明(并且提供实现)静态方法
package com.jdk8.feature; import java.util.function.Supplier; interface Defaultable { default String notRequired() { // 用关键字default声明了一个默认方法 return "Default Implements"; } } class DefaultableImpl implements Defaultable {}// 没有覆盖notRequired方法 class OverridableImpl implements Defaultable { @Override public String notRequired() {// 覆盖notRequired方法 return "Override Implement"; } } interface DefaultableFactory { static Defaultable create(Supplier<Defaultable> supplier) { return supplier.get(); } } public class NewInterface { public static void main(String[] args) { Defaultable d = DefaultableFactory.create(DefaultableImpl::new);// 方法引用,后面提到 System.out.println(d.notRequired()); Defaultable d1 = DefaultableFactory.create(OverridableImpl::new); System.out.println(d1.notRequired()); } }
2.3 方法引用
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象的方法或构造器。与Lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
package com.jdk8.feature; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; 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()); } } public class MethodRef { public static void main(String[] args) { Car car1 = Car.create(Car::new); List<Car> cars = Arrays.asList(car1); cars.forEach(Car::collide); cars.forEach(Car::repair); cars.forEach(Car.create(Car::new)::follow); } }
- 第一种方法引用是构造器引用,它的语法是Class::new,或更一般的Class<T>::new,请注意构造器没有参数;
- 第二种方法引用是静态方法引用,它的语法是Class::static_method,方法接受一个Car类型的参数;
- 第三种方法引用是特定类的任意对象的方法引用,它的语法是Class:method,这个方法没有参数;
- 第四种方法引用是特定对象的方法引用,它的语法是instance::method,这个方法接受一个Car类型的参数。
2.4 重复注解
使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明多次。Java8引入了重复注解机制,这样相同的注解可以在同一地方声明多次。
重复注解机制本身必须用@Repeatable注解,事实上,这并不是语言层面上的改变,更多的是编译期的技巧,底层的原理保持不变。
package com.jdk8.feature; 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; public class RepeatAnnotation { @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Filters { Filter[] value(); } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Repeatable(Filters.class) public @interface Filter { String value(); } @Filter("f1") @Filter("f2") public interface Filterable {} public static void main(String[] args) { for (Filter f : Filterable.class.getAnnotationsByType(Filter.class)) { System.out.println(f.value()); } System.out.println(Filterable.class.getAnnotation(Filters.class)); } }
运行截图:
使用@Repeatable(Filters.class)注解的注解类Filter,Filters仅仅是Filter注解的数组,但Java编译期并不想让程序员意识到Filters的存在。这样接口Filterable就拥有了两次Filter注解。
2.5 更好的类型推测机制
Java8在类型推测方面有了很大的提高,在很多情况下,编译期可以推测出确定的参数类型,这样就能使代码更整洁。
package com.jdk8.feature; public class GenericInference { static class Value<T> { public static <T> T defaultValue() { return null; } public T getOrDefault(T value, T defaultValue) { return (value != null) ? value : defaultValue; } } public static void main(String[] args) { Value<String> vs = new Value<>(); String v = vs.getOrDefault("22", Value.defaultValue()); System.out.println(v); } }
Value.defaultValue()的参数类型可以被推测出,所有就不必明确给出。在Java7中,相同的例子将不会通过编译,正确的方式Value.<String>defaultValue().
2.6 扩展注解的支持
Java8扩展了注解的上下文,现在几乎可以为任何东西添加注解:局部变量,泛型类,父类与接口的实现,就连方法的异常也能添加注解。
package com.jdk8.feature; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collection; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) @interface NonEmpty {} class Holder<@NonEmpty T> extends @NonEmpty Object { public void method() throws @NonEmpty Exception {} } public class AnnotationExt { public static void main(String[] args) { final Holder<String> h = new @NonEmpty Holder<>(); @NonEmpty Collection<@NonEmpty String> ss = new ArrayList<>(); System.out.println(h + ", " + ss); } }
ElementType.TYPE_USE和ElementType.TYPE_PARAMETER是两个新添加的用于描述适当的注解上下文的元素类型。
3. Java编译器的新特性
3.1 参数名称
Java程序员一直在发明不同的方式使得方法参数的名字能保留在Java字节码中,并且能够在运行时获取它们。在Java8中把这个强烈要求的功能添加到语言层面(通过反射API与Parameter.getName()方法)与字节码文件(通过新版的javac -parameter选项)。
package com.jdk8.feature; import java.lang.reflect.Method; import java.lang.reflect.Parameter; public class ParameterName { public static void main(String[] args) throws NoSuchMethodException, SecurityException { Method m = ParameterName.class.getMethod("main", String[].class); for (Parameter p : m.getParameters()) { System.out.println("Parameter: " + p.getName()); System.out.println(p.isNamePresent()); } } }
使用使用-parameter参数来编译这个类,然后运行这个类,结果如下:
如果不使用-parameter参数来编译这个类,结果如下:
isNamePresent()来验证是否可以获取参数的名字。
可以通过eclipse设置编译选项:
4. Java类库的新特性
Java8通过增加大量新类,扩展已有类的功能的方式来改善堆并发编程、函数式编程、日期/时间相隔操作以及其他更多方面的支持。
4.1 Optional
空指针异常是导致Java应用程序失败的最常见原因,收到Google Guava启发,Optional类称为Java8类库的一部分。
Optional实际上是个容器:它可以保持类型T的值或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
package com.jdk8.feature; import java.util.Optional; public class OptionalFeature { public static void main(String[] args) { Optional<String> fullName = Optional.ofNullable(null); System.out.println(fullName.isPresent()); System.out.println(fullName.orElseGet(() -> "[none]")); System.out.println(fullName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!")); } }
如果Optional类的实例为非空值得花,isPresent返回true,否则返回false;
4.2 Stream
最新添加的Stream API(java.util.stream)把真正的函数式编程风格引入到Java中,Stream API极大简化了集合框架的处理。
package com.jdk8.feature; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; enum Status { OPEN, CLOSED } final class Task { private final Status status; private final Integer points; public Task(final Status status, final Integer points) { this.status = status; this.points = points; } public Integer getPoints() { return this.points; } public Status getStatus() { return this.status; } @Override public String toString() { return String.format("[%s, %d]", status, points); } } public class StreamFeature { public static void main(String[] args) { final Collection<Task> tasks = Arrays.asList(new Task(Status.OPEN, 5), new Task(Status.OPEN, 13), new Task(Status.CLOSED, 8)); final long lg = tasks.stream().filter(task -> task.getStatus() == Status.OPEN).mapToInt(Task::getPoints).sum(); System.out.println(lg); final double de = tasks.stream().parallel().map(task -> task.getPoints()) .reduce(0, Integer::sum); System.out.println(de); final Map<Status, List<Task>> map = tasks.stream() .collect(Collectors.groupingBy(Task::getStatus)); System.out.println(map); } }
stream操作被分成了中间操作和最终操作这两种,中间操作返回一个新的stream对象,中间操作总是采用惰性求值方式,运行一个像filter这样的中间操作实际上没有进行任何过滤,相反它在遍历元素时会产生一个新的stream对象,这个新stream对象包含原始stream中符合给定谓词的所有元素。
注意:Stream API、Lambda表达式与方法引用在接口默认方法与静态方法的配合下是Java8堆现代软件开发范式的回应。
4.3 Date/Time API(JSR 310)
Java8通过发布新的Date-Time API来进一步加强对日期与时间的处理。
Java8新的Date-Time API在很大程度上收到Joda-Time的影响。
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
package com.jdk8.feature; import java.time.Clock; import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; import java.time.ZoneId; import java.time.ZonedDateTime; public class DateTimeExt { public static void main(String[] args) { final Clock now = Clock.systemUTC(); System.out.println(now.instant()); System.out.println(now.millis()); final LocalDate d = LocalDate.now(); System.out.println(d); final LocalDate d1 = LocalDate.now(now); System.out.println(d1); final LocalTime t = LocalTime.now(); System.out.println(t); final LocalTime t1 = LocalTime.now(now); System.out.println(t1); final LocalDateTime dt = LocalDateTime.now(); System.out.println(dt); final LocalDateTime dt1 = LocalDateTime.now(now); System.out.println(dt1); final ZonedDateTime zdt = ZonedDateTime.now(); final ZonedDateTime zdt1 = ZonedDateTime.now(now); final ZonedDateTime zdt2 = ZonedDateTime.now(ZoneId.of("America/Los_Angeles")); System.out.println(zdt); System.out.println(zdt1); System.out.println(zdt2); final LocalDateTime dtFrom = LocalDateTime.of(2014, Month.APRIL, 16, 0, 0, 0); final LocalDateTime dtTo = LocalDateTime.of(2015, Month.APRIL, 16, 23, 59, 59); final Duration dur = Duration.between(dtFrom, dtTo); System.out.println(dur.toDays()); System.out.println(dur.toHours()); } }
执行截图:
4.4 JavaScript引擎Nashorn
Nashorn,一个新的JS引擎随着Java8一起发布,它允许在JVM上开发运行某些JS引用。Nashorn就是javax.script.ScirptEngine的另一种实现,并且它们俩遵循相同的规则,允许Java与JavaScript相互调用。
package com.jdk8.feature; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; public class JavaScriptSupport { public static void main(String[] args) throws ScriptException { ScriptEngineManager sem = new ScriptEngineManager(); ScriptEngine se = sem.getEngineByName("JavaScript"); System.out.println(se.getClass().getName()); System.out.println(se.eval("function f() {return 1;}; f() + 1")); } }
执行截图:
4.5 Base64
Base64编码已经成为Java8类库的标准。
package com.jdk8.feature; import java.nio.charset.StandardCharsets; import java.util.Base64; public class Base64Support { public static void main(String[] args) { final String text = "Base64 fianlly in Java 8!"; final String en = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8)); System.out.println(en); final String de = new String(Base64.getDecoder().decode(en), StandardCharsets.UTF_8); System.out.println(de); } }
执行截图:
Base64类同时还提供了堆URL,MIME友好的编码器与解码器:Base64.getUrlEncoder(),Base64.getUrlDecoder(),Base64.getMimeEncoder(),Base64.getMimeDecoder()
4.6 并行数组
Java8增加了大量的新方法来对数组进行并行处理,最重要的是parallelSort()方法,因为它可以在多核机器上极大提高数组排序的速度。
package com.jdk8.feature; import java.util.Arrays; import java.util.concurrent.ThreadLocalRandom; public class ArrayParallel { public static void main(String[] args) { long[] arrayOfLong = new long[20000]; Arrays.parallelSetAll(arrayOfLong, index -> ThreadLocalRandom.current().nextInt(1000000)); Arrays.stream(arrayOfLong).limit(10).forEach(i -> System.out.println(i)); System.out.println(); Arrays.parallelSort(arrayOfLong); Arrays.stream(arrayOfLong).limit(10).forEach(i -> System.out.println(i)); } }
执行截图:
4.7 并发(Concurrency)
在新增Stream机制和Lambda的基础之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法来支持聚集操作。
5. 新的Java工具
5.1 Nashorn引擎:jjs
jjs是个基于Nashorn引擎的命令行工具,它接受一些JS源代码为参数,并且执行这些源代码。
5.2 类依赖分析器jdeps
可以显示Java类的包级别或类级别的依赖。它接受一个.class文件,一个目录,或者一个jar文件作为输入。
6. JVM的新特性
PermGen空间被移除了,取而代之的是Metaspace。
JVM选项-XX:PermSize与-XX:MaxPermSize分别被-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替。
参考资料
http://www.importnew.com/11908.html