这些Java9 超牛的新特性,你竟然还没用过?
共 15368字,需浏览 31分钟
·
2021-04-17 01:08
目录结构变化
有关jdk9的下载安装与环境配置在这里就不作介绍了,直接来看看它与jdk8的第一个区别,目录结构的变化。
上图是jdk8的目录结构,下图是jdk9的目录结构:
两者最明显的区别在于jdk9中已经不包含jre了,其它内容变化倒是不大。
模块化
我们知道,Java编写的项目是比较臃肿的,编译运行需要耗费大量的时间,为此,java9提供了模块化,使得开发者可以指定项目具体需要使用哪些类库,以排除无关紧要的jar包,增加项目运行效率。
首先创建一个Java项目:
在该项目下创建两个模块,创建方法为 右击项目-->New-->Module:
模块创建完成后,在module-1中编写一个Bean:
package com.wwj.bean;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
然后我们在module-2中创建一个测试文件:
会发现在module-2中是无法使用module-1中的Person类的,这是因为jdk9对项目进行了模块化,若想要使用到其它模块的类,需要作如下操作。
在module-1中创建module-info文件:
编写如下内容:
module module1 {
// 导出包
exports com.wwj.bean;
}
并在module-2中创建module-info文件,编写如下内容:
module module2 {
// 引入模块
requires module1;
}
这样我们就可以顺利地使用到module-1中com.wwj.bean包下的类了:
再举个例子,比如你想打印日志,你就需要使用Logger类,然而:
此时Logger类也是报错的,而且你会发现导包是导入不了的,此时我们就需要在module-info中引入Logger模块:
module module2 {
requires module1;
requires java.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包中有两个类,分别是:
public class Generator {
public Set<String> createStrings() {
Set<String> strings = new HashSet<String>();
strings.add("Java");
strings.add("8");
return strings;
}
}
public class Application {
public static void testMultiJar(){
Generator gen = new Generator();
System.out.println("Generated strings: " + gen.createStrings());
}
}
而java-9中的类为:
public class Generator {
public Set<String> createStrings() {
return Set.of("Java", "9");
}
}
现在我们将对这个项目进行打包,得到一个.jar文件——multijar.jar。
下面就来测试一下,新建一个Java8的项目,并编写测试代码:
public class MultiJar {
public static void main(String[] args) {
Application.testMultiJar();
}
}
记得将刚才的jar包导入到项目中,运行结果为:
Generated strings: [Java, 8]
我们再将这段代码放到Java9环境的项目中运行一下,得到结果:
Generated strings: [9, Java]
可以看到,同一段代码在不同环境下会有对应的不同表示,这就是多版本兼容的jar包。
接口可以定义私有方法了
从jdk9开始,接口可以定义私有方法了,具体的话也没有什么好说的,直接看代码:
public interface InterfaceTest {
//jdk7中只能声明全局常量(使用public static final修饰)和抽象方法(使用public abstract修饰)
int num = 10;
void add(int i,int j);
//jdk8中还能够声明静态方法和默认方法
static void staticMethod(){
}
default void defaultMethod(){
}
//jdk9中能够定义私有方法
private void privateMethod(){
}
}
集合中的泛型
在jdk8以前,我们若想定义一个带有泛型的集合,必须这样编写:
Set<String> set = new HashSet<String>();
而在jdk8中,我们可以省略后面的泛型,因为它可以进行类型的自动推断:
Set<String> set = new HashSet<>();
在jdk9中,我们还能够对集合进行如下编写:
Set<String> set = new HashSet<>(){};
这行代码的意思是创建一个继承于HashSet的匿名子类对象,它将与Set共同使用泛型,那么这样有什么好处呢?
好处在于当你需要改造Set中的某个方法时,能够很方便地实现,比如:
public static void main(String[] args) {
Set<String> set = new HashSet<>(){
@Override
public boolean add(String s) {
return super.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 = new FileInputStream("");
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
可以看到代码非常的臃肿,在jdk8中,我们还有另外一套解决方案:
try (FileInputStream in = new FileInputStream("")) {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
将资源放在try
语句的括号内,我们就不需要手动去关闭流资源了。
而在jdk9中,我们可以在try()
中调用已经实例化的资源对象:
InputStreamReader reader = new InputStreamReader(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()
方法来将一个集合转变为只读集合:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
List<String> readList = Collections.unmodifiableList(list);
// 只读集合不允许添加元素
// readList.add("d");
readList.forEach(System.out::println);
}
若是想创建只读的Set集合,只需修改方法名即可:
public static void main(String[] args) {
Set<Integer> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(1, 2, 3, 4, 5)));
set.forEach(System.out::println);
}
只读的map集合也是如此:
public static void main(String[] args) {
Map<Object, Object> map = Collections.unmodifiableMap(new HashMap<>() {
{
put("zhangsan", 20);
put("lisi", 21);
put("wangwu", 22);
}
});
map.forEach((k,v) -> System.out.println(k + ":" + v));
}
注意这里使用到了jdk9中的另一新特性来初始化Map集合,这在集合中的泛型
已经介绍过了。
以上均是jdk8中创建只读集合的方式,在jdk9中,它的创建方式只会更加简单:
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3, 4, 5);
list.forEach(System.out::println);
}
通过of()
方法创建的集合它就是一个只读集合,是不可以再对其进行修改的。
然后是Set集合和Map集合:
public static void main(String[] args) {
//创建只读Set
Set<Integer> set = Set.of(1, 2, 3, 4);
//创建只读Map
Map<String, Integer> map = Map.of("zhangsan", 20, "lisi", 21, "wangwu", 22);
//创建只读Map的第二种方式
Map<String, Integer> map = Map.ofEntries(Map.entry("zhangsan", 20), Map.entry("lisi", 21));
}
Stream的增强
首先是takeWhile
方法:
public static void main(String[] args) {
// takeWhile
List<Integer> list = Arrays.asList(1,3,2,5,4);
Stream<Integer> stream = list.stream();
Stream<Integer> newStream = stream.takeWhile(x -> x < 3);
newStream.forEach(System.out::println);
}
在该场景中,takeWhile
方法的作用是从集合第一个元素开始查找小于3的元素,第一个元素1小于3;第二个元素3不小于3,此时后面的所有元素都会被舍弃,所以运行结果为:
1
其次是dropWhile
方法:
public static void main(String[] args) {
// dropWhile
List<Integer> list = Arrays.asList(1,3,2,5,4);
Stream<Integer> stream = list.stream();
Stream<Integer> newStream = stream.dropWhile(x -> x < 3);
newStream.forEach(System.out::println);
}
在该场景中,dropWhile
方法的作用是从集合第一个元素开始查找小于3的元素,第一个元素1小于3,会被舍弃;第二个元素3不小于3,此时后面的所有元素都被保留,所以运行结果为:
3
2
5
4
最后是ofNullable
方法,它允许Stream中存放单个null值:
public static void main(String[] args) {
//ofNullable
Stream<Object> stream = Stream.ofNullable(null);
System.out.println(stream.count());
}
这样是没有任何错误的,运行结果为:
0
HttpClient
jdk9中提供了HttpClient来实现网络连接,用法如下:
public static void main(String[] args) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.baidu.com")).GET().build();
HttpResponse<String> 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
<!DOCTYPE html><!--STATUS OK-->
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
......
前言Java编译工具的升级
jdk9中升级了java的编译工具,它提供了sjavac
指令用于在多核处理器情况下提升jdk的编译速度。