JDK 新特性
主要对 java 7,8,9 进行对比
JDK 7 新特性
在2011年7月28日,Oracle正式发布了Java 7
1、switch中使用String
JDK7以前在switch 只支持
- 基本数据类型:byte, short, char, int
- 包装数据类型:Byte, Short, Character, Integer
- 枚举类型:Enum
在JDK7 开始支持字符串类型:String
在JDK1.5之前,switch循环只支持byte、short、char、int四种数据类型.
JDK1.5 在switch循环中增加了枚举类与byte、short、char、int的包装类,对四个包装类的支持是因为java编译器在底层手动进行拆箱,而对枚举类的支持是因为枚举类有一个ordinal方法,该方法实际上是一个int类型的数值.
jdk1.7开始支持String类型,但实际上String类型有一个hashCode算法,结果也是int类型.而byte、short、char类型可以在不损失精度的情况下向上转型成int类型.所以总的来说,可以认为switch中只支持int
String s = "a";
switch (s) {
case "a":
System.out.println("is a");
break;
case "b":
System.out.println("is b");
break;
default:
System.out.println("is c");
break;
}
2、try-with-resources
java7以前对某些资源的操作是需要手动关闭,如InputStream,Writes,Sockets,Sql等,需要在finally中进行关闭资源的操作,现在不需要使用finally来保证打开的流被正确关闭,现在是自动完成的,会自动释放资源,确保每一个资源在处理完成后都会关闭,就不需要我们代码去close();
在采用try-with-resources方式后,不需要再次声明流的关闭。
可以使用try-with-resources的资源有:
任何实现了java.lang.AutoCloseable接口和java.io.Closeable接口的对象。为了支持这个行为,所有可关闭的类将被修改为可以实现一个Closable(可关闭的)接口。
jdk 1.7 之前
OutputStream fos = null;
try {
fos = new FileOutputStream("D:/file");
} finally {
fos.close();
}
JDK1.7 之后
try(OutputStream fos = new FileOutputStream("D:/file");){
// 不需要再次指明fos.close();
}
3、捕获多个异常
java7以前在一个方法抛出多个异常时,只能一个个的catch,这样代码会有多个catch,显得很不友好,现在只需一个catch语句,多个异常类型用"|"隔开。
JDK1.7 之前
try {
result = field.get(obj);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
JDK1.7 之后
try {
result = field.get(obj);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
4 泛型实例化类型自动推断
运用泛型实例化类型自动推断,对通用实例创建(diamond)的type引用进行了改进
JDK1.7 之前
List<String> list = new ArrayList<String>();
JDK1.7 之后
List<String> list = new ArrayList<>();
5、增加二进制表示
Java7前支持十进制(123)、八进制(0123)、十六进制(0X12AB)
Java7增加二进制表示(0B11110001、0b11110001)
JDK1.7 之后
int binary = 0b0001_1001;
System.out.println("binary is :"+binary);
binary is :25
6、数字中可添加分隔符
Java7中支持在数字中间增加’_'作为分隔符,分隔长int以及long(也支持double,float),显示更直观,如(12_123_456)。
下划线只能在数字中间,编译时编译器自动删除数字中的下划线。
JDK1.7 之后
int intOne = 1_000_000;
long longOne = 1_000_000;
double doubleOne = 1_000_000;
float floatOne = 1_000_000;
JDK 8 新特性
1.Lambda表达式
2.Stream函数式操作流元素集合
3.接口新增:默认方法与静态方法
4.方法引用,与Lambda表达式联合使用
5.引入重复注解
6.类型注解
7.最新的Date/Time API (JSR 310)
8.新增base64加解密API
9.数组并行(parallel)操作
10.JVM的PermGen空间被移除:取代它的是Metaspace(JEP 122)元空间
1、Lambda 表达式
Lambda表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变,Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中).
首先,看看之前的代码:
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
现在可以写成这样:
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字,但是你还可以写得更短点:
Collections.sort(names, (a, b) -> b.compareTo(a));
2、方法与构造函数引用
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。
注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。
简单地说,就是一个Lambda表达式。在Java 8中,我们会使用Lambda表达式创建匿名方法,但是有时候,我们的Lambda表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰,Java 8的方法引用允许我们这样做。方法引用是一个更加紧凑,易读的Lambda表达式,注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。
例子:
假如我们有一个Person对象,要对它排序。
package com.demo.model;
import java.time.LocalDate;
public class Person {
public Person(String name, LocalDate birthday) {
this.name = name;
this.birthday = birthday;
}
String name;
LocalDate birthday;
public LocalDate getBirthday() {
return birthday;
}
public static int compareByAge(Person a, Person b) {
return a.birthday.compareTo(b.birthday);
}
@Override
public String toString() {
return this.name;
}
}
原始写法,使用匿名类:
// 使用匿名类
Arrays.sort(pArr, new Comparator<Person>() {
@Override
public int compare(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
});
其中,Arrays类的sort方法定义如下:
public static <T> void sort(T[] a, Comparator<? super T> c)
这里,我们首先要注意Comparator接口是一个函数式接口,因此我们可以使用Lambda表达式,而不需要定义一个实现Comparator接口的类,并创建它的实例对象,传给sort方法。
使用Lambda表达式,我们可以这样写:
改进一,使用Lambda表达式,未调用已存在的方法
//使用lambda表达式
Arrays.sort(pArr, (Person a, Person b) -> {
return a.getBirthday().compareTo(b.getBirthday());
});
然而,在以上代码中,关于两个人生日的比较方法在Person类中已经定义了,因此,我们可以直接使用已存在的Person.compareByAge方法。
改进二,使用Lambda表达式,调用已存在的方法
//使用lambda表达式和类的静态方法
Arrays.sort(pArr, (a ,b) -> Person.compareByAge(a, b));
因为这个Lambda表达式调用了一个已存在的方法,因此,我们可以直接使用方法引用来替代这个Lambda表达式。
改进三,使用方法引用
//使用方法引用,引用的是类的静态方法
Arrays.sort(pArr, Person::compareByAge);
在以上代码中,方法引用Person::compareByAge在语义上与Lambda表达式 (a, b) -> Person.compareByAge(a, b) 是等同的
,都有如下特性:
- 真实的参数是拷贝自
Comparator<Person>.compare
方法,即(Person, Person); - 表达式体调用Person.compareByAge方法。
四种方法引用类型
类型 | 示例 |
---|---|
引用静态方法 | ContainingClass::staticMethodName |
引用某个对象的实例方法 | containingObject::instanceMethodName |
引用某个类型的任意对象的实例方法 | ContainingType::methodName |
引用构造方法 | ClassName::new |
3、接口默认实现方法
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征又叫做扩展方法。
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
4、新工具
新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
4.1、 Nashorn引擎:jjs
jjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行。例如,我们写一个func.js文件,内容如下:
function f() {
return 1;
};
print( f() + 1 );
4.2、 类依赖分析器:jdeps
jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台
4.3、JVM的新特性
使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize。
5、Stream API
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
5.1 什么是 Stream?
Stream(流)是一个来自数据源的元素队列并支持聚合操作
元素
是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。数据源
流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
Pipelining
: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。内部迭代
: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
5.2 生成流
在 Java 8 中, 集合接口有两个方法来生成流:
- stream() − 为集合创建串行流。
- parallelStream() − 为集合创建并行流。
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
5.3 forEach
Stream 提供了新的方法 ‘forEach’ 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
5.4 map
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
5.5 filter
filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
int count = strings.stream().filter(string -> string.isEmpty()).count();
5.6 limit
limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
5.7 sorted
sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
5.8 并行(parallel)程序
parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
int count = strings.parallelStream().filter(string -> string.isEmpty()).count();
我们可以很容易的在顺序运行和并行直接切换。
5.9 Collectors
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
5.10 统计
另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
6、Date Time API
加强对日期与时间的处理。
6.1 LocalDate/LocalTime/LocalDateTime
LocalDate为日期处理类、LocalTime为时间处理类、LocalDateTime为日期时间处理类,方法都类似,具体可以看API文档或源码,选取几个代表性的方法做下介绍。
now相关的方法可以获取当前日期或时间,of方法可以创建对应的日期或时间,parse方法可以解析日期或时间,get方法可以获取日期或时间信息,with方法可以设置日期或时间信息,plus或minus方法可以增减日期或时间信息;
6.2 TemporalAdjusters
这个类在日期调整时非常有用,比如得到当月的第一天、最后一天,当年的第一天、最后一天,下一周或前一周的某天等。
6.3 DateTimeFormatter
以前日期格式化一般用SimpleDateFormat类,但是不怎么好用,现在1.8引入了DateTimeFormatter类,默认定义了很多常量格式(ISO打头的),在使用的时候一般配合LocalDate/LocalTime/LocalDateTime使用,比如想把当前日期格式化成yyyy-MM-dd hh:mm:ss的形式:
LocalDateTime dt = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
System.out.println(dtf.format(dt));
7、Optional 类
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
Optional.of()或者Optional.ofNullable():创建Optional对象,差别在于of不允许参数是null,而ofNullable则无限制。
// 参数不能是null
Optional<Integer> optional1 = Optional.of(1);
// 参数可以是null
Optional<Integer> optional2 = Optional.ofNullable(null);
// 参数可以是非null
Optional<Integer> optional3 = Optional.ofNullable(2);
Optional.empty():所有null包装成的Optional对象:
Optional<Integer> optional1 = Optional.ofNullable(null);
Optional<Integer> optional2 = Optional.ofNullable(null);
System.out.println(optional1 == optional2);// true
System.out.println(optional1 == Optional.<Integer>empty());// true
Object o1 = Optional.<Integer>empty();
Object o2 = Optional.<String>empty();
System.out.println(o1 == o2);// true:
isPresent():判断值是否存在
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
// isPresent判断值是否存在
System.out.println(optional1.isPresent() == true);
System.out.println(optional2.isPresent() == false);
ifPresent(Consumer consumer):如果option对象保存的值不是null,则调用consumer对象,否则不调用
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
// 如果不是null,调用Consumer
optional1.ifPresent(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
System.out.println("value is " + t);
}
});
// null,不调用Consumer
optional2.ifPresent(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
System.out.println("value is " + t);
}
});
orElse(value):如果optional对象保存的值不是null,则返回原来的值,否则返回value
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
// orElse
System.out.println(optional1.orElse(1000) == 1);// true
System.out.println(optional2.orElse(1000) == 1000);// true
orElseGet(Supplier supplier):功能与orElse一样,只不过orElseGet参数是一个对象
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
System.out.println(optional1.orElseGet(() -> {
return 1000;
}) == 1);//true
System.out.println(optional2.orElseGet(() -> {
return 1000;
}) == 1000);//true
orElseThrow():值不存在则抛出异常,存在则什么不做,有点类似Guava的Precoditions
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
optional1.orElseThrow(()->{throw new IllegalStateException();});
try
{
// 抛出异常
optional2.orElseThrow(()->{throw new IllegalStateException();});
}
catch(IllegalStateException e )
{
e.printStackTrace();
}
filter(Predicate):判断Optional对象中保存的值是否满足Predicate,并返回新的Optional。
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
Optional<Integer> filter1 = optional1.filter((a) -> a == null);
Optional<Integer> filter2 = optional1.filter((a) -> a == 1);
Optional<Integer> filter3 = optional2.filter((a) -> a == null);
System.out.println(filter1.isPresent());// false
System.out.println(filter2.isPresent());// true
System.out.println(filter2.get().intValue() == 1);// true
System.out.println(filter3.isPresent());// false
map(Function):对Optional中保存的值进行函数运算,并返回新的Optional(可以是任何类型)
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
Optional<String> str1Optional = optional1.map((a) -> "key" + a);
Optional<String> str2Optional = optional2.map((a) -> "key" + a);
System.out.println(str1Optional.get());// key1
System.out.println(str2Optional.isPresent());// false
flatMap():功能与map()相似,差别请看如下代码。flatMap方法与map方法类似,区别在于mapping函数的返回值不同。map方法的mapping函数返回值可以是任何类型T,而flatMap方法的mapping函数必须是Optional。
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Optional<String>> str1Optional = optional1.map((a) -> {
return Optional.<String>of("key" + a);
});
Optional<String> str2Optional = optional1.flatMap((a) -> {
return Optional.<String>of("key" + a);
});
System.out.println(str1Optional.get().get());// key1
System.out.println(str2Optional.get());// key1
8、Nashorn, JavaScript 引擎
Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
Nashorn JavaScript引擎可以在Java代码中编程调用,也可以通过命令行工具jjs使用,它在$JAVA_HOME/bin中。如果打算使用jjs,你可能希望设置符号链接来简化访问:
$ cd /usr/bin
$ ln -s $JAVA_HOME/bin/jjs jjs
$ jjs
jjs> print('Hello World');
这个教程专注于在Java代码中调用Nashron,所以让我们先跳过jjs。Java代码中简单的HelloWorld如下所示:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("print('Hello World!');");
为了在Java中执行JavaScript,你首先要通过javax.script包创建脚本引擎。这个包已经在Rhino(来源于Mozilla、Java中的遗留JS引擎)中使用了。
JavaScript代码既可以通过传递JavaScript代码字符串,也可以传递指向你的JS脚本文件的FileReader来执行:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));
Nashorn JavaScript基于ECMAScript 5.1,但是它的后续版本会对ES6提供支持:
Nashorn的当前策略遵循ECMAScript规范。当我们在JDK8中发布它时,它将基于ECMAScript 5.1。Nashorn未来的主要发布基于ECMAScript 6。
Nashorn定义了大量对ECMAScript标准的语言和API扩展。但是首先让我们看一看Java和JavaScript代码如何交互。
在JavaScript中调用Java方法
在JavaScript中调用Java方法十分容易。我们首先需要定义一个Java静态方法。
static String fun1(String name) {
System.out.format("Hi there from Java, %s", name);
return "greetings from java";
}
Java类可以通过Java.typeAPI扩展在JavaScript中引用。它就和Java代码中的import类似。只要定义了Java类型,我们就可以自然地调用静态方法fun1(),然后像sout打印信息。由于方法是静态的,我们不需要首先创建实例。
var MyJavaClass = Java.type('my.package.MyJavaClass');
var result = MyJavaClass.fun1('John Doe');
print(result);
// Hi there from Java, John Doe
// greetings from java
在使用JavaScript原生类型调用Java方法时,Nashorn 如何处理类型转换?让我们通过简单的例子来弄清楚。
下面的Java方法简单打印了方法参数的实际类型:
static void fun2(Object object) {
System.out.println(object.getClass());
}
为了理解背后如何处理类型转换,我们使用不同的JavaScript类型来调用这个方法:
MyJavaClass.fun2(123);
// class java.lang.Integer
MyJavaClass.fun2(49.99);
// class java.lang.Double
MyJavaClass.fun2(true);
// class java.lang.Boolean
MyJavaClass.fun2("hi there")
// class java.lang.String
MyJavaClass.fun2(new Number(23));
// class jdk.nashorn.internal.objects.NativeNumber
MyJavaClass.fun2(new Date());
// class jdk.nashorn.internal.objects.NativeDate
MyJavaClass.fun2(new RegExp());
// class jdk.nashorn.internal.objects.NativeRegExp
MyJavaClass.fun2({foo: 'bar'});
// class jdk.nashorn.internal.scripts.JO4
JavaScript原始类型转换为合适的Java包装类,而JavaScript原生对象会使用内部的适配器类来表示。要记住jdk.nashorn.internal中的类可能会有所变化,所以不应该在客户端面向这些类来编程。
任何标记为“内部”的东西都可能会从你那里发生改变。
ScriptObjectMirror
在向Java传递原生JavaScript对象时,你可以使用ScriptObjectMirror类,它实际上是底层JavaScript对象的Java表示。ScriptObjectMirror实现了Map接口,位于jdk.nashorn.api中。这个包中的类可以用于客户端代码。
下面的例子将参数类型从Object改为ScriptObjectMirror,所以我们可以从传入的JavaScript对象中获得一些信息。
static void fun3(ScriptObjectMirror mirror) {
System.out.println(mirror.getClassName() + ": " +
Arrays.toString(mirror.getOwnKeys(true)));
}
当向这个方法传递对象(哈希表)时,在Java端可以访问其属性:
MyJavaClass.fun3({
foo: 'bar',
bar: 'foo'
});
// Object: [foo, bar]
我们也可以在Java中调用JavaScript的成员函数。让我们首先定义JavaScript Person类型,带有属性firstName 和 lastName,以及方法getFullName。
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function() {
return this.firstName + " " + this.lastName;
}
}
JavaScript方法getFullName可以通过callMember()在ScriptObjectMirror上调用。
static void fun4(ScriptObjectMirror person) {
System.out.println("Full Name is: " + person.callMember("getFullName"));
}
当向Java方法传递新的Person时,我们会在控制台看到预期的结果:
var person1 = new Person("Peter", "Parker");
MyJavaClass.fun4(person1);
// Full Name is: Peter Parker
语言扩展
Nashorn定义了多种对ECMAScript标准的语言和API扩展。让我们看一看最新的特性:
类型数组
JavaScript的原生数组是无类型的。Nashron允许你在JavaScript中使用Java的类型数组:
var IntArray = Java.type("int[]");
var array = new IntArray(5);
array[0] = 5;
array[1] = 4;
array[2] = 3;
array[3] = 2;
array[4] = 1;
try {
array[5] = 23;
} catch (e) {
print(e.message); // Array index out of range: 5
}
array[0] = "17";
print(array[0]); // 17
array[0] = "wrong type";
print(array[0]); // 0
array[0] = "17.3";
print(array[0]); // 17
int[]数组就像真实的Java整数数组那样。但是此外,在我们试图向数组添加非整数时,Nashron在背后执行了一些隐式的转换。字符串会自动转换为整数,这十分便利。
9、函数式接口
规则:
- 只包含一个抽象方法的接口,称为函数式接口。
- 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
- 我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口(Single Abstract Method)。定义了这种类型的接口,使得以其为参数的方法,可以在调用时,使用一个lambda表达式作为参数。从另一个方面说,一旦我们调用某方法,可以传入lambda表达式作为参数,则这个方法的参数类型,必定是一个函数式的接口,这个类型必定会使用@FunctionalInterface进行修饰。
从SAM原则上讲,这个接口中,只能有一个函数需要被实现,但是也可以有如下例外:
-
默认方法与静态方法并不影响函数式接口的契约,可以任意使用,即
1)、函数式接口中可以有静态方法,一个或者多个静态方法不会影响SAM接口成为函数式接口,并且静态方法可以提供方法实现
2)、可以由 default 修饰的默认方法方法,这个关键字是Java8中新增的,为的目的就是使得某一些接口,原则上只有一个方法被实现,但是由于历史原因,不得不加入一些方法来兼容整个JDK中的API,所以就需要使用default关键字来定义这样的方法
-
可以有 Object 中覆盖的方法,也就是 equals,toString,hashcode等方法。
JDK中以前所有的函数式接口都已经使用 @FunctionalInterface 定义,可以通过查看JDK源码来确认,以下附JDK 8之前已有的函数式接口:
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
如:
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
java 9 新特性
1.目录结构
2.JShell 工具 jShell命令
3.模块化
4.多版本兼容jar包
5.接口方法的改进(在接口中 jdk7 只能声明全名常量和抽象方法 jdk8 添加了静态方法和默认方法 jdk9添加了私有方法)
6.钻石操作符升级 可以有{}
7.异常处理try升级
8.下划线标识符命名的限制 _ 不允许了
9.String底层存储结构的变更(char数组变成byte字节数组)
10.增强了Stream API(在java8基础上,新增加4个方法)
11.jdk9 中引入httpClient api 代替原有的HttpURLConnection
1、目录结构
包含jdk8及以前的jdk版本,所有目录结构以及目录含义如下表:
目录 | 说明 |
---|---|
bin | 包含命令行开发和调试工具,如javac,jar和javadoc。 它还用于包含Java命令来启动Java应用程序。 |
lib | 包含JDK工具的几个JAR和其他类型的文件。 它有一个tools.jar文件,其中包含javac编译器的Java类。 |
include | 包含在编译本地代码时使用的C/C++头文件。 |
jre/bin | 包含基本命令,如java命令。 在Windows平台上,它包含系统的运行时动态链接库(DLL)。 |
jre/lib | 包含用户可编辑的配置文件,如.properties和.policy文件。 包含几个JAR。rt.jar 文件包含运行时的java类和资源文件 |
jdk9之后,目录结构发生变化如表:
目录 | 说明 |
---|---|
bin | 包含所有命令。在Windows品台上,他集成包含系统的运行时动态了解库 |
conf | 包含用户 可编辑的配置文件,例如一千蚊于jre\lib 目录中的 .properties 和 .policy文件 |
include | 包含在编译本地代码时使用的C/C++头文件。它只存在于JDK中 |
jmods | 包含JMDO 格式的平台模块。穿件自定义运行时映像是需要他。他只存在于JDK中 |
legal | 目录包含法律声明。 |
lib | 目录包含非Windows平台上的动态链接本地库。 其子目录和文件不应由开发人员直接编辑或使用。 |
2、JShell 工具
JShell工具 是提供一个交互工具,通过它来运行和计算java中的表达式。开发者可以轻松地与JShell交互,其中包括:编辑历史,tab键代码补全,自动添加分号,可配置的imports和definitions。其他的很多主流编程语言如python都已经提供了console,便于编写一些简单的代码用于测试。值得一提的是,JShell并不是提供了新的一个交互语言,在JShell中编写的所有代码都必须符合java语言规范;图形界面和调试支持也没有,JShell的一个目标是可以在IDE中使用JShell交互,而不是实现IDE实现的功能。
每一门编程语言的第一个练习就是打印“Hello,World”,有了JShell之后,Java开发者终于不用先编写一个类,再编写“奇怪的”main方法,相信对于初学者来说是一个福音。
3、模块化
模块化特性是Java 9 最大的一个特性,Java 9起初的代号就叫Jigsaw,后来被更改为Modularity,Modularity提供了类似于OSGI框架的功能,模块之间存在相互的依赖关系,可以导出一个公共的API,并且隐藏实现的细节,Java提供该功能的主要的动机在于,减少内存的开销,我们大家都知道,在JVM启动的时候,至少会有30~60MB的内存加载,主要原因是JVM需要加载rt.jar,不管其中的类是否被classloader加载,第一步整个jar都会被JVM加载到内存当中去,模块化可以根据模块的需要加载程序运行需要的class,那么JVM是如何知道需要加载那些class的呢?这就是在Java 9 中引入的一个新的文件module.java我们大致来看一下一个例子(module-info.java)
module com.baeldung.java9.modules.car {
requires com.baeldung.java9.modules.engines;
exports com.baeldung.java9.modules.car.handling;
}
4.多版本兼容jar包
好多公司用的jdk大部分还是老版本,jdk6,7 都有,他们都不敢升级主要是因为兼容的问题,但是java9做到了这一点,就是不管公司的项目是用的java6,7,8甚至5,他都可以兼容不出错,打个比方,你之前用的是iphone5,现在出现了iPhone6,iphone7,iphon8和iphone9,但是你不敢买9,因为你自己已经适应了iphone5的所有手机的运行流程,6,7,8每个手机的运行流程不一样,但是这个9很强大,它能够识别你现在所用的版本iphone是5,所以当你升级到iphone9的时候,你的这个手机运行流程还是iphone5的流程,只是在原有基础上拥有了更多的iphone9的所有优势。
5、接口Interface的升级
public intface FilterProcess<T> {
// java 7 及以前 特性 全局常量 的抽象方法
public static final String a = "11";
boolean process(T t);
// java 8 新特性静态方法个磨人方法
default void love(){
System.out.printLn("Java8 特性默认方法");
}
default void gay(){
System.outprintln("java8 特性静态方法");
}
// java 9 特性 支持私有方法
private void java8(){}
}
6、钻石操作符的升级
// java 6 及以前
Map<String, Object> map6 = new HashMap<String, Object>();
// java 7 和 java 8
Map<String, Object> map78 = new HashMap<>();
//java 9 添加了匿名内部方法的功能 后面添加了大括号
Map<String, Object> map9 = new HashMap<>(){}
7、异常处理try升级
// java 7 以前
InputStreamReader reader = null;
try {
reader = new InputStreamReader(System.in);
reader.read();
} catch (java.io.IOException e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// java 7/8
try(InputStreamReader reader =new InputStreamReader(System.in)) {
reader.read();
} catch (java.io.IOException e) {
e.printStackTrace();
}
// java 9
InputStreamReader reader =new InputStreamReader(System.in)
try(reader) {
reader.read();
} catch (java.io.IOException e) {
e.printStackTrace();
}
8、Java9新特性之—特殊标识符增加限制
java8 之前 String _ =“hello”; 这样的标识符可以用,java9就用不到。
9、Java9新特性之—String底层存储结构更换
java8之前 String的底层结构类型都是 char[] ,但是java9 就替换成 byte[] 这样来讲,更节省了空间和提高了性能
之所以替换是因为 之前一直是最小单位是一个char,用到两个byte,但是java8是基于latin1的,而这个latin1编码可以用一个byte标识,所以当你数据明明可以用到一个byte的时候,我们用到了一个最小单位chat两个byte,就多出了一个byte的空间。所以java9在这一方面进行了更新,现在的java9 是基于ISO/latin1/Utf-16 ,latin1和ISO用一个byte标识,UTF-16用两个byte标识,java9会自动识别用哪个编码,当数据用到1byte,就会使用iSO或者latin1 ,当空间数据满足2byte的时候,自动使用utf-16,节省了很多空间
同理,StringBuilder StringBuffer也更换了底层数据结构
10、Java9新特性之—Stream API 新方法的添加
在原有Stream API 新添加了4个方法,takeWhile dropWhile ofNullable iterate(新重载方法)
首先解释 takeWhile 当达到一定条件就结束:输出结果为45 43,
List<Integer> list = Arrays.asList(45,43,87,77,90,73,67,88);
list.stram().takeWhile(x -> x < 50).forEach(System.out::println);
System.out.println();
而 dropWhile 则和takeWhile 相反
ofNullable, 在java8中 Stream中的元素不能完全为null,否则空指针异常,而在java9的升级中,允许创建null
iterate 不加条件无线循环
// 原来的控制终止方式
Stream.iterate(1, i -> i + 1).limit(10)..forEach(System.out::println);
// 现在的终止方式
Stream.iterate(1, i -> i < 100, i -> i + 1).limit(10)..forEach(System.out::println);
11. 引进HttpClient
以往我们都是通过maven添加httpclient ,java9直接引入即可
Java9所有特性都是为了提高性能和内存。