原标题:这些Java9 超牛的新特性,你竟然还没用过?
关注
汪伟俊|作者
Java技术迷| 出品
互联网技术的更新日新月异,而对于jdk,大部分人平时都是使用的jdk1.8,然而,如今jdk已经更新到了15马上变16,本篇文章我们就来看看jdk9到底更新了一些什么内容。
目录结构变化
有关jdk9的下载安装与环境配置在这里就不作介绍了,直接来看看它与jdk8的第一个区别,目录结构的变化。
上图是jdk8的目录结构,下图是jdk9的目录结构:
两者最明显的区别在于jdk9中已经不包含jre了,其它内容变化倒是不大。
模块化
我们知道,Java编写的项目是比较臃肿的,编译运行需要耗费大量的时间,为此,java9提供了模块化,使得开发者可以指定项目具体需要使用哪些类库,以排除无关紧要的jar包,增加项目运行效率。
首先创建一个Java项目:
在该项目下创建两个模块,创建方法为 右击项目-->New-->Module:
模块创建完成后,在module-1中编写一个Bean:
packagecom.wwj.bean;
publicclassPerson{
privateString name;
privateintage;
publicPerson{
}
publicPerson(String name, intage){
this.name = name;
this.age = age;
}
publicString getName{
returnname;
}
publicvoidsetName(String name){
this.name = name;
}
publicintgetAge{
returnage;
}
publicvoidsetAge( intage){
this.age = age;
}
@Override
publicString toString{
return"Person{"+
"name='"+ name + '''+
", age="+ age +
'}';
}
}
然后我们在module-2中创建一个测试文件:
会发现在module-2中是无法使用module-1中的Person类的,这是因为jdk9对项目进行了模块化,若想要使用到其它模块的类,需要作如下操作。
在module-1中创建module-info文件:
编写如下内容:
modulemodule1 {
// 导出包
exportscom.wwj.bean;
}
并在module-2中创建module-info文件,编写如下内容:
modulemodule2 {
// 引入模块
requiresmodule1;
}
这样我们就可以顺利地使用到module-1中com.wwj.bean包下的类了:
再举个例子,比如你想打印日志,你就需要使用Logger类,然而:
此时Logger类也是报错的,而且你会发现导包是导入不了的,此时我们就需要在module-info中引入Logger模块:
modulemodule2 {
requiresmodule1;
requiresjava.logging;
}
这样就能够使用Logger类了:
通过这样的方式,使得虚拟机在加载项目时只会去加载module-info中配置的模块,从而大大提升了运行效率。
jshell命令
在jdk9之前,我们若是想执行一个非常简单的程序,比如做一个加法,你需要创建java文件,然后编译执行。
这显然非常繁琐,那它能不能够像Python那样有一个交互式的编程环境呢?为此,jdk9提供了jshell。
使用方法非常简单,在cmd窗口中输入jshell:
在jshell中,我们能够进行输出、定义变量、计算等等很多操作,jshell也会在我们按下回车后立即给予我们反馈:
创建方法并调用:
jshell还提供了一些非常好用的命令,比如: /list , 通过它能够查看历史执行的命令:
jshell>/list
1 : System.out.println("Hello World
3 : System.out.println("Hello World
4 : int i = 10;
5 : int j = 10;
6 : int result = i + j;
7 : System.out.println(result);
8 : public int add(int i,int j){
return i + j;
}
9 : System.out.println(add(i,j));
/imports , 查看导入的包:
jshell>/imports
| import java.io.*
| import java.math.*
| import java.net.*
| import java.nio.file.*
| import java.util.*
| import java.util.concurrent.*
| import java.util.function.*
| import java.util.prefs.*
| import java.util.regex.*
| import java.util.stream.*
/vars , 查看定义的变量:
jshell>/vars
| int i = 10
| int j = 10
| int result = 20
/methods , 查看定义的方法:
jshell>/methods
| int add(int,int)
/edit ,弹出对话框用于修改代码:
前言多版本兼容Jar包
当一个新版本的jdk出现时,开发者并不愿意立马将其开发环境切换到新的版本,因为它的很多项目还是用之前的jdk进行开发的,当切换了新版本的jdk后,很可能会因为其不兼容一些老的jar包从而导致项目出错。
jdk9考虑到了这一点,其多版本兼容jar包的功能可以使开发者创建仅在特定版本的java环境中运行库程序选择使用的版本。
现在有这样一个项目,其中有两个包,一个java包,一个java-9包。
java包中有两个类,分别是:
publicclassGenerator{
publicSet createStrings{
Set strings = newHashSet;
strings.add( "Java");
strings.add( "8");
returnstrings;
}
}
publicclassApplication{
publicstaticvoidtestMultiJar{
Generator gen = newGenerator;
System.out.println( "Generated strings: "+ gen.createStrings);
}
}
而java-9中的类为:
publicclassGenerator{
publicSet createStrings{
returnSet.of( "Java", "9");
}
}
现在我们将对这个项目进行打包,得到一个.jar文件——multijar.jar。
下面就来测试一下,新建一个Java8的项目,并编写测试代码:
publicclassMultiJar{
publicstaticvoidmain(String[] args){
Application.testMultiJar;
}
}
记得将刚才的jar包导入到项目中,运行结果为:
Generated strings: [Java, 8]
我们再将这段代码放到Java9环境的项目中运行一下,得到结果:
Generated strings: [9, Java]
可以看到,同一段代码在不同环境下会有对应的不同表示,这就是多版本兼容的jar包。
接口可以定义私有方法了
从jdk9开始,接口可以定义私有方法了,具体的话也没有什么好说的,直接看代码:
publicinterfaceInterfaceTest{
//jdk7中只能声明全局常量(使用public static final修饰)和抽象方法(使用public abstract修饰)
intnum = 10;
voidadd( inti, intj);
//jdk8中还能够声明静态方法和默认方法
staticvoidstaticMethod{
}
defaultvoiddefaultMethod{
}
//jdk9中能够定义私有方法
privatevoidprivateMethod{
}
}
集合中的泛型
在jdk8以前,我们若想定义一个带有泛型的集合,必须这样编写:
Set set = newHashSet;
而在jdk8中,我们可以省略后面的泛型,因为它可以进行类型的自动推断:
Set set = newHashSet<>;
在jdk9中,我们还能够对集合进行如下编写:
Set set = newHashSet<>{};
这行代码的意思是创建一个继承于HashSet的匿名子类对象,它将与Set共同使用泛型,那么这样有什么好处呢?
好处在于当你需要改造Set中的某个方法时,能够很方便地实现,比如:
publicstaticvoidmain(String[] args){
Set set = newHashSet<>{
@Override
publicbooleanadd(String s){
returnsuper.add(s + "--");
}
};
set.add( "zhangsan");
set.add( "lisi");
set.add( "wangwu");
for(String str : set) {
System.out.println(str);
}
}
运行结果:
wangwu--
zhangsan--
lisi--
异常处理
对于IO流的异常处理一直为人所诟病,传统的异常处理过程如下:
FileInputStream in = null;
try{
in = newFileInputStream( "");
} catch(FileNotFoundException e) {
e.printStackTrace;
} finally{
if(in != null){
try{
in.close;
} catch(IOException e) {
e.printStackTrace;
}
}
}
可以看到代码非常的臃肿,在jdk8中,我们还有另外一套解决方案:
try(FileInputStream in = newFileInputStream( "")) {
in.close;
} catch(IOException e) {
e.printStackTrace;
}
将资源放在try 语句的括号内,我们就不需要手动去关闭流资源了。
而在jdk9中,我们可以在try 中调用已经实例化的资源对象:
InputStreamReader reader = newInputStreamReader(System.in);
try(reader) {
reader.read;
} catch(IOException e) {
e.printStackTrace;
}
这种方式在jdk9之前是不支持的。
下划线的使用限制
在jdk8中,下划线是可以单独作为变量名进行定义的:
int_ = 100;
而jdk9中禁止了这种变量的定义:
前言String存储结构的变化
在jdk8中,字符串的底层采用的是char数组:
而在jdk9中,它不再使用char数组实现,取而代之的是byte数组:
因为在UTF-16编码中,一个字符会占用两个字节,而大部分情况下,开发者使用的String中包含了较多的字母和数字,它们均只用一个字节就能够存储,所以采用char数组存储字符串会造成大量资源的浪费,为此,jdk9中特别设计了String的实现,将其底层改为了byte数组。
只读集合
jdk8中提供了unmodifiableList 方法来将一个集合转变为只读集合:
publicstaticvoidmain(String[] args){
List list = newArrayList<>;
list.add( "a");
list.add( "b");
list.add( "c");
List readList = Collections.unmodifiableList(list);
// 只读集合不允许添加元素
// readList.add("d");
readList.forEach(System.out::println);
}
若是想创建只读的Set集合,只需修改方法名即可:
publicstaticvoidmain(String[] args){
Set set = Collections.unmodifiableSet( newHashSet<>(Arrays.asList( 1, 2, 3, 4, 5)));
set.forEach(System.out::println);
}
只读的map集合也是如此:
publicstaticvoidmain(String[] args){
Map map = Collections.unmodifiableMap( newHashMap<> {
{
put( "zhangsan", 20);
put( "lisi", 21);
put( "wangwu", 22);
}
});
map.forEach((k,v) -> System.out.println(k + ":"+ v));
}
注意这里使用到了jdk9中的另一新特性来初始化Map集合,这在集合中的泛型 已经介绍过了。
以上均是jdk8中创建只读集合的方式,在jdk9中,它的创建方式只会更加简单:
publicstaticvoidmain(String[] args){
List list = List.of( 1, 2, 3, 4, 5);
list.forEach(System.out::println);
}
通过of 方法创建的集合它就是一个只读集合,是不可以再对其进行修改的。
然后是Set集合和Map集合:
publicstaticvoidmain(String[] args){
//创建只读Set
Set set = Set.of( 1, 2, 3, 4);
//创建只读Map
Map map = Map.of( "zhangsan", 20, "lisi", 21, "wangwu", 22);
//创建只读Map的第二种方式
Map map = Map.ofEntries(Map.entry( "zhangsan", 20), Map.entry( "lisi", 21));
}
Stream的增强
首先是takeWhile 方法:
publicstaticvoidmain(String[] args){
// takeWhile
List list = Arrays.asList( 1, 3, 2, 5, 4);
Stream stream = list.stream;
Stream newStream = stream.takeWhile(x -> x < 3);
newStream.forEach(System.out::println);
}
在该场景中, takeWhile 方法的作用是从集合第一个元素开始查找小于3的元素,第一个元素1小于3;第二个元素3不小于3,此时后面的所有元素都会被舍弃,所以运行结果为:
1
其次是dropWhile 方法:
publicstaticvoidmain(String[] args){
// dropWhile
List list = Arrays.asList( 1, 3, 2, 5, 4);
Stream stream = list.stream;
Stream newStream = stream.dropWhile(x -> x < 3);
newStream.forEach(System.out::println);
}
在该场景中, dropWhile 方法的作用是从集合第一个元素开始查找小于3的元素,第一个元素1小于3,会被舍弃;第二个元素3不小于3,此时后面的所有元素都被保留,所以运行结果为:
3
2
5
4
最后是ofNullable 方法,它允许Stream中存放单个null值:
publicstaticvoidmain(String[] args){
//ofNullable
Stream stream = Stream.ofNullable( null);
System.out.println(stream.count);
}
这样是没有任何错误的,运行结果为:
0
HttpClient
jdk9中提供了HttpClient来实现网络连接,用法如下:
publicstaticvoidmain(String[] args)throwsIOException, InterruptedException{
HttpClient client = HttpClient.newHttpClient;
HttpRequest request = HttpRequest.newBuilder(URI.create( "http://www.baidu.com")).GET.build;
HttpResponse response = client.send(request, HttpResponse.BodyHandler.asString);
System.out.println(response.statusCode);
System.out.println(response.version.name);
System.out.println(response.body);
}
运行结果:
200
HTTP_1_1
......
前言Java编译工具的升级
jdk9中升级了java的编译工具,它提供了sjavac 指令用于在多核处理器情况下提升jdk的编译速度。
本文作者:汪伟俊 为Java技术迷专栏作者 投稿,未经允许请勿转载。
责任编辑: