Notes on new features of Qingkong's Xiaguang Java

image-20220528201059618

Introduction to new Java features

**Note:** It is recommended to complete all the prerequisite content of this route before studying this article.

After the previous study, we have basically understood all the syntax of Java 8 and before. However, Java 8 was released by Oracle on March 18, 2014. It has been nearly ten years since today, and Java has not It stops here, but continues to develop and grow. Almost every 6 months, a new version will appear. The latest version is about to iterate to Java 20, which is different from Java 8 by a dozen or so versions. However, due to Due to the stability and ecological improvement of Java 8 (it is still the LTS long-term maintenance version), many companies still insist on using Java 8. However, with the arrival of SpringBoot 3.0, it is now mandatory to use the Java 17 version (which is also the LTS long-term maintenance version). , the era of the next Java version may be approaching.

image-20220528202952628

As these mainstream frameworks fully embrace Java 17, in order not to be eliminated by the times, our learning path must continue to move forward. Just like many years ago when Java 6 was still mainstream, it was eventually replaced by Java 8.

In this video, we will introduce all the new features of Java 9 - Java 17. We recommend that you prepare the JDK 17 environment in advance (Oracle JDK 17 has fully supported arm chip Mac computers, please feel free to eat)

image-20220528220146662

The whole video focuses on the key points, no ink, let’s get started.

Java 8 key feature review

Before we begin, let's first review the Lambda expressions and Optional classes we learned in Java 8. Regarding the Stream API, please review the Java SE video tutorial, which will not be introduced here.

lambda expression

Before Java 8, we may need to use anonymous inner classes in some cases, such as:

public static void main(String[] args) {
    
    
    //现在我们想新建一个线程来搞事情
    Thread thread = new Thread(new Runnable() {
    
       //创建一个实现Runnable的匿名内部类
        @Override
        public void run() {
    
       //具体的实现逻辑
            System.out.println("Hello World!");
        }
    });
    thread.start();
}

When creating a Thread, we need to pass in an implementation class of the Runnable interface to specify the specific tasks to be performed in the new thread. The relevant logic needs to be implemented in the method. For convenience, we will use anonymous directly run(). An implementation is passed in through an internal class, but this way of writing is too bloated.

After Java 8, we can reduce the writing of anonymous inner classes like this. In fact, if we observe, we will find that the really useful part of the code is actually our specific implementation of the method, and the other run()parts In fact, writing is exactly the same everywhere, so can we optimize for this situation? All we need now is a short lambda expression:

public static void main(String[] args) {
    
    
    //现在我们想新建一个线程来做事情
    Thread thread = new Thread(() -> {
    
    
        System.out.println("Hello World!");  //只需留下我们需要具体实现的方法体
    });
    thread.start();
}

We can find that everything that originally needed to be completely written, including classes and methods, is no longer needed. Instead, it () ‐> { 代码语句 }can be replaced directly with a similar form. Doesn’t it feel like the code is instantly refreshed N times?

Of course, this is just a way of writing. If you don't understand it well, you can think of it as a shortening of the previous anonymous inner class writing method.

But note that its bottom layer is not just a simple syntax sugar replacement, but is invokedynamicimplemented through instructions. It is not difficult to find that anonymous inner classes will create a separate class file at compile time, but lambda will not, indirectly Note that after compilation, lambda does not exist in the form of an anonymous inner class:

//现在我们想新建一个线程来做事情
Thread thread = new Thread(() -> {
     
     
 throw new UnsupportedOperationException();   //这里我们拋个异常看看
});
thread.start();

image-20220529214948350

As you can see, the exception is actually thrown by the method in the Main class lambda$main$0(), but there is no such method in our Main class at all. It is obviously automatically generated. Therefore, rather than saying that Lambda is syntactic sugar for anonymous inner classes, it is better to say that we provide a method for the required interface as its implementation. run()For example, the Runnable interface needs a method body to implement its methods, and here we give it a method body in the form of lambda, so that everything is ready, and then we only need to leave it to the JVM to create the implementation class . .

Let’s take a look at the specific specifications of Lambda expressions:

  • The standard format is:([参数类型 参数名称,]...) ‐> { 代码语句,包括返回值 }
  • Unlike anonymous inner classes, Lambda only supports interfaces, not abstract classes.
  • There must be one and only one abstract method inside the interface (there can be multiple methods, but other methods must be guaranteed to have default implementations, and one abstract method must be left out)

For example, the Runable class we used before:

@FunctionalInterface   //添加了此注解的接口,都支持lambda表达式,符合函数式接口定义
public interface Runnable {
    
    
    public abstract void run();   //有且仅有一个抽象方法,此方法返回值为void,且没有参数
}

Therefore, the anonymous inner class implementation of Runable can be abbreviated as:

Runnable runnable = () -> {
    
        };

We can also write one for fun:

@FunctionalInterface
public interface Test {
    
       //接口类型
    String test(Integer i);    //只有这一个抽象方法,且接受一个int类型参数,返回一个String类型结果
}

Its implementation of Lambda expression can be written as:

Test test = (Integer i) -> {
    
     return i+""; };  //这里我们就简单将i转换为字符串形式

However, optimization can also be done. First, the method parameter type can be omitted:

Test test = (i) -> {
    
     return i+""; };

Since there is only one parameter, you don’t need to add parentheses (required for multiple parameters):

Test test = i -> {
    
     return i+""; };

Since there is only one line of return statement, the final returned result can be written directly without the need for curly braces:

Test test = i -> i+"";

In this way, compared to writing an anonymous inner class directly before, it is much simpler. Of course, in addition to manually writing the method body of the abstract method in the interface, if there is already a well-implemented method, you can directly use it, such as:

String test(Integer i);   //接口中的定义
public static String impl(Integer i){
    
       //现在有一个静态方法,刚好匹配接口中抽象方法的返回值和参数列表
    return "我是已经存在的实现"+i;
}

Therefore, we can directly implement this method as the method body of lambda expression (in fact, this is a method reference, which refers to a method. This is why I said before, are you more and more aware of the essence of 是我们为所需要的接口提供了一个方法作为它的实现this sentence? ):

public static void main(String[] args) {
    
    
    Test test = Main::impl;    //使用 类名::方法名称 的形式来直接引用一个已有的方法作为实现
}

public static String impl(Integer i){
    
    
    return "我是已经存在的实现"+i;
}

For example, we now need to sort an array:

public static void main(String[] args) {
    
    
    Integer[] array = new Integer[]{
    
    4, 6, 1, 9, 2, 0, 3, 7, 8, 5};   //来个数组
    Arrays.sort(array, new Comparator<Integer>() {
    
       //Arrays.sort()可以由我们自己指定排序规则,只需要实现Comparator方法即可
        @Override
        public int compare(Integer o1, Integer o2) {
    
    
            return o1 - o2;
        }
    });
    System.out.println(Arrays.toString(array));   //按从小到大的顺序排列
}

But we found that there is a comparestatic method called in the Integer class:

public static int compare(int x, int y) {
    
    
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

This method is a static method, but it Comparatoris exactly the same as the return value and parameter definition of the method that needs to be implemented, so everyone who understands it understands:

public static void main(String[] args) {
    
    
    Integer[] array = new Integer[]{
    
    4, 6, 1, 9, 2, 0, 3, 7, 8, 5};
    Arrays.sort(array, Integer::compare);   //直接指定一手,效果和上面是一模一样
    System.out.println(Arrays.toString(array));
}

So what if it is not a static method but an ordinary member method? We noticed that the method Comparator requires us to implement is:

public int compare(Integer o1, Integer o2) {
    
    
     return o1 - o2;
}

Among them, o1 and o2 are both of type Integer. We found that there is a compareTomethod in the Integer class:

public int compareTo(Integer anotherInteger) {
    
    
    return compare(this.value, anotherInteger.value);
}

It’s just that this method is not static, but owned by the object:

Integer[] array = new Integer[]{
    
    4, 6, 1, 9, 2, 0, 3, 7, 8, 5};
Arrays.sort(array, new Comparator<Integer>() {
    
    
    @Override
    public int compare(Integer o1, Integer o2) {
    
    
        return o1.compareTo(o2);   //这样进行比较也行,和上面效果依然是一样的
    }
});
System.out.println(Arrays.toString(array));

But at this time we will find that IDEA prompts us to abbreviate it. Why is this? In fact, when we use a non-static method, the first one in the abstract parameter list will be used as the target object, and the subsequent parameters will be used as parameters of the target object's member method. That is to say, at this time, as the target object, as a parameter o1, o2exactly The method is matched compareTo, so abbreviate it directly:

public static void main(String[] args) {
    
    
    Integer[] array = new Integer[]{
    
    4, 6, 1, 9, 2, 0, 3, 7, 8, 5};
    Arrays.sort(array, Integer::compareTo);  //注意这里调用的不是静态方法
    System.out.println(Arrays.toString(array));
}

Member methods can also prevent the object itself from becoming a participating party and only refer to the method:

public static void main(String[] args) {
    
    
    Main mainObject = new Main();
    Integer[] array = new Integer[]{
    
    4, 6, 1, 9, 2, 0, 3, 7, 8, 5};
    Arrays.sort(array, mainObject::reserve);  //使用Main类的成员方法,但是mainObject对象并未参与进来,只是借用了一下刚好匹配的方法
    System.out.println(Arrays.toString(array));
}

public int reserve(Integer a, Integer b){
    
      //现在Main类中有一个刚好匹配的方法
    return b.compareTo(a);
}

Of course, the constructor of a class can also be passed as a method reference:

public interface Test {
    
    
    String test(String str);   //现在我们需要一个参数为String返回值为String的实现
}

We found that there is exactly one in the String class:

public String(String original) {
    
       //由于String类的构造方法返回的肯定是一个String类型的对象,且此构造方法需要一个String类型的对象,所以,正好匹配了接口中的
    this.value = original.value;
    this.coder = original.coder;
    this.hash = original.hash;
}

Ever since:

public static void main(String[] args) {
    
    
    Test test = String::new;   //没错,构造方法直接使用new关键字就行
}

Of course, in addition to the above-mentioned situations where you can use method references, there are many other places where you can use them. Please explore on your own. Java 8 also provides us with some built-in functional interfaces for us to use: Consumer, Function, Supplier, etc. For details, please review the JavaSE video tutorial.

Optional class

The Optional feature was newly introduced in Java 8 to allow us to handle null pointer exceptions more elegantly. Let’s first look at the following example:

public static void hello(String str){
    
       //现在我们要实现一个方法,将传入的字符串转换为小写并打印
    System.out.println(str.toLowerCase());  //那太简单了吧,直接转换打印一气呵成
}

But if we implement it in this way, we will have to consider one less problem. What if the person who comes in stris null? If so, wouldn't it be a direct null pointer exception when nullcalling the method? toLowerCaseSo we still have to judge:

public static void hello(String str){
    
    
    if(str != null) {
    
    
        System.out.println(str.toLowerCase());
    }
}

But writing like this cannot be done in one go. Now that I have obsessive-compulsive disorder, I want to solve it in one line. At this time, Optional comes, and we can wrap any variable into the Optional class for use:

public static void hello(String str){
    
    
    Optional
            .ofNullable(str)   //将str包装进Optional
            .ifPresent(s -> {
    
       //ifPresent表示只有对象不为null才会执行里面的逻辑,实现一个Consumer(接受一个参数,返回值为void)
                System.out.println(s);   
            });
}

Since there is only one sentence to print here, let’s optimize it:

public static void hello(String str){
    
    
    Optional
            .ofNullable(str)   //将str包装进Optional
            .ifPresent(System.out::println);  
  	//println也是接受一个String参数,返回void,所以这里使用我们前面提到的方法引用的写法
}

In this way, we can finish it in one go. Doesn’t it feel more elegant than the previous writing method?

In addition to the operations performed when not empty, you can also get the wrapped object directly from the Optional:

System.out.println(Optional.ofNullable(str).get());

However, when the wrapped object is null, an exception will be thrown directly. Of course, we can also specify an alternative if the get object is null:

System.out.println(Optional.ofNullable(str).orElse("VVV"));   //orElse表示如果为空就返回里面的内容

For other operations, please review the JavaSE video tutorial.

Java 9 new features

In this part, we will introduce the new features that Java 9 brings to us. The main features of Java 9 include the new module mechanism, the private method of the interface, etc.

Module mechanism

In our previous development, I wonder if you have noticed a problem, that is, when we import a jarpackage as a dependency (including the JDK official library), we actually do not use many functions, but because they belong to the same Dependencies are bundled together, so that we may only use part of the content, but need to reference a complete class library. In fact, we can exclude unused class libraries and greatly reduce the size of the dependent library.

Therefore, Java 9 introduced the module mechanism to optimize this situation. Our previous project looked like this:

image-20220528210803658

After introducing the module mechanism:

image-20220528210958964

As you can see, a module can be composed of one or more Java packages that are together. By dividing these packages into different modules, we can manage them in the form of modules. Here we create a new project and create a new file srcin the directory to indicate that this project uses the module management mechanism:module-info.java

module NewHelloWorld {
    
      //模块名称随便起一个就可以,但是注意必须是唯一的,以及模块内的包名也得是唯一的,即使模块不同
    
}

Next we create a main class:

image-20220528213210752

The program can run normally, and it seems to be no different from before. However, we found that some of the frameworks provided by JDK for us are missing:

image-20220528213428296

What about the related log libraries provided by Java logging? Did we find that it's missing now? In fact, it exists alone as a module. Here we need to import the module:

module NewHelloWorld {
    
      //模块名称随便起一个就可以
    requires java.logging;   //除了JDK的一些常用包之外,只有我们明确需要的模块才会导入依赖库
  	//当然如果要导入JavaSE的所有依赖,想之前一样的话,直接 requires java.se;  即可
}

Here, after we import the java.logging related modules, we can use Logger normally:

image-20220528214247006

image-20220528214308194

Do you feel that writing code instantly feels much more refreshing? The new modular mechanism provides another level of visibility and accessibility control of Java code. However, do you think it is just a separation of packages? We can try to obtain the fields in the classes provided by JDK through reflection:

//Java17版本的String类
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {
    
    
    @Stable
    private final byte[] value;  //自JDK9后,为了提高性能,String底层数据存放的是byte[]而不是char[]
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    
    
    Class<String> stringClass = String.class;
    Field field = stringClass.getDeclaredField("value");   //这里我们通过反射来获取String类中的value字段
    field.setAccessible(true);   //由于是private访问权限,所以我们修改一下
    System.out.println(field.get("ABCD"));
}

But we found that after the program was run, the modification operation was blocked:

image-20220528221817482

The Java 9 encapsulation and security of the reflection API have been improved. If the module does not explicitly authorize other modules to use reflection, then other modules are not allowed to use reflection for modification. It seems that the Unsafe class cannot be used.

Let’s take a closer look at this module mechanism. First, there are four types of modules:

  • **System modules:** Modules from JDK and JRE (officially provided modules, such as the ones we used above), we can also directly use java --list-modulescommands to list all modules, different modules will export different packages for us to use .
  • **Application module:** A Java module project written by ourselves.
  • **Automatic module:** There may be some libraries that are not module projects above Java 9. In this case, compatibility needs to be done. By default, all packages are exported directly and you can access the classes provided by all other modules. Otherwise, previous versions The library can no longer be used.
  • **Unnamed module:** A Java project we created ourselves. If it is not created module-info.java, it will be processed as an unnamed module. The unnamed module can also access the classes provided by all other modules, so that the Java 8 code we wrote before It can run normally under Java 9 and later versions. However, since the new module features of Java 9 are not used, unnamed modules can only be exposed to other unnamed modules and automatic modules by default, and application modules cannot access these classes (actually, it is the traditional programming mode below Java 8, because there is no The module only needs to import the package)

Here we will create two projects to see how to use the module mechanism. First, we add a User class to project A. Project B will need to use it later:

package com.test;

public class User {
    
    
    String name;
    int age;

    public User(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
    
    
        return name+" ("+age+"岁)";
    }
}

Next we write the module settings for project A:

image-20220528230842617

Here we com.testexpose all the content under the package. By default, all packages are private and cannot be used even if other projects rely on this project.

Then we now want to use the User class of project A in project B. We need to import:

image-20220528232033318

module.aNow we can use the package contents exposed in the module in the Main class :

import com.test.User;   //如果模块module.a不暴露,那么将无法导入

public class Main {
    
    
    public static void main(String[] args) {
    
    
        User user = new User("lbw", 18);
        System.out.println(user);
    }
}

Of course, in addition to ordinary exportspackage exposure, we can also directly specify to expose the package to specified modules:

module module.a {
    
    
    exports com.test to module.b;   //这里我们将com.test包暴露给指定的模块module.b,非指定的模块即使导入也无法使用
}

But there is still a question. If a module module.adepends on other modules, will it be passed to module.athe module that depends on the module?

module module.a {
    
    
    exports com.test to module.b;   //使用exports将com.test包下所有内容暴露出去,这样其他模块才能导入
    requires java.logging;   //这里添加一个模块的依赖
}

image-20220529103614788

It can be seen that in the module module.b, there is no dependency transfer, which means that the dependencies imported by which module can only be used by that module. But now we hope that the dependencies can be transferred, that is, which module uses which dependency, and the modules that depend on this module will automatically To make dependencies, we can solve it with a keyword:

module module.a {
    
    
    exports com.test to module.b;   //使用exports将com.test包下所有内容暴露出去,这样其他模块才能导入
    requires transitive java.logging;   //使用transitive来向其他模块传递此依赖
}

Now it's ready to use:

image-20220529103828560

As well as the reflection we demonstrated earlier, we found that if we depend on a module, we cannot directly perform reflection operations:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    
    
    User user = new User("AAA", 18);
    Class<User> userClass = User.class;
    Field field = userClass.getDeclaredField("name");
    field.setAccessible(true);   //尝试修改访问权限
    System.out.println(field.get(user));
}

image-20220529104451040

So how can we use reflection? We can open certain classes for other modules to run using reflection:

open module module.a {
    
       //直接添加open关键字开放整个模块的反射权限
    exports com.test to module.b;
}
module module.a {
    
    
    exports com.test to module.b;
    opens com.test;   //通过使用opens关键字来为其他模块开放反射权限
  	//也可以指定目标开放反射 opens com.test to module.b;
}

We can also specify the abstract class or interface implementation that the module needs to use:

package com.test;

public interface Test {
    
    
}
open module module.a {
    
    
    exports com.test to module.b;
    uses com.test.Test;  //使用uses指定,Test是一个接口(比如需要的服务等),模块需要使用到
}

We can implement it in module B, and then declare that we provide the implementation class:

package com.main;

import com.test.Test;

public class TestImpl implements Test {
    
    

}
module module.b {
    
    
    requires module.a;   //导入项目A的模块,此模块暴露了com.test包
    provides com.test.Test with com.main.TestImpl;  //声明此模块提供了Test的实现类
}

After understanding the above relevant knowledge, we can simply use the module. For example, now we create a new Maven project:

image-20220529112208486

Then we imported the dependencies of the lombok framework. If we do not create module-info.javaa file, it will be an unnamed module. The unnamed module can use the classes provided by all other modules by default. This is actually our previous development mode:

package com.test;

import lombok.extern.java.Log;

@Log
public class Main {
    
    
    public static void main(String[] args) {
    
    
        log.info("Hello World!");   //使用lombok提供的注解,可以正常运行
    }
}

Now we want to develop according to the new modular development model and improve our project from unnamed modules to application modules, so we first create the module-info.javafile:

module com.test {
    
    
}

As you can see, an error is reported directly:

image-20220529112707958

The lombok dependency is obviously imported, but it cannot be used. This is because we still need to rely on the corresponding module:

module com.test {
    
    
    requires lombok;   //lombok模块
    requires java.logging;    //JUL日志模块,也需要使用到
}

image-20220529112909452

In this way, we can use it normally. Later, for the convenience of tutorial demonstration, we will not use the module.

JShell interactive programming

Java 9 provides us with an interactive programming tool JShell, let alone, it really tastes like Python.

image-20220529141547082

After the environment configuration is completed, we only need to enter jshellcommands to start interactive programming. It allows us to operate one command at a time.

For example, let's do a simple calculation:

image-20220529141719363

We enter one line at a time (you can skip the semicolon), first define a=10 and b=10, and then define c and get the result of a+b. As you can see, it is very convenient, but please note that the syntax is still the same as Java. of.

image-20220529141954494

We can also quickly create a method for subsequent calls. When we press the Tab key, we can also perform automatic completion:

image-20220529142340030

In addition to directly running the code we wrote, it also supports the use of commands and input helpto view the command list:

image-20220529142440584

For example, we can use /varsthe command to display the currently defined variable list:

image-20220529142757286

When we don’t want to use jshell, we /exitcan directly enter to exit:

image-20220529142852920

Private methods in interfaces

In Java 8, methods in interfaces support adding defaultkeywords to add default implementations:

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

In Java 9, the interface has been strengthened again, and now private methods can exist in the interface:

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

Note that private methods must provide a method body, because the permissions are private, and only here can the specific implementation of the method be carried out, and this method can only be called by other private methods in the interface or the default implementation.

New factory method added to collection class

Previously, if we wanted to quickly create a Map we could only:

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);
}

After Java 9, we can ofquickly create directly through methods:

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

    System.out.println(map);
}

Doesn’t it feel very convenient? The of method has been overloaded many times, which is suitable for quickly creating a Map containing 0~10 key-value pairs:

image-20220529144905646

However, note that the Map created in this way is similar to the List created through Arrays and cannot be modified.

Of course, in addition to Map, other collection classes have corresponding ofmethods:

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

Improved Stream API

Remember the Stream stream we learned in JavaSE before? Of course, this does not refer to the stream for IO operations, but the new Stream API in JDK1.8, which greatly facilitates our programming.

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

Since the introduction of Stream, some of our operations on collections have been greatly simplified. Batch processing of elements in the collection only needs to be done in one go in Stream (for specific detailed operations, please review the JavaSE article)

Such a convenient framework has been further enhanced in Java 9:

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

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

Also, we can quickly generate a set of data through iteration (in fact, it is available in Java 8, and the new thing here is to allow the end of iteration):

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);
}

Stream also adds a new truncation operation for data. For example, we want to truncate when a certain element is read and no longer continue to operate on subsequent elements:

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);
}

Other minor changes

The try-with-resource syntax now no longer requires a complete declaration of a variable, we can just throw the existing variable in:

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());
    }
}

The Optional class was introduced in Java 8, which solves the null problem very well:

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()));
}

This way of writing is a bit like the syntax in Kotlin or JS:

fun main() {
    
    
    test(null)
}

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

Some more convenient operations have been added in 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的情况
    });
}

We can also use or()methods to quickly replace with another Optional class:

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

Of course, direct conversion to Stream is also supported, so I won’t go into details here.

In Java 8 and before, anonymous inner classes cannot use the diamond operator for automatic type inference:

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;
        }
    };
}

Of course, in addition to the above features, there are also Java 9's multi-version JAR package support, improvements to the CompletableFuture API, etc. Since they are not commonly used, they will not be introduced here.

Java 10 new features

Java 10 mainly brings some internal updates. Compared with Java 9, there are not many intuitive changes. The most prominent one is local variable type inference.

Local variable type inference

In Java we can use automatic type inference:

public static void main(String[] args) {
    
    
    // String a = "Hello World!";   之前我们定义变量必须指定类型
    var a = "Hello World!";   //现在我们使用var关键字来自动进行类型推断,因为完全可以从后面的值来判断是什么类型
}

But note that varthe keyword must be located on a variable with an initial value, otherwise the devil knows what type you want to use.

image-20220529171216795

Let’s see if the type can also be obtained normally:

public static void main(String[] args) {
    
    
    var a = "Hello World!";
    System.out.println(a.getClass());
}

Although the var keyword is used here for automatic type inference, it will eventually become a String type, and the resulting Class is also a String type. But after all, Java does not perform dynamic inference like JS. This type inference only occurs during compilation, and will still become a specific type after the final compilation is completed:

image-20220529170538383

And varthe keyword only applies to local variables, we cannot use it in other places, such as member variables of a class:

image-20220529171444062

Regarding some other improvements added in Java 10, I will not mention them here.

Java 11 new features

Java 11 is another long-term maintenance version of TLS after Java 8. Before the emergence of Java 17, this version has been the widely used version. The most critical one is the formal parameter local variable syntax for Lambda.

Formal parameter local variable syntax for Lambda

In Java 10 we met varthe keyword, which can directly allow local variables to automatically perform type inference, but it does not support use in lambdas:

image-20220529235822891

But in fact, type inference is completely possible here, so in Java 11, it is finally supported, so no errors will be reported when writing:

image-20220529235935071

Method enhancements for String class

In Java 11, some more convenient operations are added for String:

public static void main(String[] args) {
    
    
    var str = "AB\nC\nD";
    System.out.println(str.isBlank());    //isBlank方法用于判断是否字符串为空或者是仅包含空格
    str
            .lines()   //根据字符串中的\n换行符进行切割,分为多个字符串,并转换为Stream进行操作
            .forEach(System.out::println);
}

We can also use repeat()methods to repeatedly splice strings:

public static void main(String[] args) {
    
    
    String str = "ABCD";   //比如现在我们有一个ABCD,但是现在我们想要一个ABCDABCD这样的基于原本字符串的重复字符串
    System.out.println(str.repeat(2));  //一个repeat就搞定了
}

We can also quickly remove spaces:

public static void main(String[] args) {
    
    
    String str = " A B C D ";
    System.out.println(str.strip());   //去除首尾空格
    System.out.println(str.stripLeading());  //去除首部空格
    System.out.println(str.stripTrailing());   //去除尾部空格
}

Brand new HttpClient usage

In Java 9, the new Http Client API has actually been introduced to replace the older HttpURLConnection class. The new API supports the latest HTTP2 and WebSocket protocols.

public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
    
    
    HttpClient client = HttpClient.newHttpClient();   //直接创建一个新的HttpClient
  	//现在我们只需要构造一个Http请求实体,就可以让客户端帮助我们发送出去了(实际上就跟浏览器访问类似)
    HttpRequest request = HttpRequest.newBuilder().uri(new URI("https://www.baidu.com")).build();
  	//现在我们就可以把请求发送出去了,注意send方法后面还需要一个响应体处理器(内置了很多)这里我们选择ofString直接吧响应实体转换为String字符串
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
  	//来看看响应实体是什么吧
    System.out.println(response.body());
}

Using the new client, we can even easily make a crawler (for learning purposes only, don’t do anything illegal, if you play well with the crawler, you can eat as much as you want). For example, now we want to download a website in batches wallpaper:

image-20220530112549225

Website address: https://pic.netbian.com/4kmeinv/

We clicked on a wallpaper at random and found that the URL format of the website is:

image-20220530112701156

And different wallpapers seem to be like this: https://pic.netbian.com/tupian/digits.html. Now you can almost start the whole thing:

public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
    
    
    HttpClient client = HttpClient.newHttpClient();
    for (int i = 0; i < 10; i++) {
    
      //先不要一次性获取太多,先来10个
        HttpRequest request = HttpRequest.newBuilder().uri(new URI("https://pic.netbian.com/tupian/"+(29327 + i)+".html")).build();  //这里我们按照规律,批量获取
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());  //这里打印一下看看网页
    }
}

As you can see, the console finally successfully obtained the website pages of these images:

image-20220530113039571

Next we need to observe how the website's HTML is written and extract the address of the image:

image-20220530113136156

Okay, now that you know where the picture is, it’s easy to intercept the string directly:

public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
    
    
    HttpClient client = HttpClient.newHttpClient();
    for (int i = 0; i < 10; i++) {
    
    
        ...
        String html = response.body();
        
        String prefix = "<a href=\"\" id=\"img\"><img src=\"";  //先找好我们要截取的前面一段,作为前缀去匹配位置
        String suffix = "\" data-pic=";   //再找好我们要截取的屁股后面紧接着的位置,作为后缀去匹配位置
      	//直接定位,然后前后截取,得到最终的图片地址
        html = html.substring(html.indexOf(prefix) + prefix.length());
        html = html.substring(0, html.indexOf(suffix));
        System.out.println(html);  //最终的图片地址就有了
    }
}

Okay, now the image addresses can also be obtained in batches. Get these images directly and save them locally:

public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
    
    
    HttpClient client = HttpClient.newHttpClient();
    for (int i = 0; i < 10; i++) {
    
    
        ...
				//创建请求,把图片取到
        HttpRequest imageRequest = HttpRequest.newBuilder().uri(new URI("https://pic.netbian.com"+html)).build();
      	//这里以输入流的方式获取,不过貌似可以直接下载文件,各位小伙伴可以单独试试看
        HttpResponse<InputStream> imageResponse = client.send(imageRequest, HttpResponse.BodyHandlers.ofInputStream());
      	//拿到输入流和文件输出流
        InputStream imageInput = imageResponse.body();
        FileOutputStream stream = new FileOutputStream("images/"+i+".jpg"); //一会要保存的格式
        try (stream;imageInput){
    
      //直接把要close的变量放进来就行,简洁一些了
            int size;   //下面具体保存过程的不用我多说了吧
            byte[] data = new byte[1024];  
            while ((size = imageInput.read(data)) > 0) {
    
      
                stream.write(data, 0, size);
            }
        }
    }
}

Let’s take a look at the effect now. The beauty’s picture has been successfully saved locally:

image-20220530114824605

Of course, this is just a relatively simple crawler, but our ultimate goal is to hope that you can learn to use the new HttpClient API.

Java 12-16 new features

Since the update iteration speed of Java version has been every six months since Java 9 (a full three years passed between Java 8 and Java 9), there are relatively few updates between each version. For the remaining 6 versions, we have multiple versions Put them together and explain them.

image-20220530120729757

The five versions of Java12-16 are not long-term support versions, so many features are experimental features. Versions 12/13 introduced some experimental features and made adjustments based on feedback. Finally, they were officially opened for use in subsequent versions. In fact, it is the feeling of experiencing the server.

New switch syntax

The new switch syntax is introduced in Java 12, allowing us to use switch statements more flexibly. For example, if we want to write a method to get grades based on grades:

/**
 * 传入分数(范围 0 - 100)返回对应的等级:
 *      100-90:优秀
 *      70-80:良好
 *      60-70:及格
 *      0-60:寄
 * @param score 分数
 * @return 等级
 */
public static String grade(int score){
    
    
    
}

Now we want to use switch to implement this function (no, no, no one wants to use switch for a long time to implement it), the previous writing was:

public static String grade(int score){
    
    
    score /= 10;  //既然分数段都是整数,那就直接整除10
  	String res = null;
    switch (score) {
    
    
        case 10:
        case 9:
            res =  "优秀";   //不同的分数段就可以返回不同的等级了
        		break;   //别忘了break,不然会贯穿到后面
        case 8:
        case 7:
            res = "良好";
        		break;
        case 6:
            res = "及格";
        		break;
        default:
            res = "不及格";
        		break;
    }
  	return res;
}

But now we can use new features:

public static String grade(int score){
    
    
    score /= 10;  //既然分数段都是整数,那就直接整除10
    return switch (score) {
    
       //增强版switch语法
        case 10, 9 -> "优秀";   //语法那是相当的简洁,而且也不需要我们自己考虑break或是return来结束switch了(有时候就容易忘记,这样的话就算忘记也没事了)
        case 8, 7 -> "良好"; 
        case 6 -> "及格";
        default -> "不及格";
    };
}

However, the final compiled look seems to be the same as before:

image-20220530222918174

This brand-new switch syntax is called switch表达式, and its significance is not only reflected in the simplification of the syntax. Let's take a look at its detailed rules:

var res = switch (obj) {
    
       //这里和之前的switch语句是一样的,但是注意这样的switch是有返回值的,所以可以被变量接收
    case [匹配值, ...] -> "优秀";   //case后直接添加匹配值,匹配值可以存在多个,需要使用逗号隔开,使用 -> 来返回如果匹配此case语句的结果
    case ...   //根据不同的分支,可以存在多个case
    default -> "不及格";   //注意,表达式要求必须涵盖所有的可能,所以是需要添加default的
};

So what if we are not able to return immediately, but need to do some other work to return the result?

var res = switch (obj) {
    
       //增强版switch语法
    case [匹配值, ...] -> "优秀";
    default -> {
    
       //我们可以使用花括号来将整套逻辑括起来
        //... 我是其他要做的事情
        yield  "不及格";  //注意处理完成后需要返回最终结果,但是这样并不是使用return,而是yield关键字
    }
};

Of course, it can also be like this:

var res = switch (args.length) {
    
       //增强版switch语法
    case [匹配值, ...]:
        yield "AAA";   //传统的:写法,通过yield指定返回结果,同样不需要break
    default:
    		System.out.println("默认情况");
        yield "BBB";
};

This brand-new syntax can be said to greatly facilitate our coding. Not only is the code short, but the semantics are clear. The only regret is that interval matching is still not supported.

**Note:** The switch expression was only officially opened for use in Java 14, so the code level of our project needs to be adjusted to 14 or above.

text block

If you have studied Python, you must know the three quotation marks:

#当我们需要使用复杂字符串时,可能字符串中包含了很多需要转义的字符,比如双引号等,这时我们就可以使用三引号来囊括字符串
multi_line =  """
                nice to meet you!
                  nice to meet you!
                      nice to meet you!
                """
print multi_line

Yes, Java13 also brings such a feature, which is designed to make it easier for us to write complex strings, so that we no longer need to use so many escape characters:

image-20220530230225037

As you can see, such triple quotes can also be used to represent strings in Java, and we can use special characters in it at will, including double quotes, etc., but the final compiled result will actually become a string used like this String with escaped characters:

image-20220530230343933

Think about it carefully, wouldn't it be more comfortable for us to write SQL or HTML this way?

**Note:** Text block expressions were only officially opened for use in Java 15, so the code level of our project needs to be adjusted to 15 or above.

New instanceof syntax

In Java 14, instanceof has received a wave of small updates (haha, this version of instanceof has been strengthened again, and the version has strong syntax)

For example, we previously wanted to override the equals method of a class:

public class Student {
    
    
    private final String name;

    public Student(String name) {
    
    
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
    
    
        if(obj instanceof Student) {
    
       //首先判断是否为Student类型
            Student student = (Student) obj;  //如果是,那么就类型转换
            return student.name.equals(this.name);  //最后比对属性是否一样
        }
        return false;
    }
}

In the past, we have always used this method of first determining the type, then converting the type, and finally using it. However, after the enhancement of instanceof in this version, we no longer need it. We can directly replace student with a pattern variable:

image-20220530232252253

public class Student {
    
    
    private final String name;

    public Student(String name) {
    
    
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
    
    
        if(obj instanceof Student student) {
    
       //在比较完成的屁股后面,直接写变量名字,而这个变量就是类型转换之后的
            return student.name.equals(this.name);  //下面直接用,是不是贼方便
        }
        return false;
    }
}

After instanceofthe judgment type is established, the type will be automatically forced to the specified type, simplifying our manual conversion steps.

**Note:** The new instanceof syntax is only officially available in Java 16, so the code level of our project needs to be adjusted to 16 or above.

Null pointer exception improvements

I believe that all of you often encounter null pointer exceptions when debugging code, such as the following example:

public static void test(String a, String b){
    
    
    int length = a.length() + b.length();   //可能给进来的a或是b为null
    System.out.println(length);
}

Then when it is empty, it will directly:

image-20220530232755797

But since we have called length()methods on both a and b here, although the null pointer exception tells us that the problem occurs in this line, is a null or b null? We can't get it directly (deduct 1 for those who have encountered this kind of problem, it can only be debugged, which is a headache)

But when we run on Java 14 or higher:

image-20220530233031005

It will be clearly pointed out which variable call has a null pointer. Doesn't it feel particularly user-friendly?

record type

After classes, interfaces, enumerations, and annotations, another new type has arrived. Its name is "record". It appears for the first time in Java 14. With this appearance, Lombok's nightmare is coming.

In actual development, many classes just act as an entity class and store some immutable data, such as the account information we query from the database, which will eventually be mapped to an entity class:

@Data
public class Account {
    
       //使用Lombok,一个注解就搞定了
    String username;
    String password;
}

Lombok can be said to be an artifact that simplifies code. It can automatically generate getters and setters, constructors, toString() methods and other implementations during compilation. When writing these entity classes, it is simply not easy to use, and this wave, the official I couldn’t stand it anymore, so I made a record type myself.

The record type is essentially an ordinary class, but it is a final type and inherits from the java.lang.Record abstract class. It will automatically compile out methods such public get hashcodeas , equals, toStringand etc. at compile time. Good guy, this is going to kill Lombok. ah.

public record Account(String username, String password) {
    
      //直接把字段写在括号中

}

It is also very convenient to use. The constructor and the public get method of the member field are automatically generated:

image-20220530235609885

And toString has also been rewritten:

image-20220530235719341

equals()The method only compares values ​​between member fields, which also helps us achieve it:

Account account0 = new Account("Admin", "123456");
Account account1 = new Account("Admin", "123456");   //两个属性都是一模一样的
System.out.println(account0.equals(account1));  //得到true

Does it feel like this type is specially created for this type of entity?

public record Account(String username, String password) implements Runnable {
    
      //支持实现接口,但是不支持继承,因为继承的坑位已经默认被占了

    @Override
    public void run() {
    
    
        
    }
}

**Note:** The record type was only officially opened for use in Java 16, so the code level of our project needs to be adjusted to 16 or above.

Java 17 new features

Java 17 is the new LTS long-term maintenance version. Let’s take a look at what has been updated (excluding preview features, including the second enhancement of switch, haha, it is still not strong enough, and it has been enhanced for two consecutive versions)

Seal type

Sealed types can be said to be another major type officially launched in Java 17. It was first proposed in Java 15 and tested in two versions.

In Java, we can achieve class capability reuse, extension, and enhancement through inheritance (extends keyword). But sometimes, not all classes may be inherited. Therefore, we need some restrictive means of controlling the inheritance relationship, and the function of the sealed class is to restrict the inheritance of the class .

In fact, if we didn’t want others to inherit our class before, we could add finalkeywords directly:

public final class A{
    
       //添加final关键字后,不允许对此类继承
    
}

This has a disadvantage. If keywords are added final, no one, including ourselves, can implement inheritance. But now we have a requirement, which only allows classes written by ourselves to inherit A, but does not allow classes written by others. Inheriting A, what should I write at this time? It was very troublesome to implement before Java 17.

But now we can use sealed types to achieve this functionality:

public sealed class A permits B{
    
       //在class关键字前添加sealed关键字,表示此类为密封类型,permits后面跟上允许继承的类型,多个子类使用逗号隔开

}

The seal type has the following requirements:

  • It can be based on ordinary classes, abstract classes, interfaces, or it can be inherited from subclasses of other abstract classes or classes that implement other interfaces.
  • There must be subclass inheritance, and it cannot be in the form of anonymous inner classes or lambdas.
  • sealedWrite it in the original finalposition, but it cannot appear with and keyword at the same time. You can only choose one of them final.non-sealed
  • Inherited subclasses must be explicitly marked as final, sealedor non-sealedtype.

The standard declaration format is as follows:

public sealed [abstract] [class/interface] 类名 [extends 父类] [implements 接口, ...] permits [子类, ...]{
    
    
		//里面的该咋写咋写
}

Note that the subclass format is:

public [final/sealed/non-sealed] class 子类 extends 父类 {
    
       //必须继承自父类
			//final类型:任何类不能再继承当前类,到此为止,已经封死了。
  		//sealed类型:同父类,需要指定由哪些类继承。
  		//non-sealed类型:重新开放为普通类,任何类都可以继承。
}

For example, now we have written these classes:

public sealed class A  permits B{
    
       //指定B继承A

}
public final class B extends A {
    
       //在子类final,彻底封死

}

We can see that other classes cannot be compiled whether they inherit A or B:

image-20220531090136485

image-20220531090152743

But if we actively set B as non-sealedtype at this time:

public non-sealed class B extends A {
    
    

}

In this way, it can be inherited normally, because B specifies that it non-sealedactively gives up the sealing feature, which makes it very flexible.

Of course, we can also use reflection to get whether the class is a sealed type:

public static void main(String[] args) {
    
    
    Class<A> a = A.class;
    System.out.println(a.isSealed());   //是否为密封
}

At this point, the main new features of Java 9-17 have been explained.

Guess you like

Origin blog.csdn.net/doomwatcher/article/details/128257646