安装环境

  1. JavaSE: 核心内容,基础不牢开发地动山摇
  2. JRE: Java运行环境,安装后Java程序才可以运行
  3. JDK: 开发者工具,spring3开始只支持JDK17以上的版本

下载安装

  1. 网址: https://www.azul.com/downloads/?version=java-8-lts&os=windows&package=jdk#zulu
    • 说明1: 最新版或17版,选Windows,选x86 64版,鼠标移动到Download,下载.msi
    • 说明2: 系统macOS,选ARM版,鼠标移动到Download,下载.dmg
  2. 打开软件,一直点next,打开cmd,输入java -version查看版本

安装IDEA

  1. 集成开发工具,以后都用它来写代码,我跟老师版本走,选Ultimate,版本2022.2.2
  2. 下载方式懂得都懂
  3. 插件
    • Chinese Language:汉化包
    • Dracula Theme:最喜欢的主题
    • Smart Input:自动切换中英文输入法
  4. 常用快捷键
    • psvm:主方法
    • sout: 打印
    • command + n:生成构造方法、toString、重写方法
    • control + o:生成JavaBean
    • command + shift + u:选中的内容,英文切换大小写

设置IDEA

  1. 设置-编辑器-常规-自动导入-Java-勾选下面两个功能
    • 动态添加明确的import
    • 动态优化import
  2. 按两下shift,输入更改内存设置,调成2048,如果内存不够用再调大一点,idea底部栏右键勾选内存指示器

变量与常量

  1. 常用的类型;JVM会对添加了final关键字的属性进行优化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 变量
    String name = "幽蓝";
    int age = 9;
    Boolean youAreBeautiful = true;

    // 允许一行定义
    int a = 1, b = 2, c = 3;

    // final就是常量,不允许修改
    final int id = 666;
  2. 通常用double表示小数,考虑到性能才会用float
    1
    2
    3
    4
    5
    // 单精度,32bit, 4字节
    float a = 1.1F;

    // 双精度,64bit, 8字节
    double b = 1.1;

各种运算符

  1. 不同类型的整数一起运算,会发生转换问题;括号可以提升运算的优先级;左移表示乘数,右移表示除数
  2. 字符串+字符串会进行拼接
    1
    String sayAge = "我的年龄是:" + 9 ; //我的年龄是:9
  3. 糖语法
    1
    2
    3
    4
    int a = 10, b = 20, c = 30;
    a += 5; //15
    b *= 2; //40
    c /= 10; //3
  4. 用2取模,可以判断偶数和奇数
    1
    2
    3
    int a = 10, b = 9;
    System.out.println(a % 2); //0
    System.out.println(b % 2); //1
  5. 三目运算符
    1
    2
    3
    int a = 7, b = 15;
    String str = a > b ? "行" : "不行"; // 判断条件(只能是boolean,或返回boolean的表达式) ? 满足的返回值 : 不满足的返回值
    System.out.println("汉堡做的行不行?"+str); //汉堡做的行不行?不行
  6. 问题一:Java采用的是IEEE754标准实现浮点数的表达和运算,导致精度丢失,可以用BigDecimal方法解决
    1
    2
    3
    4
    5
    6
    7
    8
    9
    BigDecimal a1 = new BigDecimal("0.1"), a2 = new BigDecimal("0.2");
    BigDecimal b1 = new BigDecimal("10"), b2 = new BigDecimal("3");
    System.out.println(a1.add(a2)); // 0.3
    System.out.println(b1.divide(b2, 2, RoundingMode.DOWN)); //10除以3,保留2位,舍弃其余小数,结果3.33

    BigDecimal add(BigDecimal); //加
    BigDecimal subtract(BigDecimal); //减
    BigDecimal multiply(BigDecimal); //乘
    BigDecimal divide(BigDecimal); //除
  7. 问题二:超出int范围除2会变负数,可以用位移运算符解决
    1
    2
    3
    int a = 2000000000, b = 2000000000;
    int c = (a + b) / 2; //-147483648
    int d = (a + b) >>> 1; //2000000000

方法的定义

  1. 定义方法格式;return 1有返回结果,返回值类型指定为int,其他同理;return无返回结果,返回值类型指定为void;无论return位于哪里,都会立即结束方法
    1
    2
    3
    4
    [返回值类型] 方法名称([参数]){
    //方法体
    return 结果;
    }
  2. 参数传递机制有两种:值传递和引用传递
    • 值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数
    • 引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数
  3. 调用pass方法不会影响到实参;调用pass2方法则会影响到实参,这里this关键字指向实参
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class Test {
    int i = 10; //我是实参

    public static void main(String[] args) {
    Test t = new Test();
    System.out.println("pass方法调用前,i的值为 = " + t.i); //pass方法调用前,i的值为 = 10
    t.pass(t.i); //pass方法中,i的值为 = 30
    System.out.println("pass方法调用后,i的值为 = " + t.i); //pass方法调用后,i的值为 = 10
    }

    public void pass(int i) { //我是形参
    i *= 3;
    System.out.println("pass方法中,i的值为 = " + i);
    }

    public void pass2(int i) {
    this.i *= 3;
    System.out.println("pass方法中,i的值为 = " + i);
    }
    }
  4. 调用pass方法会影响到实参;调用pass2方法不会影响到实参,原理是创建一个新对象,新对象复制了原有的属性和方法,调用setName方法也只是修改了新对象的名字,并不会影响到原有的名字
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class Test {
    public static void main(String[] args) {
    Test testName = new Test();
    User user = new User();
    System.out.println("pass方法调用前,user = " + user.getName()); //pass方法调用前,user = 幽蓝
    testName.pass(user);
    System.out.println("pass方法调用后,user = " + user.getName()); //pass方法调用后,user = 椰奶
    }

    public void pass(User user) {
    user.setName("椰奶");
    System.out.println("pass方法中,name的值为 = " + user.getName()); //pass方法中,name的值为 = 椰奶
    }

    public void pass2(User user) {
    user = new User();
    user.setName("椰奶");
    System.out.println("pass方法中,name的值为 = " + user.getName());
    }
    }

    class User {
    private String name = "幽蓝"; //我是实参

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }
    }

重载

  1. 一个类中可以包含多个同名的方法,但是需要的形式参数不一样
    1
    2
    3
    4
    5
    6
    7
    int sum(int a, int b){   //只有int支持,不灵活!
    return a + b;
    }

    double sum(double a, double b){ //重载一个double类型的,就支持小数计算了
    return a + b;
    }

构造方法

  1. 初始化成员属性
    1
    2
    3
    4
    5
    6
    7
    public class Student {
    String name;

    Student(){
    name = "幽蓝";
    }
    }
  2. 手动指定有参构造,当遇到名称冲突时,需要用到this关键字
    1
    2
    3
    4
    5
    6
    7
    public class Student {
    String name;

    Student(String name){ //形参和类成员变量冲突了,Java会优先使用形式参数定义的变量!
    this.name = name; //通过this指代当前的对象属性,this就代表当前对象
    }
    }

    只能在类的成员方法中使用this,不能在静态方法中使用this关键字

  3. 同时需要有参和无参构造,就需要用到方法的重载
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Student {
    String name;

    Student(){

    }

    Student(String name){
    this.name = name;
    }
    }
  4. 成员变量的初始化始终在构造方法执行之前

代码块

  1. new对象时自动运行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Student {
    {
    System.out.println("我是代码块");
    }

    public static void main(String[] args) {
    Student s1 = new Student(); //我是代码块
    }
    }

静态

  1. static关键字相当于全局共享,任何地方都能调用与修改;没必要new对象调用,可以直接类名.xxx的形式访问
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Student {
    static String name;
    }

    public static void main(String[] args) {
    Student s1 = new Student(), s2 = new Student();
    s1.name = "幽蓝";
    System.out.println(s2.name); //幽蓝

    System.out.println(Student.name); //幽蓝
    }

    只能在类的成员方法中使用this,不能在静态方法中使用this关键字

main方法

  1. main方法是Java应用程序的入口方法,程序在运行的时候,第一个执行的方法就是main()方法
  2. 如何调用本类中的静态与非静态方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Student {
    public void sayName() {
    System.out.println("幽蓝");
    }

    public static void sayAge() {
    System.out.println("⑨");
    }

    public static void main(String[] args) {
    //sayName(); // 错误调用
    sayAge(); // 静态方法可以直接调用

    Student s1 = new Student();
    s1.sayName(); // 非静态方法,通过new对象来调用
    }
    }

  1. 包是用来区分类位置的东西,换句话说,不同名的文件夹,可以存放同名的类
  2. 一般包按照个人或是公司域名的规则倒过来写 顶级域名.一级域名.二级域名 com.java.xxxx
  3. 如果需要使用其他包里面的类,那么我们需要import
    1
    2
    import com.test.Student;                  //导入指定位置的类
    import com.test.* //导入包下的全部
  4. 静态导入可以直接导入某个类的静态变量或静态方法,导入后,相当于这个方法或是类在定义在当前类中,可以直接调用该方法
    1
    2
    3
    4
    5
    6
    7
    import static com.test.ui.Student.test;

    public class Student {
    public static void main(String[] args) {
    test();
    }
    }

访问权限

  1. private用于封装属性或方法,然后通过JavaBean的方式来访问与修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    private String name;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }
  2. java文件中只能有一个public class
    1
    2
    3
    4
    5
    6
    7
    public class Student {

    }

    class Test{ //不能添加权限修饰符!只能是default

    }

封装、继承和多态

  1. 封装思想其实是把实现细节给隐藏了,外部只需知道这个方法是什么作用,而无需关心实现
  2. 子类可以使用父类中非私有的成员;如果父类使用默认构造方法,子类可以不管,否则子类必须实现,且使用super;所有类都默认继承自Object类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Student {
    private String name;
    private int age;

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

    public class ArtStudent extends Student{
    public ArtStudent(String name, int age) {
    super(name, age);
    }
    }
  3. 多态是同一个行为具有多个不同表现形式或形态的能力;也就是同样的方法,由于实现类不同,执行的结果也不同,看看下面的重写就清楚了

重写

  1. 重写是直接覆盖原有方法;重写的条件是需要和父类的返回值类型和形参一致
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //父类中的study
    public void study(){
    System.out.println("我爱学习");
    }

    //子类中的study
    @Override //声明这个方法是重写的
    public void study(){
    System.out.println("给你看点好康的");
    }

    public static void main(String[] args) {
    SportsStudent student = new SportsStudent("幽蓝", 9);
    student.study(); //输出子类定义的内容
    }
  2. 重写方法时,不仅想使用我们自己的逻辑,同时还希望执行父类的逻辑
    1
    2
    3
    4
    public void study(){
    super.study(); //"我爱学习"
    System.out.println("给你看点好康的");
    }
  3. 如果想访问父类的成员变量,也可以使用super关键字来访问;方法中访问的默认是 形参列表中 > 当前类的成员变量 > 父类成员变量
    1
    2
    3
    4
    5
    public void setTest(int test){
    test = 1;
    this.test = 1;
    super.test = 1;
    }
  4. 强制转换为对应的类型,这样的类型转换称为向下转型
    1
    2
    3
    Student student = new ArtStudent();   //是由ArtStudent进行实现的
    ArtStudent ps = (ArtStudent)student; //让它变成一个具体的子类
    ps.study(); //调用具体实现类的方法

instanceof判断类型

  1. 判断类型,原理是判断对象是否为某个类的实例,就是继承关系;int类型是基础类型,必须转换成Integer对象,才能使用instanceof
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    String name = "幽蓝";
    Integer age = 9;
    Boolean youAreBeautiful = true;

    public class ArtStudent extends Student{}
    ArtStudent a1 = new ArtStudent();

    System.out.println(name instanceof String); //true
    System.out.println(age instanceof Number); //true
    System.out.println(youAreBeautiful instanceof Boolean); //true
    System.out.println(a1 instanceof Student); //true

抽象类

  1. 抽象类一般只用作继承使用;new的对象是个抽象类,只能创建带实现的匿名内部类,才可以使用
    1
    2
    3
    4
    5
    6
    Student s1 = new Student() {
    @Override
    public void study() {

    }
    };

接口

  1. 类似于一个插件,只能作为一个附属功能加在主体上,同时具体实现还需要由主体来实现;接口中定义的变量,默认为public static final
  2. 通过声明default关键字来给抽象方法一个默认实现
    1
    2
    3
    4
    5
    public interface Eat {
    default void eat(){
    //do something...
    }
    }
  3. 一个类可以实现很多个接口,但是不能理解为多继承,每个接口之间用逗号隔开
    1
    2
    3
    4
    5
    6
    public class SportsStudent extends Student implements Eat, ...{
    @Override
    public void eat() {

    }
    }

枚举类

  1. final不方便修改,但使用枚举类就很方便了,有点像下拉框;比如我想添加一个状态(跑步、学习、睡觉)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    public enum Status {
    RUNNING("跑步"), STUDY("学习"), SLEEP("睡觉"); //无参构造方法被覆盖,创建枚举需要添加参数(本质就是调用的构造方法!)

    private final String name; //枚举的成员变量
    Status(String name) { //覆盖原有构造方法(默认private,只能内部使用!)
    this.name = name;
    }

    public String getName() { //获取封装的成员变量
    return name;
    }
    }

    public class Student {
    private final String name;
    private Status myStatus;

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

    public Status getStatus() {
    return myStatus;
    }

    public void setStatus(Status status) { //不再是String,而是我们指定的枚举类型
    this.myStatus = status;
    }

    public void sayStatus() {
    System.out.println(this.name + "的状态是:" + this.myStatus.getName());
    }
    }

    public static void main(String[] args) {
    Student student = new Student("幽蓝");
    student.setStatus(Status.SLEEP);
    System.out.println(student.getStatus()); //SLEEP
    System.out.println(student.getStatus().getName()); //睡觉
    student.sayStatus(); //幽蓝的状态是:睡觉
    }
  2. 枚举类还自带一些继承下来的实用方法
    1
    2
    Status.valueOf("")   //将名称相同的字符串转换为枚举
    Status.values() //快速获取所有的枚举

基本类型包装类

  1. 自动拆箱运算
    1
    2
    3
    Integer a = 10, b = 20;
    int c = a * b; //直接自动拆箱成基本类型参与到计算中
    System.out.println(c);
  2. 判断包装类型是否相等
    1
    2
    3
    Integer a = 128, b = 128;
    System.out.println(a == b); //判断是两个对象是否为同一个对象(内存地址是否相同)
    System.out.println(a.equals(b)); //这个才是真正的值判断!

数组

  1. 常用的方法,待更新
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    arr.length;                 //数组长度
    Arrays.toString(arr); //打印数组
    Arrays.equals(arr, arr2); //判断数组是否相等,equals仅适用于一维数组
    Arrays.sort(arr); //数组排序

    int[] arr3 = new int[10];
    Arrays.fill(arr3, 66); //创建10个空位,每个空位填入66

    int[] arr4 = {1, 2, 3, 4, 5};
    int[] target = Arrays.copyOf(arr4, 5); //拷贝数组的全部内容,并生成一个新的数组对象
    int[] target2 = Arrays.copyOfRange(arr4, 3, 5); //也可以只拷贝某个范围内的内容

    int[] target3 = new int[10];
    System.arraycopy(arr4, 0, target, 0, 5); //将一个数组中的内容拷贝到其他数组中,使用System.arraycopy进行搬运

    int[][] array = {{2, 8, 4, 1}, {9, 2, 0, 3}};
    System.out.println(Arrays.deepToString(array)); //打印多维数组
    System.out.println(Arrays.deepEquals(a, b)); //判断多维数组是否相等,需要使用deepEquals来进行深层次判断
  2. 迭代器,foreach遍历数组
    1
    2
    3
    4
    int[] arr = new int[]{1, 2, 3};
    for (int i : arr) {
    System.out.println(i);
    }
  3. 动态扩容,待更新
  4. 可变长参数
    1
    2
    3
    4
    5
    6
    7
    public static void main(String[] args) {
    test("AAA", "BBB", "CCC"); //可变长,最后都会被自动封装成一个数组
    }

    private static void test(String... test){
    System.out.println(test[0]); //其实参数就是一个数组
    }

字符串

  1. 常用的方法,待更新
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Integer num = 9;
    String str = new String("Hello World"), str2 = new String("Hello World");
    String name = "小明";

    System.out.println(name.length()); //判断字符数量,结果2
    System.out.println(num.toString()); //将对象转换为字符串,结果9
    System.out.println(str.equals(str2)); //字符串的内容比较,一定要用equals
    System.out.println(name.contains("小")); //判断name属性是否包含"小",结果true
    System.out.println(str.substring(0, 3)); //分割字符串,并返回一个新的子串对象,结果Hel

    String[] strings = str.split(" "); //使用split方法进行字符串分割,比如这里就是通过空格分隔,得到一个字符串数组
    for (String string : strings) {
    System.out.println(string);
    }

    char[] chars = new char[]{'奥', '利', '给'};
    String str = new String(chars);
    System.out.println(str); //奥利给
  2. StringBuilder类型用于构造字符串,可以使用它来对字符串进行拼接、裁剪等操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    StringBuilder builder = new StringBuilder();   //一开始创建时,内部什么都没有
    builder.append("AAA"); //我们可以使用append方法来讲字符串拼接到后面
    builder.append("BBB");
    System.out.println(builder.toString()); //当我们字符串编辑完成之后,就可以使用toString转换为字符串了


    StringBuilder builder2 = new StringBuilder("AAABBB"); //在构造时也可以指定初始字符串
    builder.delete(2, 4); //删除2到4这个范围内的字符
    System.out.println(builder.toString());
  3. 正则表达式:判断字符串是否符合规则
    1
    2
    3
    String str = "oooo";
    //matches方法用于对给定正则表达式进行匹配,匹配成功返回true,否则返回false
    System.out.println(str.matches("o+")); //+表示对前面这个字符匹配一次或多次,这里字符串是oooo,正好可以匹配
    字符 描述
    * 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。*等价于 {0,}
    + 匹配前面的子表达式一次或多次。例如,zo+ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}
    ? 匹配前面的子表达式零次或一次。例如,do(es)? 可以匹配”do”、”does”、”doxy”中的”do”。?等价于 {0,1}
    {n} n是一个非负整数。匹配确定的n次。例如,o{2}不能匹配”Bob”中的o,但是能匹配”food”中的两个o
    {n,} n是一个非负整数。至少匹配n次。例如,o{2,}不能匹配”Bob”中的o,但能匹配”foooood”中的所有o。o{1,}等价于o+。o{0,}则等价于o*
    {n,m} m和n均为非负整数,其中n <= m。最少匹配n次且最多匹配m次。例如,o{1,3}将匹配”fooooood”中的前三个o。o{0,1}等价于o?。请注意在逗号和两个数之间不能有空格
    1
    2
    String str = "abcabccaa";
    System.out.println(str.matches("[abc]*")); //表示abc这几个字符可以出现 0 - N 次
    字符 描述
    [ABC] 匹配[…]中的所有字符,例如[aeiou]匹配字符串 “google runoob taobao” 中所有的 e o u a 字母
    [^ABC] 匹配除了 […] 中字符的所有字符,例如 [^aeiou] 匹配字符串 “google runoob taobao” 中除了 e o u a 字母的所有字母
    [A-Z] [A-Z] 表示一个区间,匹配所有大写字母,[a-z] 表示所有小写字母
    . 匹配除换行符(\n、\r)之外的任何单个字符,相等于 [^\n\r]
    [\s\S] 匹配所有。\s 是匹配所有空白符,包括换行,\S 非空白符,不包括换行
    \w 匹配字母、数字、下划线。等价于 [A-Za-z0-9_]

内部类

  1. 俗称套娃,使用不好会出现一大坨屎山,谨慎使用吧
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Test {
    class Inner{ //类中定义的一个内部类

    }
    }

    public static void main(String[] args) {
    Test test = new Test();
    Test.Inner inner = test.new Inner(); //写法有那么一丝怪异,但是没毛病!
    }
  2. 静态内部类就和类中的静态变量和静态方法一样,是属于类拥有的,我们可以直接通过类名.去访问
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Test {
    static class Inner{

    }
    }

    public static void main(String[] args) {
    Test.Inner inner = new Test.Inner(); //不用再创建外部类对象了!
    }
  3. 匿名内部类是实现lambda表达式的原理;lambda表达式(匿名内部类)只能访问外部的final类型或是隐式final类型的局部变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void main(String[] args) {
    Eat eat = new Eat() {
    @Override
    public void eat() {
    //DO something...
    }
    };
    }

    public static void main(String[] args) {
    Eat eat = () -> {}; //等价于上述内容
    }
  4. 方法引用就是将一个已实现的方法,直接作为接口中抽象方法的实现(当然前提是方法定义得一样才行)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public interface Study {
    int sum(int a, int b); //待实现的求和方法
    }

    public static void main(String[] args) {
    Study study = (a, b) -> a + b; //普通写法
    Study study2 = (a, b) -> Integer.sum(a, b); //Integer类中默认提供了求两个int值之和的方法
    Study study3 = Integer::sum; //使用双冒号来进行方法引用,静态方法使用 类名::方法名 的形式
    System.out.println(study3.sum(10, 20));
    }
  5. 普通从成员方法,同样需要使用对象来进行方法引用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public interface Study {
    String none();
    }

    public class Test {
    public static void main(String[] args) {
    Test test = new Test();
    Study study = test::sayName;
    System.out.println(study.none());
    }

    public String sayName() {
    return "椰奶棒棒哒!";
    }
    }

异常机制

  1. 不使用自己不熟悉的库,以及遵守团队制定的规范,能减少很多不必要的麻烦
  2. 所有的运行时异常都继承自RuntimeException;默认继承自Exception类的异常都是编译时异常
  3. 抛出异常,如果throws写在参数的右侧,它会往上抛出,再由调用方处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void main(String[] args) {
    System.out.println(test(10, 0));
    }

    public static int test(int a, int b) {
    if(b == 0) throw new ArithmeticException("除数不能为0");
    return a / b;
    }

    public static int test2() throws {
    ...
    }
  4. 既然错误无法避免,那只能进行预判处理了;try语句负责捕获,catch语句负责处理,每个错误都有自己的解决方式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    try {
    //....
    } catch (NullPointerException e) { //空指针异常

    } catch (IndexOutOfBoundsException e){ //数组越界异常

    } catch (RuntimeException e){ //如果放在最上面,空指针和数组越界异常不会运行

    } finally {
    System.out.println("无论是否出现异常,都会在最后执行");
    }

数学工具类

  1. 常用的方法,待更新
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Math.pow(5, 3);   //我们可以使用pow方法直接计算a的b次方
    Math.abs(-1); //abs方法可以求绝对值
    Math.max(19, 20); //快速取最大值
    Math.min(2, 4); //快速取最小值
    Math.sqrt(9); //求一个数的算术平方根
    Math.ceil(4.5); //通过使用ceil来向上取整
    Math.floor(5.6); //通过使用floor来向下取整

    Random random = new Random(); //创建Random对象
    System.out.print(random.nextInt(100)); //nextInt方法可以指定创建0 - x之内的随机数

泛型

  1. 可以将某些不明确的类型在具体使用时再明确;类、接口、抽象类都支持泛型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Student<A, B, C> {   //多个类型变量使用逗号隔开
    public A a;
    public B b;
    public C c;
    }

    public static void main(String[] args) {
    Student<String, Integer, Boolean> s1 = new Student<>(); //使用钻石运算符可以省略其中的类型
    s1.a = "幽蓝";
    s1.b = 9;
    s1.c = true;
    }
  2. 当某个方法(无论是是静态方法还是成员方法)需要接受的参数类型并不确定时,我们也可以使用泛型来表示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Test {
    public static void main(String[] args) {
    String str = sayInfo("Hello World!");
    }

    private static <T> T sayInfo(T t){ //在返回值类型前添加<>并填写泛型变量表示这个是一个泛型方法
    return t;
    }
    }
  3. 数据从大到小排列
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public static void main(String[] args) {
    Integer[] arr = {1, 4, 5, 2, 6, 3, 0, 7, 9, 8};
    Arrays.sort(arr, new Comparator<Integer>() {
    //通过创建泛型接口的匿名内部类,来自定义排序规则,因为匿名内部类就是接口的实现类,所以说这里就明确了类型
    @Override
    public int compare(Integer o1, Integer o2) { //两个需要比较的数会在这里给出
    return o2 - o1;
    //compare方法要求返回一个int来表示两个数的大小关系,大于0表示大于,小于0表示小于
    //这里直接o2-o1就行,如果o2比o1大,那么肯定应该排在前面,所以说返回正数表示大于
    }
    });
    System.out.println(Arrays.toString(arr));
    }

    //用lambda表达式简化一下
    public static void main(String[] args) {
    Integer[] arr = {1, 4, 5, 2, 6, 3, 0, 7, 9, 8};
    Arrays.sort(arr, (o1, o2) -> o2 - o1); //瞬间变一行,效果跟上面是一样的
    System.out.println(Arrays.toString(arr));
    }
  4. 设定泛型上限,可以灵活地控制泛型的具体类型范围
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Student<T extends Number> {

    }

    public static void main(String[] args) {
    Student<Integer> s1 = new Student<>();
    Student<?> s2 = new Student<>(); //?可以接受任意类型
    Student<? extends Number> s3 = new Student<>(); //限定上界,只能是Number类型或它的子类
    Student<? super Number> s4 = new Student<>(); //限定下界,只能是Number类型或它的父类
    //Student<String> s5 = new Student<>(); //不能指定String类型
    }

函数式接口

  1. 优先扩展性强,减少重复代码,方便维护;缺点学习成本高,可阅读性差
  2. 主要介绍四个函数式接口
    • Function函数型函数式接口:这个接口消费一个对象,然后会向外供给一个对象
    • Supplier供给型函数式接口:这个接口是专门用于供给使用的,其中只有一个get方法用于获取需要的对象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      import java.util.function.Function;
      import java.util.function.Supplier;

      public class Test {
      public static void main(String[] args) {
      Test t = new Test();
      t.functionTest();
      t.supplierTest();
      }

      //通过apply返回结果
      public <T, R> void functionMethod(Function<T, R> function, T param) {
      System.out.println(function.apply(param));
      }

      //第一个传入参数乘10,第二个拼接字符串,第三个执行String的有参构造方法会返回棒棒哒
      public void functionTest(){
      Function<Integer, Integer> multiplyTen = (num) -> num * 10;
      functionMethod(multiplyTen, 100);

      Function<String, String> subStr = (name) -> name + "想睡觉😴";
      functionMethod(subStr, "幽蓝");

      Function<String, String> goodJob = String::new;
      functionMethod(goodJob, "棒棒哒!");
      }

      //第一个通过get方法返回字符串
      //第二个通过方法引用构造狗的实现,通过get获取狗的实例
      //第三个通过实例获取狗叫声的方法
      public void supplierTest() {
      Supplier<String> supplier = () -> "哎哟,你干嘛!!!";
      System.out.println(supplier.get());

      Supplier<Dog> supplier1 = Dog::new;
      Dog dog = supplier1.get();
      System.out.println(dog);

      Supplier<String> supplier2 = dog::getCry;
      System.out.println(supplier2.get());
      }
      }

      class Dog {
      private String name;
      private String cry;

      public Dog() {
      this.name = "大黄";
      this.cry = "萌新请问,刚抽出的SSR强吗,值不值得练🐶";
      }

      public String getCry() {
      return cry;
      }
      }
    • Consumer消费型函数式接口:这个接口专门用于消费某个对象的
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      import java.util.function.Consumer;

      public class Test {
      private static final Consumer<Student> STUDENT_CONSUMER = student -> System.out.println(student+" 真好吃!");

      public static void main(String[] args) {
      Student student = new Student();
      STUDENT_CONSUMER //我们可以提前将消费之后的操作以同样的方式预定好
      .andThen(stu -> System.out.println("我是吃完之后的操作!"))
      .andThen(stu -> System.out.println("好了好了,吃饱了!"))
      .accept(student); //预定好之后,再执行
      }
      }

      class Student {
      public void hello() {
      System.out.println("我是学生");
      }
      }
    • Predicate断言型函数式接口:接收一个参数,然后进行自定义判断并返回一个boolean结果
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      import java.util.function.Predicate;

      public class Test {
      private static final Predicate<Student> STUDENT_PREDICATE = student -> student.score >= 60;
      public static void main(String[] args) {
      Student student = new Student();
      student.score = 80;

      if(STUDENT_PREDICATE.test(student)) { //test方法的返回值是一个boolean结果
      System.out.println("及格了,真不错,今晚奖励自己一次");
      } else {
      System.out.println("不是,Java都考不及格?隔壁初中生都在打ACM了");
      }

      boolean b = STUDENT_PREDICATE
      .and(stu -> stu.score > 90) //需要同时满足这里的条件,才能返回true,比如满足60分且超过90分
      .test(student);
      if(!b) System.out.println("Java到现在都没考到90分?你的室友都拿国家奖学金了");
      }
      }

      class Student {
      public int score = 100;
      }

集合

  1. 集合只能存入对象;目前有List、Queue与Deque、Set、Map

List

  1. List:有序、尾插、有下标、可重复
  2. ArrayList:底层数组
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    List<String>list = new ArrayList<>();             //推荐使用第一种,后期方便修改
    List<String>list2 = new LinkedList<>();
    ArrayList<String>list3 = new ArrayList<>();
    LinkedList<String>list4 = new LinkedList<>();

    list.add("AAA");
    list.add(0, "BBB");
    list.addAll(Arrays.asList("CCC", "DDD", "AAA"));
    list.sort(String::compareTo); //排序,[AAA, AAA, BBB, CCC, DDD]

    //list.remove("BBB"); //删除最前面的一个
    //list.removeIf(s -> s.length() == 3); //删除指定长度为3的字符串,结果[]
    //list.clear(); //清空

    list.set(4, "幽蓝"); //修改,[AAA, AAA, BBB, CCC, 幽蓝]

    System.out.println(list.contains("椰奶")); //查询元素是否存在,结果false
    System.out.println(list.indexOf("AAA")); //从最前面开始找,返回元素的下标位置,结果0
    System.out.println(list.lastIndexOf("AAA")); //从最后面开始找,返回元素的下标位置,结果1

    Object[] arr = list.toArray(new String[0]); //返回Object类型的数组,参数为返回的类型
    System.out.println(arr[1]); //AAA
    System.out.println(list.subList(3, 5)); //分割,[CCC, 幽蓝]
  3. List迭代器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    List<String> list = new ArrayList<>(Arrays.asList("幽蓝", "椰奶", "帕帕子"));
    for (String s : list)
    System.out.println(s);
    list.forEach(str -> System.out.println(str));
    list.forEach(System.out::println); //效果等同上面
    //幽蓝
    //椰奶
    //帕帕子

    Iterator<String> iterator = list.iterator();
    System.out.println(iterator.next()); //遍历第一个,幽蓝
    System.out.println("Hi");
    System.out.println(iterator.next()); //遍历下一个,椰奶
    System.out.println("Hi");
    iterator.forEachRemaining(System.out::println); //剩下的全部遍历,帕帕子
  4. LinkedList:底层链表,除了底层不同功能跟ArrayList一样

Queue与Deque

  1. Queue:队列接口,支持队列相关操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public interface Queue<E> extends Collection<E> {
    //队列的添加操作,是在队尾进行插入(只不过List也是一样的,默认都是尾插)
    //如果插入失败,会直接抛出异常
    boolean add(E e);

    //同样是添加操作,但是插入失败不会抛出异常
    boolean offer(E e);

    //移除队首元素,但是如果队列已经为空,那么会抛出异常
    E remove();

    //同样是移除队首元素,但是如果队列为空,会返回null
    E poll();

    //仅获取队首元素,不进行出队操作,但是如果队列已经为空,那么会抛出异常
    E element();

    //同样是仅获取队首元素,但是如果队列为空,会返回null
    E peek();
    }

    public static void main(String[] args) {
    Queue<String> queue = new LinkedList<>(); //当做队列使用,还是很方便的
    queue.offer("AAA");
    queue.offer("BBB");
    System.out.println(queue.poll());
    System.out.println(queue.poll());

    Queue<Integer> queue2 = new PriorityQueue<>((a, b) -> b - a); //默认自然排序,改成b-a则是倒序
    queue2.offer(10);
    queue2.offer(4);
    queue2.offer(5);
    System.out.println(queue2.poll());
    System.out.println(queue2.poll());
    System.out.println(queue2.poll());
    }
  2. Deque:双端队列接口,支持双端队列操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    //在双端队列中,所有的操作都有分别对应队首和队尾的
    public interface Deque<E> extends Queue<E> {
    //在队首进行插入操作
    void addFirst(E e);

    //在队尾进行插入操作
    void addLast(E e);

    //不用多说了吧?
    boolean offerFirst(E e);
    boolean offerLast(E e);

    //在队首进行移除操作
    E removeFirst();

    //在队尾进行移除操作
    E removeLast();

    //不用多说了吧?
    E pollFirst();
    E pollLast();

    //获取队首元素
    E getFirst();

    //获取队尾元素
    E getLast();

    //不用多说了吧?
    E peekFirst();
    E peekLast();

    //从队列中删除第一个出现的指定元素
    boolean removeFirstOccurrence(Object o);

    //从队列中删除最后一个出现的指定元素
    boolean removeLastOccurrence(Object o);

    // *** 队列中继承下来的方法操作是一样的,这里就不列出了 ***

    ...

    // *** 栈相关操作已经帮助我们定义好了 ***

    //将元素推向栈顶
    void push(E e);

    //将元素从栈顶出栈
    E pop();


    // *** 集合类中继承的方法这里也不多种介绍了 ***

    ...

    //生成反向迭代器,这个迭代器也是单向的,但是是next方法是从后往前进行遍历的
    Iterator<E> descendingIterator();

    }

    public static void main(String[] args) {
    Deque<String> deque = new LinkedList<>();
    deque.push("AAA");
    deque.push("BBB");
    System.out.println(deque.pop());
    System.out.println(deque.pop());
    }

Set

  1. Set: 无序、无下标、无重复,底层哈希表
  2. HashSet:底层HashMap,哈希值排序
  3. LinkedHashSet:底层LinkedHashMap,尾插排序
  4. TreeSet:自然排序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    Set<String> set = new HashSet<>();
    set.add("AAA");
    set.add("AAA");
    set.add("BBB");
    set.add("CCC"); //此时打印是无序的,[AAA, CCC, BBB]

    Set<String> set2 = new LinkedHashSet<>();
    set2.add("AAA");
    set2.add("BBB");
    set2.add("CCC"); //此时打印是有序的,[AAA, BBB, CCC]

    Set<Integer> set3 = new TreeSet<>((a, b) -> b - a);
    set3.add(1);
    set3.add(3);
    set3.add(2); //此时打印是倒序的,[3, 2, 1]

    //Set迭代器
    set.forEach(System.out::println);
    set2.forEach(System.out::println);
    set3.forEach(System.out::println);

Map

  1. Map:有序、无下标、键无重复,底层哈希表
  2. HashMap:底层就是我,键值对
  3. LinkedHashMap:底层LinkedHashMap,尾插排序
  4. TreeMap:底层二叉红黑树,默认自然排序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    Map<Integer, String> map = new HashMap<>();
    map.put(2, "椰奶");
    map.put(3, "帕帕子");
    map.put(1, "幽蓝");
    map.put(1, "幽蓝蓝"); //此时打印{1=幽蓝蓝, 2=椰奶, 3=帕帕子}

    //map.remove(1); //删除指定的键

    System.out.println(map.keySet()); //获取全部键,[1, 2, 3]
    System.out.println(map.get(1)); //获取1的值,幽蓝蓝
    System.out.println(map.containsKey(4)); //查询是否有4,false
    System.out.println(map.containsValue("椰奶")); //查询是否有椰奶,true

    Map<Integer, String> map2 = new LinkedHashMap<>();
    map2.put(2, "椰奶");
    map2.put(3, "帕帕子");
    map2.put(1, "幽蓝"); //此时打印{2=椰奶, 3=帕帕子, 1=幽蓝}

    Map<Integer, String> map3 = new TreeMap<>((a, b) -> b - a);
    map3.put(2, "椰奶");
    map3.put(3, "帕帕子");
    map3.put(1, "幽蓝"); //此时打印{3=帕帕子, 2=椰奶, 1=幽蓝}

    map.forEach((k, v) -> System.out.println(k + " = " + v));
    map2.forEach((k, v) -> System.out.println(k + " = " + v));
    map3.forEach((k, v) -> System.out.println(k + " = " + v));
  5. 查询key是否存在,在对value进行操作,最后返回value,实在不理解就运行一下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "A");
    map.put(2, "B");
    map.compute(1, (k, v) -> { //compute会将指定Key的值进行重新计算,若Key不存在,v会返回null
    return v+"M"; //这里返回原来的value+M
    });
    map.computeIfPresent(1, (k, v) -> { //当Key存在时存在则计算并赋予新的值
    return v+"M"; //这里返回原来的value+M
    });
    System.out.println(map);
    }

Stream流

  1. Stream流原理
    Stream流原理图
  2. Stream流和SQL语句很像,特点是高效率、简洁、可读性强
  3. filter正常情况是保留,加了!进行取反才是筛选
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("A");
    list.add("B");
    list.add("C");

    //移除为B的元素
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()){
    if(iterator.next().equals("B")) iterator.remove();
    }

    //Stream操作
    list = list //链式调用
    .stream() //获取流
    .filter(e -> !e.equals("B")) //只允许所有不是B的元素通过流水线
    .collect(Collectors.toList()); //将流水线中的元素重新收集起来,变回List
    System.out.println(list);
    }
  4. 去重排序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(3);

    list = list
    .stream()
    .distinct() //去重(使用equals判断)
    .sorted((a, b) -> b - a) //进行倒序排列
    .map(e -> e+1) //每个元素都要执行+1操作
    .limit(2) //只放行前两个元素
    .collect(Collectors.toList());

    System.out.println(list);
    }
  5. 分割
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("A,B");
    list.add("C,D");
    list.add("E,F"); //我们想让每一个元素通过,进行分割,变成独立的6个元素
    list = list
    .stream() //生成流
    .flatMap(e -> Arrays.stream(e.split(","))) //分割字符串并生成新的流
    .collect(Collectors.toList()); //汇成新的List
    System.out.println(list); //得到结果
    }
  6. 随机数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void main(String[] args) {
    Random random = new Random(); //没想到吧,Random支持直接生成随机数的流
    random
    .ints(-100, 100) //生成-100~100之间的,随机int型数字(本质上是一个IntStream)
    .limit(10) //只获取前10个数字(这是一个无限制的流,如果不加以限制,将会无限进行下去!)
    .filter(i -> i < 0) //只保留小于0的数字
    .sorted() //默认从小到大排序
    .forEach(System.out::println); //依次打印
    }
  7. 快速进行统计
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static void main(String[] args) {
    Random random = new Random(); //Random是一个随机数工具类
    IntSummaryStatistics statistics = random
    .ints(0, 100)
    .limit(100)
    .summaryStatistics(); //获取语法统计实例
    System.out.println(statistics.getMax()); //快速获取最大值
    System.out.println(statistics.getCount()); //获取数量
    System.out.println(statistics.getAverage()); //获取平均值
    }

Collections工具类

  1. 常用的工具类,待更新
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    List<Integer> list = new ArrayList<>(Arrays.asList(1, 4, 9, 5, 3));
    Collections.sort(list);
    System.out.println(list); //[1, 3, 4, 5, 9]
    System.out.println(Collections.max(list)); //最大9
    System.out.println(Collections.min(list)); //最小1
    System.out.println(Collections.binarySearch(list, 4)); //查询4并返回下标,结果2

    List<Integer> newList = Collections.unmodifiableList(list); ////设置成只读并返回新集合,无法添加删除

    int search = Collections.indexOfSubList(list, Arrays.asList(5, 9));
    System.out.println(search); //查询子集开始位置,结果3
  2. 泛型有一些漏洞,能在集合中存入任意的类型,原理是泛型检查只在编译阶段,你不写具体类型它就不检查;使用checkedXXX可以给集合类进行包装,运行时会进行类型检查
    1
    2
    3
    4
    5
    6
    7
    List list = new ArrayList<>(Arrays.asList(1,2,3));
    list.add("aaa");
    list.add(true);
    System.out.println(list); //[1, 2, 3, aaa, true]

    list = Collections.checkedList(list, Integer.class); //这里的.class关键字,表示Integer这个类型
    //list.add("bbb"); //运行时报错,不能添加String类型
  3. 判断集合相等
    1
    2
    3
    List<String> list = new ArrayList<>(Arrays.asList("幽蓝"));
    List<String> list2 = new ArrayList<>(Arrays.asList("幽蓝"));
    System.out.println(list.equals(list2)); //true
  4. 判断对象相等
    • 如果s2 = s1,那他两就是同一个对象
    • 未重写之前,name属性都是”幽蓝”却不相等,让人匪夷所思,原因出在哈希表上
    • 重写equals与hashcode方法,如果两个方法都相同,那他两的属性就相等
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      public static void main(String[] args) {
      Student s1 = new Student("幽蓝");
      Student s2 = new Student("幽蓝");
      //s2 = s1;
      System.out.println(s1 == s2); //比较是否为同一个对象,false
      System.out.println(s1.equals(s2)); //比较name值是否相同,true
      System.out.println(
      "s1:" + s1.hashCode() + ", s2: " + s2.hashCode() //s1:783903, s2: 783903
      );
      }

      static class Student {
      String name;

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

      @Override
      public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      Student student = (Student) o;
      return Objects.equals(name, student.name);
      }

      @Override
      public int hashCode() {
      return Objects.hash(name);
      }
      }

Java I/O

  1. IO就是读取写入

文件字节流

  1. 输入流:读取文本数据,暂不考虑二进制文本;项目的根目录创建test.txt,里面写个hello world!测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    try (FileInputStream stream = new FileInputStream("test.txt")) {
    byte[] bytes = new byte[stream.available()];
    stream.read(bytes);
    System.out.println(new String(bytes));

    //stream.read(); //默认读取1字节,可在byte设置读取量
    //stream.skip(3); //跳过3字节在读取
    //stream.available; //读取全部
    } catch (IOException e) {
    e.printStackTrace();
    }
  2. 输出流:写入数据到文本;默认关闭覆盖全文本,开启追加模式,它会在已有的内容上继续写入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    try (FileOutputStream outputStream = new FileOutputStream(
    "student.txt", true)) { //开启追加模式
    outputStream.write("椰奶yyds!".getBytes()); //写入
    outputStream.write("帕帕子yyds!".getBytes());
    outputStream.write("哎哟,你干嘛!".getBytes(), 0, 1); //同上输入流
    outputStream.flush(); //刷新
    } catch (IOException e) {
    e.printStackTrace();
    }

文件字符流

  1. 字符流:复制文本,内容椰奶yyds,帕帕子yyds,让我康康!
    • 读取:按具体字符进行读取,只适合纯文本的文件,不支持其他类型的文件;只支持char[]类型作为存储
    • 写入:没啥想说的(´-ω-`)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      try(FileReader reader = new FileReader("test.txt");
      FileWriter writer = new FileWriter("test的副本.txt")) {
      char[] chars = new char[3];
      int len;

      while ((len = reader.read(chars)) != -1) {
      writer.write(chars, 0, len);
      }

      writer.write('牛');
      writer
      .append('啊')
      .append('牛'); //其实功能和write一样
      } catch (IOException e) {
      e.printStackTrace();
      }
  2. File对象:管理和操作硬盘上的文件,比如创建文件夹和文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    File file = new File("test.txt");
    System.out.println(file.getAbsolutePath()); //获取路径

    File file2 = new File("newFile");
    System.out.println(file2.mkdirs()); //创建文件夹,返回布尔类型表示是否成功

    File file3 = new File("newFile/newTest.txt"); //指定位置创建文件需保证文件夹已存在
    try {
    System.out.println(file3.createNewFile()); //创建文件,返回布尔类型表示是否成功
    } catch (IOException e) {
    e.printStackTrace();
    }
    System.out.println(file2.isDirectory()); //判断是否为文件夹
    System.out.println(file3.isFile()); //判断是否为文件

    File file4 = new File("/");
    System.out.println("当前硬盘容量剩余:" + file4.getFreeSpace() / 1024 / 1024 / 1024 + "G");

    File file5 = new File("./src");
    for (String s : Objects.requireNonNull(file5.list()))
    System.out.println(s); //.表示根路径,list查看当前路径下的全部文件(包含文件夹)

    //System.out.println(file.exists()); //此文件是否存在
    //System.out.println(file.length()); //获取文件的大小
    //System.out.println(file.isDirectory()); //是否为一个文件夹
    //System.out.println(file.canRead()); //是否可读
    //System.out.println(file.canWrite()); //是否可写
    //System.out.println(file.canExecute()); //是否可执行
  3. 复制视频,随便找个视频丢到项目的根目录,记得修改代码中文件名
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    File file = new File("华扇-东京不夜城.mp4");
    try (FileInputStream in = new FileInputStream(file); //可以传参
    // try (FileInputStream in = new FileInputStream("华扇-东京不夜城.mp4"); //效果同上
    FileOutputStream out = new FileOutputStream("华扇-东京不夜城的副本.mp4")) {
    byte[] bytes = new byte[1024];
    int len;
    long total = file.length(), sum = 0;
    System.out.println(total);
    while ((len = in.read(bytes)) != -1) {
    out.write(bytes, 0, len); //每次读取不一定是1024字节,填入len作为限制
    sum += len;
    System.out.println("文件已拷贝:" + ((sum * 100) / total) + "%");
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
  4. 总结:字节流适合处理非文本的文件,字符流适合处理纯文本

缓冲流

  1. 缓冲流原理
    缓冲流原理图
  2. BufferedInputStream区别在底层的读取机制改变了,这种像套娃一样的写法,说好听点叫装饰者模式
    • mark()就像游戏保存一样,输入流会以某种方式保留之后读取的readlimit数量的内容,当读取的内容数量超过readlimit,其他的内容不会被保留
    • reset()就像读取存档一样,使读取位置回到mark()调用时的位置
    • 默认readlimit缓冲8192字符,当设置mark(8193)大于readlimit才会生效,表示最多读取8193字符
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      //test.txt中的内容:Hello World
      try (BufferedInputStream stream = new BufferedInputStream(new FileInputStream("test.txt"))) {
      System.out.print((char) stream.read());
      System.out.print((char) stream.read());
      stream.mark(0); //存档存档
      System.out.print((char) stream.read());
      System.out.println((char) stream.read());
      stream.reset(); //读档读档
      System.out.print((char) stream.read());
      System.out.print((char) stream.read());
      System.out.print((char) stream.read());
      } catch (IOException e) {
      throw new RuntimeException(e);
      }
  3. BufferedOutputStream写入,补一下如何写入中文
    1
    2
    3
    4
    5
    try (BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream("test.txt"))) {
    stream.write("椰奶YYDS".getBytes(StandardCharsets.UTF_8)); //写入中文
    } catch (IOException e) {
    throw new RuntimeException(e);
    }
  4. BufferedReader读取,没啥想说的(´-ω-`)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    try (BufferedReader stream = new BufferedReader(new FileReader("test.txt"))){
    stream
    .lines() //获得行
    .limit(3) //最多读取多少行
    .distinct() //去重
    .sorted() //只支持英文排序
    .forEach(System.out::println);
    }catch (IOException e) {
    e.printStackTrace();
    }
  5. BufferedWriter写入,没啥想说的(´-ω-`)
    1
    2
    3
    4
    5
    6
    7
    8
    try (BufferedWriter stream = new BufferedWriter(new FileWriter("test.txt"))){
    stream.write("椰奶YYDS");
    stream.newLine(); //换行
    stream.write("帕帕子YYDS");
    stream.flush(); //刷新
    }catch (IOException e) {
    e.printStackTrace();
    }

转换流

  1. 可以将FileOutputStream转换成FileWriter来使用
    1
    2
    3
    4
    5
    try (OutputStreamWriter stream = new OutputStreamWriter(new FileOutputStream("test.txt"))){
    stream.write("椰奶YYDS");
    }catch (IOException e) {
    e.printStackTrace();
    }
  2. 还可以疯狂套娃,这样就能使用各种方法😂
    1
    2
    3
    4
    5
    try (BufferedReader stream = new BufferedReader(new InputStreamReader(new FileInputStream("test.txt")))){
    System.out.println(stream.readLine()); //打印一行
    }catch (IOException e) {
    e.printStackTrace();
    }

打印流

  1. 打印流原理
    打印流原理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //打印字符到文本
    try (PrintStream stream = new PrintStream(new FileOutputStream("test.txt"))){
    stream.print("帕帕子YYDS");
    }catch (IOException e) {
    e.printStackTrace();
    }

    //占位符 .2表示只保留2位
    int a = 10, b = 20;
    System.out.printf("a = %d, b = %d, c = %f, d = %.2f", a, b, 1.1, 2.2);

    //打印格式 3d表示3位 \n换行
    for (int i = 0; i <= 100; i++)
    System.out.printf("%3d\n", i);
    //System.out.printf("%-3d\n", i); //负号表示向左对齐
    占位符 说明
    %d 格式化输出整数
    %f 格式化输出浮点数
    %e 格式化科学计数法的浮点数
    %s 格式化输出字符串
  2. 向控制台输出内容并打印,读取文本内容并打印
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //可以向控制台输入信息在打印
    Scanner scanner = new Scanner(System.in);
    System.out.println(scanner.nextLine());

    //读取文本内容再向控制台打印
    try (Scanner scanner1 = new Scanner(new FileInputStream("test.txt"))) {
    System.out.println(scanner1.nextLine());
    } catch (IOException e) {
    e.printStackTrace();
    }

数据流

  1. 数据流DataInputStreamFilterInputStream的子类,采用装饰者模式,不同是它支持基本数据类型的直接读取
    1
    2
    3
    4
    5
    try (DataInputStream dataInputStream = new DataInputStream(new FileInputStream("test.txt"))){
    System.out.println(dataInputStream.readBoolean()); //直接将数据读取为任意基本数据类型
    }catch (IOException e) {
    e.printStackTrace();
    }
  2. 写入基本数据类型,暂不清楚用途
    1
    2
    3
    4
    5
    try (DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("test.txt"))){
    dataOutputStream.writeBoolean(false);
    }catch (IOException e) {
    e.printStackTrace();
    }

对象流

  1. ObjectOutputStream不仅支持基本数据类型,通过对对象的序列化操作,以某种格式保存对象,来支持对象类型的IO
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public static void main(String[] args) {
    try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.txt"));
    ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("test.txt"))) {

    //people对象,有一个name属性,值为幽蓝
    People people = new People("幽蓝");
    outputStream.writeObject(people); //people对象写入到文本
    outputStream.flush(); //刷新文本

    people = (People) inputStream.readObject(); //将文本中的对象重新赋值给people对象
    System.out.println(people.name);

    }catch (IOException | ClassNotFoundException e) { //使用readObject需要捕获ClassNotFoundException异常
    e.printStackTrace();
    }
    }

    static class People implements Serializable{ //必须实现Serializable接口才能被序列化
    String name;

    public People(String name){
    this.name = name;
    }
    }
  2. 用来区分对象流版本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    static class People implements Serializable{
    private static final long serialVersionUID = 123456; //在序列化时,会被自动添加这个属性,它代表当前类的版本,我们也可以手动指定版本。

    String name;

    public People(String name){
    this.name = name;
    }
    }
  3. transient关键字能使属性不参与到序列化中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public static void main(String[] args) {
    try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("output.txt"));
    ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("output.txt"))){

    People people = new People("幽蓝");
    outputStream.writeObject(people);
    outputStream.flush();

    people = (People) inputStream.readObject();
    System.out.println(people.name); //虽然能得到对象,但是name属性并没有保存,因此为null

    }catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
    }
    }

    static class People implements Serializable{
    private static final long serialVersionUID = 1234567;

    transient String name;

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

图书管理系统

  1. 实现一个图书管理系统(控制台),支持以下功能:添加、删除、修改、查询、保存书籍信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    import java.io.*;
    import java.util.*;

    public class Test {
    private static List<Book> LIST;
    public static void main(String[] args) {
    load(); //加载书籍信息
    Scanner scanner = new Scanner(System.in);
    while(true) {
    System.out.println("=============图书管理系统=============");
    System.out.println("1. 录入书籍");
    System.out.println("2. 删除书籍");
    System.out.println("3. 修改书籍");
    System.out.println("4. 查询书籍");
    System.out.println("5. 退出系统");
    switch (scanner.nextInt()) {
    case 1 -> insert(scanner);
    case 2 -> delete(scanner);
    case 3 -> modify(scanner);
    case 4 -> list();
    case 5 -> {
    System.out.println("保存数据中...");
    save();
    System.out.println("感谢你的使用,再见👋");
    return;
    }
    }
    }
    }

    //录入书籍信息
    private static void insert(Scanner scanner) {
    scanner.nextLine(); //小bug,用来吸收掉回车键
    System.out.print("请输入书籍的标题:");
    String title = scanner.nextLine();
    System.out.print("请输入书籍的作者:");
    String author = scanner.nextLine();
    System.out.print("请输入书籍的价格:");
    int price = scanner.nextInt();
    scanner.nextLine();

    Book book = new Book(title, author, price);
    LIST.add(book);
    System.out.println("添加成功!书籍信息:" + book);
    }

    //删除书籍信息
    private static void delete(Scanner scanner) {
    scanner.nextLine();
    System.out.print("请输入你想要删除的书籍编号ID:");
    int index = scanner.nextInt();
    scanner.nextLine();
    while ((index < 0) || index > LIST.size() - 1 ) {
    System.out.print("没有对应的书籍,请重新输入:");
    index = scanner.nextInt();
    scanner.nextLine();
    }
    LIST.remove(index);
    System.out.println("已删除!");
    }

    //修改书籍信息
    private static void modify(Scanner scanner) {
    scanner.nextLine();
    System.out.print("请输入你想要删除的书籍编号ID:");
    int index = scanner.nextInt();
    scanner.nextLine();
    if ((index < 0) || index > LIST.size() - 1 ) {
    System.out.print("没有对应的书籍,请重新输入:");
    index = scanner.nextInt();
    scanner.nextLine();
    }

    Book book = LIST.get(index);
    System.out.print("请输入书籍的标题:");
    book.setTitle(scanner.nextLine());
    System.out.print("请输入书籍的作者:");
    book.setAuthor(scanner.nextLine());
    System.out.print("请输入书籍的价格:");
    book.setPrice(scanner.nextInt());

    System.out.println("修改成功!书籍信息:" + book);
    }

    //查询书籍信息
    private static void list() {
    for (int i = 0; i < LIST.size(); i++)
    System.out.println("编号:"+ i + "," + LIST.get(i));
    }

    //保存书籍信息,别忘了new FileOutputStream的时候会创建data文件
    private static void save() {
    try(ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("data"))) {
    outputStream.writeObject(LIST);
    outputStream.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    //读取书籍信息
    @SuppressWarnings("unchecked") //太碍眼了,打个注解屏蔽掉警告
    private static void load() {
    File file = new File("data");
    if (file.exists()) {
    try(ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file))) {
    LIST = (List<Book>) inputStream.readObject();
    } catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
    }
    } else {
    LIST = new LinkedList<>();
    }
    }
    }

    class Book implements Serializable {
    private String title;
    private String author;
    private int price;

    public Book(String title, String author, int price) {
    this.title = title;
    this.author = author;
    this.price = price;
    }

    public void setTitle(String title) {
    this.title = title;
    }

    public void setAuthor(String author) {
    this.author = author;
    }

    public void setPrice(int price) {
    this.price = price;
    }

    @Override
    public String toString() {
    return "《" + title + "》作者:" + author + ",价格:" + price + "元";
    }
    }

多线程

  1. 多线程原理
    多线程原理图
  2. 两个线程各干各的互不影响,t1与t2谁先完成不好说,看cpu分配资源给谁
    • start:创建线程
    • yield:表示将当前资源让位给其他同优先级线程
    • join:加入到其他线程得到优先处理,处理完后其他线程才能继续处理
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread(() -> { //里面是Runnable方法,只不过用lambda简写了
      for (int i = 0; i < 50; i++) {
      System.out.println("1号线程:"+i);

      //if (i % 5 == 0) {
      // Thread.yield();
      // System.out.println("0或5的倍数让位1次" + i);
      //}

      }
      });
      Thread t2 = new Thread(() -> {
      for (int i = 0; i < 50; i++) {
      System.out.println("2号线程:"+i);

      //if (i == 10) {
      // try {
      // System.out.println("1号线程加入到此线程:" + i);
      // t1.join(); //i==10时,让1号线程加入,先完成1号线程的内容,在继续当前内容
      // } catch (InterruptedException e) {
      // e.printStackTrace();
      // }
      //}

      }
      });
      t1.start(); //创建新线程并执行,前提有cpu资源
      t2.start();

      for (int i = 0; i < 3; i++) {
      Thread.sleep(1000); //1000毫秒等于1秒
      System.out.println("每隔1秒打印1次");
      }
      }
  3. 写个终止多没意思,反过来让它运行10次在结束,或许更有意思
    • interrupt:指定线程添加一个中断标记以告知线程需要立即停止运行或是进行其他操作
    • currentThread:获取当前线程对象
    • isInterrupted:判断线程是否存在中断标志
    • interrupted:解除中断标记
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      Thread t = new Thread(() -> {
      int result = 0;
      while(Thread.currentThread().isInterrupted()) { //身上有标记则无限循环
      if(result < 10) {
      result++;
      } else {
      Thread.interrupted(); //result=10时,解除中断标记
      }
      //break; //没兴趣玩,直接break结束循环
      }
      System.out.println("我自由啦!");
      });
      t.start();
      t.interrupt(); //打上标记
  4. Java采用的是抢占式调度方式,优先级越高的线程,获得CPU资源的概率会越大,并不是说一定优先级越高的线程越先执行
    • MAX_PRIORITY:最高优先级
    • NOM_PRIORITY:常规优先级
    • MIN_PRIORITY:最低优先级
      1
      2
      3
      4
      5
      Thread t = new Thread(() -> {
      System.out.println("线程开始运行!");
      });
      t.start();
      t.setPriority(Thread.MIN_PRIORITY); //通过使用setPriority方法来设定优先级

线程锁和同步

  1. 同一时间操作同个变量,多线程之间会发生冲突
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public static int value = 0;

    public static void main(String[] args) throws InterruptedException {
    new Thread(() -> {
    for(int j = 0; j < 100000; j++) value++;
    }).start();

    new Thread(() -> {
    for(int j = 0; j < 100000; j++) value++;
    }).start();

    Thread.sleep(5000);
    System.out.println(value); //不保证是20万
    }
  2. 以上面的例子作为说明,主内存有个变量value,两条工作内存各复制一份变量value,两边各干各的,最终会把结果返回给主内存进行替换,如果返回的时间恰好一样,同样的值会被替换2次,导致value的值到不了20万。也不能把话说死,中20万还是有可能的😊
    多线程赋值图
  3. synchronized:就像是游乐园的设施,检票人只认可obj票,线程A提交了门票进去玩,线程B持有同样的门票,只能乖乖排队等着,直到A出来了换B进去玩,A想要继续玩只能重新拿票等着
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public static int value = 0;

    public static void main(String[] args) throws InterruptedException {
    Object obj = new Object();

    new Thread(() -> {
    for(int j = 0; j < 100000; j++) {
    synchronized(obj) { //使用synchronized关键字创建同步代码块
    value++;
    }
    }
    }).start();

    new Thread(() -> {
    for(int j = 0; j < 100000; j++) {
    synchronized(obj) {
    value++;
    }
    };
    }).start();

    Thread.sleep(1000); //主线程停止1秒,保证两个线程执行完成
    System.out.println(value); //20万
    }
  4. 只要使用的是同一把锁,哪种写法都可以,static方法要用第一种写法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public static int value = 0;

    public static void main(String[] args) throws InterruptedException {
    Object o = new Object();

    new Thread(() -> {
    for(int j = 0; j < 100000; j++) {
    synchronized(Main.class) { //第一种写法,以类作为锁
    value++;
    }
    synchronized(o) { //第二种写法,以对象作为锁
    value++;
    }
    add(); //第三种写法,以成员方法作为锁
    }
    }).start();
    }

    private synchronized void add() {
    value++;
    }

死锁

  1. 死锁指的是两个线程互相持有锁但不释放,又想要对方的锁导致卡住
    死锁图
  2. 先让项目跑起来,再去终端输入指令才能看到项目进程
    • jstack:检测死锁
    • jps:查询java进程号
    • jconsole:监视面板
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      public static void main(String[] args) {
      Object o1 = new Object();
      Object o2 = new Object();
      Thread t1 = new Thread(() -> {
      synchronized (o1){
      try {
      Thread.sleep(1000);
      synchronized (o2){
      System.out.println("线程1");
      }
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      });
      Thread t2 = new Thread(() -> {
      synchronized (o2){
      try {
      Thread.sleep(1000);
      synchronized (o1){
      System.out.println("线程2");
      }
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      });
      t1.start();
      t2.start();
      }

wait和notify

  1. wait()暂停线程进入等待状态,同时释放锁;notify()随机唤醒一个等待状态的线程,直到t2线程执行完毕才会释放锁;notifyAll()全部唤醒
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public static void main(String[] args) throws InterruptedException {
    Object o1 = new Object();
    Thread t1 = new Thread(() -> {
    synchronized (o1){
    try {
    System.out.println("开始等待");
    o1.wait(); //进入等待状态并释放锁
    System.out.println("等待结束!");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    });
    Thread t2 = new Thread(() -> {
    synchronized (o1){
    System.out.println("开始唤醒!");
    o1.notify(); //唤醒处于等待状态的线程
    for (int i = 0; i < 50; i++) {
    System.out.println(i);
    }
    //唤醒后依然需要等待这里的锁释放之前等待的线程才能继续
    }
    });
    t1.start();
    Thread.sleep(1000);
    t2.start();
    }

    wait方法设置等待时间。比如:o1.wait(1000); //1秒后自动唤醒

ThreadLocal

  1. 可以单独给线程创建变量,仅供自己使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public static void main(String[] args) {
    ThreadLocal<String> local = new ThreadLocal<>();
    local.set("幽蓝");
    System.out.println(local.get()); //幽蓝

    new Thread(()-> {
    local.set("椰奶");
    System.out.println(local.get()); //椰奶
    }).start();

    new Thread(()-> {
    System.out.println(local.get()); //null
    }).start();
    }
  2. 如果想使用其他线程的变量,可以通过继承实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ThreadLocal<String> local = new InheritableThreadLocal<>();
    local.set("幽蓝");

    new Thread(()-> {
    System.out.println(local.get()); //幽蓝
    local.set("椰奶");
    System.out.println(local.get()); //椰奶
    }).start();

    System.out.println(local.get()); //幽蓝

定时器

  1. 没啥想说的(´-ω-`)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void main(String[] args) throws InterruptedException {
    Timer timer = new Timer(); //创建定时器对象
    System.out.println(new Date()); //获取当前时间
    timer.schedule(new TimerTask() { //注意这个是一个抽象类,不是接口,无法使用lambda表达式简化,只能使用匿名内部类
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()); //打印当前线程名称
    }
    }, 3000, 1000); //执行延时任务,第一个参数为第一次执行的时间,第二个参数为循环执行的时间
    Thread.sleep(5000);
    timer.cancel(); //5秒后终止
    }

守护进程

  1. 主线程结束,守护线程跟着结束
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Thread t = new Thread(() -> {
    Thread it = new Thread(() -> {
    while (true){
    try {
    System.out.println("程序正常运行中...");
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    });
    it.start();
    });
    t.setDaemon(true); //设置为守护线程(必须在开始之前,中途是不允许转换的)
    t.start();
    for (int i = 0; i < 5; i++) {
    Thread.sleep(1000);
    }

反射

  1. 我们可以通过反射机制,获取到类的一些属性,包括类里面有哪些字段,有哪些方法,继承自哪个类,甚至还能获取到泛型;默认情况下,每个类都有一个Class对象并存放在JVM中
  2. 获取与判断
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Class<Student> clazz = Student.class;       //创建反射,这里的Student类
    System.out.println(clazz.getSuperclass()); //获取到父类的Class对象

    String str = "";
    System.out.println(str instanceof String); //正常情况用instanceof进行类型比较
    System.out.println(str.getClass()); //class java.lang.String
    System.out.println(str.getClass() == String.class); //直接判断是否为这个类型
    str.getClass().asSubclass(String.class); //判断是否为接口、类的实现、子类

    //遍历ArrayList实现了哪些接口
    Class<?>[] clazz2 = ArrayList.class.getInterfaces();
    for(Class<?> aClass: clazz2) System.out.println(aClass);
  3. 反射创建类对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public class Student {
    private String name;
    private int age;

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

    public void sayInfo() {
    System.out.println(name + " " + age);
    }
    }

    public static void main(String[] args) throws ReflectiveOperationException {
    Class<Student> clazz = Student.class;
    Class<Student> clazz2 = Student.class;

    //getConstructor()只能获取public权限的构造方法
    Constructor<Student> constructor = clazz.getConstructor(String.class);
    Student student = constructor.newInstance("幽蓝");

    //getDeclaredConstructor()可以获取private权限的构造方法
    Constructor<Student> constructor2 = clazz2.getDeclaredConstructor(String.class, int.class);
    constructor2.setAccessible(true); //修改访问权限,可以无视权限修饰符访问类的内容
    Student student2 = constructor2.newInstance("幽蓝", 9);

    student.sayInfo();
    student2.sayInfo();
    }
  4. 反射类方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class Student {
    private String name;
    private int age;

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

    private void sayInfo() { //设置成私有
    System.out.println(name + " " + age);
    }
    }

    public static void main(String[] args) throws ReflectiveOperationException {
    Class<Student> clazz = Student.class;
    //Method test = clazz.getMethod("sayInfo");
    Method test = clazz.getDeclaredMethod("sayInfo"); //私有方法照样调用
    test.setAccessible(true);
    test.invoke(new Student("幽蓝", 9)); //调用已经获取到的方法
    }
  5. 反射修改类属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Student {
    public String name; //设置公有
    private int age;

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

    public static void main(String[] args) throws ReflectiveOperationException {
    Class<?> clazz = Class.forName("com.test.Student"); //通过forName找到这个类并创建新对象
    Field name = clazz.getField("name");
    Field age = clazz.getDeclaredField("age");
    age.setAccessible(true);
    System.out.println(name.get(new Student("幽蓝", 9))); //幽蓝
    }
  6. 反射静态属性与方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Student {
    public static String food = "红烧肉";
    public static void sayFood() {
    System.out.println("我想吃" + food);
    }
    }

    public static void main(String[] args) throws ReflectiveOperationException {
    Field food = Student.class.getDeclaredField("food");
    Method sayFood = Student.class.getDeclaredMethod("sayFood");
    System.out.println(food.get(null)); //红烧肉
    sayFood.invoke(Student.class); //我想吃红烧肉
    }

注解

  1. 元注解是作用于注解上的注解,用于我们编写自定义的注解
    • @Retention:标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
    • @Documented:标记这些注解是否包含在用户文档中
    • @Target:标记这个注解应该是哪种 Java 成员
    • @Inherited:标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
    • @Repeatable:Java 8 开始支持,标识某注解可以在同一个声明上使用多次
  2. 看看@Override是如何定义;@Target限定为只能作用于方法上,ElementType是一个枚举类型,用于表示此枚举的作用域,一个注解可以有很多个作用域。@Retention表示此注解的保留策略,包括三种策略,在上述中有写到,而这里定义为只在代码中。一般情况下,自定义的注解需要定义1个@Retention和1-n个@Target
    1
    2
    3
    4
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }
  3. 自定义注解;运行时通过反射调用注解
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Target({ElementType.TYPE, ElementType.METHOD}) //可以打在类、方法名上面
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Test {
    String value() default "幽蓝";
    }

    @Retention(RetentionPolicy.RUNTIME)
    @interface TestArray {
    String[] value();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static void main(String[] args) throws NoSuchMethodException {
    Class<Main> mainClass = Main.class;
    Method test = mainClass.getMethod("test");
    Test annotation = test.getAnnotation(Test.class);
    System.out.println(annotation.value()); //椰奶
    }

    //@Test(value = "椰奶")
    @Test("椰奶") //value可以简写,如果是其他则需要全写
    public static void test() {}

    @TestArray({"AAA", "BBB"})
    public static void test2() {}
  4. spring开始会用到注解开发

Lambda表达式

  1. Lambda表达式是个糖语法,例子懒得写了,Ctrl+F搜索一下就能看到

Optional类

  1. 用来判断字符串不为null,使用了stream流写法,极大地提升扩展功能
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static void main(String[] args) {
    String hi = "HELLO,幽蓝";
    Main.hello(hi);
    }

    public static void hello(String str) {
    if (str != null) System.out.println(str.toLowerCase()); //正常写法

    //Optional可以判断字符不为空,等价于上面写法,优点在于扩展功能
    Optional
    .ofNullable(str)
    .ifPresent(s -> System.out.println(str.toLowerCase()));
    }

Java9

模块

  1. 模块能优化包大小,减少JVM加载的类,优化内存和运行速度
    • exports:导出
    • requires:引入模块
    • import:导入
  2. 通过maven创建项目,试着体验看看
    模块导入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    //module-b > pom.xml文件
    //添加以下代码,表示模块b依赖于模块a
    <dependencies>
    <dependency>
    <groupId>org.example</groupId>
    <artifactId>module-a</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    </dependencies>

    //导出
    module module.a {
    //exports com.test; //导出com.test目录下的全部文件
    exports com.test to module.b; //导出com.test目录指定给模块b
    requires transitive java.logging; //可以传递给模块b
    }

    //引入模块a
    module module.b {
    requires module.a;
    }

    //User类
    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+"岁)";
    }
    }
  3. 导入lombok插件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //项目 > pom.xml文件
    //添加lombok插件
    <dependencies>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.26</version>
    </dependency>
    </dependencies>

    //等maven加载好了,就能导入了
    module module.b {
    requires lombok;
    requires java.logging;
    }

    @Log
    public class Main {
    public static void main(String[] args) {
    log.info("你好,椰奶👋");
    }
    }

jshell

  1. 配置完成后,终端或cmd打开控制台输入jshell
    • help:查看命令列表
    • /vars:定义的变量列表
    • /exit:退出jshell

接口中的私有方法

  1. 接口可以存在私有方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public interface Test {
    default void test(){
    System.out.println("我是test方法默认实现");
    this.inner(); //接口中方法的默认实现可以直接调用接口中的私有方法
    }

    private void inner(){ //声明一个私有方法
    System.out.println("我是接口中的私有方法!");
    }
    }

集合类的工厂方法

  1. Map.of用来解决一行一行添加问题,默认倒序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    List<String> list = List.of("BBB", "CCC", "AAA");
    Set<String> set = Set.of("AAA", "CCC", "BBB");
    Map<Integer, String> map = Map.of(
    2, "幽蓝",
    1, "椰奶",
    3, "帕帕子"
    );

    System.out.println(list); //[BBB, AAA, CCC]
    System.out.println(set); //[AAA, CCC, BBB]
    System.out.println(map); //{2=幽蓝, 3=帕帕子, 1=椰奶}
    map.forEach((k, v) -> System.out.println(k + " = " + v));

Stream API

  1. iterate新增允许结束迭代的;takeWhile不满足条件时进行截断,不再继续操作后面的元素;dropWhile反过来满足条件时进行截断
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Stream
    .ofNullable(null) //使用新增的ofNullable方法,这样就不会了,不过这样的话流里面就没东西了
    .forEach(System.out::println);

    Stream
    //不知道怎么写?参考一下:for (int i = 0;i < 20;i++)
    .iterate(0, i -> i < 20, i -> i + 1) //快速生成一组0~19的int数据,中间可以添加一个断言,表示什么时候结束生成
    .takeWhile(i -> i < 10) //当i小于10时正常通过,一旦大于等于10直接截断
    //.dropWhile(i -> i < 10) //和上面相反,上来就是截断状态,只有当满足条件时再开始通过
    .forEach(System.out::println);

其他小型变动

  1. Try-with-resource语法现在不需要再完整的声明一个变量了,我们可以直接将现有的变量丢进去
    1
    2
    3
    4
    5
    6
    7
    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());
    }
    }
  2. 改进Optional
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    String str = null;
    Optional.ofNullable(str).ifPresentOrElse(s -> { //通过使用ifPresentOrElse,我们同时处理两种情况
    System.out.println("被包装的元素为:"+s); //第一种情况和ifPresent是一样的
    }, () -> {
    System.out.println("被包装的元素为null"); //第二种情况是如果为null的情况
    });

    //效果同上,看起来会更简洁一点
    Optional.ofNullable(str)
    .or(() -> Optional.of("如果为空则打印我,不为空则返回自身"))
    .ifPresent(System.out::println);

Java10

var关键字

  1. var关键字进行自动类型推断,最终编译成具体类型;仅适用于局部变量,比如无法在成员变量中使用
    1
    2
    var a = "幽蓝";
    System.out.println(a);

Java11

  1. Lambda形参可以用var关键字了
    1
    2
    3
    4
    Consumer<String> consumer = (var s) -> {
    System.out.println(s);
    };
    consumer.accept("AAA");

String增强

  1. 判断是否字符串为空或者是仅包含空格
    1
    2
    3
    4
    5
    6
    7
    String s1 = "幽蓝";
    String s2 = " ";
    String s3 = null;

    System.out.println(s1.isBlank()); //false
    System.out.println(s2.isBlank()); //true
    //System.out.println(s3.isBlank()); //报错
  2. 字符串重复拼接
    1
    2
    String str = "幽蓝";
    System.out.println(str.repeat(2)); //幽蓝幽蓝
  3. 去除空格
    1
    2
    3
    4
    String str = " A B C D ";
    System.out.println(str.strip()); //去除首尾空格
    System.out.println(str.stripLeading()); //去除首部空格
    System.out.println(str.stripTrailing()); //去除尾部空格

HttpClient

  1. 新的API支持最新的HTTP2和WebSocket协议
    1
    2
    3
    4
    5
    6
    7
    8
    9
    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());
    }

Java12-16

switch表达式

  1. switch表达式,我喜欢这种写法😍
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * 传入分数(范围 0 - 100)返回对应的等级:
    * 100-90:优秀
    * 70-80:良好
    * 60-70:及格
    * 0-60:寄
    * @param score 分数
    * @return 等级
    */

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

文本块

  1. 可以编写更复杂的字符串了,不需要你去写转义字符
    1
    2
    3
    4
    5
    6
    7
    String str =
    """
    ""这里写双引号没事
    ''这里写单引号没事
    只要不是三个双引号就行
    """;
    System.out.println(str);

新的instanceof语法

  1. 简化强制转换类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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); //最后比对属性是否一样
    //}

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

空指针异常的改进

  1. Java14或更高版本运行时,会告诉你是哪一个变量调用出现了空指针
    1
    2
    3
    4
    5
    6
    7
    8
    public static void main(String[] args) {
    test("111", null); //报错会告诉你b为空,Cannot invoke "String.length()" because "b" is null
    }

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

记录类型

  1. Java14之前,开发者都是用lombok插件,自动生成getter和setter、构造方法、toString()方法;一来不用自己手写,二来代码清爽很多
    1
    2
    3
    4
    5
    @Data
    public class Account { //使用Lombok,一个注解就搞定了
    String username;
    String password;
    }
  2. Java14开始出现记录类型record,本质上是个普通类,final类型且继承自java.lang.Record的抽象类,它会在编译时自动编译出 public get hashcode 、equals、toString等方法
    1
    2
    3
    4
    5
    6
    7
    8
    public record Account(String username, String password) {
    }

    public static void main(String[] args) {
    Account account = new Account("ulan", "1234567");
    System.out.println(account.username()); //ulan
    System.out.println(account); //Account[username=ulan, password=1234567]
    }
  3. 反编译后结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public record Account(String username, String password) {
    public Account(String username, String password) {
    this.username = username;
    this.password = password;
    }

    public String username() {
    return this.username;
    }

    public String password() {
    return this.password;
    }
    }

Java17

密封类型

  1. 密封类型有以下要求:
    • 可以基于普通类、抽象类、接口,也可以是继承自其他接抽象类的子类或是实现其他接口的类等
    • 必须有子类继承,且不能是匿名内部类或是lambda的形式
    • sealed写在原来final的位置,但是不能和final、non-sealed关键字同时出现,只能选择其一
    • 继承的子类必须显式标记为final、sealed或是non-sealed类型
  2. 标准格式
    1
    2
    3
    public sealed [abstract] [class/interface] 类名 [extends 父类] [implements 接口, ...] permits [子类, ...]{
    //里面的该咋写咋写
    }
  3. 子格式
    1
    2
    3
    4
    5
    public [final/sealed/non-sealed] class 子类 extends 父类 {   //必须继承自父类
    //final类型:任何类不能再继承当前类,到此为止,已经封死了。
    //sealed类型:同父类,需要指定由哪些类继承。
    //non-sealed类型:重新开放为普通类,任何类都可以继承。
    }
  4. 通过反射来获取类是否为密封类型
    1
    2
    3
    4
    public static void main(String[] args) {
    Class<A> a = A.class;
    System.out.println(a.isSealed()); //是否为密封
    }