青空の霞光Java新特性笔记

cst-python联合仿真的问题解决 cst与python联合仿真出现无法调用CST的问题解决 阅读详情

image-20220528201059618

Java新特性介绍

**注意:**推荐完成此路线所有前置内容后,再来学习本篇。

经过前面的学习,我们基本已经了解了Java 8及之前的所有语法,不过,Java 8是Oracle 公司于 2014 年 3 月 18 日发布的,距离今天已经过了近十年的时间了,Java并没有就此止步,而是继续不断发展壮大,几乎每隔6个月,就会冒出一个新版本,最新的版本已经快要迭代到Java 20了,与Java 8相差了足足十来个版本,但是由于Java 8的稳定和生态完善(目前仍是LTS长期维护版本),依然有很多公司在坚持使用Java 8,不过随着SpringBoot 3.0的到来,现在强制要求使用Java 17版本(同样也是LTS长期维护版本),下一个Java版本的时代,或许已经临近了。

image-20220528202952628

随着这些主流框架全面拥抱Java 17,为了不被时代所淘汰,我们的学习之路,也要继续前行了。就像很多年前Java 6还是主流的时代,终究还是被Java 8所取代一样。

在本篇视频中,我们将介绍Java 9 - Java 17这些版本的所有新增特性,这里推荐各位小伙伴提前准备好JDK 17环境(Oracle JDK 17已全面支持arm芯片的Mac电脑,请放心食用)

image-20220528220146662

全篇视频挑重点说,不墨迹,开始吧。

Java 8 关键特性回顾

在开始之前,我们先来回顾一下Java 8中学习的Lambda表达式和Optional类,有关Stream API请各位小伙伴回顾一下Java SE篇视频教程,这里不再进行介绍。

Lambda表达式

在Java 8之前,我们在某些情况下可能需要用到匿名内部类,比如:

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

在创建Thread时,我们需要传入一个Runnable接口的实现类,来指定具体的在新的线程中要执行的任务,相关的逻辑需要我们在run()方法中实现,这时为了方便,我们就直接使用匿名内部类的方式传入一个实现,但是这样的写法实在是太过臃肿了。

在Java 8之后,我们可以对类似于这种匿名内部类的写法,进行缩减,实际上我们进行观察会发现,真正有用的那一部分代码,实际上就是我们对run()方法的具体实现,而其他的部分实际上在任何地方编写都是一模一样的,那么我们能否针对于这种情况进行优化呢?我们现在只需要一个简短的lambda表达式即可:

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

我们可以发现,原本需要完整编写包括类、方法在内的所有内容,全部不再需要,而是直接使用类似于() ‐> { 代码语句 }的形式进行替换即可。是不是感觉瞬间代码清爽了N倍?

当然这只是一种写法而已,如果各位不好理解,可以将其视为之前匿名内部类写法的一种缩短。

但是注意,它的底层其实并不只是简简单单的语法糖替换,而是通过invokedynamic指令实现的,不难发现,匿名内部类会在编译时创建一个单独的class文件,但是lambda却不会,间接说明编译之后lambda并不是以匿名内部类的形式存在的:

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

image-20220529214948350

可以看到,实际上是Main类中的lambda$main$0()方法抛出的异常,但是我们的Main类中压根没有这个方法,很明显是自动生成的。所以,与其说Lambda是匿名内部类的语法糖,不如说是我们为所需要的接口提供了一个方法作为它的实现。比如Runnable接口需要一个方法体对它的run()方法进行实现,而这里我们就通过lambda的形式给了它一个方法体,这样就万事具备了,而之后创建实现类就只需要交给JVM去处理就好了。

我们来看一下Lambda表达式的具体规范:

  • 标准格式为:([参数类型 参数名称,]...) ‐> { 代码语句,包括返回值 }
  • 和匿名内部类不同,Lambda仅支持接口,不支持抽象类
  • 接口内部必须有且仅有一个抽象方法(可以有多个方法,但是必须保证其他方法有默认实现,必须留一个抽象方法出来)

比如我们之前使用的Runable类:

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

因此,Runable的的匿名内部类实现,就可以简写为:

Runnable runnable = () -> {    };

我们也可以写一个玩玩:

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

它的Lambda表达式的实现就可以写为:

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

不过还可以进行优化,首先方法参数类型是可以省略的:

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

由于只有一个参数,可以不用添加小括号(多个参数时需要):

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

由于仅有返回语句这一行,所以可以直接写最终返回的结果,并且无需花括号:

Test test = i -> i+"";

这样,相比我们之前直接去编写一个匿名内部类,是不是简介了很多很多。当然,除了我们手动编写接口中抽象方法的方法体之外,如果已经有实现好的方法,是可以直接拿过来用的,比如:

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

所以,我们可以直接将此方法,作为lambda表达式的方法体实现(其实这就是一种方法引用,引用了一个方法过来,这也是为什么前面说是我们为所需要的接口提供了一个方法作为它的实现,是不是越来越体会到这句话的精髓了):

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

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

比如我们现在需要对一个数组进行排序:

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));   //按从小到大的顺序排列
}

但是我们发现,Integer类中有一个叫做compare的静态方法:

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

这个方法是一个静态方法,但是它却和Comparator需要实现的方法返回值和参数定义一模一样,所以,懂的都懂:

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

那么要是不是静态方法而是普通的成员方法呢?我们注意到Comparator要求我们实现的方法为:

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

其中o1和o2都是Integer类型的,我们发现Integer类中有一个compareTo方法:

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

只不过这个方法并不是静态的,而是对象所有:

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

但是此时我们会发现,IDEA提示我们可以缩写,这是为什么呢?实际上,当我们使用非静态方法时,会使用抽象方参数列表的第一个作为目标对象,后续参数作为目标对象成员方法的参数,也就是说,此时,o1作为目标对象,o2作为参数,正好匹配了compareTo方法,所以,直接缩写:

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

成员方法也可以让对象本身不成为参与的那一方,仅仅引用方法:

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

当然,类的构造方法同样可以作为方法引用传递:

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

我们发现,String类中刚好有一个:

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

于是乎:

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

当然除了上面提到的这些情况可以使用方法引用之外,还有很多地方都可以,还请各位小伙伴自行探索了。Java 8也为我们提供了一些内置的函数式接口供我们使用:Consumer、Function、Supplier等,具体请回顾一下JavaSE篇视频教程。

Optional类

Java 8中新引入了Optional特性,来让我们更优雅的处理空指针异常。我们先来看看下面这个例子:

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

但是这样实现的话,我们少考虑了一个问题,万一给进来的str是null呢?如果是null的话,在调用toLowerCase方法时岂不是直接空指针异常了?所以我们还得判空一下:

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

但是这样写着就不能一气呵成了,我现在又有强迫症,我就想一行解决,这时,Optional来了,我们可以将任何的变量包装进Optional类中使用:

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

由于这里只有一句打印,所以我们来优化一下:

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

这样,我们就又可以一气呵成了,是不是感觉比之前的写法更优雅。

除了在不为空时执行的操作外,还可以直接从Optional中获取被包装的对象:

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

不过此时当被包装的对象为null时会直接抛出异常,当然,我们还可以指定如果get的对象为null的替代方案:

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

其他操作还请回顾JavaSE篇视频教程。

Java 9 新特性

这一部分,我们将介绍Java 9为我们带来的新特性,Java 9的主要特性有,全新的模块机制、接口的private方法等。

模块机制

在我们之前的开发中,不知道各位有没有发现一个问题,就是当我们导入一个jar包作为依赖时(包括JDK官方库),实际上很多功能我们并不会用到,但是由于它们是属于同一个依赖捆绑在一起,这样就会导致我们可能只用到一部分内容,但是需要引用一个完整的类库,实际上我们可以把用不到的类库排除掉,大大降低依赖库的规模。

于是,Java 9引入了模块机制来对这种情况进行优化,在之前的我们的项目是这样的:

image-20220528210803658

而在引入模块机制之后:

image-20220528210958964

可以看到,模块可以由一个或者多个在一起的 Java 包组成,通过将这些包分出不同的模块,我们就可以按照模块的方式进行管理了。这里我们创建一个新的项目,并在src目录下,新建module-info.java文件表示此项目采用模块管理机制:

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

接着我们来创建一个主类:

image-20220528213210752

程序可以正常运行,貌似和之前没啥区别,不过我们发现,JDK为我们提供的某些框架不见了:

image-20220528213428296

Java为我们提供的logging相关日志库呢?我们发现现在居然不见了?实际上它就是被作为一个模块单独存在,这里我们需进行模块导入:

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

这里我们导入java.logging相关模块后,就可以正常使用Logger了:

image-20220528214247006

image-20220528214308194

是不是瞬间感觉编写代码时清爽了许多,全新的模块化机制提供了另一个级别的Java代码可见性、可访问性的控制,不过,你以为仅仅是做了包的分离吗?我们可以来尝试通过反射获取JDK提供的类中的字段:

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

但是我们发现,在程序运行之后,修改操作被阻止了:

image-20220528221817482

反射 API 的 Java 9 封装和安全性得到了改进,如果模块没有明确授权给其他模块使用反射的权限,那么其他模块是不允许使用反射进行修改的,看来Unsafe类是玩不成了。

我们现在就来细嗦一下这个模块机制,首先模块具有四种类型:

  • **系统模块:**来自JDK和JRE的模块(官方提供的模块,比如我们上面用的),我们也可以直接使用java --list-modules命令来列出所有的模块,不同的模块会导出不同的包供我们使用。
  • **应用程序模块:**我们自己写的Java模块项目。
  • **自动模块:**可能有些库并不是Java 9以上的模块项目,这种时候就需要做兼容了,默认情况下是直接导出所有的包,可以访问所有其他模块提供的类,不然之前版本的库就用不了了。
  • **未命名模块:**我们自己创建的一个Java项目,如果没有创建module-info.java,那么会按照未命名模块进行处理,未命名模块同样可以访问所有其他模块提供的类,这样我们之前写的Java 8代码才能正常地在Java 9以及之后的版本下运行。不过,由于没有使用Java 9的模块新特性,未命名模块只能默认暴露给其他未命名的模块和自动模块,应用程序模块无法访问这些类(实际上就是传统Java 8以下的编程模式,因为没有模块只需要导包就行)

这里我们就来创建两个项目,看看如何使用模块机制,首先我们在项目A中,添加一个User类,一会项目B需要用到:

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+"岁)";
    }
}

接着我们编写一下项目A的模块设置:

image-20220528230842617

这里我们将com.test包下所有内容都暴露出去,默认情况下所有的包都是私有的,就算其他项目将此项目作为依赖也无法使用。

接着我们现在想要在项目B中使用项目A的User类,我们需要进行导入:

image-20220528232033318

现在我们就可以在Main类中使用模块module.a中暴露出来的包内容了:

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

当然除了普通的exports进行包的暴露之外,我们也可以直接指定将包暴露给指定的模块:

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

不过现在还有一个问题,如果模块module.a依赖于其他模块,那么会不会传递给依赖于模块module.a的模块呢?

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

image-20220529103614788

可以看到,在模块module.b中,并没有进行依赖传递,说明哪个模块导入的依赖只能哪个模块用,但是现在我们希望依赖可以传递,就是哪个模块用了什么依赖,依赖此模块的模块也会自动进行依赖,我们可以通过一个关键字解决:

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

现在就可以使用了:

image-20220529103828560

还有我们前面演示的反射,我们发现如果我们依赖了一个模块,是没办法直接进行反射操作的:

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

那么怎么样才可以使用反射呢?我们可以为其他模块开放某些运行使用反射的类:

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

我们还可以指定模块需要使用的抽象类或是接口实现:

package com.test;

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

我们可以在模块B中去实现一下,然后声明我们提供了实现类:

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的实现类
}

了解了以上的相关知识后,我们就可以简单地进行模块的使用了。比如现在我们创建了一个新的Maven项目:

image-20220529112208486

然后我们导入了lombok框架的依赖,如果我们不创建module-info.java文件,那么就是一个未命名模块,未命名模块默认可以使用其他所有模块提供的类,实际上就是我们之前的开发模式:

package com.test;

import lombok.extern.java.Log;

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

现在我们希望按照全新的模块化开发模式来进行开发,将我们的项目从未命名模块改进为应用程序模块,所以我们先创建好module-info.java文件:

module com.test {
}

可以看到,直接报错了:

image-20220529112707958

明明导入了lombok依赖,却无法使用,这是因为我们还需要去依赖对应的模块才行:

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

image-20220529112909452

这样我们就可以正常使用了,之后为了教程演示方便,咱们还是不用模块。

JShell交互式编程

Java 9为我们通过了一种交互式编程工具JShell,你还别说,真有Python那味。

image-20220529141547082

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

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

image-20220529141719363

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

image-20220529141954494

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

image-20220529142340030

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

image-20220529142440584

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

image-20220529142757286

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

image-20220529142852920

接口中的private方法

在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("我是接口中的私有方法!");
    }
}

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

集合类新增工厂方法

在之前,如果我们想要快速创建一个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:

image-20220529144905646

但是注意,通过这种方式创建的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

还记得我们之前在JavaSE中学习的Stream流吗?当然这里不是指进行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中一气呵成(具体的详细操作请回顾JavaSE篇)

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

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

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

还有,我们可以通过迭代快速生成一组数据(实际上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);
}

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

其他小型变动

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的改进等,因为不太常用,这里就不做介绍了。

Java 10 新特性

Java 10主要带来的是一些内部更新,相比Java 9带来的直观改变不是很多,其中比较突出的就是局部变量类型推断了。

局部变量类型推断

在Java中,我们可以使用自动类型推断:

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

但是注意,var关键字必须位于有初始值设定的变量上,否则鬼知道你要用什么类型。

image-20220529171216795

我们来看看是不是类型也能正常获取:

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

这里虽然是有了var关键字进行自动类型推断,但是最终还是会变成String类型,得到的Class也是String类型。但是Java终究不像JS那样进行动态推断,这种类型推断仅仅发生在编译期间,到最后编译完成后还是会变成具体类型的:

image-20220529170538383

并且var关键字仅适用于局部变量,我们是没办法在其他地方使用的,比如类的成员变量:

image-20220529171444062

有关Java 10新增的一些其他改进,这里就不提了。

Java 11 新特性

Java 11 是继Java 8之后的又一个TLS长期维护版本,在Java 17出现之前,一直都是此版本作为广泛使用的版本,其中比较关键的是用于Lambda的形参局部变量语法。

用于Lambda的形参局部变量语法

在Java 10我们认识了var关键字,它能够直接让局部变量自动进行类型推断,不过它不支持在lambda中使用:

image-20220529235822891

但是实际上这里是完全可以进行类型推断的,所以在Java 11,终于是支持了,这样编写就不会报错了:

image-20220529235935071

针对于String类的方法增强

在Java 11为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);
}

我们还可以通过repeat()方法来让字符串重复拼接:

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

我们也可以快速地进行空格去除操作:

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());   //去除尾部空格
}

全新的HttpClient使用

在Java 9的时候其实就已经引入了全新的Http Client API,用于取代之前比较老旧的HttpURLConnection类,新的API支持最新的HTTP2和WebSocket协议。

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

利用全新的客户端,我们甚至可以轻松地做一个爬虫(仅供学习使用,别去做违法的事情,爬虫玩得好,牢饭吃到饱),比如现在我们想去批量下载某个网站的壁纸:

image-20220530112549225

网站地址:https://pic.netbian.com/4kmeinv/

我们随便点击一张壁纸,发现网站的URL格式为:

image-20220530112701156

并且不同的壁纸似乎都是这样:https://pic.netbian.com/tupian/数字.html,好了差不多可以开始整活了:

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());  //这里打印一下看看网页
    }
}

可以看到,最后控制台成功获取到这些图片的网站页面了:

image-20220530113039571

接着我们需要来观察一下网站的HTML具体怎么写的,把图片的地址提取出来:

image-20220530113136156

好了,知道图片在哪里就好办了,直接字符串截取:

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);  //最终的图片地址就有了
    }
}

好了,现在图片地址也可以批量拿到了,直接获取这些图片然后保存到本地吧:

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

我们现在来看看效果吧,美女的图片已经成功保存到本地了:

image-20220530114824605

当然,这仅仅是比较简单的爬虫,不过我们的最终目的还是希望各位能够学会使用新的HttpClient API。

Java 12-16 新特性

由于Java版本的更新迭代速度自Java 9开始为半年更新一次(Java 8到Java 9隔了整整三年),所以各个版本之间的更新内容比较少,剩余的6个版本,我们就多个版本放在一起进行讲解了。

image-20220530120729757

Java12-16这五个版本并非长期支持版本,所以很多特性都是一种处于实验性功能,12/13版本引入了一些实验性功能,并根据反馈进行调整,最后在后续版本中正式开放使用,其实就是体验服的那种感觉。

新的switch语法

在Java 12引入全新的switch语法,让我们使用switch语句更加的灵活,比如我们想要编写一个根据成绩得到等级的方法:

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

现在我们想要使用switch来实现这个功能(不会吧不会吧,不会有人要想半天怎么用switch实现吧),之前的写法是:

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

但是现在我们可以使用新的特性了:

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 -> "不及格";
    };
}

不过最后编译出来的样子,貌似还是和之前是一样的:

image-20220530222918174

这种全新的switch语法称为switch表达式,它的意义不仅仅体现在语法的精简上,我们来看看它的详细规则:

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

那么如果我们并不是能够马上返回,而是需要做点什么其他的工作才能返回结果呢?

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

当然,也可以像这样:

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

这种全新的语法,可以说极大地方便了我们的编码,不仅代码简短,而且语义明确。唯一遗憾的是依然不支持区间匹配。

**注意:**switch表达式在Java 14才正式开放使用,所以我们项目的代码级别需要调整到14以上。

文本块

如果你学习过Python,一定知道三引号:

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

没错,Java13也带了这样的特性,旨在方便我们编写复杂字符串,这样就不用再去用那么多的转义字符了:

image-20220530230225037

可以看到,Java中也可以使用这样的三引号来表示字符串了,并且我们可以随意在里面使用特殊字符,包括双引号等,但是最后编译出来的结果实际上还是会变成一个之前这样使用了转义字符的字符串:

image-20220530230343933

仔细想想,这样我们写SQL或是HTML岂不是就舒服多了?

**注意:**文本块表达式在Java 15才正式开放使用,所以我们项目的代码级别需要调整到15以上。

新的instanceof语法

在Java 14,instanceof迎来了一波小更新(哈哈,这版本instanceof又加强了,版本强势语法)

比如我们之前要重写一个类的equals方法:

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

在之前我们一直都是采用这种先判断类型,然后类型转换,最后才能使用的方式,但是这个版本instanceof加强之后,我们就不需要了,我们可以直接将student替换为模式变量:

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

在使用instanceof判断类型成立后,会自动强制转换类型为指定类型,简化了我们手动转换的步骤。

**注意:**新的instanceof语法在Java 16才正式开放使用,所以我们项目的代码级别需要调整到16以上。

空指针异常的改进

相信各位小伙伴在调试代码时,经常遇到空指针异常,比如下面的这个例子:

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

那么为空时,就会直接:

image-20220530232755797

但是由于我们这里a和b都调用了length()方法,虽然空指针异常告诉我们问题出现在这一行,但是到底是a为null还是b为null呢?我们是没办法直接得到的(遇到过这种问题的扣个1吧,只能调试,就很头疼)

但是当我们在Java 14或更高版本运行时:

image-20220530233031005

这里会明确指出是哪一个变量调用出现了空指针,是不是感觉特别人性化。

记录类型

继类、接口、枚举、注解之后的又一新类型来了,它的名字叫"记录",在Java 14中首次出场,这一出场,Lombok的噩梦来了。

在实际开发中,很多的类仅仅只是充当一个实体类罢了,保存的是一些不可变数据,比如我们从数据库中查询的账户信息,最后会被映射为一个实体类:

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

Lombok可以说是简化代码的神器了,他能在编译时自动生成getter和setter、构造方法、toString()方法等实现,在编写这些实体类时,简直不要太好用,而这一波,官方也是看不下去了,于是自己也搞了一个记录类型。

记录类型本质上也是一个普通的类,不过是final类型且继承自java.lang.Record抽象类的,它会在编译时,会自动编译出 public get hashcode 、equals、toString 等方法,好家伙,这是要逼死Lombok啊。

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

}

使用起来也是非常方便,自动生成了构造方法和成员字段的公共get方法:

image-20220530235609885

并且toString也是被重写了的:

image-20220530235719341

equals()方法仅做成员字段之间的值比较,也是帮助我们实现好了的:

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

是不是感觉这种类型就是专门为这种实体类而生的。

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

    @Override
    public void run() {
        
    }
}

**注意:**记录类型在Java 16才正式开放使用,所以我们项目的代码级别需要调整到16以上。

Java 17 新特性

Java 17作为新的LTS长期维护版本,我们来看看都更新了什么(不包含预览特性,包括switch第二次增强,哈哈,果然还是强度不够,都连续加强两个版本了)

密封类型

密封类型可以说是Java 17正式推出的又一重磅类型,它在Java 15首次提出并测试了两个版本。

在Java中,我们可以通过继承(extends关键字)来实现类的能力复用、扩展与增强。但有的时候,可能并不是所有的类我们都希望能够被继承。所以,我们需要对继承关系有一些限制的控制手段,而密封类的作用就是限制类的继承。

实际上在之前我们如果不希望别人继承我们的类,可以直接添加final关键字:

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

这样有一个缺点,如果添加了final关键字,那么无论是谁,包括我们自己也是没办法实现继承的,但是现在我们有一个需求,只允许我们自己写的类继承A,但是不允许别人写的类继承A,这时该咋写?在Java 17之前想要实现就很麻烦。

但是现在我们可以使用密封类型来实现这个功能:

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

}

密封类型有以下要求:

  • 可以基于普通类、抽象类、接口,也可以是继承自其他接抽象类的子类或是实现其他接口的类等。
  • 必须有子类继承,且不能是匿名内部类或是lambda的形式。
  • sealed写在原来final的位置,但是不能和final、non-sealed关键字同时出现,只能选择其一。
  • 继承的子类必须显式标记为final、sealed或是non-sealed类型。

标准的声明格式如下:

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

注意子类格式为:

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

比如现在我们写了这些类:

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

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

}

我们可以看到其他的类无论是继承A还是继承B都无法通过编译:

image-20220531090136485

image-20220531090152743

但是如果此时我们主动将B设定为non-sealed类型:

public non-sealed class B extends A {

}

这样就可以正常继承了,因为B指定了non-sealed主动放弃了密封特性,这样就显得非常灵活了。

当然我们也可以通过反射来获取类是否为密封类型:

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

至此,Java 9 - 17的主要新特性就讲解完毕了。

[Unity开发] [PICO]避坑指南:PICO商店上传APP时PXR Platform APPID设定详解 PICO开发者上传Unity应用时常见的"APPID不符"问题,主要源于混淆了开发者后台两个不同位置的APPID。 阅读详情

相关推荐

量化回测系统实战指南:从VectorBT到自研框架的深度解析

本文深入解析量化回测系统实战,从VectorBT和Backtesting.py的对比到自研框架miniQuant的设计与优化。探讨如何平衡速度与灵活性,解决实盘适配性问题,并提供性能调优技巧和可视化分析工具,帮助开发者构建高效可靠的量化交易系统。

weixin_28308325的博客 276

Java SE 笔记(扩展篇)JDK 9-17 新特性介绍

Java9-17新特性

qq_25928447的博客 2793

从实验室到生活:构建一份全面的手机相机主观画质评测指南

本文详细介绍了如何从实验室数据转向实际体验,构建全面的手机相机主观画质评测指南。通过基础装备清单、室内外场景实战评测及数据之外的感知艺术,帮助用户发现手机相机在色彩还原、动态范围和夜景模式等方面的真实表现。特别关注主观体验与客观参数的差异,提供实用的评测技巧和报告撰写心法。

weixin_30940783的博客 307

jni入门实现

javajni,保姆级入门教程

qq_48317702的博客 174

从编解码芯片看Tesla T4的隐藏实力:双NVDEC如何实现70路1080P解码?

本文深入剖析了英伟达Tesla T4显卡的隐藏实力,重点解读其独特的双NVDEC硬件解码器设计如何突破性能瓶颈。通过分析Turing架构、对比HEVC/H.264解码差异,并结合驱动与SDK优化,揭示了实现高达70路1080P视频流并发解码的原理与实战配置,为云游戏、直播转码等高并发场景提供高效解决方案。

weixin_29209801的博客 484

(全网最详细教程)康耐视读码器(以DM260S为例子)详细实用步骤

(全网最详细教程)康耐视读码器(以DM260S为例子)详细实用步骤

视觉人机器视觉的博客 7463

通俗易懂的Java9新特性教程(含配套资料)

本教程为授权出品教程本套《Java9新特性》视频涵盖oracle公司2017年9月公布的java 9 新特性的核心内容。 主要包含:模块化系统,REPL工具:jshell命令,多版本兼容jar包,语法的新变化:接口私有方法、异常处理、钻石操作符、String存储结构变化等,新增API:Stream、List、Set、图像处理等。可以说java 9 的更新是一个大的工程。

国科大学习资料--数据挖掘(刘莹)--第三章习题及参考答案

国科大学习资料–数据挖掘(刘莹)–第三章习题及参考答案 国科大学习资料–数据挖掘(刘莹)–第三章习题及参考答案 国科大学习资料–数据挖掘(刘莹)–第三章习题及参考答案 第 3 章分类与回归 3.1简述决策树分类的主要步骤。 答:决策树生成的过程如下: (1)对数据源进行数据预处理, 得到训练集和测试集; (2)对训练集进行训练; (3)对初始决策树进行树剪枝; (4)由所得到的决策树提取分类规则; (5)使用测试数据集进行预测,评估决策树模型; 3.2给定决策树,选项有:(1)将决策树转换成规则,然后对结果

无名何许人的博客 3090

一种适用于 Java 8 至 Java 17 的反射修改 static final 属性的方法

网上能查到的反射修改 static final 属性的方法基本从 Java 12 开始失效了,本文介绍一种同时适用于 Java 8 至 Java 17 的反射修改 static final 属性的方法。

wu_weijie的博客 7561

[实践篇]13.13 再来梳理一下HAB的设计原理

HAB全称为Hypervisor Abstraction,即硬件抽象,主要用于提供对HOST OS硬件资源的访问。

从0到1,突破自己 7256

Virtuoso 中的tech file 详细说明

工艺文件(Techfile)是集成电路设计中的核心配置文件,定义了从晶圆制造到版图验证全流程所需的工艺规则和图层属性。在Cadence Virtuoso平台中,Techfile作为连接设计与制造的桥梁,主要包含以下内容: 图层体系定义 金属层、介质层、多晶硅层等物理图层的编号、名称和显示属性 图层用途(DRAW、MASK等)及图层-用途组合(LPP)的优先级 设计规则约束 几何尺寸规则(最小线宽/间距) 包围和延伸规则 密度规则和特殊工艺规则(如天线效应) 器件定义 MOS管、电阻等器件的图层构成 电学参数

u013620135的博客 8118

SpringCloud笔记(三)微服务应用

微服务应用 前面我们已经完成了SpringCloudAlibaba的学习,我们对一个微服务项目的架构体系已经有了一定的了解,那么本章我们将在应用层面继续探讨微服务。 分布式权限校验 虽然完成前面的部分,我们已经可以自己去编写一个比较中规中矩的微服务项目了,但是还有一个问题我们没有解决,登录问题。假如现在要求用户登录之后,才能进行图书的查询、借阅等操作,那么我们又该如何设计这个系统呢? 回顾我们之前进行权限校验的原理,服务器是如何判定一个请求是来自哪个用户的呢? 首先浏览器会向服务端发送请求,访问我们的网站

qq_25928447的博客 4263

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

JavaSE学习感悟(青空の霞光)

笔者也算刚刚开始,后边持续更新。

m0_63475094的博客 561

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

JavaSE学习·(青空の霞光)

前言青空の霞光的JavaSE课程在bilibili上算是非常好的新手Java课程,较为通俗易懂,笔者也是打算过一遍,顺便写写感触。

m0_63475094的博客 626

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

JavaSE学习(青空の霞光)

青空の霞光的JavaSE课程在bilibili上算是非常好的新手Java课程,较为通俗易懂,笔者也是打算过一遍,顺便写写感触。笔者也算刚刚开始,后边持续更新。

m0_63475094的博客 390

最新解决办法!Could not find artifact mysql:mysql-connector-java:pom:8.0.42 in aliyunmaven

(central.sonatype.com)这里,搜索你想要添加的依赖,比如我搜索的是mysql。然后其实<version>版本不对,可以去到。我一开始是denpendcies没加。回车之后选择第一个,或者找到你想要的。然后复制<depenedcy>的内容。我看了他的视频才解决的。

Amos_D_c的博客 931

Java入门——面向对象实战

今天看了青空の霞光大佬b站的教学视频,有感而发记录一下java入门的学习。

loserrrrrr的博客 477

java设计模式

java设计模式

陈塘关哪吒 127

class文件加载从java到C++的初步研究

目录测试类自定义加载类解析defineClass 在学习反射时,实现了自定义ClassLoader类加载器,我们都知道点击运行后.java代码文件在编译成.class字节码文件后,jvm自动加载.class文件,而在自定义类加载器后,不再是jvm自动加载,而是手动选择加载编译后的.class文件,此文是研究class文件从加载到运行中的具体过程.

qq_38598708的博客 1404

JavaSE笔记基础(没细致深入)

Method method = cat.getClass().getMethod("方法名",参数Class);使用Field field = Student.class.getField("name")方法获取指定的字段;fielf.set(student,"小明");

2303_80137578的博客 749

B站大学Java学习路线v1.0

java学习路线推荐

戏言的博客 1447

java初级

按两下Shift键出现搜索框,输入“registry”找到ide.experimental.ui,将其勾选上,重启IDEA就变成新的UI样式了。注释:单行注释://多行注释:/**//**来进行更加详细的文档注释:final关键字定义的变量为常量,无法重复定义为什么会出现以上的情况呢,注意跟整型常量赋值给浮点型变量一样,会隐式类型转换不同,由于float类型精度不如double,这个直接赋值是对float类型变量赋值了一个double类型数据,会不合法。

m0_56552415的博客 1834

JavaWeb学习个人笔记(一)

参考。

qq_54241969的博客 163

Java学习查漏补缺及知识点整理

在Java代码旁用鼠标进行点击标点,然在工作台console导航部分进行断点测试,就可以知道程序进行的每一步过程吧。在声明了类变量之后,在主方法中可以通过类命.类变量名或者实例化类之后通过,对象名.类变量名来进行访问。代码块可以理解为只有方法体的方法,多个构造器中都有相同的语句。可以使用代码块进行初始化隐藏了功能的实现细节,实现步骤,首先将变量私有化,然后使用set、get方法,set、get方法可以配合构造器使用。1.父类私有的方法和变量是不可以访问的,需要间接的通过公有的方法去访问。 2.当子类继承了父

吉屋安的博客 978

[Java] 中的具体集合

集合表示一组对象,每一个对象我们都称其为元素;不同的集合有着不同的性质,比如一些集合允许重复的元素,而另一些则不允许;集合类是为了更好地组织、管理和操作我们的数据而存在的,包括列表、集合、队列、映射等数据结构集合跟数组一样,可以表示同样的一组元素相同都是容器,都能容纳一组元素不同数组的大小是固定的,集合的大小是可变的数组可以存放基本数据类型,集合只能存放对象。

m0_73976305的博客 615

Reids学习笔记

Reids是键值数据库,可以像Map一样操作,通过键值对向数据库中添加数据(类似HashMap)Reids中,数据库是由一个整数索引标识而不是由一个数据库名称,默认情况下,我们连接Redis数据库后会使用0号数据库,可以通过Redis配置文件中的参数修改数据库参数总数,默认为16个。所有存入的数据默认以字符串的形式保存,键值有一定的命名规范,以方便我们可以快速定位我们的数据库属于哪一部分,比如用户的数据。通过下面的命令还可以查询某个键值对的过期时间还剩多少。可以将一个数据库的内容移动到另一个数据库。

2501_90127265的博客 284
上一篇: JUC系列(九) CAS 与锁的理解
下一篇: 图解设计模式:动动手玩转迭代器模式
冷环渊
博客等级 码龄7年 1130粉丝 176原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冷环渊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值