Java知识整理&总结(二) o(* ̄︶ ̄*)o

131 阅读16分钟

十、面向对象-基础

1. 面向对象概述

面向对象(Object-Oriented-Programming,简称 OOP) 是软件开发中的一类编程思想、开发范式。它将现实世界中的事物抽象为 “对象”,通过对象之间的交互来实现功能。其核心是以 “对象” 为中心,强调事物的属性行为,并通过封装、继承、多态三大特性提高代码的可维护性、复用性和灵活性。

除了面向对象,还有面向过程指令式编程函数式编程。在所有的编程范式中,我们接触最多的还是面向过程和面向对象两种。

维度面向过程(Procedure-Oriented-Programming)面向对象(Object-Oriented-Programming)
核心以 “过程” 为中心,强调步骤和函数以 “对象” 为中心,强调事物的属性和行为
特点代码模块化(函数),数据与操作分离代码封装为类,数据与操作绑定
适用场景简单程序(如计算器、脚本)复杂程序(如电商系统、游戏)
扩展性修改难度大,牵一发而动全身易扩展,通过继承、多态灵活扩展功能

面向对象面向过程二者相辅相成,并非是对立的关系!

2. Java语言的基本元素(类、对象)

2.1 核心概念

类(Class)

  • 定义:对一类具有相同属性和行为的对象的抽象描述,是对象的模板;
  • 方便理解:人类,灵长类等;
  • 举例:“学生类” 定义了所有学生共有的属性(姓名、年龄等)和行为(上课、考试等);

对象(Object)

  • 定义:具体的事物或抽象的概念,具有属性和行为;
  • 方便理解:一个名叫张三的人,一只5岁的黑猩猩等;
  • 举例:“学生对象 张三” 是一个具体的学生,是学生类的实例化对象;

2.2 类的概述

类(class)是一组相关属性行为的集合,这也是类最基本的两个成员;

属性:是类中定义的变量,用于存储对象的状态。也称为 “成员变量”

  • 属性 <=> 成员变量 <=> Field

行为:是类中定义的方法,用于描述对象的行为。也称为 “成员方法”

  • 行为 <=> 成员方法 <=> Method

类的声明

[权限修饰符] class 类名 {
    属性声明;
    方法声明;
}

-- 举例 --
public class Person {
    String name;// 属性(成员变量)name
    int age;// 属性(成员变量)age
    
    // 行为(成员方法)showName
    public void showName() {
        System.out.println("name =" + name);
    }
    
    // 行为(成员方法)showAge
    public void showAge() {
        System.out.println("age =" + age);
    }
}

2.3 对象的概述

对象(Object)是类的实例化结果,可以将理解为现实世界中具体存在的事物在程序中的抽象表示。

特点

  • 封装性:对象将数据(属性)和操作数据的方法封装在一起,对外隐藏内部实现细节。
  • 独立性:每个对象都是独立的个体,拥有自己的属性值,不同对象之间互不干扰。
  • 交互性:对象之间可以通过方法调用进行交互,协作完成复杂功能。

对象的创建
在 Java 中,通过 new 关键字创建对象,语法如下:

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

// 举例:创建Person类的实例对象
Person person = new Person();

对象的使用

  • 访问属性:通过 对象名.属性名 访问或修改对象的状态
  • 调用方法:通过 对象名.方法名() 让对象执行特定操作
// 举例:创建Person类的实例对象
Person person = new Person();

// 获取person对象的name属性
String name = person.name;
// 将person对象的name属性修改为“张三”
person.name = "张三";
// 调用person对象的showName方法
person.showName();

3. 对象的内存解析

3.1 JVM内存结构划分

HotSpot JVM(Java虚拟机)的架构图如下:

20250820152019.png

其中我们主要关心的是运行时数据区部分(Runtime Data Area)。

  • 栈(Stack):是指虚拟机栈,用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不是对象本身,而是对象在堆内存的首地址)。方法执行完成后,自动释放。

  • 堆(Heap):此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。

  • 方法区(Method Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

3.2 代码内存解析

示例代码:

// 定义一个“人”类
class Person {
    String name;
    int age;
}

// 定义一个测试类,在测试类中使用 main 方法执行具体代码
public void Test {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "TheShy";
        p1.age = 25;
        
        Person p2 = new Person();
        p2.name = "clearlove7";
        p2.name = 32;
        
        P2 = P1;
        
        Person p3 = new Person();
    }
}

内存解析流程图:

wechat_2025-08-28_103544_419.png

Tips:

  • 新创建一个类的多个对象时(比如p1、p2),则每个对象都拥有当前类的一个“副本”,多个对象之间互不影响,当修改其中一个对象的属性时,不会影响其它对象此属性的值。
  • 当一个变量指向另一个变量进行赋值时(比如p2 = p1),此时并没有在堆空间中创建新的对象,而是两个变量共同指向了堆空间中同一个对象。此时,通过其中一个变量修改指向对象的属性时,将会影响所有指向该对象的变量的调用。

3.3 对象数组

数组的元素可以是 基本数据类型,也可以是 引用数据类型

当元素是引用数据类型中的类时,我们称为 对象数组

示例:

public class Person() {
    int number; //编号
    int age; //年龄
}

public class Test() {
    Person[] personArr = new Person[8];
    for (int i = 0; i < personArr.length; i++) {
        personArr[i] = new Person();
        personArr[i].number = i;
        personArr[i].age = i+20;
    }
}

内存解析:

image.png

Tips:

  • 定义对象数组时,必须确定数组长度,否则对象数组为null

4. 类的成员之一:变量

前面我们提到 “属性,即是类中定义的变量”,在Java中,可分为成员变量局部变量两种变量。

变量的声明格式

[修饰符] 数据类型 变量名 [= 初始化值]; 

4.1 成员变量和局部变量

位置在类中,方法体外 的变量,即为 成员变量;

位置在类中,方法体内 的变量,即为 局部变量。

示例:

public class Person {
    String name; // 成员变量1
    int age; // 成员变量2
    
    public void sayHello(String yourName) { // 局部变量1
        String info = "hello!"; // 局部变量2
        System.out.println(info + yourName);
    }
}

4.2 成员变量 VS 局部变量

相同点:

  • 声明格式相同,所有变量的声明格式都是移植的
  • 所有变量都仅在其自己的作用域内有效

不同点:

  • 位置不同

    • 成员变量的位置是,在类中,方法体外的变量
    • 局部变量的位置是,在类中,方法体内的变量(代码块中也属于局部变量)
  • 内存中存储位置不同

    • 成员变量在内存中存储于堆内存
    • 局部变量在内存在存储于栈内存
  • 生命周期不同

    • 成员变量与对象的生命周期一致,跟随对象的创建而产生,跟随对象的销毁而销毁;即使多个对象的数据类型相同,但每个对象的成员变量独立存在
    • 局部变量与方法的执行周期一致,跟随方法的调用而产生,跟随方法的结束而销毁。即使多次方法的调用,但每次方法调用的局部变量独立存在
  • 作用域不同

    • 成员变量在本类中可直接调用,其他类中获取方式有 对象.变量名get方法
    • 局部变量仅在本方法中可调用
  • 可用修饰符不同

    • 成员变量:public,privite,final,static,volatile
    • 局部变量:final
  • 默认值不同

    • 成员变量有默认值,引用数据类型的默认值为null
    • 局部变量无默认值,只有在方法被调用时才有意义,其中形参的依靠实参进行初始化

5. 类的成员之二:方法

前面我们提到 “行为,即是是类中定义的方法”,在其他语言中也常称为函数过程

5.1 什么是方法?

  • 方法是一个类行为特征的抽象,用来完成某个功能或一组操作
  • 方法存在的目的是为了实现代码重用,减少冗余
  • 在Java中,方法无法独立存在,必须定义在类中

方法声明的格式

权限修饰符 [其他修饰符] 返回值类型 方法名(形参列表) [throws 异常列表] {
    // 方法体
    [return xxx;] //当返回值类型不为void时,方法体中必须使用return结束方法进行返回
}

注:[]表示非必填
权限修饰符:publicprotected、缺省、private
其他修饰符:staticfinal等
返回值类型:基本数据类型、引用数据类型、void表示无返回值
方法名:该方法的标识符
形参列表:调用该方法需要传递的参数,可无
异常列表:后续讲解
方法体:整个方法的核心,方法体中的内容决定该方法可以干什么
return1. 作用是结束方法的执行,同时将方法的结果返回
    2. 当返回值类型为void时,该方法可以没有return或直接写 "return;"
    3. 当返回值类型为基本数据类型或引用数据类型时,该方法必须有"return xxx;",其中"xxx"的数据类型必须与返回值类型一致
    4. return语句后面的"所有代码将不会执行!且不可再编写代码!"

方法的举例:

  • Math.random()的random()方法
  • Math.sqrt(x)的sqrt(x)方法
  • Arrays类中的sort()、equals()方法
  • 类中常见的get、set方法

5.2 方法的调用

方法调用格式:对象.方面名(参数);

示例:

/**
 * 方法调用案例演示
 */
public class MethodInvokeDemo {
    public static void main(String[] args) {
        System.out.println("-----------------------方法调用演示-------------------------");

        //调用Demo类中无参无返回值的方法sayHello,调用一次,执行一次,不调用不执行
        Demo md = new Demo();
        md.sayHello();
        //调用Demo类中有参无返回值的方法sayHello,调用一次,执行一次,不调用不执行
        String name = "刘旭涛";
        int age = 25;
        md.sayHello(name,age);
        //调用Demo类中有参无返回值的方法sayHello,调用一次,执行一次,不调用不执行
        String name = "刘旭涛";
        int age = 25;
        String info = md.sayHello(name,age);
        //举例 info 的内容为:大家好,我是刘旭涛,我今年25岁
    }
}

5.3 方法相关的注意事项

  • 必须先声明后使用,且方法必须定义在类的内部
  • 调用一次就执行一次,不调用不执行
  • 方法中可以调用类中的方法或属性,不可以在方法内部定义方法

5.4 方法中的 return 语句

  • 作用: 结束一个方法,同时返回一个结果
  • 当返回值类型为void时,该方法可以没有return或直接写 "return;"
  • 当返回值类型为基本数据类型或引用数据类型时,该方法必须有"return xxx;",其中"xxx"的数据类型必须与返回值类型一致
  • return语句后面的"所有代码将不会执行!且不可再编写代码!"

5.5 方法调用的内存解析

  • 方法在未被调用的时候,都在方法区中的字节码(.class)文件中存放
  • 方法在被调用的时候,需要进入栈内存中执行,称之为入栈,即在栈内存中开辟独立的内存供该方法执行,该内存中将存储该方法执行过程中产生的局部变量等
  • 方法在被调用结束的时候,会释放刚才分配的内存,称之为出栈。当该方法有返回值时,会将返回值返回至调用处;当没有返回值时,则继续执行调用处的后续代码
  • 栈结果的特点:先进后出,后进先出

示例代码:

public class Person {
    String name;
    
    /**
    * 调用eat方法时,会先调用sleep方法,再执行打印睡醒了
    */
    public void eat() {
        sleep();
        System.out.println(name+"睡醒了,可以吃饭了!");
    }
    /**
    * 调用sleep方法时,会先调用sport方法,再执行打印运动结束
    */
    public void sleep() {
        sport();
        System.out.println(name+"运动结束,可以睡觉了!");
    }
    /**
    * 调用sport方法时,直接打印开始运动
    */
    public void sport() {
        System.out.println(name+"开始运动!");
    }
}

public class Test {
    public static void main() {
        Person p1 = new Person();
        p1.eat();
    }
}

内存解析图:

image.png

5.6 方法进阶-重载

概念: 在一个类中,允许存在相同方法名的方法,前提是他们的形参列表不相同

意味着,当一个类中的多个方法的方法名相同,但形参列表各不相同时,我们称之为方法的 重载

在方法被调用时,JVM 通过形参列表调用最匹配的方法,先找形参个数、类型最匹配的,其次找个数和类型可兼容的,多个兼容时会报错。例如:形参为int、double、long 等类型时。

5.7 方法进阶-可变个数形参

在JDK5版本中,新增了 “Varargs(variable number of arguments)” 机制,即当定义一个方法时,形参列表的个数可动态不确定,通俗讲就是 可变个数形参

格式:

方法名(参数类型... 参数名)

举例1:
add(int... numbers) {
    //方法体
}
举例2:
add(String name, int... numbers) {
    //方法体
}

Tips:

  • 可变个数形参等同于数组参数,二者不可同时存在
  • 可变个数形参必须声明在参数列表的最后
  • 一个方法的形参列表中只能存在一个可变个数形参

5.8 方法进阶-参数传递机制

Java中的参数传递方式只有一种: 值传递

  • 当参数类型为基本数据类型时,实参将数据值传递给形参
  • 当参数类型为引用数据类型时,实参将地址值传递给形参

简单理解为:将实参在栈内存中的数据进行传递

  • 基本数据类型不需要在堆中开辟空间,直接从常量池中获取实际数据
  • 引用数据类型会在堆内存中开辟空间,所以栈内存中存储的是地址值

举例:

public class Test {
    public static void main(String[] args) {
        // 基本数据类型
        int a = 8;
        int b = a;
        System.out.println("a = " + a + ", b = " + b); // a=8,b=8
        a++;
        System.out.println("a = " + a + ", b = " + b); // a=9,b=8
        b++;
        System.out.println("a = " + a + ", b = " + b); // a=9,b=9
        
        // 引用数据类型
        int[] arr1 = new int[]{1,2,3};
        int[] arr2 = arr1;
        arr1[0] = 8;
        // 此时,arr1的0位为8,arr2的0位为8
    }
}

内存解析:

image.png

5.9 方法进阶-递归

概念:递归就是 “自己调自己”

  • 直接递归:一个方法中,直接调用这个方法他自己本身
  • 间接递归:A方法中调用B,B方法中调用C,C方法中调用A

注意事项:

  • 递归方法其本质是一种隐式的循环
  • 递归方法必须向已知方向递归,否则会导致无限递归,进入死循环状态,导致栈内存溢出问题!

代码示例:

public class Test {
    public static void main(String[] args) {
        int sum = getSum(3);
        System.out.println(sum); // 6
    }
    
    // 递归方法:求num到1的和(num不能小于0)
    public int getSum(int num) {
        if(num <= 0) {
            return 0;
        }
        if(num == 1) {
            return 1;
        }
        return num + getSum(num - 1);
    }
}

内存解析:

image.png

6. 类的成员之三:构造器

在Java中,当我们 new 一个对象时,所有的成员变量都是默认值,但当我们需要赋予其他值时,则需要手动挨个赋值,这样就很麻烦,有没有更简洁的方式呢?

有!那就是构造器 (Constructor),其作用就是在 new 对象的同时进行赋值

语法格式:

[修饰符] class 类名{
    [修饰符] 构造器名(){
    	// 实例初始化代码
    }
    [修饰符] 构造器名(参数列表){
        // 实例初始化代码
    }
}

注意事项:
1. "构造器名必须与类名相同!"
2. 构造器"无需声明返回值类型,没有返回值!"
3. 构造器的修饰符"只能是权限修饰符"public、protect、缺省、private),不能被其他修饰符修饰!
4. 创建类以后,在没有显式声明构造器时,默认存在一个空参构造;"一旦声明构造器,则默认的无参构造器失效!"
5. 调用子类的构造器时,通常会"直接或间接调用父类的构造器"

举例:

public class Student {
    private String name;
    private int age;
    
    // 无参构造
    public Student() {}
    
    // 有参构造
    public Student(String n,int a) {
        name = n;
        age = a;
    }
}

public class Test {
    public static void main(String[] args) {
        //调用无参构造创建学生对象
        Student s1 = new Student();
        //调用有参构造创建学生对象
        Student s2 = new Student("张三",23);
    }
}

6. 类的成员之四:代码块

6.1 代码块的概念

成员变量想要初始化的值不是一个硬编码的常量值,而是需要通过复杂的计算或读取文件、或读取运行环境信息等方式才能获取的一些值,此时可通过代码块实现

简述:可用于对 Java 类或对象进行初始化

6.2 (普通)代码块

每次创建对象时都会执行一次,类似构造器的作用一样,也是用于实例变量的初始化等操作

如果多个重载的构造器有公共代码,并且这些代码都是先于构造器其他代码执行的,那么可以将这部分代码抽取到非静态代码块中,减少冗余代码

语法格式:

[修饰符] class 类{
    {
        // 代码块
    }
    [修饰符] 构造器名(){
        // 实例初始化代码
    }
    [修饰符] 构造器名(参数列表){
        // 实例初始化代码
    }
}

特点:

  • 可以对类的属性、类的声明进行初始化操作
  • 除了调用非静态的结构外,还可以调用静态的变量或方法
  • 若有多个非静态的代码块,那么按照从上到下的顺序依次执行
  • 每次创建对象的时候,都会执行一次。且先于构造器执行

6.3 静态代码块

类加载时就会执行,想要为静态变量初始化,可直接给静态变量直接赋值,也可使用 静态代码块

语法格式:

[修饰符] class 类{
    // 普通代码块
    {
        // 代码
    }
    // 静态代码块
    static {
        // 代码
    }
}

特点:

  • 可以对类的属性、类的声明进行初始化操作
  • 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法
  • 若有多个静态的代码块,那么按照从上到下的顺序依次执行
  • 静态代码块的执行要先于非静态代码块
  • 静态代码块 随着类的加载而加载,且只执行一次