返回

Java基础-2

java基础-1

方法(即函数)

what is 方法

1、理解:方法是程序中最小的执行单元,如main方法【其实就是函数

2、什么时候用,有什么用:重复的代码、或具有普遍特性和功能的代码可以抽取到方法中,如发射功能

3、好处

  • 将一些代码打包在一起,提高代码的复用性
  • 提高代码的可维护性

4、写方法时注意思维,三连问:我要干嘛?我需要用到什么?是否需要返回值?

方法的格式

1、分为两步:(1)方法定义(2)方法调用

2、形参和实参

  • 形参:方法定义中的参数
  • 实参:方法调用中的参数

3、注意事项

  • 方法不调用就不执行
  • 方法与方法平级,且不能互相嵌套(可以嵌套)
  • return关键字
    • 方法没有返回值:可以省略不写;如果一定要写,只写return,后面不能跟具体数据,表示结束方法
    • 方法有返回值:必须要写,且和返回值类型要一致
    • return与方法有关,与循环无关,执行return后整个方法全部结束(包括方法里的循环)(与break有点像)
 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
//不带返回值的方法定义和调用
//缺点:大部分情况我们希望能够拿到返回值并用该值进行后续一系列操作,而不是把值打印在控制台上
public static void 方法名(参数1,参数2,...) {...}

public static void main(String[] args){
    getSum(10,20);   //实参
}
public static void getSum(int num1,int num2){     //形参
    int result = num1 + num2;
    System.out.println(result);
}

//-----------------------------------------------------------------

//带返回值的方法定义和调用
public static 返回值类型 方法名(参数){
    方法体
    return 返回值
}

public static void main(String[] args){
                                 //直接调用,上述示例就是
    int sum = getSum(10,20);    //赋值调用
    System.out.println(getSum(10,20));    //输出调用
}
public static int getSum(int a,int b){    
    int result = a + b;
    return result;
}

方法重载

image-20250421115829334

这样定义方法和调用方法就不需要那么麻烦了,只需要一个名称就可以了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Demo1{

public static void Sum(int num1,int num2){     
    return num1 + num2;
}
public static void Sum(int num1,int num2,int num3){     
    return num1 + num2 + num3;
}

}

方法内存图★

  • 方法被调用之后就会进运行(因为可以嵌套,符合后进先出)
  • 方法执行完毕之后,出栈,方法里定义的变量随之消失
image-20250421120133090
image-20250421120217742
image-20250421120348118
image-20250421120814582

基本和引用数据类型🤔

  • 简单记忆:除了基本数据类型其他都是引用数据类型
  • 理解记忆:(内存角度理解)只要是new出来的都是引用数据类型,用的不是真实的数据

基本数据类型变量中存储的是真实的值;而引用数据类型中变量存储的是地址值而不是真实的值,即引用了其他空间中的数据

image-20250421121128909
  • 基本数据类型:存储的真实数据
    • 赋值给其他变量的,也是赋的真实值
  • 引用数据类型:存储的是地址值,真实的数据存储在其他空间中
    • 赋值给其他变量,赋的是地址值

面向对象

简单理解就是找现成工具使用。重点学的就是:学习获取已有对象并使用、学习如何设计对象并使用。

类和对象

(1)理解

在java中,必须先设计类,才能获得对象。两者之间的关系为:

  • 类:是对象共同特征的描述(设计图)
  • 对象:是真实存在的具体东西/实例(根据设计图创造出的工具)
    • new一个东西就是创建了一个对象
1
2
3
4
类是一个模板,描述对象的状态和行为
对象是类的一个实例,有状态和行为
方法就是行为,一个类可以有很多行为
实例变量就是对象的实例,对象状态由zhe'x
image-20250421125503041

(2)类的定义

类的组成是由属性和行为两部分组成,图如上

  • 属性:在类中通过成员变量来体现
  • 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Phone {
    //成员变量
    String brand;
    int price;

    //成员方法
    public void call() {
        System.out.println("打电话");
    }

    public void sendMessage() {
        System.out.println("发短信");
    }
}

(3)对象的使用

  • 创建对象的格式:类名 对象名 = new 类名();
  • 调用成员的格式:对象名.成员变量对象名.成员方法()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class PhoneDemo {
    public static void main(String[] args) {
        //创建对象
        Phone p = new Phone();

        //使用成员变量
        System.out.println(p.brand);
        System.out.println(p.price);

        p.brand = "小米";
        p.price = 2999;

        System.out.println(p.brand);
        System.out.println(p.price);

        //使用成员方法
        p.call();
        p.sendMessage();
    }
}

(4)类的注意事项

  • 用来描述一类事物的类,专业叫做:Javabean类。在Javabean类中,是不写main方法的;
  • 之前,编写main方法的类,叫做测试类。我们可以在测试类中创建javabean类的对象并进行赋值调用;
  • 建议一个java文件只写一个类,因为一个文件中只能有一个类是public修饰的,写多个类没意义

类名首字母大写,驼峰命名。类中成员变量的完整定义格式为:

  • 修饰符 数据类型 变量名称 = 初始化值;
  • 数据类型 变量名称; (一般写法,比如上面定义的手机类)

但是修饰符可以先不写(还没学到);因为类是抽象的,一般也不写初始化值(虽然没写,但会有一个默认值),而是在创建对象的时候赋值。

image-20250421154940310

封装

介绍

(1)理解

封装是面向对象三大特征之一(封装,继承,多态)

封装就是把数据的属性和方法捆绑在一起,形成一个独立的单元。

封装代码实现将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。访问成员变量private,提供对应的get xxx()/set xxx()方法。

(2)封装好处

那可太多了,首先就是让编程变简单,java API文档已经有很多封装好的类以及对应的方法(比如String类,里面封装了很多方法),只需要查文档,找到相应的类,调方法就行。用什么找什么。

private 关键字

为了使数据更安全,防止非法数据,在编写成员变量和方法时看情况使用private关键字。private是一个权限修饰符,可以用来修饰成员(成员变量,成员方法)

被private修饰的成员,只能在本类进行访问,而public正好相反,能被所有类访问

针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作:

  • 提供get变量名()方法,用于获取成员变量的值,方法用public修饰(无参有返回)
    • 因为无返回值,类型是void
  • 提供set变量名(参数)方法,用于设置成员变量的值,方法用public修饰(有参无返回)
    • 有返回值,类型是定义的类型
 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
class Student {
    //成员变量
    private String name;
    private int age;

    //get/set方法
    public void setName(String n) {
        name = n;
    }

    public String getName() {
        return name;
    }

    public void setAge(int a) {    //可以设置年龄范围,超出此范围非法
        age = a;
    }

    public int getAge() {
        return age;
    }

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

   
//----------------------- 学生测试类---------------
public class StudentDemo {
    public static void main(String[] args) {
        //创建对象
        Student s = new Student();

        //使用set方法给成员变量赋值
        s.setName("林青霞");
        s.setAge(30);

        s.show();

        //使用get方法获取成员变量的值
        System.out.println(s.getName() + "---" + s.getAge());
        System.out.println(s.getName() + "," + s.getAge());

    }
}

上面代码存在一个问题,即变量名字要知其义,如果改为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//--------------错误示例,输出为null------------
class Student {
    //成员变量
    private String name;  //默认值为null
    private int age;

    //get/set方法
    public void setName(String name) {
        name = name;
    }
    public String getName() {
        return name;
    }
}

这样的话,无论在测试类中传进来的参数是什么,输出得到的name永远是null(形参name自己赋给自己,成员变量name值没有变,仍为null)。这需要先了解两个概念:

  • 成员变量和局部变量
image-20250421172241385

根据就近原则,age输出的是局部变量值10。如果想要输出成员变量值null,就需要this关键字。

1
2
3
4
5
6
7
public class GirFriend{
	private int age;
	public void method(){
		int age = 10;
		System.out.printIn(this.age);
	}
}

this 关键字

  • this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题
    • 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
    • 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
 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
public class Student {
    private String name;
    private int age;

    public void setName(String name) {
        //等号右边:name为局部变量,即形参中传递的数据
        //等号左边:成员变量
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

构造方法

作用:在创建对象的时候给成员变量赋值的,完成对象数据的初始化

分类:无参构造(通过set、get赋值和取值)和有参构造(创建对象时自动初始化,get取值)

格式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Student{
	private String name;
	private int age;
	
	// ---空参构造方法---
	public Student(){
		...
	}
	
	// ---带参构造方法---
	public Student(String name,int age){
		...
	}
}

特点

  • 方法名与类名相同,大小写也要一致
  • 没有返回值类型,连void也没有
  • 没有返回值,不能写return

执行时机:

  • 创建对象的时候,虚拟机会自动调用构造方法,作用是给成员变量初始化的
  • 每创建一次对象,就会调用一次构造方法
 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
class Student {
    private String name;
    private int age;
	
    //-------------------分割线-----------------
    public Student() {}    //空参构造

    public Student(String name,int age) {   //有参构造
        this.name = name;
        this.age = age;
    }
    //-------------------分割线------------------

    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public int getAge() {
        return age;
    }
    public void show() {
        System.out.println(name + "," + age);
    }
}

//--------------------下面是测试类------------

public class StudentDemo {
    public static void main(String[] args) {
        //创建对象,调用空参构造,后续赋值的话通过set/get赋值
        Student s1 = new Student();
        s1.show();

        //创建对象,调用有参构造
        Student s2 = new Student("林青霞",30);
        s2.show();
    }
}

注意事项:

  • 构造方法的创建
    • 如果没有定义构造方法,系统将给出一个默认的无参数构造方法(上面写出的就是)
    • 如果定义了构造方法,系统将不再提供默认的构造方法
  • 构造方法的重载
    • 带参构造和无参构造,两者方法名相同,但参数不同,这叫构造方法的重载
  • 推荐的使用方式
    • 无论是否使用,带参构造和无参构造都写

任何类定义出来,默认就自带了无参构造器,写不写都有;

但一旦定义了有参构造,默认的无参构造就消失了,此时需要自己写无参构造器了;

标准JavaBean类

以上就是面向对象的全部知识,下面就是整合上面所学知识。

  • 类名驼峰命名、成员变量使用private修饰
  • 提供两个构造方法:无参构造和带参构造
  • get()和set()方法:提供每一个成员变量对应的set/get方法
  • 其他行为方法

带参和无参构造方法、get/set方法生成快捷键:alt + insert

插件:PTG-1秒生成标准javabean

 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
class Student {
    //成员变量
    private String name;
    private int age;

    //无参和带参构造方法
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //成员方法:get/set
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public int getAge() {
        return age;
    }

	//其他方法
    public void show() {
        System.out.println(name + "," + age);
    }
}

对象内存图

前面学过 java 的内存空间,先学习其中三个:

  • 栈:方法以及方法内的变量运行时使用的内存
  • 堆:new创建出来的存储在堆中,会为其分配一段地址空间,返回的值一个地址起始值
  • 方法区:类的字节码文件加载时进入此内存

多个对象内存图

image-20250422202238217
  • 多个对象在堆内存中,都有不同的内存划分,成员变量存储在各自的内存区域中,成员方法多个对象共用的一份
  • 创建对象时,对象存储的是一个地址值(new出来的存在堆中,堆给出的是地址值)
  • 对象中的成员变量是存储在堆中的

多个引用指向同一对象

image-20250422202518218

基本&引用数据类型

  • 基本数据类型:存的是真实值,存在自己空间中
    • 赋值给其他变量,也是赋的真实的值
  • 引用数据类型:自己空间存的是地址值,即引用的是别的地址空间存的值(数据存在其他空间)
    • 赋值给其他变量,赋的是地址值

this的内存原理

  • this的作用:区分局部变量和成员变量
  • this的本质:所在方法调用者的地址值
image-20250422204802386

成员变量和局部变量

了解即可

image-20250422205437981

字符串

java API

API,应用程序接口。(学会使用帮助文档)

  • API:就是JDK中提供的各种功能的Java类(这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习如何使用即可)
  • 简单理解:别人已经写好的东西,不需要自己编写,直接使用即可

String类-1

(1)String类概述

String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例。也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象。String 类在 java.lang 包下,所以使用的时候不需要导包!

(2)String类的特点

  • 字符串不可变,它们的值在创建后不能被更改
  • 虽然 String 的值是不可变的,但是它们可以被共享
  • 字符串效果上相当于字符数组( char[] ),但是底层原理是字节数组( byte[] )
1
2
3
String name = "嘿嘿"
name = "111"
// "嘿嘿"的内容并没有被“111”代替,而是创建了新的字符串“111”,name指向它而已

(3)String类的构造方法/创建String对象的方式

方法名 说明
public String() 创建一个空白字符串对象,不含有任何内容
public String(char[] chs) 根据字符数组的内容,来创建字符串对象
public String(byte[] bys) 根据字节数组的内容,来创建字符串对象
String s = “abc”; 直接赋值的方式创建字符串对象,内容就是abc,最常用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class StringDemo01 {
    public static void main(String[] args) {
        //public String():创建一个空白字符串对象,不含有任何内容
        String s1 = new String();
        System.out.println("s1:" + s1);

        //public String(char[] chs):根据字符数组的内容,来创建字符串对象
        char[] chs = {'a', 'b', 'c'};
        String s2 = new String(chs);
        System.out.println("s2:" + s2);

        //public String(byte[] bys):根据字节数组的内容,来创建字符串对象(查码表)
        byte[] bys = {97, 98, 99};
        String s3 = new String(bys);
        System.out.println("s3:" + s3);

        //String s = “abc”;	直接赋值的方式创建字符串对象,内容就是abc(最常用)
        String s4 = "abc";
        System.out.println("s4:" + s4);
    }
}

(4)字符串创建方式区别

  • 通过构造方法创建

    通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同

  • 直接赋值方式创建

    以“”方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护(有相同的字符串直接复用)

字符串操作-1

下面学习字符串的常见操作,也就是String类中方法的使用

字符串比较

(1)==原理

  • 比较基本数据类型:比较的是具体的值
  • 比较引用数据类型:比较的是对象地址值

(2)equals方法的使用

1
2
boolean equals(String s)     比较两个字符串内容是否相同、区分大小写
boolean equalslgnoreCase(String s)    忽略大小写比较

示例:

 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 class StringDemo02 {
    public static void main(String[] args) {
        //构造方法的方式得到对象
        char[] chs = {'a', 'b', 'c'};
        String s1 = new String(chs);
        String s2 = new String(chs);
        
        Scanner sc = new Scanner(System.in);
        String str1 = sc.next();

        //直接赋值的方式得到对象
        String s3 = "abc";
        String s4 = "abc";

        //比较字符串对象地址是否相同
        System.out.println(s1 == s2);    //False
        System.out.println(s1 == s3);    //False
        System.out.println(str1 == s1);  //False
        System.out.println(s3 == s4);    //True
        System.out.println("--------");

        //比较字符串内容是否相同
        System.out.println(s1.equals(s2));   //True
        System.out.println(s1.equals(s3));   //True
        System.out.println(s3.equals(s4));   //True
    }
}
  • Scanner键盘录入的内容是new出来的
  • 如果想比较字符串的内容,用String里的方法

遍历字符串

两个方法:

  • charAt():会根据索引获取对应的字符
  • length(): 会返回字符串的长度
1
2
3
4
5
public char charAt(int index):             根据索引返回字符
public int length():                       返回此字符串的长度

数组的长度:数组名.length                    是属性,不加括号
字符串的长度:字符串对象.length()             是方法,加括号

键盘录入一个字符串,使用程序实现在控制台遍历该字符串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Test2字符串直接遍历 {
    public static void main(String[] args) {

        //1.键盘录入一个字符串
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入字符串");
        String str = sc.next();
        System.out.println(str);

        //2.遍历
        for (int i = 0; i < str.length(); i++) {
            //快捷键:str.length().fori
            //ctrl + alt + V 自动生成左边的接受变量
            char c = str.charAt(i);
            System.out.println(c);
        }
    }
}

统计字符串

键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数

 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 Test4统计个数 {
    public static void main(String[] args) {
        //1.键盘录入一个字符串
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串");
        String str = sc.next();

        //2.统计 --- 计数器count
        int bigCount = 0;
        int smallCount = 0;
        int numberCount = 0;
        //得到这个字符串里面每一个字符
        for (int i = 0; i < str.length(); i++) {
            //i 表示字符串中的索引
            //c 表示字符串中的每一个字符
            char c = str.charAt(i);
            //char类型的变量在计算时会自动转换为int,查询码表(数字也应该带引号比较)
            if (c >= 'a' && c <= 'z') {
                smallCount++;
            }else if(c >= 'A' && c <= 'Z'){
                bigCount++;
            }else if(c >= '0' && c <= '9'){
                numberCount++;
            }
        }

        //3.当循环结束之后,三个变量记录的就是对应的个数
        System.out.println("大写字符有:" + bigCount + "个");
        System.out.println("小写字符有:" + smallCount + "个");
        System.out.println("数字字符有:" + numberCount + "个");
    }
}

字符串反转

定义一个方法,实现字符串反转。键盘录入一个字符串,调用该方法后,在控制台输出结果

 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 class Test6反转字符串 {
    public static void main(String[] args) {
        //1.定义一个字符串
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串");
        String str = sc.next();
        //2.定义一个方法,反转字符串,可以把字符串倒着遍历,再拼接
        String result = reverse(str);
        System.out.println(result);
    }

    //把传递进来的字符串进行反转
    public static String reverse(String str){
        //核心思想:倒着遍历并进行拼接就可以了
        //fori :正着遍历  forr:倒着遍历
        String s = "";     //拼接
        for (int i = str.length() - 1; i >= 0; i--) {
            s = s + str.charAt(i);
        }
        
        return s;

    }
}

字符串截取

字符串子串截取方法,下面是方法实现(方法重载)

  • String substring(int beginindex, int endindex) 左闭右开,截取后原字符串不变,因为字符串不会发生改变
  • String substring(int beginindex) 截取到末尾
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Stringdemo {
    public static void main(String[] args) {
		String phoneNumber = "1313669913"

        //截取手机号码中的前三位
        phoneNumber.substring(0, 3);
        String star = phoneNumber.substring(0, 3);
		
		//注意,字符串截取并不会对原字符串产生影响,如果想得到截取的子串,需要赋给变量接受
        System.out.println(phoneNumber);   //1313669913
        System.out.println(star);          //131

    }
}
  • 因为字符串(截取等)不会对原串发生影响,因此如果想要对字符串修改,比如屏蔽中间几位,就需要分别截取前中后三段并对中间修改,在拼接后实现

字符串替换

  • replace(target, replacement) 将字符串target替换为replacement
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class Test10多个敏感词替换 {
    public static void main(String[] args) {
        //1.先键盘录入要说的话
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入要说的话");
        String talk = sc.next();

        //2.定义一个数组用来存多个敏感词
        String[] arr = {"TMD","GDX","ctmd","ZZ","lj","FW","nt"};

        //3.把说的话中所有的敏感词都替换为***
        for (int i = 0; i < arr.length; i++) {
            talk = talk.replace(arr[i],"***");
        }

        //4.打印结果
        System.out.println(talk);    //后裔你玩什么啊,***,***,***,***
    }
}

StringBuilder类-2

(1)概述

StringBuilder 可以看成是一个容器,创建之后里面的内容是可变的。(与字符串最大区别)

作用:提高字符串的操作效率

应用场景:当我们在拼接字符串和反转字符串的时候会使用到

1
2
3
4
5
String result = s1 + s2 + ... + s99;     //假设s1-s99都是字符串
/* 
这样的话效率很低,因为字符串内容不可边,从左往右计算时,每两个相加都会产生一个新的字符串,会产生很多没用的中间结果,影响效率
所以可以使用StringBuilder来提高效率,它可看作是一个容器,其内容可变,在拼接时字符串都放进这个容器,只会有一个StringBuilder对象
*/

(2)构造方法

  • StringBuilder() 空参构造,创建一个空白可变字符串对象,不含任何内容
  • StringBuilder(String str) 创建(包含所给字符串str的)可变字符串对象

(3)StringBuilder 常用方法

  • public StringBuilder append(任意类型) 添加数据,并返回对象本身
  • public StringBuilder reverse() 反转容器中的内容
  • public int length() 返回长度,字符出现的个数
  • public String toString() 把StringBuilder对象转换为String

因为其内容是可变的,因为对其的操作都是直接作用在其自身的,修改后不需要赋给新变量接受

 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 class StringBuilderDemo3 {
    public static void main(String[] args) {
        //1.创建对象
        StringBuilder sb = new StringBuilder("abc");

        //添加元素
        sb.append(1);
        sb.append(2.3);
        sb.append(true);
        //反转
        sb.reverse();
        //获取长度
        int len = sb.length();
        System.out.println(len);

        //普及:因为StringBuilder是Java已经写好的类
        //java在底层对他做了一些特殊处理。因此打印对象不是地址值而是属性值
        System.out.println(sb);
        
        
        //1.创建对象
        StringBuilder sb = new StringBuilder();
        //2.添加字符串
        sb.append("aaa").append("bbb").append("ccc").append("ddd");
        System.out.println(sb);    //aaabbbcccddd
        //3.再把StringBuilder变回字符串
        String str = sb.toString();
        System.out.println(str);    //aaabbbcccddd
    }
}

(4)链式编程

上面涉及到了一个概念,链式编程:当我们在调用一个方法时,不需要用变量接受其结果,可以继续调用其他方法

1
2
int len = s1.substring(1).replace("A","Q").length();    //s1是字符串
sb.append("aaa").append("bbb").append("ccc").append("ddd");    //sb是StringBuilder对象

字符串操作-2

对称字符串

键盘接受一个字符串,程序判断出该字符串是否是对称字符串,并在控制台打印是或不是

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class StringBuilderDemo6 {
    public static void main(String[] args) {
        //1.键盘录入一个字符串
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串");
        String str = sc.next();

        //2.反转键盘录入的字符串
        String result = new StringBuilder().append(str).reverse().toString();

        //3.比较
        if(str.equals(result)){
            System.out.println("当前字符串是对称字符串");
        }else{
            System.out.println("当前字符串不是对称字符串");
        }

    }
}

拼接字符串

需求:定义一个方法,把 int 数组中的数据按照指定的格式拼接成一个字符串返回。

 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
//例如:数组为int[] arr = {1,2,3};执行方法后的输出结果为:[1, 2, 3]
public class StringBuilderDemo7 {
    public static void main(String[] args) {

        int[] arr = {1,2,3};
        //调用方法把数组变成字符串
        String str = arrToString(arr);
        System.out.println(str);
    }

    public static String arrToString(int[] arr){
        StringBuilder sb = new StringBuilder();
        sb.append("[");

        for (int i = 0; i < arr.length; i++) {
            if(i == arr.length - 1){
                sb.append(arr[i]);
            }else{
                sb.append(arr[i]).append(", ");
            }
        }
        sb.append("]");

        return sb.toString();
    }
}

StringJoiner类-3

  • StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的。
  • 作用:提高字符串的操作效率,而且代码编写特别简洁,但是目前市场上很少有人用。
  • JDK8出现的

(1)构造方法

  • public StringJoiner(间隔符号) 创建一个StringJoiner对象,指定拼接时的符号
  • public StringJoiner(间隔符号,开始符号,结束符号) 创建一个StringJoiner对象,指定拼接、开始、结束符号

(2)成员方法

  • public StringJoiner add(内容) 添加数据,并返回对象本身
  • public int length() 返回长度(字符出现个数,包括添加的符号和空格)
  • public String toString() 转换为字符串
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//1.创建对象
StringJoiner sj = new StringJoiner(", ","[","]");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
int len = sj.length();
System.out.println(len);   //15
//3.打印
System.out.println(sj);//[aaa, bbb, ccc]
String str = sj.toString();
System.out.println(str);//[aaa, bbb, ccc]

字符串原理

首先,字符串是引用类型的,存的是地址值

其次,字符串内容不可改,不可改,不可改!

如果我们看到要修改字符串的内容,有两个办法:

  • 用subString进行截取,把左边的字符截取出来拼接到右侧去

  • 可以把字符串先变成一个字符数组,然后调整字符数组里面数据,最后再把字符数组变成字符串。

(1)字符串存储的内存原理

  • 直接赋值会复用字符串常量池中的
    • 先检查字符串常量池中有没有字符串,如果有,不会创建新的,而是直接复用。如果没有,才会创建一个新的。
  • new出来的不会复用,而是开辟一个新的空间

(2)==

  • 基本数据类型比较的是数据值
  • 引用数据类型比较的是地址值

(3)字符串拼接底层原理

…看视频了解即可…

如果很多字符串变量拼接,不要直接+。在底层会创建多个对象,浪费时间,浪费性能,可以使用StringBuilder或StringJoiner。

(4)StringBuilder提高效率原理图

会创建一个容器(一个地址),添加字符串时都是添加到这个容器内,不会产生额外对象

image-20250424165209196
image-20250424165304386
image-20250424165339977

集合ArrayList

集合 vs 数组

  • 长度
    • 集合长度可变:添加数据的时候不需要考虑索引,默认将数据添加到末尾(自动变化)
    • 而数组长度不可变
  • 存储类型
    • 集合只能存储引用数据类型,如果要存基本数据类型,需要先变成其对应的包装类
    • 而数组既可以存基本数据类型,也可以存引用数据类型

ArrayList概述

  • 什么是集合:提供一种存储空间可变的存储模型,存储的数据容量可以发生改变
  • ArrayList集合特点:长度可以变化,只能存储引用数据类型。
  • 泛型的使用:用于约束集合中存储元素的数据类型(泛型写什么看元素类型)

因为 ArrayList 只能存储引用数据类型,创建对象时需要限定数据类型,例如创建数据对象时int[] arr = {1,2,3},限定了此数组只能存储int类型数据。所以用泛型来限定集合的存储类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class ArrayListDemo{
	public static void main(String[] args){
		//1.创建集合的对象
		//创建的是ArrayList的对象,而ArrayList是java已经写好的一个类
		//这个类在底层做了一些处理
		//打印对象输出的不是地址值,而是集合中存储的数据内容
		//在展示的时候会拿[]把所有的数据进行包裹
		ArrayList<String> list = new ArrayList<>();
		System.out.printIn(list);   // 输出为[]
	
	}
}

成员方法/常用方法

方法名 说明
public boolean add(E e) 将指定的元素追加到此集合的末尾
public boolean remove(E e) 删除指定元素,返回值表示是否删除成功
public E remove(int index) 删除指定索引处的元素,返回被删除的元素
public E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
public E get(int index) 返回指定索引处的元素
public int size() 返回集合中的元素的个数
  • 注意,集合的长度是可变的,一开始初始化长度为0,即长度为0

第三个方法可以根据索引删除,说明集合是有索引的(也就是说,集合里可以有重复元素)

 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
public class ArrayListDemo02 {
 public static void main(String[] args) {
     //创建集合
     ArrayList<String> list = new ArrayList<>();

     //添加元素,返回值是布尔类型
     list.add("hello");
     list.add("hello");
     list.add("world");
     list.add("java");

     //删除元素
     boolean result1 = list.remove("hello");   //只会删除第一个出现的hello
     System.out.println(result1);     //True
     System.out.println(list);      //[hello,world,java]
     //根据索引删除元素
     String str = list.remove(2);   
     System.out.println(str);     //world
     System.out.println(list);      //[hello,hello,java]

     //修改指定索引处的元素
     String result = list.set(1,"Hello")
     System.out.println(result);    // hello
     System.out.println(list);      //[hello,Hello,world,java]

     //查询元素
		String s = list.get(0);
     System.out.println(s);    // hello

     //获取集合长度(元素的个数),遍历集合
     //list.fori
     for (int i = 0; i < list.size(); i++){
         String str = list.get(i);
         System.out.println(str);
     }

 }
}

包装类

基本数据类型对应的包装类:大部分都是首字母变大写即可(两个特殊)

1
2
3
byte --> Byte   double --> Double  ...
char --> Character(特殊1)
int  --> Integer(特殊2)

集合操作练习

(1)遍历集合元素

  • list.fori 正向遍历; list.forr 倒着遍历
 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 class ArrayListDemo3 {
    public static void main(String[] args) {
        /*创建一个存储字符串的集合,使用程序实现在控制台遍历该集合
        格式为[元素1,元素2,元素3]  */
        
        //1.创建集合对象
        ArrayList<String> list = new ArrayList<>();

        //2.添加元素
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");

        //3.遍历
        System.out.print("[");
        for (int i = 0; i < list.size(); i++) {
            if(i == list.size() - 1){
                System.out.print(list.get(i));
            }else{
                System.out.print(list.get(i) + ", ");
            }
        }
        System.out.print("]");
    }
}

(2)集合存储对象并遍历

  • 泛型写什么看元素类型,题目要求是存储学生对象,所以应该写相应的学生类
 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
public class ArrayListDemo4 {
    public static void main(String[] args) {
        //需求:创建一个存储学生对象的集合,使用程序实现在控制台遍历该集合
        
        //1.创建集合对象,用来存储数据(Student是一个学生类)
        ArrayList<Student> list = new ArrayList<>();

        //2.创建学生对象
        Student s1 = new Student("zhangsan",16);
        Student s2 = new Student("lisi",15);
        Student s3 = new Student("wangwu",18);

        //3.把学生对象添加到集合中
        list.add(s1);
        list.add(s2);
        list.add(s3);

        //4.遍历
        for (int i = 0; i < list.size(); i++) {
            //i 依次表示集合中的每一个索引
            Student stu = list.get(i);
            System.out.println(stu.getName() + ", " + stu.getAge());
        }

    }
}

(3)集合存储对象并遍历(键盘录入)

  • 集合长度是可变的,一开始长度为0,所以遍历创建对象时不能用i < list.size(),这样就没有进入循环
  • 创建对象必须写在循环里,创建对象会分配一个地址值,写在循环外只会分配一个对象,修改的永远是这一个对象的值
 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 ArrayListDemo4 {
    public static void main(String[] args) {
        //需求:创建一个存储学生对象的集合,使用程序实现在控制台遍历该集合
        
        //1.创建集合对象,用来存储数据(Student是一个学生类)
        ArrayList<Student> list = new ArrayList<>();

        //2.创建学生对象
        Scanner sc = new Scanner(System.in);
        for (int i = 0;i < 3;i++){
        	Student s = new Student();   //必须写在循环里
        	System.out.,printIn("请输入学生姓名:");
        	String name = sc.next();
        	System.out.,printIn("请输入学生年龄:");
        	int age = sc.next();
        	
        	//把name和age赋给对象
        	s.setName(name);
        	s.setAge(age);
        	
        	//3.把学生对象添加到集合中
        	list.add(s);
        }

        //4.遍历
        for (int i = 0; i < list.size(); i++) {
            //i 依次表示集合中的每一个索引
            Student stu = list.get(i);
            System.out.println(stu.getName() + ", " + stu.getAge());
        }

    }
}

(4)查找用户索引

 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
public class ArrayListDemo6 {
    public static void main(String[] args) {
        /*需求:
        1,main方法中定义一个集合,存入三个用户对象。
        用户属性为:id,username,password
        2,要求:定义一个方法,根据id查找对应的学生信息。
        如果存在,返回索引
        如果不存在,返回-1*/

        //1.创建集合对象
        ArrayList<User> list = new ArrayList<>();

        //2.创建用户对象
        User u1 = new User("heima001", "zhangsan", "123456");
        User u2 = new User("heima002", "lisi", "1234");
        User u3 = new User("heima003", "wangwu", "1234qwer");

        //3.把用户对象添加到集合当中
        list.add(u1);
        list.add(u2);
        list.add(u3);

        //4.调用方法,通过id获取对应的索引
        int index = getIndex(list, "heima001");

        System.out.println(index);
    }


    public static int getIndex(ArrayList<User> list, String id) {
        //遍历集合得到每一个元素
        for (int i = 0; i < list.size(); i++) {
            User u = list.get(i);
            String uid = u.getId();
            if(uid.equals(id)){
                return i;
            }
        }
        //因为只有当集合里面所有的元素都比较完了,才能断定id是不存在的。
        return -1;
    }
}

(5)返回多个数据

  • return只能返回一个数据,如果在方法中要返回多个数据,可以把这些数据先放到一个容器中,再把容器返回
  • 容器可以是集合(首选,因为长度可变)、数组
 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
/*
需求:
        定义Javabean类:Phone
        Phone属性:品牌,价格。
        main方法中定义一个集合,存入三个手机对象。
        分别为:小米,1000。苹果,8000。锤子 2999。
        定义一个方法,将价格低于3000的手机信息返回。
*/
public class Test8 {
    public static void main(String[] args) {

        //1.创建集合对象
        ArrayList<Phone> list = new ArrayList<>();

        //2.创建手机的对象
        Phone p1 = new Phone("小米",1000);
        Phone p2 = new Phone("苹果",8000);
        Phone p3 = new Phone("锤子",2999);

        //3.添加数据
        list.add(p1);
        list.add(p2);
        list.add(p3);

        //4.调用方法 & 遍历集合
        ArrayList<Phone> phoneInfoList = getPhoneInfo(list);

        for (int i = 0; i < phoneInfoList.size(); i++) {
            Phone phone = phoneInfoList.get(i);
            System.out.println(phone.getBrand() + ", " + phone.getPrice());
        }
    }

	
	//方法返回类型是一个集合,所以要写集合
    public static ArrayList<Phone> getPhoneInfo(ArrayList<Phone> list){
        //定义一个集合用于存储价格低于3000的手机对象
        ArrayList<Phone> resultList = new ArrayList<>();

        for (int i = 0; i < list.size(); i++) {
            Phone p = list.get(i);
            int price = p.getPrice();
            //如果当前手机的价格低于3000,那么就把手机对象添加到resultList中
            if(price < 3000){
                resultList.add(p);
            }
        }
        //返回resultList
        return resultList;
    }
}

面向对象进阶

回顾复习

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Student {
 // 1.成员变量
 private String name ;
 private char sex ;
 private int age;

 //2.成员方法(行为)
 public void study(){
 	System.out.printIn(name + "正在学习");
 }

 //3.构造方法(初始化成员变量)
 空参构造有参构造get()set()方法
}


// 创建对象
Student stu = new Student();

1、封装:

  • 使用 private 关键字来修饰成员变量
  • 使用public修饰getter和setter方法

2、构造方法:创建对象时给成员变量初始化

3、this关键字:代表所在类的当前对象的引用(地址值),即代表当前对象

static

概述

(1)概述

static是静态的意思。 static可以修饰成员变量或者修饰方法。

关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被static修饰的成员是属于类的是放在静态区中,没有static修饰的成员变量和方法则是属于对象的。我们上面案例中的成员变量(name、age等)都是没有static修饰的,所以属于每个对象。

在java中,变量和方法等是存在属性的,Java是通过static关键字来区分的。static关键字在Java开发非常的重要,对于理解面向对象非常关键。

(2)我的理解

先前所写的private所修饰的name、age等成员变量,在创建很多个对象后是每个对象都有的属性,通过赋不同值得到不同对象,可以认为是每个对象私有的

而static修饰符所修饰的成员变量或方法就是公共的,相当于在类里开辟了一段公共空间,每个人都可以共享和修改此区域。所创建的任意一个对象修改static所修饰的变量的值后,其他对象使用这个值也会跟着改变。因为是公共的,所以推荐通过类名调用,也可以通过对象名调用。

格式和使用

特点:被该类所有对象共享

调用方式:

  • 类名调用(推荐) Student.schoolName = "黑马"
  • 对象名调用 stu.schoolName = "黑马"

(1)static修饰静态变量

有static修饰成员变量,说明这个成员变量是属于类的,这个成员变量称为类变量或者静态成员变量。 直接用 类名访问即可。因为类只有一个,所以静态成员变量在内存区域中也只存在一份。所有的对象都可以共享这个变量。(没有被static修饰的就是成员变量或叫实例成员变量)

格式:修饰符 static 数据类型 变量名 = 初始值;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Student {
    public static String schoolName = "传智播客" // 属于类,只有一份。
    // .....
}

public static void  main(String[] args){
    System.out.println(Student.schoolName); // 传智播客
    Student.schoolName = "黑马程序员";     //修改公共变量
    System.out.println(Student.schoolName); // 黑马程序员
}

(2)static修饰静态方法

有static修饰成员方法,说明这个成员方法是属于类的,这个成员方法称为类方法或者静态方法。 直接用 类名访问即可。因为类只有一个,所以静态方法在内存区域中也只存在一份。所有的对象都可以共享这个方法。(没有被static修饰的就是成员方法或实例成员方法)

特点:多用在测试类和工具类中;Javabean类中很少会用;静态方法没有this关键字

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Student{
    public static String schoolName = "传智播客" // 属于类,只有一份。
    // .....
    public static void study(){
    	System.out.println("我们都在黑马程序员学习");   
    }
}

public static void  main(String[] args){
    Student.study();
}

扩展补充,现在学过的几种类:

  • Javabean类:基本类,用来描述一类事物的类,如Student类
  • 测试类:用来检查其他类是否正确,带有main方法的类,程序入口
  • 工具类:帮助做一些事情,但是不描述任何事物的类
    • 工具类一般私有化构造方法,不能直接被创建对象;方法定义为静态
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class ArrayUtil{
	//私有化构造方法,不让外界创建对象
	private ArrayUtil(){}
	
	//其他方法(行为)定义为静态的(公共的),方便调用
    //求浮点数组的平均值
	public static double getAverage(double[] arr){
		double sum = 0;
		for (int i = 0;i < arr.length;i++){
			sum = sum + arr[i];
		}
		return sum / arr.length;
	}
}

小结😊

  • static 修饰成员变量或者成员方法时,该变量称为静态变量,该方法称为静态方法。该类的每个对象都共享同一个类的静态变量和静态方法。任何对象都可以更改该静态变量的值或者访问静态方法。但是不推荐这种方式去访问。因为静态变量或者静态方法直接通过类名访问即可,完全没有必要用对象去访问。

  • 无static修饰的成员变量或者成员方法,称为实例变量,实例方法,实例变量和实例方法必须创建类的对象,然后通过对象来访问。

  • static修饰的成员属于类,会存储在静态区,是随着类的加载而加载的,且只加载一次,所以只有一份,节省内存。存储于一块固定的内存区域(静态区)。无static修饰的成员,是属于对象,对象有多少个,他们就会出现多少份。所以必须由对象调用。

  • 总结:静态方法不能访问非静态;非静态可以访问所有;静态方法没有this关键字(因为是共享的,被所有对象共享,根本不知道指的是那个对象,所以没有this关键字)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Student{
    String name;
    static String teacherName;
	
    //下面这个静态方法运行会报错,
    //静态区根本找不到name,只有teacherName,因此报错,所以静态方法不能访问非静态
    /*
    public static void method(){
    	System.out.println(name + "---" + teacherName);   
    }
    */
    
    //测试类在调用的时候会指定具体的对象,比如s1.show()说明this-->s1
    //这时候指的就是s1.name
    public void show(){
    	System.out.println(name + "---" + teacherName);   
    }
}

public static void  main(String[] args){
    Student s1 = new Student("张三",23);
    s1.show();
}

继承🌙

概述

1、继承:就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为(私有的子类当然不能访问了呦)。继承的类称为子类(派生类);被继承的类称为父类(基类或超类)

2、好处:提高代码的复用性

3、格式:public class 子类 extends 父类 {}

4、特点:Java是单继承的,一个类只能继承一个直接父类;不支持多继承,但支持多层继承

  • 比如田园猫继承自猫类,猫继承自动物类,那猫可以使用动物类中公有的变量和方法(符合实际)

5、始祖:Java中所有的类都直接或者间接继承于Object类(如果一个类没有父类,默认继承Object类)

 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
//--------------------父类----------------
public class Human {
private String name ;
private int age ;

// get()、set()省略
}


//---------------子类:教师类----------------
public class Teacher extends Human {
private double salary ;   // 工资

// 特有方法
public void teach(){
   System.out.println("老师在认真教技术!")
}

// get()、set()省略
}


//---------------子类:学生类----------------
public class Student extends Human{
}


//-----------------测试类----------------
public class Test {
 public static void main(String[] args) {
     //(间接和直接)父类的非私有化方法和属性都能继承
     Teacher dlei = new Teacher();
     dlei.setName("播仔");
     dlei.setAge("31");
     dlei.setSalary(1000.99);
     System.out.println(dlei.getName());
     System.out.println(dlei.getAge());
     System.out.println(dlei.getSalary());
     dlei.teach();

     Student xugan = new Student();
     xugan.setName("播仔");
     xugan.setAge("31");
     System.out.println(xugan.getName());
     System.out.println(xugan.getAge());
 }
}

子类继承😂

父类中有的就三种:构造方法、成员变量、成员方法。子类到底可以继承父类中的哪些内容?(重点)

  • 误区一:父类私有的东西,子类就无法继承
  • 误区二:父类中非私有的成员,就被子类继承下来了
image-20250427104810668

子类不能继承父类的构造方法。值得注意的是子类可以继承父类的私有成员(成员变量,(虚)方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。

1
2
3
理解:父类中私有的成员变量/方法就是父类自己的(私房钱),子类如果能继承一旦修改了(花掉了),那父类就没有了;因为实际是子类可以继承,但不能直接使用,相当于私有成员上了一把锁,如果要使用的话需要通知父类一声(即getter/setter方法)

子类使用成员变量的时候,会先在子类(即本类)里找,如果找不到,则从父类里找;但子类使用成员方法的时候不是这样,java对方法做了优化,会直接从虚方法表里找(从Object类一层一层继承和添加下来的),如果没有则是父类里找、间接父类里找...

私有成员变量可以被继承,只不过要通过getter/setter方法间接使用;而私有成员方法无法被继承和使用(没有getter/setter方法),只有虚方法表中的方法可以被继承

image-20250427105201503
 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 class Demo03 {
    public static void main(String[] args) {
        Zi z = new Zi();
        System.out.println(z.num1);
		// System.out.println(z.num2); // 私有的子类无法使用
        // 通过getter/setter方法访问父类的private成员变量
        System.out.println(z.getNum2());

        z.show1();
        // z.show2(); // 私有的子类无法使用
    }
}

class Fu {
    public int num1 = 10;
    private int num2 = 20;

    public void show1() {
        System.out.println("show1");
    }

    private void show2() {
        System.out.println("show2");
    }

    public int getNum2() {
        return num2;
    }
    public void setNum2(int num2) {
        this.num2 = num2;
    }
}

class Zi extends Fu {
}
  • 在创建对象的时候会把子类和其父类都加载进堆内存

继承内存图(公有成员变量可以直接使用)

image-20250427105836014

继承内存图(私有成员变量不能直接继承)

image-20250427105050279

继承内存图(只有虚方法表中的方法可以直接继承和使用)

image-20250427105527453

继承后-成员变量

1、当类之间产生了继承关系后,成员变量的访问特点:就近原则,谁近用谁(先在局部位置找–>本类找–>父类找)

2、如果想直接访问父类的成员变量,使用super关键字。super代表的是父类对象的引用,this代表的是当前对象的引用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Fu {
    String name = "父";
    String age = 18;
}

class Zi extends Fu {
    String name = "子";
    public void ziShow(){
        String name = "ziShow";
        System.out.printIn(age);   //18   就近原则,先从局部方法里找,没有的话一级一级往上
        System.out.printIn(this.age);   //18  直接从本类里找,没有,从父类里找
        System.out.printIn(super.age);    //18   直接从父类;里找
        System.out.printIn(name);    //ziShow
        System.out.printIn(this.name);   //子    调用本类中的name
        System.out.printIn(super.name);    //父   调用父类中的name
    }
}

总结,如果出现重名成员变量:

  • System.out.printIn(name); 从局部位置开始往上找
  • System.out.printIn(this.name); 从本类成员位置开始往上找
  • System.out.printIn(super.name); 从父类成员位置开始往上找

继承后-成员方法

当类之间产生了关系,成员方法访问特点:也是就近原则。(先本类,在父类)

super调用,直接访问父类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Fu {
	public void show() {
		System.out.println("Fu show");
	}
}

class Zi extends Fu {
	//子类重写了父类的show方法
	public void show() {
        super.show();
		System.out.println("Zi show");
	}
}

public class ExtendsDemo05{
	public static void main(String[] args) {
		Zi z = new Zi();
		z.show();  // 同时输出Fu show和Zi show   子类中有show方法,优先子类
	}
}

上面一段代码子类和父类中的方法名一样,这称为方法重写。(与重载不同)

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

使用场景:当父类的方法不满足子类现在的需求时,需要进行方法重写,覆盖继承的父类方法

格式@Override

1
2
3
@Override:重写注解,放在重写的方法上,校验子类重写时语法是否正确
这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。
建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!

示例代码:

1
2
3
4
5
6
7
8
class Zi extends Fu {
	//子类重写了父类的show方法
	@Override
	public void show() {
        super.show();
		System.out.println("Zi show");
	}
}

注意事项:方法重写本质-覆盖虚方法表中的方法

  • 只有添加到虚方法表中的方法才能被重写
  • 方法重写是发生在子父类之间的关系。(必须要有继承)
  • 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。(空着不写<protected<public)
  • 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
image-20250427115445930

继承后-构造方法

首先,子类无法继承父类的构造方法

  • 构造方法的名字是与类名一致的。所以子类无法继承父类构造方法,但可以通过super调用

  • 构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子)

继承后子类构造方法的特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法。如果想调用父类有参构造,必须手动写super进行调用

 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
class Person {
    private String name;
    private int age;
	
	//无参构造
    public Person() {
        System.out.println("父类无参构造");
    }
}


class Student extends Person {
    private double score;
	
    //无参构造
    public Student() {
        super();     // 调用父类无参构造,默认就存在,可以不写,必须在第一行
        System.out.println("子类无参构造");
    }
    
    //有参构造
     public Student(double score) {
        super();    // 子类所有构造方法默认调用父类无参构造,可以不写,默认存在
        this.score = score;    
        System.out.println("子类有参构造");
     }

}

public class Demo07 {
    public static void main(String[] args) {
        Student s1 = new Student();
        System.out.println("----------");
        Student s2 = new Student(99.9);
    }
}

输出结果
父类无参构造
子类无参构造
----------
父类无参构造
子类有参构造

如果调用父类有参构造

 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
class Person {
    private String name;
    private int age;
	
	//无参构造
    public Person() {
        System.out.println("父类无参构造");
    }
    
    //有参构造
    public Person(String name,int age) {
        this.name = name;
        this.age = age;
    }
}


class Student extends Person {
    private double score = 100;
    
    //无参构造
    public Student() {
        super();     // 调用父类无参构造,默认就存在,可以不写,必须在第一行
        System.out.println("子类无参构造");
    }
    
    //有参构造
     public Student(String name,int age,double score) {
        super(name,age);    // 调用父类有参构造,必须手动书写super 
        this.score = score;  
        System.out.println("子类有参构造");
     }
}

public class Demo07 {
    public static void main(String[] args) {
        Student s1 = new Student();
        System.out.println("----------");
        Student s2 = new Student("zhangsan",22,99.9);  //直接调用有参构造,即使父类变量是私有的
        
        System.out.println(s2.getScore()); // 99.9
        System.out.println(s2.getName()); // 输出 zhangsan
        System.out.println(s2.getAge()); // 输出 22
    }
}

this和super

从上面的代码中总结thissuper用法

  • 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()
  • super(..)是根据参数去确定调用父类哪个构造方法的
image-20250427135930161

补充,this构造方法使用

含义:表示使用本类(this)的其他构造方法,根据参数来确定具体调用哪一个构造方法。在创建对象时如果有值则用,没有给school的值则用默认的值”上海大学“

使用场景:通常用在要给变量赋默认的值的时候

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Student {
    String name;
    int age;
    String school;
	
    //无参构造
    public Student() {
        // 表示调用本类的其他构造方法(即有参构造)
        //此时,无参构造就不会在默认添加super()了。因为已经存在this()
        //只有在调用无参构造时才会使用,此时执行后会调用有参构造执行
        this(null,0,"上海大学");
    }
    
    //有参构造
    public Student(String name,int age,String school) {
        this.name = name;
        this.age = age;
        this.school = school;
     }
}
  • super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
 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
//综合案例
/*  写一个继承的标准Javabean类
猫:属性,姓名,年龄,颜色
狗:属性,姓名,年龄,颜色,吼叫
*/

public class Animal {
    //姓名,年龄,颜色
    private String name;
    private int age;
    private String color;


    public Animal() {
    }

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

    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 String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
}


public class Cat extends Animal{
    //空参
    public Cat() {
    }

    //需要带子类和父类中所有的属性
    public Cat(String name, int age, String color) {
        super(name,age,color);
    }
}


public class Dog extends Animal{
    //Dog :吼叫
    private String wang;

    //空参构造
    public Dog() {
    }

    //带参构造:带子类加父类所有的属性
    public Dog(String name, int age, String color,String wang) {
        //共性的属性交给父类赋值
        super(name,age,color);
        //独有的属性自己赋值
        this.wang = wang;
    }
	
    //只需要写子类独有的变量
    public String getWang() {
        return wang;
    }
    public void setWang(String wang) {
        this.wang = wang;
    }
}

public class Demo {
    public static void main(String[] args) {
        //创建狗的对象-带参构造(也可以无参,然后通过d.set()方法一个一个赋值)
        Dog d = new Dog("旺财",2,"黑色","嗷呜~~");
        System.out.println(d.getName()+", " + d.getAge() + ", " + d.getColor() + ", " + d.getWang());

        //创建猫的对象-带参构造(也可以无参,然后通过c.set()方法一个一个赋值)
        Cat c = new Cat("中华田园猫",3,"黄色");
        System.out.println(c.getName() + ", " + c.getAge() + ", " + c.getColor());
    }
}

多态

多态是继封装、继承之后,面向对象的第三大特性。

多态,就是指对象的多种形态。同一行为,具有多个不同的表现形式。

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

好处:使用父类类型作为参数,可以接受所有子类对象

格式:父类 变量名 = new 子类();——————person p = new Student();

当在一个方法中参数是一个类的时候,就涉及到了多态。此方法可以接受这个类所有的子类对象

 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
例如现在有一个需求写一个学生老师管理员注册的方法
如果没有多态只能在父类Person中定义一个通用register方法在这种情况下需要在三个子类中分别重写三个不同的register方法分别接收学生老师和管理员这样很麻烦而且如果在添加其他子类还需要重写
有了多态之后方法的形参就可以定义为共同的父类Person

public class Person {
    private String name;
    private int age;

    ...空参构造/带参构造/get和set方法...

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


public class Administrator extends Person {
    @Override
    public void show() {
        System.out.println("管理员的信息为:" + getName() + ", " + getAge());
    }
}


public class Student extends Person{

    @Override
    public void show() {
        System.out.println("学生的信息为:" + getName() + ", " + getAge());
    }
}


public class Teacher extends Person{

    @Override
    public void show() {
        System.out.println("老师的信息为:" + getName() + ", " + getAge());
    }
}


public class Test {
    public static void main(String[] args) {
        Student s = new Student();
        s.setName("张三");
        s.setAge(18);

        Teacher t = new Teacher();
        t.setName("王建国");
        t.setAge(30);

        Administrator admin = new Administrator();
        admin.setName("管理员");
        admin.setAge(35);


        register(s);
        register(t);
        register(admin);
    }



    //这个方法既能接收老师,又能接收学生,还能接收管理员
    //只能把参数写成这三个类型的父类
    public static void register(Person p){
        p.show();
    }
}
  • 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象
  • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象(后面会学)。
  • 而且多态还可以根据传递的不同对象来调用不同类中的方法。

多态使用

多态的运行特点:

  • 调用成员变量时:编译看左边,运行看左边
  • 调用成员方法时:编译看左边,运行看右边
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
理解:
比如  Animal a = new Dog();
(1)调用成员变量   a.name
因为成员变量a所属的是Animal类,所以会看左边的父类中有没有这个变量
如果有,编译成功,如果没有,编译失败
实际运行时获取的也是 左边父类中成员变量的值

(2)调用成员方法  a.show()
方法和变量不一样,每个类会有一个虚方法表,且子类会重写一些方法。
所以java在编译时会看左边父类中是否有这个方法,有则成功,无则失败
但在实际运行时运行的是右边 子类中的方法,即Dog类中的show()方法

因为a是Animal类型的,所以默认都会从Animal这个类中去找,这也和前面学的一样
--成员变量:在子类的对象中,会把父类的成员变量也继承下来,但优先从父类中找(因为定义为Animal)
--成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法覆盖的
 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 class Test {
    public static void main(String[] args) {
        //创建对象(多态方式)
        Animal a = new Dog();
        System.out.println(a.name);//动物
        a.show();///Dog --- show方法
    }
}


class Animal{
   String name = "动物";   //我们一般不在类里赋初值

   public void show(){
        System.out.println("Animal --- show方法");
    }
}

class Dog extends Animal{
    String name = "狗";

    @Override
    public void show() {
        System.out.println("Dog --- show方法");
    }

}

class Cat extends Animal{
    String name = "猫";

   @Override
    public void show() {
        System.out.println("Cat --- show方法");
    }
}

优势和弊端

(1)多态优势

在多态形式下,右边对象可以实现解耦合,便于扩展和维护

1
2
Person p = new Student();   //当需要更换对象时,直接修改Student即可
p.work();   //业务逻辑发生改变时,后续代码无需修改

定义方法时,使用父类作为参数,可以接受所有子类对象,体现多态的扩展性和便利

1
2
StringBuilder sb = new StringBuilder();
sb.append(1);   //在Idea中,append(Object obj)中是Object obj,表示所有类型都可以添加

(2)多态弊端

已经知道多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了

 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
class Animal{
    public  void eat()
        System.out.println("动物吃东西!")
    
}
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
   
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    } 
    public void watchHouse() {  
        System.out.println("看家");  
    }  
}

class Test{
    public static void main(String[] args){
        Animal a = new Cat();
        a.eat();
        //a.catchMouse();//编译报错,编译看左边,Animal没有这个方法
    }
}

解决方案:强制变回子类型就可以了

【回顾:数据类型转换分为两种:自动类型转换和强制类型转换】

【注意,不能瞎转,上面定义的是Cat类,在强转的时候,也只能强转回Cat类,转为Dog类会报错】

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Test{
    public static void main(String[] args){
        //自动类型转换
        Animal a = new Cat();
        a.eat();
        
        //正确写法(强制类型转换)
        Cat c = (Cat)a;       
        c.catchMouse(); 		// 调用的是 Cat 的 catchMouse
        
        //错误写法
        //Dog d = (Dog)a;       
        //d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】
    }
}

运行时却报出了 ClassCastException 类型转换异常
这是因为明明创建了Cat类型对象运行时当然不能转换成Dog对象的

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验

 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
格式变量名 instanceof 数据类型 
如果变量属于该数据类型或者其子类类型返回true
如果变量不属于该数据类型或者其子类类型返回false

class Test{
    public static void main(String[] args){
        Animal a = new Cat();
        a.eat();
        
        // 写法1:实际中为了避免出错的写法 
        if (a instanceof Cat){
            Cat c = (Cat)a;       
            c.catchMouse();        // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
            Dog d = (Dog)a;       
            d.watchHouse();       // 调用的是 Dog 的 watchHouse
        }
        
        
        // 写法2:简化代码,JDK14的时候提出了新特性,把判断和强转合并成了一行
        //先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
        //如果不是,则不强转,结果直接是false
        if(a instanceof Dog d){
            d.lookHome();
        }else if(a instanceof Cat c){
            c.catchMouse();
        }else{
            System.out.println("没有这个类型,无法转换");
        }  

    }
}

包就是文件夹,用来管理各种不同功能的Java类,方便后期维护

包名一般是公司域名的倒写,必须用”.“连接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
什么时候需要导包?
​ 情况一:在使用Java中提供的非核心包中的类时,比如java.lang
​ 情况二:使用自己写的其他包中的类时
(同时使用两个包中的同类名,需要用全类名)

什么时候不需要导包?
​ 情况一:在使用Java核心包(java.lang)中的类时
​ 情况二:在使用自己写的同一个包中的类时

为什么需要导包?
包的完整名称为:包名+类名,如com.itheima.homework.demo1.Student
在一个类中,每次使用类时都写完整包名太麻烦了,
com.itheima.homework.demo1.Student s = new com.itheima.homework.demo1.Student();
所以需要导包:import com.itheima.homework.demo1.Student
这样的话在类中使用很方便:Student s = new Student();

使用不同包下的相同类怎么办?
那则需要使用全类名:包名 + 类名
因为如果两个包下都有Student类,导入两个包后,在使用Student类时我怎么知道是哪个包下的?
//拷贝全类名的快捷键:选中类名crtl + shift + alt + c 或者用鼠标点copy,再点击copy Reference
com.itheima.homework.demo1.Student s1 = new com.itheima.homework.demo1.Student();
com.itheima.homework.demo2.Student s2 = new com.itheima.homework.demo2.Student();

final关键字

子类可以在父类的基础上改写父类内容,比如,方法重写。如果有一个方法我不想别人去改写里面内容,该怎么办呢?

Java提供了**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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//--------------------final修饰方法-------------------
//格式:修饰符 final 返回值类型 方法名(参数列表){}
//使用场景:方法是一种规则,不希望被改变(使用不多)
class Fu2 {
	public final void show1() {
		System.out.println("Fu2 show1");
	}
	public void show2() {
		System.out.println("Fu2 show2");
	}
}

class Zi2 extends Fu2 {
	//子类不能重写父类方法shou1()
	@Override
	public void show2() {
		System.out.println("Zi2 show2");
	}
}

//--------------------final修饰类-------------------
//格式:final class 类名 {}
//使用场景:(使用不多)
final class Fu {}
// class Zi extends Fu {} // 报错,不能继承final的类

//--------------------final修饰局部变量-------------------
//基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改
final int b = 10;  // 声明变量,直接赋值,使用final修饰
b = 20; // 报错,不可重新赋值

//类型的局部变量,被final修饰后,只能赋值一次
final Student s = new Student("zhangsan",23);
S.setName("lisi");   //引用数据类型内部数据值可以被改变
S = new Student();  //报错,不能创建第二个学生对象


//--------------------final修饰成员变量-------------------
//成员变量涉及到初始化的问题,初始化方式有显示初始化和构造方法初始化,只能选择其中一个
//显示初始化(在定义成员变量的时候立马赋值)(常用)
public class Student {
    final int num = 10;
}

//构造方法初始化(在构造方法中赋值一次)(不常用,了解即可)
//注意:每个构造方法中都要赋值一次!!
public class Student {
    final int num2;

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

被final修饰的常量名称,一般都有书写规范,单个单词全部大写,多个单词全部大写且用下划线隔开

  • final修饰的变量是基本类型:那么变量存储的数据值不能发生改变
  • final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,对象内部的可以改变

权限修饰符

在Java中提供了四种权限修饰符,被修饰的内容会有不同的访问权限。

可以修饰成员变量、方法、构造方法、内部类

作用范围:private < 默认(空着不写) < protected < public

public protected 默认 private
同一类中
同一包中的类
不同包的子类
不同包中的无关类

编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用private ,隐藏细节。
  • 构造方法使用public ,方便创建对象。
  • 成员方法使用public ,方便调用方法。(如果是共性的方法,不想被改变,则需要私有)

小贴士:不加权限修饰符,就是默认权限

不同包的子类指的是一个包中的一个类继承了另一个包的父类

代码块

(1)分类:局部代码块(淘汰)、构造代码块(慢慢淘汰)、静态代码块

(2)格式:static{}

(3)特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次

(4)使用场景:在类加载的时候,做一些数据初始化的时候使用

抽象类

1
2
抽象方法: 没有方法体的方法。
抽象类:包含抽象方法的类。

抽象方法:将**共性的行为(方法)**抽取到父类之后。由于每一个子类执行的内容是不一样的,所以在父类中不能确定具体的方法体。那么该方法就可以定义为抽象方法。

抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类

1
2
3
4
5
6
7
8
9
abstract是抽象的意思,用于修饰方法方法和类
修饰的方法是抽象方法,修饰的类是抽象类。

抽象方法格式:
修饰符 abstract 返回值类型 方法名 (参数列表);
public abstract void run();

抽象类格式:
abstract class 类名字 {..}

(1)注意事项:

  • 抽象类不能实例化(反过来想,如果允许,创建对象调用抽象方法执行什么?looking my eyes,why)
  • 抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。
  • 可以有构造方法,即空参构造和有参构造
  • 继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。
 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
// 父类,抽象类
abstract class Employee {
	private String id;
	private String name;
	private double salary;

	public Employee() {
	}
	public Employee(String id, String name, double salary) {
		this.id = id;
		this.name = name;
		this.salary = salary;
	}

	// 抽象方法
	// 抽象方法必须要放在抽象类中
	abstract public void work();
}

// 定义一个子类继承抽象类
class Manager extends Employee {
	public Manager() {
	}
	//通过父类的构造方法初始化id、name。所以抽象类写构造方法不是没有用
	public Manager(String id, String name, double salary) {
		super(id, name, salary);
	}
	// 2.重写父类的抽象方法
	@Override
	public void work() {
		System.out.println("管理其他人");
	}
}

// 定义一个子类继承抽象类
class Cook extends Employee {
	public Cook() {
	}
	public Cook(String id, String name, double salary) {
		super(id, name, salary);
	}
	@Override
	public void work() {
		System.out.println("厨师炒菜多加点盐...");
	}
}

// 测试类
public class Demo10 {
	public static void main(String[] args) {
		// 创建抽象类,抽象类不能创建对象
		// 假设抽象类让我们创建对象,里面的抽象方法没有方法体,无法执行.所以不让我们创建对象
		//Employee e = new Employee();


		// 3.创建子类
		Manager m = new Manager();
		m.work();

		Cook c = new Cook("ap002", "库克", 1);
		c.work();
	}
}

此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法

(2)抽象类存在的意义

抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义。抽象类可以强制让子类,一定要按照规定的格式进行重写。

我的理解:抽象类关键在于抽象方法,也就是方法写什么都不合适,把它抽象,强制要求只要继承了我这个抽象类,就必须重写里面的抽象方法。另外就是抽象类不能被创建对象,只能通过子类继承抽象类,通过子类创建对象(弊端)

不需要背,只要当idea报错之后,知道如何修改即可。

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

    理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

    理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

    理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。

    理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

  5. 抽象类存在的意义是为了被子类继承。

    理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。

接口

(1)初识

即侧重的不是继承体系和一类事物,而是谁有这个功能(行为/方法)且这个功能常用常见,哪怕是完全不相关的类

接口就是一种规则,是对**行为(方法)**的抽象

image-20250427195614267

(2)定义和使用

  • 接口用关键字interface来定义,格式为public interface 接口名 {}
  • 接口不能实例化(即不能创建对象)
  • 接口和类之间是实现关系,通过implements关键字表示,如public class 类名 implements 接口名 {}
  • 如果一个类实现了接口(实现类),必须重写接口中的所有抽象方法;否则这个类必须定义成抽象类
1
2
3
4
5
6
7
8
类实现接口的意义:
接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。

注意点:
1)接口和类的实现关系,可以单实现,也可以多实现
public class 类名 implements 接口1,接口2 {}
2)实现类还可以在继承一个类的同时实现多个接口
public class 类名 extends 父类 implements 接口1,接口2 {}
 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
//定义游泳接口
public interface Swim {
    public abstract void swim();
}


//父类-因为有抽象方法,定义为抽象类
public abstract class Animal {
    private String name;
    private int age;
 
    public Animal() {
    }
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    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 abstract void eat();
}


//子类,继承父类同时实现一个接口
public class Dog extends Animal implements Swim{
    public Dog() {
    }
    public Dog(String name, int age) {
        super(name, age);
    }
 
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
 
    @Override
    public void swim() {
        System.out.println("狗刨");
    }
}


//子类
public class Rabbit extends Animal{
    public Rabbit() {
    }
    public Rabbit(String name, int age) {
        super(name, age);
    }
 
    @Override
    public void eat() {
        System.out.println("兔子在吃胡萝卜");
    }
}


//测试类
public class Test {
 
    public static void main(String[] args) {
       //创建青蛙的对象
        Frog f = new Frog("小青",1);
        System.out.println(f.getName() + ", " + f.getAge());
 
        f.eat();
        f.swim();
    }
}

(3)接口中成员特点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(1)成员变量
只能是常量
默认修饰符public static final
在接口中定义的成员变量默认会加上 public static final修饰也就是说在接口中定义的成员变量实际上是一个常量并且是静态化的变量可以直接用接口名访问

(2)构造方法没有

(3)成员方法
JDK7以前接口中只能定义抽象方法默认修饰符public abstract
JDK8新特性接口中可以定义有方法体的方法
JDK9新特性接口中可以定义私有方法

public interface InterF {
    //    public abstract void run();
    void run();   //会自己默认加上public abstract

    //    public abstract String getName();
    String getName();

    // public static final int AGE = 12 ;
    int AGE  = 12; //常量,会自己默认加上public static final
    String SCHOOL_NAME = "黑马程序员";
}

(4)接口和类

  • 类和类的关系
    • 继承关系,只能单继承,不能多继承,但是可以多层继承
  • 类和接口的关系
    • 实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
  • 接口与接口的关系
    • 继承关系,可以单继承,也可以多继承
    • 细节:如果实现类实现了最下面的子接口,那么就需要重写所有的抽象方法
    • 接口继承接口就是把其他接口的抽象方法与本接口进行了合并
 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
//接口1
public interface Abc {
    void test();
}

//接口2
public interface Law {
    void rule();
    void test();
}

//接口3,继承前两个接口,最底层的接口,下面没儿子了
public interface SportMan extends Law , Abc {
    void run();
}

//实现类,因为继承的是最底层的接口,重写所有抽象方法(这里默认是JDK7)
public class InterTemp implements SportMan{
	@Override
	public void test(){}

    @Override
	public void rule(){}

    @Override
	public void run(){}
}

我的理解:接口和抽象类有相似之处,即里面都有抽象方法,所以只要一个类继承了接口或者抽象类,就必须对所有抽象方法进行重写(本来就是抽象才定义的,子类继承时当然要写具体一点),只不过接口没有继承关系罢了。同样的不能被实例化,因为里面都是抽象方法,只能通过一个类实现接口(实现类),创建实现类的对象,然后通过对象名调用接口中的方法【弊端】

接口其他扩展

扩展-1:JDK8接口中新增的特性-默认方法

(1)允许在接口中定义默认方法,需要使用关键字default修饰

  • 作用:解决接口升级的问题(当接口升级时,调用接口的类不用每次都重写接口里的方法)

(2)接口中默认方法的定义格式

  • public default 返回值类型 方法名(…) {}
  • 范例:public default void show() {}

(3)接口中默认方法的注意事项

  • 默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候需去掉default关键字
  • public 可以省略,default 不能省略(否则会当成抽象方法)
  • 如果一个类实现了多个接口,且多个接口中存在相同名字的默认方法,则该类就必须对该方法进行重写

扩展-1:JDK8接口中新增的特性-静态方法

(1)允许在接口中定义静态方法,需要用static修饰

(2)接口中静态方法定义格式

  • public static 返回值类型 方法名 (..) {}
  • 范例:public static void show() {}

(3)接口中静态方法的注意事项

  • 静态方法只能通过接口名调用,不能通过实现类名或对象名调用,即接口名.方法()
  • public可以省略,static不能省略(否则会当成抽象方法)

(之前即JDK7接口中只能有抽象方法,不能通过接口名调用接口中的方法,因为没法实例化,是抽象的;只能和抽象类一样,通过一个类实现接口,创建实现类的对象,然后通过对象名调用接口中的方法)

扩展-1:JDK9接口中新增的特性-私有方法

(1)接口中私有方法格式-1

  • private 返回值类型 方法名(..) {}
  • private void show() {}
  • 普通私有方法,默认方法修改为私有方法时使用

(2)接口中私有方法格式-1

  • private static 返回值类型 方法名(..) {}
  • private static void show() {}
  • 静态私有方法,静态方法修改为私有方法时使用

扩展-2:接口应用

当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称为接口多态

不需要背,只要当idea报错之后,知道如何修改即可。

关于接口的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  • 当两个接口中存在相同抽象方法的时候,该怎么办?

只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。

  • 实现类能不能继承A类的时候,同时实现其他接口呢?

继承的父类,就好比是亲爸爸一样 实现的接口,就好比是干爹一样 可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。

  • 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?

实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。

  • 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?

处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。 处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。

  • 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?

可以在接口跟实现类中间,新建一个中间类(适配器类) 让这个适配器类去实现接口,对接口里面的所有的方法做空重写。 让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。 因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象

内部类

(1)初识

  • 类中五大成分之一(成员变量、方法、构造器、内部类、代码块【慢慢在淘汰】)

  • 如果一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类

  • 场景:当一个类的内部,包含了一个完整的事物,且这个事物没必要单独设计,就设计成内部类,比如汽车内部的发动机

  • 分类:成员内部类(了解)、静态内部类(了解)、局部内部类(了解)、匿名内部类(重点)

    • 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
    • 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
    • 局部内部类,类定义在方法内
    • 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。

(2)成员内部类

格式:外部类.内部类 变量 = new 外部类().new 内部类();

访问内部类的类型都是用 外部类.内部类

 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 class Test {
    public static void main(String[] args) {
        //  宿主:外部类对象。
       // Outer out = new Outer();
        // 创建内部类对象。
        Outer.Inner oi = new Outer().new Inner();
        oi.method();
    }
}

class Outer {
    //外部类成员变量
    private int age = 99;
    
    // 成员内部类,属于外部类对象的。
    public class Inner{
        // 这里面的东西与类是完全一样的。
        private String name;
        private int age = 88;
        
        ..构造方法和getter/setter初始化..
        
        public void method(){
        	int age = 66;
            System.out.println(age);  //66
            System.out.println(this.age);  //88
            System.out.println(Outer.this.age);  //99
        }
    }
}

在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。

(3)静态内部类

  • 格式:Outer.Inter in = new Outer.Inter();
  • 静态内部类是一种特殊的成员内部类。
  • 有static修饰的内部类,属于外部类自己持有。
  • 总结:静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类。

拓展1:静态内部类可以直接访问外部类的静态成员。

拓展2:静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Outer01{
    private static  String sc_name = "黑马程序";
    // 静态内部类
    public static class Inner01{
        private String name;
        public Inner01(String name) {
            this.name = name;
        }
        public void showName(){
            System.out.println(this.name);
            // 拓展:静态内部类可以直接访问外部类的静态成员。
            System.out.println(sc_name);
        }
    }
}

public class InnerClassDemo01 {
    public static void main(String[] args) {
        // 创建静态内部类对象。
        // 外部类.内部类  变量 = new  外部类.内部类构造器;
        Outer01.Inner01 in  = new Outer01.Inner01("张三");
        in.showName();
    }
}

(4)局部内部类(看一下就行,了解都没必要)

局部内部类 :定义在方法中的类

匿名内部类

是内部类的简化写法。是一个隐含了名字的内部类。开发中,最常用到的内部类就是匿名内部类了。

1
2
3
4
5
new 类名或者接口名(..) {
     类体(一般是方法重写);
};

// 可以看到,包含了:继承或实现关系、方法重写、创建对象

特点:匿名内部类本质就是一个子类,并会立即创建出一个子类对象出来

场景:如果我们希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用

是为了简化代码。

要求:匿名内部类必须继承一个父类或者实现一个父接口

 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
//--------------实现接口的一个完整流程---------
interface Swim {
    public abstract void swimming();
}

// 1. 定义接口的实现类
class Student implements Swim {
    // 2. 重写抽象方法
    @Override
    public void swimming() {
        System.out.println("狗刨式...");
    }
}

public class Test {
    public static void main(String[] args) {
        // 3. 创建实现类对象
        Student s = new Student();
        // 4. 调用方法
        s.swimming();
    }
}

//---------------匿名内部类-------------
interface Swim {
    public abstract void swimming();
}

public class Demo07 {
    public static void main(String[] args) {
        // 匿名内部类使用场景,可以看作一个子类
        //zhegn'c将创建的对象赋给s3接受
        Swim s3 = new Swim() {
            @Override
            public void swimming() {
                System.out.println("蝶泳...");
            }
        };
        goSwimming(s3);  // 传入匿名内部类
        
        
        // 完美方案: 一步到位(实际中经常这样使用,将匿名内部类作为方法参数)
        goSwimming(new Swim() {
            public void swimming() {
                System.out.println("大学生, 蛙泳...");
            }
        });
		
        //相当于又创建了一个子类
        goSwimming(new Swim() {
            public void swimming() {
                System.out.println("小学生, 自由泳...");
            }
        });
    }

    // 定义一个方法,模拟请一些人去游泳
    public static void goSwimming(Swim s) {
        s.swimming();
    }
}

类型转换

(自己补充,上面也有–多态–day14.md)

  • 子类转父类(向上转型):可以自动完成,不过通过父类引用只能访问父类中定义的方法。
  • 父类转子类(向下转型):必须进行显式强制类型转换,并且要保证对象在运行时实际是子类的实例,否则会抛出ClassCastException异常。
 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
//子类对象转换为父类(向上转型)
//这是可行的,而且无需进行显式强制类型转换。
//因为子类继承了父类的所有属性和方法,所以子类对象能够被视作父类对象
class Animal {
    public void eat() {
        System.out.println("动物进食");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
    
    public void bark() {
        System.out.println("汪汪叫");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Animal animal = dog; // 子类转父类,自动转换
        animal.eat(); // 输出:狗吃骨头
        // animal.bark(); // 编译错误,父类引用无法调用子类特有的方法
    }
}
 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
//父类对象转换为子类(向下转型)
//这种转换不一定能成功,必须进行显式强制类型转换,并且在运行时要确保父类对象实际上是子类的一个实例
class Animal {
    public void eat() {
        System.out.println("动物进食");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
    
    public void bark() {
        System.out.println("汪汪叫");
    }
}

public class Main {
    public static void main(String[] args) {
        // 情况1:父类引用指向子类对象
        Animal animal1 = new Dog(); // 向上转型
        Dog dog1 = (Dog) animal1; // 向下转型,可行
        dog1.bark(); // 输出:汪汪叫
        
        // 情况2:父类引用指向父类对象
        Animal animal2 = new Animal();
        // Dog dog2 = (Dog) animal2; // 运行时会抛出ClassCastException异常
        
        // 安全的向下转型方式
        if (animal2 instanceof Dog) {
            Dog dog2 = (Dog) animal2;
            dog2.bark();
        } else {
            System.out.println("无法进行类型转换"); // 会执行此分支
        }
    }
}

总结:

多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。

  • 向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。 当父类引用指向一个子类对象时,便是向上转型。
  • 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。 一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
1
2
3
4
5
6
7
8
//--------------向上转型(自动转换)----------
父类类型  变量名 = new 子类类型();
Animal a = new Cat();

//--------------向下转型(强制转换)----------
子类类型 变量名 = (子类类型) 父类变量名;
:Aniaml a = new Cat();
   Cat c =(Cat) a;  

常用API

即一些封装好的java类,不需要记忆,只需要了解类名和基本作用即可,随用随查

Math类

作用:执行数值操作的方法,如指数、幂数、对数、三角函数

public final class Math extends Object,不能被继承

方法等均被static修饰,直接通过类名调用,如Math.PI

向上取整和向下取整(有小数就进一或减一)不等于四舍五入

System类

作用:提供了一些和系统相关的方法

方法等均被static修饰,直接通过类名调用

System.currentTimeMillis():获取当前时间的毫秒值,经常通过结束时间-起始时间计算程序所花费的时间

Runtime类

Runtime表示当前虚拟机的运行环境

image-20250513141307169

Object类

Java中的顶级父类,所有的类都直接或间接的继承于Object类

image-20250513145542835
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-----------------扩展:System.out.printIn()原理--------------
System  类名
out  静态变量
printIn()  方法
当我们打印一个对象的时候,底层会调用对象的toString方法,把对象变成字符串,然后打印在控制台上

----------toString方法结论:-------------------
默认情况下,因为Object类中的toString方法返回的是地址值,打印一个对象输出的就是地址值
如果我们打印一个对象,想要看到属性值的话,重写toString方法就可以了
在重写的方法中,把对象的属性值进行拼接
直接使用快捷键/右键即可
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//Object中的equal方法是比较两个对象是否相等,即比较的是两个对象的地址值
//实际中,我们想要比较的是对象的地址值,所以经常会重写equal方法,如果没有重写,就是调用Object中的equal方法,比较的

public class ObjectDemo3{
	public static void main(String[] args){
        String s = "abc";
        StringBuilder sb = new StringBuilder("abc");
        
        //调用的是字符串类中的equal方法
        System.out.printIn(s.equals(sb));   //false,因为sb它不是一个对象
        //调用的是StringBuilder的equal方法,由于其没有这个方法,所以用的是Object类中的equal方法
        System.out.printIn(sb.equals(s));   //false,比较的是地址值
    }
}
1
2
3
4
5
6
7
java中的克隆分两种:浅克隆和深克隆
--浅克隆:不管对象内部的属性是基本数据类型还是引用数据类型(拷贝地址值),都完全拷贝过来
  这样的话引用数据类型就共用一个空间,修改任意一个值都会改变
--深克隆:基本数据类型拷贝过来;字符串复用(共用);引用数据类型会重新创建新的


Object中的克隆属于浅克隆方式

(简单说了一下jar包导入和lib库)

Objects是一个工具类,提供了一些方法去完成一些功能

image-20250513151816611

BigInteger、BigDecima类

任意大的整数和小数

image-20250516194210263

通过静态方法BigInteger.valueOf获取对象,内部有一些优化和细节(在内部对-16~16进行了优化,提前创建好了BigInteger对象,如果多次获取不会重新创建新的)

image-20250516193123269
image-20250516195437202
  • 如果要表示的数字不大,没有超出double的取值范围,建议用静态方法
  • 如果要表示的数字比较大,超出了double的取值范围,建议用构造方法

正则表达式

API说明文档中的Pattern

  • 正则表达式中[]表示一个范围;{}表示出现次数;
  • ()就是分组,\数字表示复用第几组的数据
    • 正则内部使用:\\组号 (java中\表示一个\)
    • 正则外部使用:$组号

是一个一个字符匹配的,即一个[]判断一个字符是否符合要求。([xx[x]]也是判断一个字符;[xx][xxx]才是判断两个字符

在java中,\表示转义字符,具有特殊意义。如果想要输出原本意思,需要写\\,前面的\是一个转义字符,改变了后面\原本的含义,把其变为普普通通的\

image-20250516202138474

时间

Date类

image-20250518160955072
  • 对象比较的是地址值,如果想要比较时间大小,可以用get方法获取后(转为数值,即毫秒值)再进行比较
  • 缺点:不能改变时间的格式,是固定的(输入是毫秒值,输出是固定的时间格式:The Jan 01 08:00:00 CST 1970

SimpleDateFormat类

  • 作用:(1)格式化时间,变为想要的格式;(2)解析,把字符串表示的时间变成Date对象
  • 解析的目的是将字符串形式的时间变回对象,从而可以用get方法获取对应的(毫秒)数值,便于后续比较等操作
image-20250518163610929

Calendar类

  • 代表了系统当前时间的日历对象,可以单独修改、获取时间中的年月日
  • 细节:Calendar是一个抽象类,不能直接创建对象,而是通过一个静态方法获取子类对象
最后更新于 2025-05-19 13:35
光终究会洒在你的身上,你也会灿烂一场!
本博客已稳定运行