这些Java9 超牛的新特性,你竟然还没用过?

贝塔学JAVA

共 15368字,需浏览 31分钟

 · 2021-04-17

互联网技术的更新日新月异,而对于jdk,大部分人平时都是使用的jdk1.8,然而,如今jdk已经更新到了15马上变16,本篇文章我们就来看看jdk9到底更新了一些什么内容。                                                                                                              

目录结构变化

有关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(12345)));
    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(12345);
    list.forEach(System.out::println);
}

通过of()方法创建的集合它就是一个只读集合,是不可以再对其进行修改的。

然后是Set集合和Map集合:

public static void main(String[] args) {
    //创建只读Set
    Set<Integer> set = Set.of(1234);
    //创建只读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的编译速度。

浏览 32
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报