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