侧边栏壁纸
博主头像
不做科研废物🌸

行动起来,活在当下

  • 累计撰写 16 篇文章
  • 累计创建 8 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Java学习笔记

AlaskaGulf
2025-02-20 / 0 评论 / 0 点赞 / 27 阅读 / 0 字

Java基础

常用命令行窗口命令:D: dir cd cls

开发Java程序: .java源代码->通过javac编译->.class字节码文件->使用java运行

文件名称必须与类名称一致

JDK组成:JVM虚拟机(Java虚拟机、真正运行Java程序的地方)和核心类库组成JRE(JAVA的运行环境)、Java、Javac...

image-20250217180844164

IDEA管理Java程序的结构:project,module,package,class,便于管理项目代码

Java注释

 //单行注释
 ​
 /*  
 ​
 多行注释
 ​
 */
 ​
 /** 
 ​
 文档注释 
 ​
 */

字面量

Java支持整数、小数、字符(\n换行 \t代表一个tab、空字符)、字符串、布尔值、

Java支持二进制、八进制、十六进制的数据,具体用0b(0B)、0、0x(0X)表示

基本数据类型

image-20240916181110860

引用数据类型:String 字符串类型(注意开头大写),变量存储的是对象的地址

整数字面量默认为int类型,如果希望整形字面量默认为long类型,需要在后面加l/L

小数字面量默认是double,如果需要float后面需要加f/F

在表达式中,最终结果是由表达式中的最高类型决定的,byte short char是直接转化成int类型参与运算的

如何区分字面量(Literal)和数据类型(Data Type)?

数据类型是变量或表达式存储数据的“分类没包括基本数据类型(int char float double boolen byte short long)和引用数据类型(String 数组 接口等)

字面量是直接在代码中编写的“固定值”,表示具体的数值、字符或字符串。(如42 3.14 'A' "Hello" true 100L 5.0f

原码 补码 反码

补码取反再加1后可以得到原码

正数的原码、反码、补码均相同 ,补码的此特性使得计算机只需一套加法电路即可处理正负数运算。

以+5为例:

原码:0000 0101

反码:0000 0101

补码:0000 0101

以-5为例:

原码:1000 0101最高位1表示负号

反码:1111 1010符号位不变,数值位取反

补码:1111 1011(反码+1)

对补码按位取反(包括符号位):000 0100

再加1:000 0101

恢复符号位:1000 0101(最高位改为1,即原码)

类型转换

类型范围小的变量,可以直接赋值给范围类型大的变量

 byte a = 12;
 ​
 int b = a;  

在表达式中,小范围类型的变量,会自动转换成表达式中较大范围的类型,在参与运算。

表达式的最终结果类型由表达式中的最高类型决定,在表达式中,byte short char 是直接转换为int 类型参与运算的。(防止数值溢出)

 byte i = 10;
 short j = 30;
 int rs = i + j;  //最高类型为int,而不是short

范围类型大的数据或者变量,直接赋值给类型范围小的变量会报错。

 int a = 20;
 byte b = a; //报错
 byte b = (byte)a;//强制类型转换
 ​
 double d = 99.5;
 int m = (int)d; //m = 99,丢掉小数部分,保留整数部分

运算符

基本运算符: + - * / %

在Java中,两个整数相除的结果还是整数(截断)在工程中如果需要小数可以用1.0 * i / j

 int a = 5;
 int b = 2;
 System.out.println(5 / 2); //结果为2
 System.out.println(5.0 / 2); //结果为2.5
 System.out.println(1.0*a / b); //结果为2.5

+符号可以做连接符,其结果依然是一个字符串

 "abc" + 5 --->"abc5"
 int a = 5;
 System.out.println("abc" + a); //abc5
 System.out.println(a + 5); //10
 System.out.println("ilovejava" + a + 'a'); //ilovejava5a
 System.out.println(a + 'a' + "ilovejava"); //102ilovejava

自增自减运算符: ++ --

 int a = 10;
 a++; // a= a + 1 
 //++a;
 System.out.println(a); //a = 11
 a--; // a = a - 1
 //--a;
 System.out.println(a); //a = 10

与C语言同理,++a先运算后输出,a++先输出后运算。只能操作变量,不能操作字面量

 //int a = 10++; //报错,不能操作字面量
 ​
 int i = 10;
 int rs = ++i; // 先加后用
 System.out.println(i); // i = 11
 System.out.println(rs);// rs = 11
 ​
 int j = 10;
 int rs2 = j++; // 先用后加
 System.out.println(j); // j = 11
 System.out.println(rs2);// rs2 = 10
 ​
 int c = 10;
 int d = 5;
 int rs3 = c++ + ++c - --d - ++d + 1 + c--;
 System.out.println(rs3); // rs3 = 10 + 12 - 4 - 5 + 1 + 12 = 26
 System.out.println(c); // c = 11
 System.out.println(d); // d = 5

扩展赋值运算符: += -= *= /= %=

image-20250218112906112

 byte x = 10;
 ​
 byte y = 30;
 ​
 x = x + y;//错误的
 ​
 x = (byte)(x + y);//正确的
 ​
 x += y;//等价于(byte)(x + y),正确的

关系运算符

image-20250218120330901

逻辑运算符

&逻辑与、|逻辑或、!逻辑非、^逻辑异或,右边都要执行

&&短路与、||短路或,如果左边可以判定结果,则右边不执行(判断结果与&|相同,只是过程不同。效率更高,在实际开发中用的更多)

 int i = 10;
 int j = 20;
 system.out.println(i > 100 && ++j > 99); //i > 100不成立,右边不参与计算
 system.out.println(j); // j = 20

image-20240916212005571

三元运算符:条件表达式 ? 值1 : 值2;

首先计算关系表达式的值,如果值为true,返回值1,如果为false,返回值2.

 // 找出两个整数中的最大值,并输出
 int a = 99;
 int b = 67;
 int max = a > b ? a : b;
 System.out.println(max);
 // 找出三个整数中的最大值,并输出
 int i = 10;
 int j = 45;
 int k = 34;
 int max = i > j && i > k ? i : j > k ? j : k;
 System.out.println(max);

运算符优先级

 System.out.println(10 > 3 || 10 > 3 && 10 < 3); // &&优先级大于||  10 > 3 && 10 < 3 为false  10 > 3 || false为True
 System.out.println((10 > 3 || 10 > 3) && 10 < 3); // (10 > 3 || 10 > 3)为True,True && 10 < 3为false

流程控制

分支结构

if分支

image-20250218153055983

switch分支(性能较好)

image-20250218160108578

表达式类型不支持double float long boolean

case给出的值不允许重复,且只能是字面量,不能是变量。

加入break语句防止出现穿透现象。

穿透性有时也可以简化代码,存在多个case分支的代码一样时,可以把代码写到一个case块,其他case块通过穿透性能,穿透到该case块即可。

循环结构

for循环

 for (int i = 0; i < 3; i++){
     System.out.println("Hello World");
 }

image-20250218164804235

while循环

image-20250218174637066

 int i = 0;
 while(i < 5){
     System.out.println("Hello World");
     i++;
 }

do-while循环

image-20250218204754281

 int i = 0;
 do{
     System.out.println("Hello World");
     i++;
 }while(i < 3)

跳转关键字

break :跳出并结束当前所在循环

continue :跳出当前所在循环,直接进入下一次循环

数组

int[] arr = {1,2,3};

String[] names = {"张三",“李四”};

数组属于引用数据类型,存储的是数组在内存中的地址信息。

静态初始化数组

定义数组的时候直接给数组赋值

 //完整格式
 int[] ages = new int[]{1, 2, 3};
 double[] scores = new double[]{99.5, 98.5, 97.5};
 ​
 //简化格式
 int[] ages = {1,2,3};
 double[] scores = {99.5, 98.5, 97.5};
 ​
 //数据类型[] 数组名也可以写成数据类型 数组名[]
 int ages[] = {1,2,3};       //写法同C语言
 double scores[] = {99.5, 98.5, 97.5};

动态初始化数组

定义数组时先不存入具体的元素值,只确定数组存储的数据类型和数组的长度。

int[] arr = new int[3] 元素值默认为都为0

int[] arr = new int[3]{30, 40, 50}这种写法是错误的,两种方法不能混用

image-20250219100906726

数组的访问

数组名[索引]

 System.out.println(arr[0]);// 取值
 ​
 arr[2] = 100;   //赋值
 System.out.println(arr[2]);// 100
 ​
 System.out.println(arr.length); //获取数组长度
 //数组的遍历
 int[] ages = {12, 24, 36};
 for(int i= 0; i < ages.length; i++){        //ages.length = 3
     System.out.println(ages[i]);
 } 

数组的内存执行原理

在Java中,程序实在JVM虚拟机中执行的,JVM为了便于执行程序,将内存区域成五个区域:方法区、栈、堆、本地方法栈、程序计数器

方法区:存放编译后的class字节码文件。

栈内存:方法运行时所进入的内存,变量(基本数据类型)存放在这里。

堆内存:new出来的东西会在这块内存中开辟空间并产生地址数组(引用数据类型)就存放在这里。

image-20250219103940663

在上面代码中,a是变量,直接放在栈内存中,a变量里存储的数据就是10这个值。

int[] arr = {11, 22, 33};是创建一个数组对象arr,会在堆内存中开辟区域存储三个整数。

arr是变量,在栈内存中,arr中存储的是数组对象在堆内存中的地址值

如果某个数组变量存储的地址是null,那么该数组变量将不再指向任何对象。

 int[] arr1 = null;
 System.out.println(arr1); // null
 System.out.println(arr1[1]); // 空指针异常
 System.out.println(arr1.length);// 空指针异常

浅拷贝

当多个变量指向同一个数组对象时

 int[] arr1 = [11, 22, 33];
 int[] arr2 = arr1; // 将int类型的数组变量arr1赋值给int类型的数组变量arr2
 ​
 System.out.println(arr1); // [I@4c873330
 System.out.println(arr2); // [I@4c873330 说明指向了同一地址
 ​
 arr2[1] = 99; // 改变arr2数组变量中的第二个元素
 System.out.println(arr1[1]); // arr1[1] = 99; 由于arr2和arr1指向同一地址,因此改变arr2数组变量中的元素,就相当于改变了arr1数组变量里的元素。

image-20250219105603175

方法

方法是一种语法结构,可以把一段代码封装成一个功能,以便重复调用。

image-20250219130004917

 public static void main(String[] args) {
     int rs = sum(10,20); //调用求和方法
     System.out.println(rs);
 }
 public static int sum(int a,int b){
     return a + b;
 } //求和方法

方法在类中的位置没有要求,但一个方法不能定义在另一个方法里面。

方法的返回值类型为void(无返回申明)时,方法内就不能用使用return返回数据,如果方法的返回类型写了具体类型,则方法内部必须使用return返回对应的数据类型。

调用有参数类型的方法时,必须严格匹配方法的参数情况。

方法类型

无参数、无返回值

 //打印三行Hello World
 public static void printHelloWorld(){
     for(int i = 1; i <= 3; i++){
         Systemp.out.println("Hello World");
     }
 }

有参数、无返回值

 //打印多行Hello World
 public static void printHelloWorld2(int num){  //形参不能定义初始值
     for(int i = 1; i <= num; i++){
         Systemp.out.println("Hello World");
     }
 }

无参数、有返回值

有参数、无返回值

方法的内存执行原理

方法被调用的时候,是进入到栈内存中运行。先进后出,可以保证一个方法调用完另一个方法后可以回来。

image-20250219164300054

image-20250219164415899

image-20250219164433824

image-20250219164451312

参数传递机制

值传递:在传输实参给方法的形参时,传输的是实参变量中存储的值的副本

基本类型的参数传递

 public static void main(String[] args){
     int a = 10;
     change(a);
     System.out.println(a); // 此时a仍然为10
 }
 public static void change(int a){
     System.out.println(a); // a = 10
     a = 20;
     System.out.println(a); // a = 20
 }

引用类型的参数传递

 public static void main(String[] args){
     int[] arrs = new int[]{10, 20, 30};
     change(arrs);
     System.out.println(arrs[1]); // 此时传递的是数组的地址值的副本,通过地址访问,改变数组元素,arr[1] = 222
 }
 public static void change(int[] arrs){ 
     System.out.println(arrs[1]); // arr[1] = 20
     arrs[1] = 222;
     System.out.println(arrs[1]); // arr[1] = 222
 }

image-20250219170651593

image-20250219170715840

方法重载

一个类中,出现多个方法的名称相同,但是形参列表(个数、类型、顺序)不同,那么这些方法成为方法重载。

 public static void main(String[] args) {
     test(); // ===test1===
     test(1);// ===test2===1
 }
 public static void test(){
     System.out.println("===test1===");
 }
 public static void test(int a){
     System.out.println("===test2===" + a);
 }
 int test(int a){     // 形参列表冲突,与前面是否有修饰符无关
     return a;
 }
 ​
 void test(int a, double b){    
 }
 void test(int b, double a){ //形参列表冲突,与形参名称无关
 }

return关键字使用技巧

return;可以用在无返回值的方法中,作用是立即跳出并结束当前方法的执行

 public static void main(String[] args){
     chu(10, 0);
 }
 public static void chu(int a , int b){
     if(b == 0){
         System.err.println("除数不能为0!!!");
         return;     // 直接跳出并结束当前chu方法的执行
     }
     int c = a / b;
     System.out.println("结果为" + c);
 }

面向对象基础

 public class Student {
     String name;
     double chinese;
     double math;
 ​
     public void printTotalScore(){
         System.out.println(name + "的总成绩是:" + (chinese + math));
     }
     public void printAverageScore(){
         System.out.println(name + "的总成绩是:" + (chinese + math) / 2.0);
     }
 }
 public class Test {
     public static void main(String[] args) {
         // 创建一个学生对象,封装数据
         Student s1 = new Student();
         s1.name = "学生1";
         s1.chinese = 100;
         s1.math = 90;
         s1.printAverageScore();  // 学生1的平均成绩是:95.0
         s1.printTotalScore();    // 学生1的总成绩是:190.0
 ​
         Student s2 = new Student();
         s2.name = "学生2";
         s2.chinese = 100;
         s2.math = 100;
         s2.printAverageScore();  // 学生2的平均成绩是:100.0
         s2.printTotalScore();    // 学生2的总成绩是:200.0
     }
 }

面向对象变成有什么好处?

万物皆对象,更符合人类思维习惯,变成更简单、更直观。

对象本质上是一种特殊的数据结构,里面包含各种变量(对象的属性)和方法(对象的行为)。

public class 类名{
    //1、变量,用来说明对象可以处理什么数据
    //2、方法,描述对象有什么功能,也就是可以对数据进行什么样的处理
    //……
}

//类名 对象名 = new 类名();

对象的内存执行原理

image-20250220154057196

Student s1 = new Student(); //创建学生对象

学生类放在方法区,s1变量放在栈内存中,new出来的对象会放在堆内存中。堆内存中的对象有自己的地址,并且还有一个类的地址信息,指向方法区中的学生类。

System.out.println(s1); // ren.imuzi0221.object.Student@4f3f6b24
System.out.println(s2); // ren.imuzi0221.object.Student@15aeb7ab

注意事项

类名建议用英文单词,首字母大写,满足驼峰模式,且要有意义。如:StudentCar

成员变量本身存在默认值,所以在定义成员变量时不需要赋初始值。

Student s = new Student();
System.out.println(s.name); // null
System.out.println(s.chinese); // 0.0
System.out.println(s.math); // 0.0

一个代码文件中,可以写多个class类,但只能有一个public修饰,且public修饰的类名必须成为代码文件名。

对象与对象之间的数据不会相互影响,但多个变量指向同一个对象会相互影响。

Student s1 = new Student();
s1.name = "张三"; 

Student s2 = new Student();
s2.name = "李四";  
System.out.println(s1.name);// s1的name还是张三
Student s1 = new Student();
s1.name = "张三"; 

Student s2 = s1;
s2.name = "李四"; 
System.out.println(s1.name);// 此时s1的name也变成了李四

image-20250220161556242

this关键字

this就是一个变量,可以用在方法中,来拿到当前对象。主要用来解决变量名称冲突问题

image-20250220183203113

public class Student{
    double score;
    public void printThis(){
        System.out.println(this);
    }
    public void printPass(double score){
        if(this.score > score){ // 使用this.score来指代Student类中的score属性,防止变量名称冲突
            
        }
    }
}

构造器

用于创建对象时对对象属性的初始化赋值

public class Student{
    // 无参数构造器
    public Student(){
        ...
    }
    // 有参数构造器
    public Student(String name, double score){
        ...
    }
}

Student s = new Student();  // 执行无参数构造器
Student s = new Student("张三",100);  // 执行有参数构造器

类在设计时,如果不写构造器,Java会为类自动生成一个无参构造器。一旦定义了有参数构造器,Java就不会自动生成无参构造器了。

封装

用类设计对象处理某一个事物的数据时,应该把要处理的数据,和处理这些数据的方法,设计到一个对象中去。

设计规范:合理隐藏、合理暴露

通过关键字:publicprivate实现

public static void main(String[] args){
    Student s1 = new Student();
    s1.score = -99; // 无法访问,不能赋值
    s1.setScore(-99); // 通过共有方法,可以赋值,但是方法内部有参数校验
    System.out.println(s1.getScore()); // 通过共有方法获取值
}

public class Student{
    //public double score;  // 共有成员变量,此时可以通过外界访问
    private double score;   // 私有成员变量,外界无法访问
    // 此时如果业务对score变量有需求,此时可以通过共有方法的方式,对变量进行赋值
    public void setScore(double socre){
        if(score >= 0 && score <=100){
            this.score = score; 
        }else{
            System.out.println("数据非法!");
        }
    }
    public void getScore(double socre){
        return score;
    }
    public void printPass(){
        System.out.println(score >= 60 ? "成绩及格" : "成绩不及格"); 
    }
}

实体JavaBean(实体类)

类中的成员变量都是私有,并且要有对外提供相应的getXxxsetXxx方法

类中必须要有一个公共的无参构造器

用来保存某个事物的数据,除此之外没有其他处理数据的方法

出现数据和数据业务处理分离(实际开发)

// 实体类
public class Student {
    private String name;   // 成员变量私有
    private double score;

    public Student() {     // 公共的无参构造器
    }

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

    // 相应的getXxx、setXxx方法
    public String getName() {
        return name;
    }

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

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}
// 操作类
public class StudentOperator {
    private Student student;
    public StudentOperator(Student student){
        this.student = student;
    }

    public void printPass(){
        if(student.getScore() >= 60){
            System.out.println(student.getName() + "学生成绩及格");
        }else {
            System.out.println(student.getName() + "学生成绩不及格");
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Student s1 = new Student();
        s1.setName("张三");
        s1.setScore(99);
        System.out.println(s1.getName());
        System.out.println(s1.getScore());

        StudentOperator operator = new StudentOperator(s1);  //创建操作类对象operator
        operator.printPass(); // 调用操作方法
    }
}

成员变量和局部变量的区别

image-20250220204355697

常用API(一)

API(Application Programming Interface:应用程序编程接口)

包是分门别类管理各种不同的程序,类似于文件夹,建包有利于程序的管理和维护。

在自己程序中调用其他包下的程序的注意事项:

同一个包下的类,互相可以直接调用

如果当前程序中,要调用其他包下的程序,则必须在当前程序中导包,才可以访问。导包格式:import 包名.类名;

如果当前程序中,要调用Java提供的程序,也需要先导包才可以使用(java.lang包下的程序不需要导包,可以直接使用)。

如果当前程序中,访问多个其他包下的程序,且这些程序名一样的情况下,默认只能导入一个程序,另一个程序必须带报名和类名来访问。

例如此时ren.imuzi0221.pkg包下有pkg1pkg2两个包,pkg1下新建一个Demo类,pkg2下新建个Demo类。

// pkg1包
package ren.imuzi0221.pkg.pkg1;

public class Demo {
    public void print(){
        System.out.println("pkg1");
    }
}
// pkg2包
package ren.imuzi0221.pkg.pkg2;

public class Demo {
    public void print(){
        System.out.println("pkg2");
    }
}
// 主程序
import ren.imuzi021.pkg.pkg2.Demo;

public calss Test{
    public static void main(String[] args) {
        Demo d1 = new Demo(); // 在创建对象时,可以选择两个包中的其中一个进行导入,可以选择此时导入的是pkg2中的方法
        d1.print();  // 此时会打印pkg2,此时说明调用的确实是pkg2下的程序
        ren.imuzi0221.pkg.pkg1.Demo d2 = new ren.imuzi0221.pkg.pkg1.Demo(); // 如果要调用pkg1下的方法,此时必须要写上全包名ren.imuzi0221.pkg.pkg1.Demo
        d2.print();  // 此时会打印pkg1,说明调用的是pkg1下的程序
    }
}

String类

String概述

java.lang.String代表字符串,用来封装字符串数据和处理字符串的方法。

创建字符串对象,封装字符串数据,调string的方法

1.直接通过双引号得到字符串对象,封装字符串数据

String name = "iloveJava";
System.out.println(name);  // 此时name指向的是对象的地址,但是Java内部进行了处理,可以打印字符串内容

2.通过new String类创建字符串对象,并调用构造器初始化字符串

String rs1 = new String();
System.out.println(rs1); // 输出空字符 ""

String rs2 = new String("iloveJava");
System.out.println(rs2); // 输出iloveJava

char[] chars = {'study', "Java"};
String rs3 = new String(chars);
System.out.println(rs3); // 输出studyJava

byte[] bytes = {97, 98 ,99};
String rs4 = new String(bytes);
System.out.println(rs4); // 输出abc

String的常用方法

String使用时的注意事项

String对象的内容不可改变,被称为不可变字符串对象

public static void main(String[] args {
    String name = "我";
    name += "爱";
    name += "Java";
    System.out.println(name);  // 我爱Java
}

这里就有问题了,name指向的字符串对象确实变化了,

image-20250221120528501

事实上,每次试图改变字符串对象,实际上是产生了新的字符串对象,变量每次都是指向了新的字符串对象,所以之前字符串对象的内容确实没有改变。

只要是以双引号"…"方式写出的字符串对象,会存储到字符串常量池,且相同内容的字符串只存储一份(节约内存)。而通过new方式创建字符串对象,每new一次都会产生一个新的对象放在堆内存中。

String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);  // true,相同内容的字符串只存储一份,s1和s2本质是一个对象,因此地址相同

String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s3 == s4);  // false,每new一次都会产生一个新的对象放在堆内存中,因此s3和s4的地址不同

image-20250221122539666image-20250221122842589

ArrayList类

ArraryList概述

集合是一种容器,用来存储数据,类似于数组。

image-20250221135852602

数组和集合的区别?

当数组定义完成并启动后,长度就固定了。而集合大小可变,开发中用的更多。

ArraryList就是一种常用的集合

ArraryList的常用方法

image-20250221140518711

// 创建ArrayList集合对象
ArrayList list = new ArrayList(); 
//ArrayList<String> list = new ArrayList<>(); // 约束集合只能存放字符串类型
// 增删改查的方法
list.add("Java");
list.add(666);
list.add(66.6);
System.out.println(list); // [Java, 666, 66.6]

list.add(1,"MySQL"); // [Java, MySQL, 666, 66.6]  在指定索引位置处添加一个数据

System.out.println(list.get(1));	// MySQL 获取指定索引位置处的值

System.out.println(list.size());    // 5 返回集合中元素的个数

System.out.println(list.remove(1));	// 返回被删除的元素MySQL 删除指定索引位置的元素

System.out.println(list); // [Java, 666, 66.6]

list.remove("Java");	// 直接删除指定的元素 [666, 66.6],如果集合中存在多个相同元素,默认只删除第一次出现的元素

System.out.println(list.set(0,"ABC"));	// 修改指定索引位置的元素,返回被删除的元素Java, 此时集合变成[ABC, 66.6] 

面向对象高级

static

static修饰成员变量

static:静态,可以修饰成员变量、成员方法。

成员变量按照有无static修饰,分为两种:类变量、示例变量(对象的变量)

public class Student{
    static String name; // 类变量,有static修饰,属于类,在计算机里只有一份,会被类的全部对象共享
    
    int age;	// 实例变量,无static修饰,属于每个对象的。
}

public static void main(String[] args){
    Student.name = "Java";	// 可以通过 类名.类变量访问(推荐)
    
    Student s1 = new Student();
    s1.name = "ABC";	// 也可以通过 对象名.类变量访问(不推荐)
    
    Student s2 = new Student();
    s2.name = "AAA";	
    
    System.out.println(s1.name);	// 全部对象共享
    
    System.out.println(Student.age)	// 报错,实例变量属于每个对象,不能共享,无法通过类名访问
}

image-20250225143112143

案例:在开发中,如果某个数据只需要一份,且希望能够被共享(访问、修改),则该数据可以定义成类变量来记住

public class User{
    // 类变量
    public static int number;	// 用户类可以记住自己创建了多少个用户对象(这个变量只需要一份,可以定义为类变量)
    
    // 构造器
    public User(){
        // User.number++;
        number++; // 在同一个类中,访问自己类的类变量,才可以省略类名不写。
    }
}

public static void main(String[] args){
    User u1 = new User();	
    User u2 = new User();	
    User u3 = new User();	
    User u4 = new User();	
    
    System.out.println(User.number); //4
}

static修饰成员方法

成员方法按照有无static修饰,分为两种:类方法、示例方法(对象的方法)

public class Student{
    double score;
    // 类方法
    public static void printHelloWorld(){
        System.out.println("Hello World");
        System.out.println("Hello World");
	}
    
    // 实例方法
    public void printPass(){
        System.out.println("成绩:" + (score >=60 ? "及格" : "不及格"));
    }
}

public static void main(String[] args){
    Student.printHelloWorld();	//类名.类方法(推荐)
    
    Student s = new Student();
    s.printHelloWorld();	// 对象.类方法(不推荐)
    s.printPass();	// 对象.实例方法
    Student.printPass(); //报错,实例方法只能用对象来访问
}

main方法也是一种类方法

类方法最常用的应用场景是做工具类

工具类中的方法都是一些类方法,每个方法都是用来完成一个功能的,工具类是给开发人员功能共同使用的。

使用类方法设计工具类可以提高代码的服用性,调用方便,提高开发效率。

public class XxxxUtil {
    private XxxxUtil(){	// 将构造器私有化,防止从外部创建对象
        
    }
    
    public static void xxx(){
        // ......
    }
    public static boolean xxxx(String email){
        // ......
    }
    public static String xxxxx(int n){
        // ......
    }
}

static注意事项

类方法中可以直接访问类的成员,不可以直接访问实例成员

实例方法中既可以直接访问类的成员,也可以直接访问实例成员

实例方法中可以出现this关键字,类方法中不可以出现this关键字

pubilc class Student(){
    static String schoolName;	// 类变量
    double score;	// 实例变量
    
    // 类方法中可以直接访问类的成员,不可以直接访问实例成员
    public static void printHelloWorld(){
        schoolName = "Alaska";	// 同一个类中,访问类成员可以省略类名不写
        printHelloWorld2();
        
        System.out.println(score);	// 报错,不能直接访问实例成员
        printPass();;	// 报错
        
        System.out.println(this); // 报错,类方法没有对象
    }
    
    // 类方法
    pubilc static void printHelloWorld2(){
        
    }
    
    // 实例方法中既可以直接访问类的成员,也可以直接访问实例成员
    pubilc void printPass(){
        schoolName = "Alaska2";	// 实例方法可以访问类成员
        printHelloWorld2();
        
        System.out.println(socre);	// 实例方法可以访问实例成员
        printPass2();
        
        System.out.println(this);	
    }
    
    // 实例方法
    pubilc void printPass2(){
        
    }
}

单例设计模式

设计模式:一个问题的最优解法,称之为设计模式

单例设计模式确保一个类只有一个对象,可以避免浪费内存。

饿汉式单例写法: 将类的构造器私有。

定义一个类变量记住类的一个对象

定义一个类方法,返回对象。

// 饿汉式单例 在获取类的对象时,对象已经创建好了
pubilc class A{
    // 定义类变量记住类的一个对象
    private static A a = new A();	// 该语句只会被执行一次
    
    // 构造器私有
    private A(){
        
    }
    
    // 定义类方法返回对象
    public static A getObject(){
        return a;
    }
}


public static void main(String[] args) {
    new A();	//报错,不能从外部通过new创建对象
    A a1 = A.getObject();
    A a2 = A.getObject();	// 只会得到一个对象
}

饿汉式单例写法: 将类的构造器私有。

定义一个类变量存储对象

提供一个类方法,保证返回的是同一个对象。

// 懒汉式单例 拿对象时,才开始创建对象
pubilc class B{
    // 定义类变量用于存储对象。
    private static B b;	// 该语句只会被执行一次
    
    // 构造器私有
    private B(){
        
    }
    
    // 提供一个类方法,这个方法要保证第一次调用是才创建一个对象,后面调用时都会用这同一个对象返回
    public static B getInstance(){
        if(B ==null){
            b = new B();
        }
        return b;
    }
}


public static void main(String[] args) {
    B b1 = B.getInstance(); 	// 第一次拿对象
    B b2 = B.getInstance();		// 不再创建对象
}

继承

extends关键字可以让一个类和另一个类建立起父子关系,子类能继承父类的非私有成员(成员变量、成员方法),子类的对象是由子类和父类共同完成的。

pubilc class B extends A{	// A类称为父类,B类称为子类
    
}
// 父类
public class A{
    // 公开成员
    public int i;
    public void print1(){
        System.out.println("===print===1");
    }
    
    // 私有成员
    private int j;
    private void print2(){
        System.out.println("===print2===");
    }
}
// 子类
public class B entends A{
    public void print3{
        System.out.println(i);	// i是公开的成员变量,可以被继承
        print1();	// print1是公开的成员方法,可以被继承
        
        // System.out.println(j);	私有成员不可以被继承
        // pring2();
    }
}
public static void main(String[] args) {
    B b = new B();
    System.out.println(b.i);	//公开
    
    b.print1();	//公开
    // b.pring2();	//私有
}

使用继承的好处:减少重复代码的编写

image-20250225200924716

image-20250225201225398

在子类方法中访问其他成员,依照就近原则

public class A{
    String name = "我是父类";
    public void print1(){
        System.out.println("==父类的print1方法执行==");
    }
}
public class B extends A{
    String name = "我是子类";
    public void showName(){
        String name = "局部名称";
        System.out.println(name);	//输出:局部名称
        System.out.println(this.name);	//子类成员变量:我是子类
        System.out.println(super.name);	//父类成员变量:我是父类
    }
}
public static void main(String[] args) {
    B b = new B();
    b.showName();	//输出:局部名称,就近原则   局部名称->我是子类->我是父类
}

权限修饰符

image-20250226154414715

单继承

Java是单继承的,Java中的类不支持多继承,但是支持多层继承Object类是Java中所有类的祖宗。

class A{}	// class A extends Object{} 默认继承Object类
class B extends A{}
class C extends B{}; // C类继承B类,单继承
// class C extends B,A(); 报错,不能多继承
class D extends B{}; // 支持多层继承

image-20250226165624718

方法重写

当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个和方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。

重写后,方法的访问,Java会遵循就近原则

子类重写父类方法时,访问权限必须大于或等于父类该方法的权限。

重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小。

私有方法、静态方法不能被重写。

public class A{
    public void print1(){
   		System.out.println("111");   
    }
    public void print2(int a, int b){
   		System.out.println("111111");   
    }
}
public class B extends A{
    // 方法重写
    @Override	// 使用Override注解,可以让编译器帮助检查重写的格式是否正确,同时增强代码的可读性
    public void print1(){
   		System.out.println("666");   
    }
    // 方法重写
    @Override
    public void print2(int a, int b){
   		System.out.println("666666");   
    }
}
public static void main(String[] args) {
    B b = new B();
    b.print1();	// 输出666
    b.pring2(2,3);	// 输出666666
}

子类重写Object类的toString()方法,以便方法对象的内容

public class C{
    // 默认返回的是对象的地址
    // 重写Object类的toString()方法
    public void toString(){
        return ....;
    }
}

子类构造器

子类的全部构造器,都会先调用父类的构造器,再执行自己

如果父类没有无参构造器,子类的全部构造器都会报错,此时需要使用super(...)来调用父类的有参构造器。

public class F{
    public F(){
        System.out.println("父类F的无参数构造器执行了");
    }
}

public class Z extends F{
    public Z(){
        super(); 	//默认存在此行代码,调用父类的构造器
        System.out.println("子类Z的无参数构造器执行了");
    }
}

public static void main(String[] args) {
    Z z = new Z();	
    // 此时会输出:
    // 父类F的无参数构造器执行了
    // 子类Z的无参数构造器执行了
}

this(...)调用兄弟构造器(该类的其他构造器)

image-20250226191224286

多态

多态实在继承/实现情况下的一种现象,表现为:对象多态、行为多态。

多态的前提:有继承/实现关系;存在父类引用子类对象;存在方法重写

People p1 = new Student();
p1.run();

People p2 = new Teacher();
p2.run();

在多态形式下,右边对象是解耦合的,更便于扩展和维护。

定义方法时,使用父类类型的变量作为形参,可以接收一切子类对象,扩展性更强、更便利。

Student s = new Student();
go(s);

Teacher t = new Teacher();
go(t);

public static void go(People p){
    
}

多态下存在的问题:无法直接调用子类的独有功能。

public class Teacher extends People{
    public void teach(){
        // 教书
    }
}

public class Student extends People{
    public void test(){
        // 考试
    }
}

public static void main(String[] args){
    People p1 = new Student();
    // p1.test() 无法调用子类(Student是People的子类)的独有功能(考试)
}

因此需要多态下的类型转换子类 变量名 = (子类)父类变量

Teacher t = (Teacher)p;	// 强制类型转换
public class Teacher extends People{
    public void teach(){
        // 教书
    }
}

public class Student extends People{
    public void test(){
        // 考试
    }
}

public static void main(String[] args){
    People p1 = new Student();
    Student s1 = (Student)p1;	//强制类型转换
    s1.test();	// 此时可以调用子类的独有功能
    
    // 强制类型在有继承或者实现关系时就可以进行强制转换,但是运行时可能出现类型转换异常
    // Teacher t1 = (Teacher)p1;	// 报错:java.lang.ClassCastException
    // instanceof
    if(p1 instanceof Student) {
        Student s2 = (Student)p1;
        s2.test();
    }else{
        Teacher t2 = (Teacher)p1;
        t2.teach();
    }
}

final关键字

final关键字可以用来修饰类、方法、变量

修饰类:该类被称为最终类,不能被继承

final class A{}	// A类被final修饰
//class B extends A{}	不能被继承

修饰方法:该方法被称为最终方法,不能被重写

class C{
    public final void test(){	// test方法被final修饰
        
    }
}
class D extends C{
  // @Override
  //  public void test(){    不能被重写
  //      
  //  }
}

修饰变量:该变量只能被赋值一次。用于常量public static final修饰的成员变量):建议名称全部大写,多个单词下划线连接。

public class test{
    public static final String MY_NAME = "Alaska";
    
    public static void main(String[] args){
        System.out.println(MY_NAME);		// 可读性更好,更易于维护
        System.out.println(MY_NAME);
        System.out.println(MY_NAME);
        System.out.println(MY_NAME);
    }
}

抽象类

abstract关键字,用于修饰类、成员方法。

// 抽象类
public abstract class A{
    // 抽象方法:必须用abstract修饰,只有方法签名,且一定不能有方法体
    public abstract void run();
}

抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。

类该有的成员(成员变量、方法、构造器),抽象类都可以有。

抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承实现。

一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。

image-20250227100410040

模版方法设计模式

image-20250227101031054

写法:

定义一个抽象类

在里面定义两个方法:一个是模板方法,把相同代码放里面去;一个是抽象方法,具体实现交给子类完成。

public abstract class People{
    public final void write(){	// 最好使用final修饰,防止被子类重写
        System.out.println("--------相同代码1----------");
        System.out.println("--------相同代码2----------");
        System.out.println("--------相同代码3----------");
        System.out.println(writeMain());	// 不同代码,交给抽象方法完成
        System.out.println("--------相同代码4----------");
    }
    // 定义一个抽象方法写不同代码,具体的实现交给子类来完成
    public abstract String writeMain();
}

public class Student extends People{
    // 子类重写方法
    @Override
    public String writeMain(){
        System.out.println("--------不同代码1----------");
    }
}
public class Teacher extends People{
    // 子类重写方法
    @Override
    public String writeMain(){
        System.out.println("--------不同代码2----------");
    }
}
public static void main(String[] args){
    Teacher t = new Teacher();
    t.write();
    Student s = new Student();
    s.write();
}

接口

interface关键字,可以定义一种特殊结构:接口。接口也是抽象的,不能创建实例对象。

public interface 接口名 {
    // 成员变量 (常量)
    // 成员方法 (抽象方法)
}
public interface A {
    String MY_NAME = "Alaska";	// 常量需要赋初始值
    // public static final String MY_NAME =  "Alaska";  默认添加有public static final,多此一举
    
    void test();	// 抽象方法
    // public abstract void test();	  默认添加有public abstract,多此一举
    
    // public A(){}  接口中不能有构造器
    // static {}     接口中不能有代码块
}

接口是用来被类实现(关键字:implemtents)的,实现接口的类成为实现类

一个类可以实现多个接口,实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义为抽象类。

修饰符 class 实现类 implements 接口1,接口2,接口3,…{
    
}
public interface B {
	void testb1();
    void testb2();
}

public interface C {
	void testc1();
    void testc2();
}

// 实现类
public class D implements B,C {
    // 必须重写完全部接口的全部抽象方法
    @Override
    public void testb1(){
        
    }
    @Override
    public void testb2(){
        
    }
    @Override
    public void testc1(){
        
    }
    @Override
    public void testc2(){
        
    }
}

接口可以弥补类单继承的不足,一个类同时可以实现多个接口。

让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现。

class Student{
    
}

//class A extends Student{	// A类不能再继承别的类了,如果想要继承多个类,可以用接口实现
//    
//}

class A extends Student implements Driver, Singer{	
    @Override
    public void drive(){
        
    }
    @Override
    public void sing(){
        
    }
}

class B extends Student implements Driver{	
    @Override
    public void drive(){
        
    }
}


interface Driver{
    void drive();
}

interface Singer{
    void sing();
}

public static void main(String[] args){
    Student s1 = new A();
    Driver s2 = new A();
    Singer s3 = new A();
    //Driver d = new A();
    Driver d = new B();	// 面向接口编程,可以随时切换实现类对象,便于后期维护的修改
}

接口的其他细节

默认方法

默认方法:必须使用default修饰,默认会被public修饰

默认方法是一种实例方法,必须使用实现类的对象来访问。

public interface A {
    default void test1(){
        System.out.println("===默认方法===");
    }
}

public class B implements A {
    
}

public static void main(String[] args){
    B b = new B();
    b.test1();
}

私有方法

私有方法:必须使用private修饰,私有方法也是一种实例方法,默认会被public修饰

public interface A {
    // 默认方法
    default void test1(){
        System.out.println("===默认方法===");
        test2();	 // 调用私有方法
    }
    // 私有方法
    private void test2(){
        System.out.println("===私有方法===");
    }
}

public class B implements A {
    
}

public static void main(String[] args){
    B b = new B();
    b.test1();
}

静态方法

静态方法:必须使用static修饰,默认会被public修饰

public interface A {
    // 默认方法
    default void test1(){
        System.out.println("===默认方法===");
        test2();	 // 调用私有方法
    }
    // 私有方法
    private void test2(){
        System.out.println("===私有方法===");
    }
    // 静态方法
    static void test3(){
        System.out.println("===静态方法===");
    }
}

public class B implements A {
    
}

public static void main(String[] args){
    B b = new B();
    b.test1();	// 调用默认方法
    A.test3();	// 调用静态方法
}

接口的多继承

interface A{}
interface B{}
interface C{}

// 接口是多继承的
interface D extends C,B,A{
    
}

// 类E实现了A、B、C、D接口
class E implements D{
    
}

一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承。

interface I {
    void test1();
}
interface J {
    String test1();
}

// 此时不能继承,存在签名冲突,不知道该重写哪个抽象方法
//interface K extends I,J{
//
//}

一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现。

interface I {
    void test1();
}
interface J {
    String test1();
}

// 此时不能实现多个接口,存在签名冲突,不知道该重写哪个抽象方法
//class E implements I,J{
//
//}

一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的。

class Fu{
    pubilc void run(){
        System.out.println("===父类的run方法执行了===");
    }
}
interface IT{
     default void run(){
        System.out.println("===接口IT中的方法执行了===");
    }
}
class Zi extends Fu implements IT{
     
}


public static void main(String[] args){
    Zi zi = new Zi();
    zi.run();	// ===父类的run方法执行了===
}

一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。

interface It1{
    default void test(){
        System.out.println("IT1");
    }
}
interface It2{
    default void test(){
        System.out.println("IT2");
    }
}

class N implements It1, IT2{
    @Override
    public void test(){
        System.out.println("自己的");
    }
}

内部类

是类中的五大成分之一(成员变量、方法、构造器、内部类、代码块),如果一个类定义在另一个类的内部,这个类就是内部类。

当一个类的内部,包含了一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类。

public class Car{
    // 内部类
    public class Engine{
    }
}

成员内部类

image-20250227173809072image-20250227173718336

静态内部类

image-20250227183332896

image-20250227183243798

局部内部类

image-20250227183509688

匿名内部类(重点)

匿名:指的是成员不需要为这个类声明名字。用于更方便的创建一个子类对象

new 类或接口(参数值...){
    类体(一般是方法重写);
};
class abstract Animal{
    public abstract void cry();
}

/*
class Cat extends Animal{
    @Override
    pubilc void cry(){
        System.out.println("喵喵喵");
    }
}
*/

public static void main(String[] args){
   // Animal a = new Cat();
   // a.cry();
    
   // 匿名内部类
   // 把这个匿名内部类编译成一个子类,然后会立即创建一个子类对象出来
    Animal a = new Animal(){
        @Override
        public void cry(){
            System.out.println("喵喵喵");
        }
    };
    a.cry();
}

匿名内部类在开发中的使用场景:通常作为一个参数传输给方法。

// 猫和狗都要参加游泳比赛
interface Swimming{
    void swim();
}

// 设计一个方法,可以接收swimming接口的一切实现类对象进来参加游泳比赛
public static void go(Swimming s){
    s.swim();
}


pubilc static void main(String[] args){
    /*
    Swimming s1 = 
    go(s1); 	// 狗在游泳...
    */
    go(new Swimming(){
      	@Override
        public void swim(){
            System.out.println("狗在游泳...");
        }
    });
}

枚举

枚举是一种特殊类,这些名称本质是常量,每个常量都会记住枚举类的一个对象。

修饰符 enum 枚举类名{
    // 第一行必须罗列的是枚举对象的名字
    名称1, 名称2, ...;
    其他成员...
}

public enum A{
    X, Y, Z;
    ...
}

枚举类的构造器都是私有的,因此枚举类对外不能创建对象。

枚举类都是最终类,不可以被继承。

抽象枚举:

public enum B {
    X(){
        @Override
        public void go(){
            
        }
    },Y(){
        @Override
        public void go(){
            
        }
    };
    public abstract void go();
}

枚举的应用场景:做信息标志和分类

// 硬编码,可读性不好
public static void check(int flag){
    switch (flag){
        case 0:
            ....
            break;
        case 1:
            ....
            break;
    }
}

public static void main(String[] args){
    check(1);
}

// 软编码,但是入参不会受约束
public static void check(int flag){
    switch (flag){
        case Constant.BOY:
            ....
            break;
        case Constant.GIRL:
            ....
            break;
    }
}

public class Constant{
    public static final int BOY = 0;
    public static final int GIRL = 1;
}

// 枚举
public enum Constant2 {
    BOY , GIRL;
}

public static void check(Constant2 sex){
    switch (flag){
        case BOY:
            ....
            break;
        case GIRL:
            ....
            break;
    }
}

public static void main(String[] args){
    check(Constant2.BOY);	// 只能填BOY、GIRL,被限制更安全
}

泛型

定义类、接口、方法时,同时声明了一个或者多个类型变量(如:<E>),称为泛型类、泛型接口、泛型方法,他们统称为泛型。

ArrayList<String> list1 = new ArrayList<>();
list1.add("java1");	//String
list1.add("java2"); //String
list1.add("java3");	//String
list1.add(new Cat());	// 报错,Class类型,只能添加String类型数据

public class Cat{
    
}

泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力,这样可以避免强制类型转换,及其可能出现的异常。

泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除

泛型不支持基本数据类型,只能支持对象类型(引用数据类型)。

// ArrayList<int> list1 = new ArrayList<>();	报错
ArrayList<Integer> list1 = new ArrayList<>();
// ArrayList<double> list1 = new ArrayList<>();
ArrayList<Double> list1 = new ArrayList<>();

泛型类

修饰符 class 类名<类型变量,类型变量,...>{
    
}

public class MyArrayList<E> {
    public boolean add(E e){
        ...
    }
}

泛型接口

修饰符 interface 接口名<类型变量,类型变量,...>{
    
}

public interface A<E>{
    ...
}

泛型方法

修饰符 <类型变量,类型变量,...> 返回值类型 方法名(形参列表){
    
}

public static <T> T test(T t){
    return t;
}

class Dog{
    
}

test("java");	// String类型
test(new Dog());	// Dog类型

// ? 通配符,在使用泛型的时候可以代表一切类型	? extends Car(上限) 	? super Car(下限)
public static void go(ArrayList<? extends Car> cars){
    
}

常用API(二)

image-20250228132807449

Object类

Object类是Java中所有类的祖宗类,Java中所有类的对象都可以直接使用Object类中提供的一些方法。

image-20250228141520536

image-20250228153108411

Objects类

Objects类是一个工具类,提供了很多操作对象的静态方法。image-20250228154637112

包装类

包装类就是把基本类型的数据包装成对象。

image-20250228165434239

Integer a2 = Integer.valueOf(12);

自动装箱:可以自动把基本类型的数据转换成对象。

Integer a3 = 12;

自动拆箱:可以自动把包装类型的对象转换成对应的基本类型数据。

int a4 = a3;

泛型和集合不支持基本数据类型,只能支持引用数据类型。

// ArrayList<int> list = new ArrayList<>(); int为基本数据类型
ArrayList<Integer> list = new ArrarList<>();
list.add(12);	// 自动装箱

int rs = list.get(1);	// 自动拆箱

image-20250228170844832

// 把基本类型的数据转换成字符串
Integer a = 23;
String rs1 = Integer.toString(a);	// "23"
System.out.println(rs + 1);	// "231"

String rs2 = a.toString();	// "23"
System.out.println(rs2 + 1);	// "231"

String rs3 = a + "";
System.out.println(rs3 + 1);	// "231"

// 把字符串类型的数值转换成对应的基本类型
String ageStr = "29";
//String ageStr = "29.85";	不能转换
// int ageI = Integer.parseInt(ageStr);	// 29
int ageI = Integer.valueOf(ageStr);	// 29
System.out.println(ageI + 1);	// 30 

String scoreStr = "99.5";
// double score = Double.parseDouble(scoreStr);	// 99.5
double score = Double.valueOf(scoreStr);	// 99.5
System.out.println(score + 0.5);	// 100.0

StringBuilder、StringBuffer、StringJoiner

StringBuilder代表可变字符串对象,相当于一个容器,它里面装的字符串是可以改变的,用来操作字符串的。

StringBuilderString更适合做字符串的修改操作,效率会更高,代码更简洁。

image-20250228172708209

StringBuilder s = new StringBuilder("Java");	// s "Java"

// 拼接内容
s.append(12);
s.append("Gulf");
s.append(true);

// 支持链式编程
s.append(666).append("imuzi");

System.out.println(s);	// s "Java12Gulftrue666imuzi"

// 反转操作
s.reverse();
System.out.println(s);	// s "izumi666eurtfluGavaJ"

// 返回字符串的长度
System.out.println(s.length());

// 把StringBuilder对象又转换成String类型
String rs = s.toString();
System.out.println(rs);

StringBuffer的用法与StringBuilder是一模一样的,但StringBuilder是线程不安全的,StringBuffer是线程安全的。

JDK8之后,跟StringBuilder一样,也是用来操作字符串的,也可以看成是一个容器,创建之后里面的内容是可变的。

StringJoinerStringBuilder在有些场景下代码会更简洁

image-20250228194140335

StringJoiner s = new StringJoiner(",", "[", "]");
s.add("java1");
s.add("java2");
s.add("java3");
System.out.println(s);	// [java1,java2,java3]  自动拼接

Math、System、Runtime

image-20250228200450725

System.out.println(Math.abs(-12));			// 12
System.out.println(Math.abs(123));			// 123
System.out.println(Math.abs(-3.14));		// 3.14

System.out.println(Math.ceil(4.00000001));	// 5.0
System.out.println(Math.ceil(4.0));			// 4.0

System.out.println(Math.floor(4.9999999));	// 4.0
System.out.println(Math.floor(4.0));		// 4.0

System.out.println(Math.round(3.4999));		// 3.0
System.out.println(Math.round(3.5001));		// 4.0

System.out.println(Math.max(10, 20));		// 20
System.out.println(Math.min(10, 20));		// 10

System.out.println(Math.pow(2, 3));			// 2的3次方 8.0

System.out.println(Math.rondom());			// 随机值,范围[0.0, 1.0)

image-20250228202813240

image-20250228203927729

BigDecimal

用于解决浮点型运算时,出现结果失真的问题。

double a = 0.1;
double b = 0.2;
double c = a + b;
System.out.println(c);	// 0.30000000000000000004

// 将double类型的数字编程字符串封装成BigDecimal对象来运算
// BigDecimal a1 = new BigDecimal(Double.toString(a));
// BigDecimal b1 = new BigDecimal(Double.toString(b));

BigDecimal a1 = BigDecimal.valueOf(a);
BigDecimal b1 = BigDecimal.valueOf(b);

BigDeciaml c1 = a1.add(b1);			// 加
BigDeciaml c2 = a1.subtract(b1);	// 减
BigDeciaml c3 = a1.multiply(b1);	// 乘
BigDeciaml c4 = a1.divide(b1);		// 除	除不尽的结果会报错
BigDeciaml c5 = a1.divide(b1,2,RoundingMode.HALF_UP);	// 精确到2位小数,四舍五入

double rs = c5.doubleValue();

image-20250228211813463

日期、时间

JDK8之前

Date日期类

image-20250228213823611

Date d = new Date();
System.out.println(d);	// Fri Feb 28 21:41:40 CST 2025

long time = d.getTime();	// 获得时间戳

time += 2 * 1000;
Date d2 = new Date(time);	// 获得2s之后的时间
System.out.println(d2);	// Fri Feb 28 21:41:42 CST 2025

Date d3 = new Date();
d3.setTime(time);		// 设置日期对象的时间为当前时间毫秒值对应的时间

SimpleDateFormat

image-20250301105455761

image-20250301105500611

Date d = new Date();
System.out.println(d);	// Sat Mar 01 10:58:32 CST 2025

long time = d.getTime();
System.out.println(time);	// 1740797912137

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE a");

String rs =  sdf.format(d);
String rs2 = sdf.format(time);
System.out.println(rs);	// 2025-03-01 10:58:32 周六 上午
System.out.println(rs2);	// 2025-03-01 10:58:32 周六 上午

image-20250301110124007

String dateStr = "2025-03-01 11:06:34";

SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");	// 指定的时间格式必须与被解析的时间格式一模一样,否则会出bug

Date d2 = sdf2.parse(dateStr);
System.out.println(d2);	// Sat Mar 01 11:06:34 CST 2025

Calendar

image-20250301112351305

image-20250301112724878

// 得到系统此时时间对应的日历对象
Calendar now  = Calendar.getInstance();
System.out.println(now);	// java.util.GregorianCalendar[time=1740799715638,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=1,ERA=1,YEAR=2025,MONTH=2,WEEK_OF_YEAR=9,WEEK_OF_MONTH=1,DAY_OF_MONTH=1,DAY_OF_YEAR=60,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=11,HOUR_OF_DAY=11,MINUTE=28,SECOND=35,MILLISECOND=638,ZONE_OFFSET=28800000,DST_OFFSET=0]

// 获取日历中的某个信息
int year = now.get(Calendar.YEAR);
System.out.println(year);	// 2025

int days = now.get(Calendar.DAY_OF_YEAR);
System.out.println(days);	// 60

// 拿到日历中记录的日期对象
Date d = now.getTime();
System.out.println(d);		// Sat Mar 01 11:32:59 CST 2025

// 拿到时间毫秒值
long time = now.getTimeInMillis();
System.out.println(time);	// 1740800085913

// 修改日历中的某个信息
now.set(Calendar.MONTH, 9); // 修改月份成为10月份
Date d2 = now.getTime();
System.out.println(d2);		// Wed Oct 01 11:32:59 CST 2025

// 为某个信息增加/减少指定的值
now.add(Calendar.MONTH, -7);    // 在当前月份基础上减去7个月
System.out.println(now.getTime());	// Sat Mar 01 11:32:59 CST 2025

JDK8之后

image-20250301114345882

image-20250301115258778

LocalDate、LocalTime、LocalDateTime

LocalDate:代表本地日期(年、月、日、星期)

LocalTime:代表本地时间(时、分、秒、纳秒)

LocalDateTime:代表本地时期、时间(年、月、日、 星期、时、分、秒、纳秒)

image-20250301115853990

// 获取本地日期对象(不可变对象)
LocalDate ld = LocalDate.now();
System.out.println(ld); // 2025-03-01
// 获取日期对象中的信息
int year = ld.getYear();    // 年
int month = ld.getMonthValue(); // 月(1-12)
int day = ld.getDayOfMonth();   // 日
int dayOfYear = ld.getDayOfYear();  // 一年中的第几天
int dayOfWeek = ld.getDayOfWeek().getValue();   // 星期几

System.out.println(year);       // 2025
System.out.println(month);      // 3
System.out.println(day);        // 1
System.out.println(dayOfWeek);  // 6
System.out.println(dayOfYear);  // 60

// 直接修改某个信息:withYear、withMouth、withDayOfMonth、withDayOfYear
LocalDate ld2 = ld.withYear(2077);
System.out.println(ld2);        // 2077-03-01
LocalDate ld3 = ld.withMonth(12);
System.out.println(ld3);        // 2025-12-01
System.out.println(ld);         // 2025-03-01

// 把某个信息加多少: plusYears、plusMonths、plusDays、plusWeeks
LocalDate ld4 = ld.plusYears(20);
System.out.println(ld4);        // 2045-03-01
LocalDate ld5 = ld.plusMonths(2);
System.out.println(ld5);        // 2025-05-01
System.out.println(ld);         // 2025-03-01

// 把某个信息减多少: minusYears、minusMonths、minusDays、minusWeeks
LocalDate ld6 = ld.minusYears(20);
System.out.println(ld6);        // 2005-03-01
LocalDate ld7 = ld.minusMonths(2);
System.out.println(ld7);        // 2025-01-01
System.out.println(ld);         // 2025-03-01

// 获取指定日期的LocalDate对象: public static LocalDate of(int year, int month, int dayOfMonth)
LocalDate ld8 = LocalDate.of(2077, 7 ,7);
System.out.println(ld8);        // 2077-07-07

// 判断2个日期对象:是否相等,在前还是在后: equals、isBefore、isAfter
System.out.println(ld8.equals(ld7));    // false
System.out.println(ld8.isAfter(ld7));   // true
// 获取本地时间对象(不可变对象)
LocalTime lt = LocalTime.now(); // 时 分 秒 纳秒
System.out.println(lt); // 2025-03-01
// 获取时间对象中的信息
int hour = lt.getHour();            // 时
int minute = lt.getMinute();        // 分
int second = lt.getSecond();        // 秒
int nano = lt.getNano();            // 纳秒

System.out.println(hour);           // 12
System.out.println(minute);         // 41
System.out.println(second);         // 38
System.out.println(nano);           // 616146200

// 直接修改某个信息:withHour、withMinute、withSecond、withNano
LocalTime lt2 = lt.withHour(22);
System.out.println(lt2);        // 22:41:38.616146200
LocalTime lt3 = lt.withMinute(12);
System.out.println(lt3);        // 12:12:38.616146200
System.out.println(lt);         // 12:41:38.616146200

// 把某个信息加多少: plusHours、plusMinutes、plusSeconds、plusNanos
LocalTime lt4 = lt.plusMinutes(20);
System.out.println(lt4);        // 13:01:38.616146200
LocalTime lt5 = lt.plusSeconds(2);
System.out.println(lt5);        // 12:41:40.616146200
System.out.println(lt);         // 12:41:38.616146200

// 把某个信息减多少: minusHours、minusMinutes、minusSeconds、minusNanos
LocalTime lt6 = lt.minusHours(20);
System.out.println(lt6);        // 16:41:38.616146200
LocalTime lt7 = lt.minusMinutes(2);
System.out.println(lt7);        // 12:39:38.616146200
System.out.println(lt);         // 12:41:38.616146200

// 获取指定时间的LocalTime对象: public static LocalTime of(int hour, int minute)
LocalTime lt8 = LocalTime.of(13, 14 ,1);
System.out.println(lt8);        // 13:14:01

// 判断2个时间对象:是否相等,在前还是在后: equals、isBefore、isAfter
System.out.println(lt8.equals(lt7));    // false
System.out.println(lt8.isAfter(lt7));   // true
// 获取本地日期时间对象(不可变对象)
LocalDateTime ldt = LocalDateTime.now(); // 年 月 日 时 分 秒 纳秒
System.out.println(ldt); // 2025-03-01T12:46:30.962569600

// 获取日期时间对象中的信息
int year = ldt.getYear();           // 年
int month = ldt.getMonthValue();    // 月
int day = ldt.getDayOfMonth();      // 日
int hour = ldt.getHour();           // 时
int minute = ldt.getMinute();       // 分
int second = ldt.getSecond();       // 秒
int nano = ldt.getNano();           // 纳秒

System.out.println(year);           // 2025
System.out.println(month);          // 3
System.out.println(day);            // 1
System.out.println(hour);           // 12
System.out.println(minute);         // 46
System.out.println(second);         // 30
System.out.println(nano);           // 962569600

// 直接修改某个信息:withYear、withMonth、withDayOfMonth、withHour、withMinute、withSecond、withNano
LocalDateTime ldt2 = ldt.withHour(22);
System.out.println(ldt2);        // 2025-03-01T22:46:30.962569600
LocalDateTime ldt3 = ldt.withMinute(12);
System.out.println(ldt3);        // 2025-03-01T12:12:30.962569600
System.out.println(ldt);         // 2025-03-01T12:46:30.962569600

// 把某个信息加多少: plusYears、plusMonths、plusDays、plusHours、plusMinutes、plusSeconds、plusNanos
LocalDateTime ldt4 = ldt.plusMinutes(20);
System.out.println(ldt4);        // 2025-03-01T13:06:30.962569600
LocalDateTime ldt5 = ldt.plusDays(2);
System.out.println(ldt5);        // 2025-03-03T12:46:30.962569600
System.out.println(ldt);         // 2025-03-01T12:46:30.962569600

// 把某个信息减多少: minusYears、minusMonths、minusDays、minusHours、minusMinutes、minusSeconds、minusNanos
LocalDateTime ldt6 = ldt.minusHours(20);
System.out.println(ldt6);        // 2025-02-28T16:46:30.962569600
LocalDateTime ldt7 = ldt.minusMonths(2);
System.out.println(ldt7);        // 2025-01-01T12:46:30.962569600
System.out.println(ldt);         // 2025-03-01T12:46:30.962569600

// 获取指定日期时间的LocalDateTime对象: public static LocalDateTime of(...)
LocalDateTime ldt8 = LocalDateTime.of(2024, 5, 20, 13, 14, 1);
System.out.println(ldt8);        // 2024-05-20T13:14:01

// 判断2个日期时间对象:是否相等,在前还是在后: equals、isBefore、isAfter
System.out.println(ldt8.equals(ldt7));    // false
System.out.println(ldt8.isAfter(ldt7));   // false

// 获取日期时间对象中的日期和时间对象
LocalTime lt = ldt.toLocalTime();
System.out.println(lt); // 12:46:30.962569600
LocalDate ld = ldt.toLocalDate();
System.out.println(ld); // 2025-03-01

// 合并日期对象和时间对象
LocalDateTime ldt10 = LocalDateTime.of(ld, lt);
System.out.println(ldt10); // 2025-03-01T12:46:30.962569600

ZoneID、ZonedDateTime

ZoneID:时区id(洲名/城市名:Asia/Shanghai、Asia/Chongqing,国家名/城市名:America/New_York...)

// 获取系统默认的时区: public static ZoneId systemDefault()
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId); // Asia/Shanghai
// 获取所有的时区: public static Set<String> getAvailableZoneIds()
System.out.println(ZoneId.getAvailableZoneIds());
// 把某个时区id封装成ZoneId对象: public static ZoneId of(String zoneId)
ZoneId zoneId1 = ZoneId.of("America/New_York");
// 获取某个时区的ZonedDateTime对象: public ZonedDateTime now(ZoneId zone)
ZonedDateTime now = ZonedDateTime.now(zoneId1);
System.out.println(now);    // 2025-03-01T00:20:33.615444800-05:00[America/New_York]

ZonedDateTime now1 =  ZonedDateTime.now(Clock.systemUTC());
System.out.println(now1);   // 2025-03-01T05:20:33.615444800Z
// 获取系统默认时区的ZonedDateTime对象: public static ZonedDateTime now()
ZonedDateTime now2 = ZonedDateTime.now();
System.out.println(now2);   // 2025-03-01T12:20:33.615444800+08:00[Asia/Shanghai]

Instant

image-20250301213331482

// 获取当前时间的时间戳对象(不可变对象)
Instant now = Instant.now();

// 获取总秒数
long second = now.getEpochSecond();
System.out.println(second);     // 1740836108

// 获取不够1秒的纳秒数
int nano = now.getNano();
System.out.println(nano);       // 639253900

System.out.println(now);        // 2025-03-01T13:35:31.639253900Z

Instant instant = now.plusNanos(111);

// Instant对象的作用:做代码的性能分析,或者记录用户的操作时间点
Instant now1 = Instant.now();
// 代码执行
Instant now2 = Instant.now();

DateTimeFormatter

格式化器,用于时间的格式化、解析

image-20250302143956493

// 创建一个时间日期格式化对象
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

// 对时间进行格式化
LocalDateTime now = LocalDateTime.now();
System.out.println(now);    // 2025-03-02T14:37:30.648196800
String rs = formatter.format(now);
System.out.println(rs);     // 2025-03-02 14:37:30

String rs2 = now.format(formatter);
System.out.println(rs2);    // 2025-03-02 14:37:30

// 对字符串进行解析:一般使用LocalDateTime.parse
String dataStr = "2025-03-02 14:37:30";
LocalDateTime ldt = LocalDateTime.parse(dataStr, formatter);
System.out.println(ldt);    // 2025-03-02T14:37:30

Period、Duration

image-20250302144143494

LocalDate start =  LocalDate.of(2025, 3, 1);;
LocalDate end =  LocalDate.of(2025, 3, 31);

// 创建Period对象,封装两个日期对象
Period period = Period.between(start, end);

// 通过Period对象获取两个日期对象相差的信息
int year = period.getYears();
int month = period.getMonths();
int day = period.getDays();
System.out.println(year);   // 0
System.out.println(month);  // 0
System.out.println(day);    // 30

image-20250302145207929

// 得到Duration对象
LocalDateTime start = LocalDateTime.of(2025, 3, 1, 0, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 3, 31, 0, 0, 0);

Duration duration = Duration.between(start, end);

// 获取两个时间对象相差的信息
long days = duration.toDays();
long hours = duration.toHours();
long minutes = duration.toMinutes();
long seconds = duration.getSeconds();
long millis = duration.toMillis();
long nanos = duration.toNanos();
System.out.println(days);    // 30
System.out.println(hours);   // 720
System.out.println(minutes); // 43200
System.out.println(seconds); // 2592000
System.out.println(millis);  // 2592000000
System.out.println(nanos);   // 2592000000000

Arrays类

image-20250302145929548

Comparable、Comparator

image-20250302152323903

// 方式一

public class Student implements Comparable<Student> {
    private String name;
    private int age;
    private double height;

    // 重写compareTo方法
    @Override
    public int compareTo(Student o) {
//        if(this.age > o.age) {
//            return 1;
//        } else if(this.age < o.age) {
//            return -1;
//        } else {
//            return 0;
//        }
        return this.age - o.age;	// 升序
//		return o-age - this.age;	// 降序
    }

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                '}';
    }
}



Student[] students = new Student[4];
students[0] = new Student("张三", 18, 1.75);
students[1] = new Student("李四", 19, 1.80);
students[2] = new Student("王五", 20, 1.85);
students[3] = new Student("赵六", 21, 1.90);

Arrays.sort(students);
System.out.println(Arrays.toString(students));  // [Student{name='张三', age=18, height=1.75}, Student{name='李四', age=19, height=1.8}, Student{name='王五', age=20, height=1.85}, Student{name='赵六', age=21, height=1.9}]
// 方式二

Student[] students = new Student[4];
students[0] = new Student("张三", 18, 1.75);
students[1] = new Student("李四", 19, 1.80);
students[2] = new Student("王五", 20, 1.85);
students[3] = new Student("赵六", 21, 1.90);

//        Arrays.sort(students);
//        System.out.println(Arrays.toString(students));  // [Student{name='张三', age=18, height=1.75}, Student{name='李四', age=19, height=1.8}, Student{name='王五', age=20, height=1.85}, Student{name='赵六', age=21, height=1.9}]

Arrays.sort(students, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
//        if(o1.getHeight() > o2.getHeight()) {
//            return 1;
//        } else if(o1.getHeight() < o2.getHeight()) {
//            return -1;
//        }
//        return 0;
        return Double.compare(o1.getHeight(), o2.getHeight());	//升序
//      return Double.compare(o2.getHeight(), o1.getHeight());	//降序
    }
});
System.out.println(Arrays.toString(students));

Lamabda表达式

image-20250302160251281

public static void main(String[] args) {
//        Animal a = new Animal(){
//            @Override
//            public void eat() {
//                System.out.println("狗在吃东西");
//            }
//        };
//        a.eat();
// 使用Lambda表达式并不能简化全部匿名内部类的写法,智能简化函数式接口的匿名内部类
//        Animal a = () -> {
//            System.out.println("狗在吃东西");
//        };
//        a.eat();

//        Swimming s = new Swimming() {
//            @Override
//            public void swim() {
//                System.out.println("狗在游泳");
//            }
//        };
//        s.swim();
        Swimming s = () ->{
            System.out.println("狗在游泳");
        };
        s.swim();
    }
}

interface Swimming{
    void swim();
}

abstract class Animal {
    public abstract void eat();
}

image-20250302160718713

Arrays.sort(students, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        return Double.compare(o1.getHeight(), o2.getHeight());	
    }
}

// Lambda表达式            
Arrays.sort(Students,(Student o1, Student o2) -> {
    return Double.compare(o1.getHeight(), o2.getHeight());	  
});            
            
// 参数类型可以省略不写
Arrays.sort(Students,(o1, o2) -> {
    return Double.compare(o1.getHeight(), o2.getHeight());	  
});
            
// 如果方法体代码只有一行代码,可以省略大括号不写,同时要省略分号。如果这行代码是return语句,也必须去掉return不写
Arrays.sort(Students,(o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()));

方法引用

进一步简化Lambda表达式

静态方法的引用

类名::静态方法

如果某个Lambda表达式里只是调用一个静态方法,并且前后参数的形式一致,就可以使用静态方法引用

// Arrays.sort(Students,(o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()));
// Arrays.sort(Students,(o1, o2) -> CompareBydata.compareByHeight(o1, o2));		Lambda表达式
Arrays.sort(Students, CompareByData::compareByAge);	// 静态方法引用

// 静态方法
public class CompareBydata{
    public static int compareByHeight(Student o1, Student o2){
        return o1.getHeight() - o2.getHeight();
    }
}

实例方法的引用

对象名::实例方法

如果某个Lmabda表达式里只是调用一个实例方法,并且前后参数的形式一致,就可以使用实例方法引用

// Arrays.sort(Students,(o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()));
CompareBydata compare = new CompareBydata();
// Arrays.sort(Students,(o1, o2) -> compare.compareByHeightDesc(o1, o2));		Lambda表达式
Arrays.sort(Students, compare::compareByHeightDesc);	// 实例方法引用

// 实例方法
public class CompareBydata{
    pubilc int compareByHeightDesc(Student o1, Student o2){
        return o1.getHeight() - o2.getHeight();
    }
}

特定类型方法的引用

类型::方法

如果某个Lambda表达式里只是调用一个实例方法,并且前面参数中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型的方法引用

String[] names = {"body", "angela", "Andy", "dlei", "caocao", "Babo", "jack", "Cici"};

//        Arrays.sort(names, new Comparator<String>() {
//            @Override
//            public int compare(String o1, String o2) {
//                return o1.compareToIgnoreCase(o2);
//            }
//        });
//        Arrays.sort(names, (o1, o2) -> o1.compareToIgnoreCase(o2) );
Arrays.sort(names, String::compareToIgnoreCase);    // 特定类型的方法引用
System.out.println(Arrays.toString(names));
}

构造器引用

类名::new

如果某个Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用

异常

image-20250303100912911

image-20250303101101845

image-20250303104854186

image-20250303102311800

public static void main(String[] args) {
//        try {
//            saveAge(160);
//        } catch (Exception e) {
//            throw new RuntimeException(e);
//        }

    try {
        saveAge2(160);
    } catch (AgeIllegalException e) {
        throw new RuntimeException(e);
    }
}

// 编译时异常
public static void saveAge2(int age) throws AgeIllegalException{
    if(age > 0 && age < 150){
        System.out.println("年龄保存成功:  " + age);
    }else {
        // 用一个异常对象封装这个问题
        // throw 抛出这个异常对象
        // throws 用在方法上,抛出方法内部的异常
        throw new AgeIllegalException("/age is illegal, your age is " + age);
    }
}

// 运行时异常
public static void saveAge(int age){
    if(age > 0 && age < 150){
        System.out.println("年龄保存成功:  " + age);
    }else {
        // 用一个异常对象封装这个问题
        // throw 抛出这个异常对象
        throw new AgeIllegalRuntimeException("/age is illegal, your age is " + age);
    }
}

// 必须让这个类继承自RuntimeException,才能成为一个运行时异常类
public class AgeIllegalRuntimeException extends RuntimeException{
    public AgeIllegalRuntimeException(String message) {
        super(message);
    }

    public AgeIllegalRuntimeException() {
    }
}

// 必须让这个类继承自Exception,才能成为一个编译时异常类
public class AgeIllegalException extends Exception{
    public AgeIllegalException(String message) {
        super(message);
    }

    public AgeIllegalException() {
    }
}

// 异常报错
Exception in thread "main" java.lang.RuntimeException: ren.imuzi0221.hello.AgeIllegalRuntimeException: /age is illegal, your age is 160
	at ren.imuzi0221.hello.Exceptiontest1.main(Exceptiontest1.java:8)
Caused by: ren.imuzi0221.hello.AgeIllegalRuntimeException: /age is illegal, your age is 160
	at ren.imuzi0221.hello.Exceptiontest1.saveAge(Exceptiontest1.java:18)
	at ren.imuzi0221.hello.Exceptiontest1.main(Exceptiontest1.java:6)

image-20250303103739477

image-20250303104347775

image-20250303104758979

集合

image-20250303105050200

Collection

Collection集合概述

image-20250303110859151

ArrayList<String> list = new ArrayList<>(); // 有序 可重复 有索引
list.add("java");
list.add("java");
list.add("java");
list.add("java");
list.add("java");
System.out.println(list);   // [java, java, java, java, java]

HashSet<String> set = new HashSet<>();  // 无序 不重复 无索引
set.add("java1");
set.add("java2");
set.add("java1");
set.add("java2");
set.add("java3");
System.out.println(set);    // [java3, java2, java1]

Collection常用方法

image-20250303112622110

Collection<String> c = new ArrayList<>();
// public boolean add(E e)  // 添加元素,返回值是boolean类型
c.add("java1");
c.add("java1");
c.add("java2");
c.add("java2");
c.add("java3");
System.out.println(c);    // [java1, java1, java2, java2, java3]
// public void clear()  // 清空集合
c.clear();
System.out.println(c);    // []
// public boolean isEmpty()  // 判断集合是否为空,返回值是boolean类型
System.out.println(c.isEmpty());    // true
// public int size()  // 返回集合中元素的个数,返回值是int类型
System.out.println(c.size());    // 0
// public boolean contains(Object o)  // 判断集合中是否包含指定元素,返回值是boolean类型
System.out.println(c.contains("java1"));    // false
// public boolean remove(E e)  // 删除指定元素,如果有多个重复元素默认删除前面的第一个,返回值是boolean类型
// c.remove("java1");
// public Object[] toArray()  // 将集合转换为数组,返回值是Object[]
Object[] arr = c.toArray();

String[] arr2 =  c.toArray(new String[c.size()]);

// 把一个集合的全部数据倒入到另一个集合中
Collection<String> c2 = new ArrayList<>();
c2.add("java1");
c2.add("java2");

Collection<String> c3 = new ArrayList<>();
c3.add("java3");
c3.add("java4");
c2.addAll(c3);  // 把c3的全部数据倒入到c2中
System.out.println(c2);    // [java1, java2, java3, java4]
System.out.println(c3);    // [java3, java4]

image-20250304095513500

Collection遍历方式

迭代器

image-20250303113144247

Collection<String> c = new ArrayList<>();
c.add("java1");
c.add("java2");
c.add("java3");
c.add("java4");
System.out.println(c);    // [java1, java2, java3, java4]

Iterator<String> it = c.iterator();
while(it.hasNext()){
    String s = it.next();
    System.out.println(s);
}

增强for循环

image-20250303122155014

Collection<String> c = new ArrayList<>();
c.add("java1");
c.add("java2");
c.add("java3");
c.add("java4");
System.out.println(c);    // [java1, java2, java3, java4]

for (String ele : c) {
    System.out.println(ele);
}

String[] name = {"java1", "java2", "java3", "java4"};
for (String ele : name) {
    System.out.println(ele);
}

Lambda

image-20250303124510116

Collection<String> c = new ArrayList<>();
c.add("java1");
c.add("java2");
c.add("java3");
c.add("java4");
System.out.println(c);    // [java1, java2, java3, java4]

c.forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
});

c.forEach(s -> System.out.println(s));  // lambda表达式

c.forEach(System.out::println);     // 方法引用

image-20250303130522345

集合中存储的是元素对象的地址

List集合

image-20250303130837574

List集合支持的遍历方式:

· for循环

· 迭代器

· 增强for循环

· Lambda表达式

List<String> list = new ArrayList<>();
list.add("java");
list.add("java2");
list.add("java3");

// for循环
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

// 增强for循环(foreach)
for (String s : list) {
    System.out.println(s);
}

// Lambda表达式
list.forEach(s -> System.out.println(s));

ArrayList

image-20250303132756708

image-20250303132952791

image-20250303133032866image-20250303133129731

ArrayList集合适合的应用场景:

适合根据索引查询数据,比如根据随机索引取数据,或者数据量不是很大时。

不适合数据量大的同时,又要频繁的进行增删操作。

LinkedList

image-20250303133548653image-20250303133720156image-20250303133756818

image-20250303134120491

应用场景:

· 设计队列

· 设计栈

Set集合

image-20250303163630869

Set<Integer> set = new HashSet<>();     // 一行经典代码  无序 不重复 无索引
Set<Integer> set1 = new LinkedHashSet<>(); // 有序 不重复 无索引
Set<Integer> set2 = new TreeSet<>(); // 可排序(升序) 不重复 无索引
set.add(666);
set.add(555);
set.add(555);
set.add(888);
set.add(888);
set.add(777);
set.add(777);
set1.add(666);
set1.add(555);
set1.add(555);
set1.add(888);
set1.add(888);
set1.add(777);
set1.add(777);
set2.add(666);
set2.add(555);
set2.add(555);
set2.add(888);
set2.add(888);
set2.add(777);
set2.add(777);
System.out.println(set);    // [888, 777, 666, 555]
System.out.println(set1);   // [666, 555, 888, 777]
System.out.println(set2);   // [555, 666, 777, 888]

HashSet

image-20250303164340034

image-20250303164945521

image-20250303165259692

image-20250303170154736

image-20250303165735110

image-20250303170903911

LinkedHashSet

image-20250303172837784

TreeSet

image-20250303173352874

并发修改异常

ArrayList<String> list = new ArrayList<>(); // 有序 可重复 有索引
list.add("王麻子");
list.add("小李子");
list.add("李爱花");
list.add("张全蛋");
list.add("晓李");
list.add("李玉刚");

// 使用迭代器,使用集合自身的remove方法会出现ConcurrentModificationException异常(并发修改异常)
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
if (name.contains("李")) {
//                iterator.remove();    // 正确做法
list.remove(name);  // 报错
}
}
System.out.println(list);

        // 使用增强for循环,使用集合自身的remove方法会出现ConcurrentModificationException异常(并发修改异常)
//        for (String s : list) {
//            if (s.contains("李")) {
//                list.remove(s);  // 报错
//            }
//        }
//        System.out.println(list);
        
        // 使用Lambda表达式,使用集合自身的remove方法会出现ConcurrentModificationException异常(并发修改异常)
//        list.forEach(s -> {
//            if (s.contains("李")) {
//                list.remove(s);  // 报错
//            }
//        });
//        System.out.println(list);
        

//        // 使用for循环遍历集合并删除集合中带李子的名字
//        for (int i = 0; i < list.size(); i++) {
//            String name = list.get(i);
//            if (name.contains("李")) {
//                list.remove(name);
////                i--;    // 删除后索引要减一,防止漏删
//            }
//        }
//        // 出现BUG导致漏删
//        System.out.println(list);    // [王麻子, 李爱花, 张全蛋, 李玉刚]

Collections类

可变参数

定义在方法、构造器的形参列表中,格式是:数据类型...参数名称;

使用可变参数在方法调用时候不传数据给它;也可以传一个或者同时传多个数据;也可以传一个数组给它,常常用来灵活的接收数据

public static void main(String[] args) {
    test(); //不传数据
    test(1); //传一个数据
    test(1,2,3,4,5,6,7,8,9,10); //传多个数据
    test(new int[]{1,2,3,4,5,6,7,8,9,10}); //传数组
}

// 注意事项: 一个形参列表中,只能有一个可变参数
//			可变参数必须放在形参列表的最后面
//			
public static void test(int...nums){
     // 可变参数在方法内部会被当做数组来处理
    System.out.println(nums.length);
    System.out.println(Arrays.toString(nums));
    //0
    //[]
    //1
    //[1]
    //10
    //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    //10
    //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}

Collections工具类

image-20250304103338648

Map

Map集合概述

image-20250304105421367

image-20250304105725766

Map<String, Integer> map = new HashMap<>();         // 无序,不重复,无索引
Map<String, Integer> map1 = new LinkedHashMap<>();  // 有序,不重复,无索引
Map<String, Integer> map2 = new TreeMap<>();         // 可排序(升序),不重复,无索引
map.put("java", 1);
map.put("java", 2); // 后面重复的数据会覆盖前面的数据(键)
map.put("java1", 3);
map.put("java2", 3);
map.put(null,null);
map.put(null,1);
map1.put("java", 1);
map1.put("java", 2); // 后面重复的数据会覆盖前面的数据(键)
map1.put("java1", 3);
map1.put("java2", 3);
map1.put(null,null);
map1.put(null,1);
map2.put("java", 5);
map2.put("java", 8); // 后面重复的数据会覆盖前面的数据(键)
map2.put("java1", 3);
map2.put("java2", 3);
//        map2.put(null,null);   // TreeMap不允许键为null
//        map2.put(null,1);
System.out.println(map);    // {null=1, java=2, java2=3, java1=3}
System.out.println(map1);   // {java=2, java1=3, java2=3, null=1}
System.out.println(map2);   // {java=8, java1=3, java2=3}

Map常用方法

image-20250304112225218

Map<String, Integer> map = new HashMap<>();
map.put("手表", 100);
map.put("手表", 220);
map.put("手机", 2);
map.put("Java", 2);
map.put(null, null);
System.out.println(map);    // {null=null, 手表=220, 手机=2, Java=2}

// 获取集合大小: pubilc int size()
System.out.println(map.size()); // 4

// 清空集合: pubilc void clear()
// map.clear();
// System.out.println(map);    // {}

// 判断集合是否为空: pubilc boolean isEmpty()
// System.out.println(map.isEmpty()); // true

// 根据键获取值: pubilc V get(Object key)
System.out.println(map.get("手表")); // 220

// 删除指定键值对: pubilc V remove(Object key)
// System.out.println(map.remove("手表")); // 220

// 判断集合中是否包含指定键: pubilc boolean containsKey(Object key)
System.out.println(map.containsKey("手表")); // true

// 判断集合中是否包含指定值: pubilc boolean containsValue(Object value)
System.out.println(map.containsValue(100)); // false

// 获取Map集合的全部键: pubilc Set<K> keySet()
System.out.println(map.keySet()); // [null, 手表, 手机, Java]

// 获取Map集合的全部值: pubilc Collection<V> values()
System.out.println(map.values()); // [null, 220, 2, 2]

// 把其他Map集合中的数据添加到当前Map集合中: pubilc V putAll(Map<? extends K, ? extends V> m)
Map<String, Integer> map2 = new HashMap<>();
map2.put("Alaska", 100);
map2.put("Gulf", 220);
map2.put("imuzi", 2);
map.putAll(map2);            // 把map2中的数据添加到map中
System.out.println(map2);    // {Alaska=100, Gulf=220, imuzi=2}
System.out.println(map);     // {null=null, 手表=220, 手机=2, Java=2

Map遍历方式

键找值

image-20250304112442923

Map<String, Double> map = new HashMap<>();
map.put("蜘蛛精", 162.5);
map.put("蜘蛛精", 169.8);
map.put("紫霞", 165.8);
map.put("至尊宝", 169.5);
map.put("牛魔王", 183.6);
System.out.println(map);    // {至尊宝=169.5, 牛魔王=183.6, 紫霞=165.8}

// 获取Map集合的全部键
Set<String> keys = map.keySet();
System.out.println(keys);    // [至尊宝, 牛魔王, 紫霞]
// 遍历全部的键,根据键获取其对应的值
for (String key : keys) {
    // 根据键获取值
    Double v = map.get(key);
    System.out.println(key + "的身高是" + v);
    // 蜘蛛精的身高是169.8
    // 牛魔王的身高是183.6
    // 至尊宝的身高是169.5
    // 紫霞的身高是165.8
}

键值对

image-20250304135641915

Set<Map.Entry<String, Double>> entries = map.entrySet();
for (Map.Entry<String, Double> entry : entries) {
    System.out.println(entry.getKey() + "的身高是" + entry.getValue());
}

Lambda

map.forEach((k,v) -> System.out.println(k + "的身高是" + v));

HashMap

image-20250304141835644

LinkedHashMap

image-20250304142815870

TreeMap

image-20250304144017917

Stream流

Stream流可以用于操作集合或者数组的数据

Stream流大量的结合了Lambda的语法风格,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好。

 List<String> names = new ArrayList<>();
 Collections.addAll(names,"张三丰","张无忌", "赵敏", "周芷若", "小昭", "李青萝", "殷素素", "黄衫女");
 System.out.println(names);
 ​
 // 找出姓张,且是3个字的名字,存入到一个新集合中去
 List<String> list = new ArrayList<>();
 for (String name : names) {
     if (name.startsWith("张") && name.length() == 3) {
         list.add(name);
     }
 }
 System.out.println(list);    // [张三丰, 张无忌]
 ​
 ​
 // 使用Stream流来实现
 List<String> list1 = names.stream().filter(name -> name.startsWith("张")).filter(name -> name.length() == 3).collect(Collectors.toList());
 System.out.println(list1);    // [张三丰, 张无忌]

image-20250304152553528

image-20250304152704242

 // 获取List集合的Stream流
 List<String> names = new ArrayList<>();
 Collections.addAll(names,"张三丰","张无忌", "赵敏", "周芷若", "小昭", "李青萝", "殷素素", "黄衫女");
 Stream<String> stream = names.stream();
 ​
 // 获取Set集合的Stream流
 Set<String> set = new HashSet<>();
 Collections.addAll(set,"张三丰","张无忌", "赵敏", "周芷若", "小昭", "李青萝", "殷素素", "黄衫女");
 Stream<String> stream1 = set.stream();
 stream1.filter(s -> s.startsWith("张")).forEach(System.out::println);
 ​
 // 获取Map集合的Stream流
 Map<String, Double> map = new HashMap<>();
 map.put("蜘蛛精", 162.5);
 map.put("蜘蛛精", 169.8);
 map.put("紫霞", 165.8);
 map.put("至尊宝", 169.5);
 map.put("牛魔王", 183.6);
 ​
 Set<String> key = map.keySet();
 Stream<String> ks = key.stream();
 Collection<Double> values = map.values();
 Stream<Double> vs = values.stream(); 
 ​
 Set<Map.Entry<String, Double>> entries = map.entrySet();
 Stream<Map.Entry<String, Double>> kvs = entries.stream();
 kvs.filter(s -> s.getKey().contains("牛")).forEach(e -> System.out.println(e.getKey() + "的身高是" + e.getValue()));
 ​
 // 获取数组的Stream流
 String[] arr = {"张三丰","张无忌", "赵敏", "周芷若", "小昭", "李青萝", "殷素素", "黄衫女"};
 Stream<String> stream2 = Arrays.stream(arr);
 Stream<String> arr1 = Stream.of(arr);

image-20250304180031258

image-20250305150351513

多线程

image-20250307120446355

线程创建

方式一:继承Thread类

image-20250307122357960

注意事项:

· 启动线程必须是调用start方法,不是调用run方法

· 不要把主线程任务放在启动子线程之前(不然永远都是主线程的任务先跑完)

 // 让子类继承Thread线程类
 public class MyThread extends Thread{
     // 重写Thread的run方法
     @Override
     public void run() {
         for (int i = 0; i < 5; i++) {
             System.out.println("子线程Mythread输出:" + i);
         }
     }
 }
 ​
 public static void main(String[] args) {
     // 创建MyThread线程类的对象代表一个线程
     Thread t = new MyThread();
     // 启动线程(自动执行run方法)
     // t.run(); 子线程先跑了,不是多线程
     t.start();  // 注册子线程
 ​
     for (int i = 0; i < 5; i++) {
         System.out.println("主线程main输出:" + i);
     }
    
     //主线程main输出:0
     //主线程main输出:1
     //主线程main输出:2
     //子线程Mythread输出:0
     //主线程main输出:3
     //子线程Mythread输出:1
     //子线程Mythread输出:2
     //主线程main输出:4
     //子线程Mythread输出:3
     //子线程Mythread输出:4
 }

方式二:实现Runnable接口

image-20250307155053068

 // 定义一个任务类,实现Runnable接口
 public class MyRunnable implements Runnable{
     // 重写runnable的run方法
     @Override
     public void run() {
         // 线程要执行的任务
         for (int i = 0; i < 5; i++) {
             System.out.println("子线程输出---->" + i);
         }
     }
 }
 ​
 ​
 public static void main(String[] args) {
     // 创建任务对象
     Runnable target = new MyRunnable();
     // 把任务对象交给一个线程对象处理
     new Thread(target).start();
     for (int i = 0; i < 5; i++) {
         System.out.println("主线程main输出---->" + i);
     }
 }
 //子线程输出---->0
 //子线程输出---->1
 //子线程输出---->2
 //主线程main输出---->0
 //子线程输出---->3
 //主线程main输出---->1
 //子线程输出---->4
 //主线程main输出---->2
 //主线程main输出---->3
 //主线程main输出---->4
 Runnable target = new Runnable() {
     @Override
     public void run() {
         // 线程要执行的任务
         for (int i = 0; i < 5; i++) {
             System.out.println("子线程输出---->" + i);
         }
     }
 };
 ​
 ​
 // 把任务对象交给一个线程对象处理
 new Thread(target).start();
 ​
 // 简化一:匿名内部类
 new Thread(new Runnable() {
             @Override
             public void run() {
                 // 线程要执行的任务
                 for (int i = 0; i < 5; i++) {
                     System.out.println("子线程输出---->" + i);
                 }
             }
         }).start();
 // 简化二:Lambda表达式
 new Thread(() -> {
                 // 线程要执行的任务
                 for (int i = 0; i < 5; i++) {
                     System.out.println("子线程输出---->" + i);
                 }
         }).start();

方式三:实现Callable接口

前两种创建方式,重写的run方法都不能直接返回结果

image-20250307170148927

image-20250307172106557

 // 让这个类实现Callabl接口
 public class MyCallable implements Callable<String> {
     private int n;
 ​
     public MyCallable(int n) {
         this.n = n;
     }
 ​
     // 重写call方法
     @Override
     public String call() throws Exception {
         int sum = 0;
         // 描述线程任务,返回线程执行的结果
         for (int i = 0; i <= n; i++) {
             sum += i;
         }
         return "线程求出了1-" + n + "的值是: " + sum;
     }
 }
 ​
 public static void main(String[] args) throws Exception {
     // 创建一个Callable的对象
     Callable<String> call = new MyCallable(100);
 ​
     // 把Callable的对象封装成一个FutureTask对象(任务对象)
     // 未来任务对象的作用:
     // 是一个任务对象,实现了Runnable对象
     // 可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果
     FutureTask<String> f1 = new FutureTask<>(call);
 ​
     // 把任务对象交给Thread对象
     new Thread(f1).start();
 ​
     // 获取线程执行完毕后返回的结果
     // 注意:如果执行到这里,假如上面的线程还没有执行完毕,这里的代码会暂停,等待上面线程执行完毕后才会获取结果
     System.out.println(f1.get());   // 线程求出了1-100的值是: 5050
 }

常用方法

image-20250307172701860

线程安全

image-20250308122733815

image-20250308123016978

 // 账户类
 public class Account {
     private double money;
 ​
     public Account() {
     }
 ​
     public Account(double money) {
         this.money = money;
     }
 ​
     public double getMoney() {
         return money;
     }
 ​
     public void setMoney(double money) {
         this.money = money;
     }
 ​
     public void drawMoney(double money) {
         String name = Thread.currentThread().getName();
         if(this.money >= money){
             System.out.println(name + "来取钱" + money + "成功");
             this.money -= money;
             System.out.println(name + "来取钱后,余额剩余:" + this.money);
         }else {
             System.out.println(name + "来取钱,余额不足");
         }
     }
 }
 ​
 ​
 // 子线程类
 public class DrawThread extends Thread{
     private Account acc;
     public DrawThread(Account acc, String name){
         super(name);
         this.acc = acc;
     }
     @Override
     public void run() {
         acc.drawMoney(100000);
     }
 }
 ​
 // main线程
 public static void main(String[] args) {
     Account acc = new Account(100000);
 ​
     new DrawThread(acc,"小明").start();   // 小明
     new DrawThread(acc,"小红").start();   // 小红
 }
 ​
 // 输出:
 // 小红来取钱100000.0成功
 // 小明来取钱100000.0成功
 // 小红来取钱后,余额剩余:0.0
 // 小明来取钱后,余额剩余:-100000.0    出现线程安全问题

线程同步

线程同步的常见方案:

· 加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。

同步代码块

作用:把访问共享资源的核心代码给上锁,以此保证线程安全。

 // 同步代码块
 synchronized{
     
 }
 ​
 public void drawMoney(double money) {
     String name = Thread.currentThread().getName();
 //    synchronized ("lock") {       // 此时锁是唯一的,不合理,会对所有对象生效
     synchronized (this) {           // 实例方法建议使用this作为锁对象
         if(this.money >= money){
             System.out.println(name + "来取钱" + money + "成功");
             this.money -= money;
             System.out.println(name + "来取钱后,余额剩余:" + this.money);
         }else {
             System.out.println(name + "来取钱,余额不足");
         }
     }
 }
 ​
 ​
 public static void test(){
     synchronized (Account.class){   // 静态方法是用类名.class作为锁
         
     }
 }

同步方法

作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

原理:同步方法其底层也是有隐式锁对象的,只是锁的范围是整个方法代码。

 修饰符 synchronized 返回值类型 方法名称(形参列表){
     
 }
 ​
 public synchronized void drawMoney(double money) {
     String name = Thread.currentThread().getName();
     if(this.money >= money){
         System.out.println(name + "来取钱" + money + "成功");
         this.money -= money;
         System.out.println(name + "来取钱后,余额剩余:" + this.money);
     }else {
         System.out.println(name + "来取钱,余额不足");
     }
 }

Lock锁

可以创建锁对象进行加锁和解锁,更灵活、更方便、更强大。

Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。

 // 创建一个锁对象
 private final Lock lk = new ReentrantLock();
 ​
 ......
     
 public void drawMoney(double money) {
     String name = Thread.currentThread().getName();
     try {
         lk.lock();  // 加锁
         if(this.money >= money){
             System.out.println(name + "来取钱" + money + "成功");
             this.money -= money;
             System.out.println(name + "来取钱后,余额剩余:" + this.money);
         }else {
             System.out.println(name + "来取钱,余额不足");
         }
     } catch (Exception e) {
         throw new RuntimeException(e);
     } finally {
         lk.unlock();    // 解锁
     }
 }

线程池

什么是线程池?

· 线程池是一种可以复用线程的技术

不是用线程池的问题?

· 用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了又要创建新的线程,而创建新线程的系统开销往往很大,当请求过多时,会产生大量的线程,严重影响系统的性能

线程池可以控制线程和任务的数量,重复利用这些线程来处理任务,不会因为线程或任务过多而导致系统资源耗尽引起系统瘫痪。

image-20250309103850699

创建线程池对象:

· 方式一: 使用ExecutorService的实现类ThreadPoolExecutor创建一个线程池对象

· 方式二: 使用Executors(线程池的工具类)调用方法返回的不同特点的线程池对象

image-20250309112809922

corePoolSize:指定线程池的核心线程数量

对于计算密集型任务:核心线程数量 = CPU核数 + 1

对于I/O密集型任务:核心线程数量 = CPU核数 * 2

maximumPoolSize: 指定线程池的最大线程数量

keepAliveTime:指定临时线程的存活时间(只有当核心线程满了的同时任务队列也满,才会创建临时线程)

unit:指定临时线程存活时间的时间单位(秒、分、时、天)

workQueue:指定线程池的任务队列 new ArrayBlockingQueue<>()基于链表实现,不限制任务队列大小 new LinkedBlockingQueue<>()基于数组实现,可以控制任务队列大小

threadFactory:指定线程池的线程工厂(负责创建线程)Executors.defaultThreadFactory()可以创建默认线程工厂

handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的情况下,新任务来了该如何处理)image-20250309120726951

image-20250309114525337

线程池处理Runnable任务

 // 定义一个任务类,实现Runnable接口
 public class MyRunnable implements Runnable{
     // 重写runnable的run方法
     @Override
     public void run() {
         // 线程要执行的任务
         System.out.println(Thread.currentThread().getName() + " ==> 输出666");
         try {
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
         }
     }
 }
 ​
 public static void main(String[] args) {
     ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
             TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
             new ThreadPoolExecutor.AbortPolicy());
 ​
     Runnable target = new MyRunnable();
     pool.execute(target);   // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
     pool.execute(target);   // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
     pool.execute(target);   // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
     pool.execute(target);   // 复用前面的核心线程
     pool.execute(target);   // 只有当核心线程满了的同时任务队列也满,才会创建临时线程
 ​
     // pool.shutdown();        // 等线程池的任务全部执行完毕后,再关闭线程池
     // pool.shutdownNow();     // 立即关闭线程池
 ​
     // 线程池里的线程本身就是用来复用的,一直存活,不应该死亡
     // 或者使用shutdown()、shutdownNow()方法来结束线程池
 ​
     //pool-1-thread-1 ==> 输出666
     //pool-1-thread-3 ==> 输出666
     //pool-1-thread-2 ==> 输出666
     //pool-1-thread-2 ==> 输出666
     //pool-1-thread-1 ==> 输出666
 }

什么时候会创建临时线程?

· 只有当核心线程满了(都在忙)的同时任务队列也满,才会创建临时线程

 // 定义一个任务类,实现Runnable接口
 public class MyRunnable implements Runnable{
     // 重写runnable的run方法
     @Override
     public void run() {
         // 线程要执行的任务
         System.out.println(Thread.currentThread().getName() + " ==> 输出666");
         try {
             Thread.sleep(Integer.MAX_VALUE);    // 让他睡眠更长的时间,以此来占用住线程
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
         }
     }
 }
 ​
 // other code...
 ​
 Runnable target = new MyRunnable();
 pool.execute(target);   // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
 pool.execute(target);   // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
 pool.execute(target);   // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
 pool.execute(target);   // 如果前面的核心线程在忙,后面的任务开始在任务队列中排队(1/4),不会创建新的临时线程
 pool.execute(target);   // 如果前面的核心线程在忙,后面的任务开始在任务队列中排队(2/4),不会创建新的临时线程
 pool.execute(target);   // 如果前面的核心线程在忙,后面的任务开始在任务队列中排队(3/4),不会创建新的临时线程
 pool.execute(target);   // 如果前面的核心线程在忙,后面的任务开始在任务队列中排队(4/4),不会创建新的临时线程
 pool.execute(target);   // 临时线程被创建
 pool.execute(target);   // 临时线程被创建
 pool.execute(target);   // 此时核心线程和临时线程都在忙,触发拒绝策略(线程池创建的是AbortPolicy())策略
 // Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task ren.imuzi0221.thread.MyRunnable@506e1b77 rejected from java.util.concurrent.ThreadPoolExecutor@6e8cf4c6[Running, pool size = 5, active threads = 5, queued tasks = 4, completed tasks = 0]
 ​
 // 输出
 //pool-1-thread-3 ==> 输出666
 //pool-1-thread-4 ==> 输出666
 //pool-1-thread-5 ==> 输出666
 //pool-1-thread-2 ==> 输出666
 //pool-1-thread-1 ==> 输出666

线程池处理Callable任务

 // 让这个类实现Callabl接口
 public class MyCallable implements Callable<String> {
     private int n;
 ​
     public MyCallable(int n) {
         this.n = n;
     }
 ​
     // 重写call方法
     @Override
     public String call() throws Exception {
         int sum = 0;
         // 描述线程任务,返回线程执行的结果
         for (int i = 0; i <= n; i++) {
             sum += i;
         }
         return Thread.currentThread().getName() + "线程求出了1-" + n + "的值是: " + sum;
     }
 }
 ​
 public static void main(String[] args) throws Exception {
 ​
     ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
             TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
             new ThreadPoolExecutor.CallerRunsPolicy());
 ​
     Future<String> f1 = pool.submit(new MyCallable(100));
     Future<String> f2 = pool.submit(new MyCallable(200));
     Future<String> f3 = pool.submit(new MyCallable(300));
     Future<String> f4 = pool.submit(new MyCallable(400));
     System.out.println(f1.get());
     System.out.println(f2.get());
     System.out.println(f3.get());
     System.out.println(f4.get());
 ​
     //pool-1-thread-1线程求出了1-100的值是: 5050
     //pool-1-thread-2线程求出了1-200的值是: 20100
     //pool-1-thread-3线程求出了1-300的值是: 45150
     //pool-1-thread-3线程求出了1-400的值是: 80200
 }

Executors工具类实现线程池:

image-20250309122043412

image-20250309123553640

并发、并行、生命周期

进程:

· 正在运行的程序(软件)就是一个独立的进程。

· 线程是属于进程的,一个进程中可以同时运行多个线程

· 进程中的多个线程是并发和并行执行的

并发:

· 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPUI会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉就是这些线程在同时执行,这就是并发。

并行:

· 在同一个时刻上,同时有多个线程在被CPU调度执行。(例8H16C的CPU可以同时处理16个线程)

生命周期:

· Java总共定义了6种线程状态

NEW:新建状态

RUNNABLE:可运行状态

BLOCKED:锁阻塞状态

WATTING:计时等待状态

TIMED_WATTING:无限等待状态

TERMINATED:终止状态

image-20250309174859420

悲观锁、乐观锁

悲观锁:一上来就加锁,每次只能一个线程进入,访问完毕后再解锁。(线程安全、性能较差)

乐观锁:一上来不上锁,线程同时执行,等要出现线程安全的时候才开始控制。(线程安全,性能较好)

· 整数修改的乐观锁:原子类AtomicInteger实现

 public static void main(String[] args) {
 Runnable target = new MyRunnable();
 ​
 for (int i = 0; i < 100; i++) {
 new Thread(target).start();
     }
 }
 ​
 // 定义一个任务类,实现Runnable接口
 public class MyRunnable implements Runnable{
     //private static int count;
     private AtomicInteger count = new AtomicInteger();  // 原子类
     // 重写runnable的run方法
     @Override
     public void run() {
         // 线程要执行的任务
         for (int i = 0; i < 100; i++) {
       //      synchronized (this) {     // 悲观锁
       //          System.out.println(Thread.currentThread().getName() +
       //                  "count =========>" + ++count);
                 System.out.println(Thread.currentThread().getName() +
                         "count =========>" + count.incrementAndGet());  // 乐观锁
             
          //   }
         }
     }
 }

Java高级

单元测试

image-20250310100246437

image-20250310100837321

image-20250310100921334

 public class StringUtil {
     public static void printNumber(String name){
         if(name == null){
             System.out.println(0);
             return;
         }
         System.out.println("名字长度是: " + name.length());
     }
 ​
     public static int getMaxIndex(String data){
         if(data == null){
             return -1;
         }
         return data.length();
         //return data.length() - 1;
     }
 }
 ​
 /**
  * 测试类
  */
 public class StringUtilTest {
     // 公开、无返回值、无参数列表
     @Test   // 测试方法注解
     public void testPrintNumber(){
         StringUtil.printNumber("admin");
         StringUtil.printNumber(null);
     }
 ​
     @Test   // 测试方法注解
     public void testlGetMaxIndex(){
         int index = StringUtil.getMaxIndex("admin");
         int index1 = StringUtil.getMaxIndex(null);
 ​
         System.out.println(index);
         System.out.println(index1);
 ​
         // 断言机制:预测业务方法的结果
         Assert.assertEquals("业务逻辑存在BUG", 4, index);
         //java.lang.AssertionError: 业务逻辑存在BUG
         //预期:4
         //实际:5
     }
 }

image-20250310111837564

反射

image-20250310112314369image-20250310112521730

获取类:

image-20250310112927627

 // 方式一:Class c1 = 类名.class
 Class c1 = Student.class;
 System.out.println(c1.getName());   // ren.imuzi0221.reflect.Student --全类名
 System.out.println(c1.getSimpleName()); // Student -- 简名
 ​
 // 方式二:调用Class提供的方法 public static Class forName(String package)
 Class c2 = Class.forName("ren.imuzi0221.reflect.Student");
 System.out.println(c1 == c2);   // true 拿到的是同一个学生对象
 ​
 // 方式三:调用Object提供的方法 public Class getClass(); Class c3 = 对象.getClass()
 Student s1 = new Student();
 Class c3 = s1.getClass();
 System.out.println(c2 == c3);   // true 拿到的是同一个学生对象

获取类的构造器:

image-20250310131240927

 public class TestConstrucutor {
     @Test
     public void testGetConstructors(){
         // 反射第一步:先得到这个类的Class对象
         Class c = Cat.class;
         
         // 获取类的全部构造器
         // Constructor[] constructors = c.getConstructors();    获取全部public修饰的构造器
         Constructor[] constructors = c.getDeclaredConstructors();   // 获取全部构造器(只要存在就能得到)
         // 遍历数组中的每个构造器对象
         for (Constructor constructor : constructors) {
             System.out.println(constructor.getName() + "--->"
             + constructor.getParameterCount());
         }
         //ren.imuzi0221.reflect.Cat--->0
         //ren.imuzi0221.reflect.Cat--->2
     }
 ​
     @Test
     public void testGetConstructor() throws Exception {
         Class c= Cat.class;
 ​
         // Constructor constructor = c.getConstructor();   // 获取某个public修饰的构造器
         Constructor constructor = c.getDeclaredConstructor();  // 获取某个构造器(只要存在就能得到)
 ​
         Cat cat = (Cat) constructor.newInstance();  // 初始化对象返回
         constructor.setAccessible(true);    // 禁止检查访问权限(破坏封装性)
         System.out.println(cat);    // Cat{name='null', age=0}
 ​
         System.out.println(constructor.getName() + "--->"
                 + constructor.getParameterCount());
         //ren.imuzi0221.reflect.Cat--->0
 ​
         Constructor constructor1 = c.getDeclaredConstructor(String.class, int.class);
 ​
         Cat cat2 = (Cat) constructor1.newInstance("叮当猫", 3);    // 初始化对象返回
         constructor1.setAccessible(true);    // 禁止检查访问权限(破坏封装性)
         System.out.println(cat2);    // Cat{name='叮当猫', age=3}
         System.out.println(constructor1.getName() + "--->"
                 + constructor1.getParameterCount());
         //ren.imuzi0221.reflect.Cat--->2
     }
 }

获取类的成员变量:

 public class TestField {
     @Test
     public void testGetFields() throws Exception {
      Class c = Cat.class;
         Field[] fields = c.getDeclaredFields();
         for (Field field : fields) {
             System.out.println(field.getName() + "--->" + field.getType());
         }
 //        name--->class java.lang.String
 //        age--->int
 //        a--->int
 //        COUNTRY--->class java.lang.String
 ​
         Field name = c.getDeclaredField("name");
         System.out.println(name.getName() + "--->" + name.getType());  // name--->class java.lang.String
 ​
         Cat cat = new Cat();
         name.setAccessible(true);   // 禁止访问控制权限
         name.set(cat, "卡菲猫");
         System.out.println(cat);    // Cat{name='卡菲猫', age=0}
         
         System.out.println(name.get(cat));  // 卡菲猫
     }
 }

获取类的成员方法:

image-20250310163507996

 public class TestMethod {
     @Test
     public void testGetMethods() throws Exception {
         Class c = Cat.class;
         Method[] methods = c.getDeclaredMethods();
         for (Method method : methods) {
             System.out.println(method.getName() + "--->"
                     + method.getParameterCount() + "--->"
                     + method.getReturnType());
             //getName--->0--->class java.lang.String
             //run--->0--->void
             //toString--->0--->class java.lang.String
             //setName--->1--->void
             //getAge--->0--->int
             //setAge--->1--->void
             //eat--->0--->void
             //eat--->1--->class java.lang.String
         }
         Method run = c.getDeclaredMethod("run");
         System.out.println(run.getName() + "--->"
                 + run.getParameterCount() + "--->"
                 + run.getReturnType());
         //run--->0--->void
         Cat cat = new Cat();
         run.setAccessible(true);
         run.invoke(cat);    // 🐱猫跑的贼快
 ​
         Method eat = c.getDeclaredMethod("eat", String.class);
         System.out.println(eat.getName() + "--->"
                 + eat.getParameterCount() + "--->"
                 + eat.getReturnType());
 ​
         eat.setAccessible(true);
         System.out.println(eat.invoke(cat, "🐟鱼"));  // 🐱猫最爱吃:🐟鱼
         //eat--->1--->class java.lang.String
     }
 }

注解

image-20250310171751482

image-20250310171716146

image-20250310172408399

image-20250310172724139


0

评论区