跳至主要內容

Java9 - 2

codejava新特性约 2068 字大约 7 分钟

Java9

JShell交互式编程

Java 9为我们通过了一种交互式编程工具JShell

alt text
alt text

环境配置完成后,我们只需要输入jshell命令即可开启交互式编程了,它支持我们一条一条命令进行操作。

比如我们来做一个简单的计算:

alt text
alt text

我们一次输入一行(可以不加分号),先定义一个a=10和b=10,然后定义c并得到a+b的结果,可以看到还是非常方便的,但是注意语法还是和Java是一样的。

alt text
alt text

我们也可以快速创建一个方法供后续的调用。当我们按下Tab键还可以进行自动补全:

alt text
alt text

除了直接运行我们写进去的代码之外,它还支持使用命令,输入help来查看命令列表:

alt text
alt text

比如我们可以使用/vars命令来展示当前定义的变量列表:

alt text
alt text

当我们不想使用jshell时,直接输入/exit退出即可

接口中的private方法

接口中的方法默认是 public abstract

在Java 8中,接口中 的方法支持添加default关键字来添加默认实现:

public interface Test {
    default void test(){
        System.out.println("我是test方法默认实现");
    }
}

而在Java 9中,接口再次得到强化,现在接口中可以存在私有方法了:

public interface Test {
    default void test(){
        System.out.println("我是test方法默认实现");
        this.inner();   
        //接口中方法的默认实现可以直接调用接口中的私有方法
    }
    
    private void inner(){   
        //声明一个私有方法
        System.out.println("我是接口中的私有方法!");
    }
}

注意私有方法必须要提供方法体,因为权限为私有的,也只有这里能进行方法的具体实现了,并且此方法只能被接口中的其他私有方法或是默认实现调用。

集合类新增工厂方法 xx.of()

在之前,如果我们想要快速创建一个Map只能:

public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();   //要快速使用Map,需要先创建一个Map对象,然后再添加数据
    map.put("AAA", 19);
    map.put("BBB", 23);

    System.out.println(map);
}

而在Java 9之后,我们可以直接通过of方法来快速创建了:

public static void main(String[] args) {
    Map<String, Integer> map = Map.of("AAA", 18, "BBB", 20);  //直接一句搞定

    System.out.println(map);
}

是不是感觉非常方便,of方法还被重载了很多次,分别适用于快速创建包含0~10对键值对的Map:

alt text
alt text

但是注意,通过这种方式创建的Map和通过Arrays创建的List比较类似,也是无法进行修改的。

当然,除了Map之外,其他的集合类都有相应的of方法:

public static void main(String[] args) {
    Set<String> set = Set.of("BBB", "CCC", "AAA");  //注意Set中元素顺序并不一定你的添加顺序
    List<String> list = List.of("AAA", "CCC", "BBB");   //好耶,再也不用Arrays了
}

改进的 Stream API

这里不是指进行IO操作的流,而是JDK1.8新增的Stream API,通过它大大方便了我们的编程。

public static void main(String[] args) {
    Stream
            .of("A", "B", "B", "C")
            //这里我们可以直接将一些元素封装到Stream中
            .filter(s -> s.equals("B"))
            //通过过滤器过滤
            .distinct()
            //去重
            .forEach(System.out::println);   //最后打印
}

自从有了Stream,我们对于集合的一些操作就大大地简化了,对集合中元素的批量处理,只需要在Stream中一气呵成

如此方便的框架,在Java 9得到了进一步的增强:

ofNullable

public static void main(String[] args) {
    Stream
            .of(null)   //如果传入null会报错
            .forEach(System.out::println);

    Stream
            .ofNullable(null) 
            //使用新增的ofNullable方法,这样就不会了,不过这样的话流里面就没东西了
            .forEach(System.out::println);
}

利用 Stream 来迭代生成数据 iterate

还有,我们可以通过迭代快速生成一组数据(实际上Java 8就有了,这里新增的是允许结束迭代的):

public static void main(String[] args) {
    Stream
            .iterate(0, i -> i + 1)   //Java8只能像这样生成无限的流,第一个参数是种子,就是后面的UnaryOperator的参数i一开始的值,最后会返回一个值作为i的新值,每一轮都会执行UnaryOperator并生成一个新值到流中,这个是源源不断的,如果不加limit()进行限制的话,将无限生成下去。
            .limit(20)   //这里限制生成20个
            .forEach(System.out::println); 
}
public static void main(String[] args) {
    Stream
            //不知道怎么写?参考一下:for (int i = 0;i < 20;i++)
            .iterate(0, i -> i < 20, i -> i + 1)  //快速生成一组0~19的int数据,中间可以添加一个断言,表示什么时候结束生成
            .forEach(System.out::println);
}

数据截断 takeWhile + dropWhile

Stream还新增了对数据的截断操作,比如我们希望在读取到某个元素时截断,不再继续操作后面的元素:

public static void main(String[] args) {
    Stream
            .iterate(0, i -> i + 1)
            .limit(20)
            .takeWhile(i -> i < 10)   //当i小于10时正常通过,一旦大于等于10直接截断
            .forEach(System.out::println);
}
public static void main(String[] args) {
    Stream
            .iterate(0, i -> i + 1)
            .limit(20)
            .dropWhile(i -> i < 10)   
            //和上面相反,上来就是截断状态,只有当满足条件时再开始通过
            .forEach(System.out::println);
}

但实际上还是遍历了20个,只不过会删除其他不符合条件的

其他小型变动

Try-with-resource语法现在不需要再完整的声明一个变量了,我们可以直接将现有的变量丢进去:

public static void main(String[] args) throws IOException {
    InputStream inputStream = Files.newInputStream(Paths.get("pom.xml"));
    try (inputStream) {   //单独丢进try中,效果是一样的
        for (int i = 0; i < 100; i++)
            System.out.print((char) inputStream.read());
    }
}

在Java 8中引入了Optional类,它很好的解决了判空问题:

public static void main(String[] args) throws IOException {
    test(null);
}

public static void test(String s){
    //比如现在我们想执行 System.out.println(str.toLowerCase())
    //但是由于我们不清楚给进来的str到底是不是null,如果是null的话会引起空指针异常
    //但是去单独进行一次null判断写起来又不太简洁,这时我们可以考虑使用Optional进行包装
    Optional
            .ofNullable(s)
            .ifPresent(str -> System.out.println(str.toLowerCase()));
}

这种写法就有点像Kotlin或是JS中的语法:

fun main() {
    test(null)
}

fun test(str : String?){   //传入的String对象可能为null,这里类型写为String?
    println(str?.lowercase())   // ?.表示只有不为空才进行调用
}

在Java 9新增了一些更加方便的操作:

public static void main(String[] args) {
    String str = null;
    Optional.ofNullable(str).ifPresentOrElse(s -> {  //通过使用ifPresentOrElse,我们同时处理两种情况
        System.out.println("被包装的元素为:"+s);     //第一种情况和ifPresent是一样的
    }, () -> {
        System.out.println("被包装的元素为null");   //第二种情况是如果为null的情况
    });
}

我们也可以使用or()方法快速替换为另一个Optional类:

public static void main(String[] args) {
    String str = null;
    Optional.ofNullable(str)
      .or(() -> Optional.of("AAA"))   //如果当前被包装的类不是null,依然返回自己,但是如果是null,那就返回Supplier提供的另一个Optional包装
      .ifPresent(System.out::println);
}

当然还支持直接转换为Stream,这里就不多说了。

在Java 8及之前,匿名内部类是没办法使用钻石运算符进行自动类型推断的:

public abstract class Test<T>{   //这里我们写一个泛型类
    public T t;

    public Test(T t) {
        this.t = t;
    }

    public abstract T test();
}
public static void main(String[] args) throws IOException {
    Test<String> test = new Test<>("AAA") {
        //在低版本这样写是会直接报错的,因为匿名内部类不支持自动类型推断,但是很明显我们这里给的参数是String类型的,所以明明有机会进行类型推断,却还是要我们自己填类型,就很蠢
        //在Java 9之后,这样的写法终于可以编译通过了
        @Override
        public String test() {
            return t;
        }
    };
}

当然除了以上的特性之外还有Java 9的多版本JAR包支持、CompletableFuture API的改进等,因为不太常用,这里就不做介绍了。

上次编辑于: