从基础语法到面向对象设计原理

一、面向对象编程基础概念 

1.1 面向过程与面向对象的对比

1.1.1 面向过程:关注问题求解的步骤

(1)定义

C语言是一种面向过程的语言 是一种以“过程”为中心的编程思想,程序由一个或多个函数组成,强调的是“怎么做”。

(2)函数 :

  1. 程序的基本单位,所有操作都封装在函数中。
  2. 数据与操作分离 :数据和操作数据的函数是分开的。
  3. 顺序执行 :程序按照顺序一步步执行函数。
  4. 结构化编程 :支持顺序、选择、循环三种结构。

(3)特点:

  1. 程序结构清晰,逻辑直观。
  2. 适合处理简单任务和小型项目。
  3. 代码复用性差,模块化程度低。
  4. 缺乏封装、继承、多态等面向对象特性。

(4)代码示例:

#include<stdio.h>
void miku()
{
printf("hello,world");
}
int main()
{
miku();
return 0;
}


1.1.2 面向对象:关注参与过程的主体

 (1)定义

  • 面向对象编程 是一种以“对象”为核心的编程思想,程序由类和对象组成,强调“谁来做 ”。Java 是一种纯面向对象语言 ,几乎所有代码都封装在类中。
  • OOP 的核心是将数据和操作封装在对象中,通过对象之间的交互完成任务。

(2)核心概念

  1. 类(Class) :对象的模板,定义对象的属性和行为。
  2. 对象(Object) :类的实例。
  3. 封装(Encapsulation) :将数据和方法封装在类中,隐藏实现细节。
  4. 继承(Inheritance) :子类可以继承父类的属性和方法。
  5. 多态(Polymorphism) :一个接口可以有多种实现方式。
  6. 抽象(Abstraction) :隐藏复杂实现,只暴露必要接口。

(3)特点

  1. 数据与行为紧密结合,封装性好。
  2. 模块化程度高,易于维护和扩展。
  3. 支持大型软件开发,适合复杂系统。
  4. 提供更高的代码复用性和灵活性。

(4)代码示例

public class miku {
    //属性
    private String mika;
    //构造方法
    public miku(String mika) {
        this.mika = mika;
    }
    //方法
    public void misa(){
        System.out.println("mika: " + mika);
    }
    //主方法
    public static void main(String[] args) {
        miku m = new miku("hello");
    }
}


1.1.3 举例说明:冰箱与大象的两种实现方式

(1)C语言(面向过程)实现方式

以函数 为核心,按照步骤一步步来完成任务。强调“怎么做"。

步骤:

  1. 打开冰箱门;
  2. 把大象放进去;
  3. 关上冰箱门。

(2)javase实现方式

以对象 为核心,将“冰箱”和“大象”看作对象,每个对象都有自己的属性和行为 。强调“谁来做 ”。

步骤:

  • 定义 Fridge 类(冰箱),具有打开、关闭等行为;
  • 定义 Elephant 类(大象),具有进入冰箱的行为;
  • 在主程序中创建对象并调用方法。 


1.1.4 图表展示:面向过程与面向对象的流程图对比

(1)面向过程

 

  •  程序由函数调用 构成。
  • 每个步骤是一个函数,顺序执行。
  • 数据(如冰箱状态、大象状态)通常作为全局变量或参数 传递。
  • 强调“怎么做 ”。

(2)面向对象

 

  •  程序由函数调用 构成。
  • 每个步骤是一个函数,顺序执行。
  • 数据(如冰箱状态、大象状态)通常作为全局变量或参数 传递。
  • 强调“怎么做 ”。
1.1.5认识null

 (1)null 在 Java 中为 "空引用", 表示不引用任何对象. 类似于 C 语言中的空指针. 如果对 null 进行 . 操作就会引发异常.

class Person {
 public String name;
 public int age;
 }
 class Test {
 public static void main(String[] args) {
 Person person = new Person();
 System.out.println(person.name.length());   
    }
 }
 // 获取字符串长度
// 执行结果
Exception in thread "main" java.lang.NullPointerException
 at Test.main(Test.java:9)

(2)字段就地初始化

 class Person {
 public String name = "张三";
public int age = 18;
 }
 class Test {
 public static void main(String[] args) {
 Person person = new Person();
 System.out.println(person.name);
 System.out.println(person.age);
    }
 }
 // 执行结果
张三
18

 1.2 类与对象的定义

1.2.1 类是对象的模板,对象是类的具体实例

(1)类的定义: 

类是对一个实体进行描述,描述其所拥有的属性和方法,例如,类就是洗衣机的设计图,它包含了对洗衣机的外观,重量等,属性和洗衣,甩干等功能。

通过class定义一个类:

class Wash{
public String color;
public int weight;
public void Washclose(){
System.out.println("关闭");
}
}

(2)使用new创建一个新的对象 

Wash mywash=new Wash();

 此时,mywash 就是一个具体的对象,它是 Wash类的一个实例,拥有独立的内存空间和实际的数据值。


1.2.2 类包含属性和行为

 (1)每一个类都由两大部分组成:属性(成员变量) 和 行为(方法) 。每一个类都由两大部分组成:属性(成员变量) 和 行为(方法) 。

  • 属性 用来描述对象的状态或特征。例如,一个人的姓名、年龄、身高、体重都是他的属性。

  • 行为 用来描述对象能够执行的操作。例如,人可以走路、说话、吃饭,这些就是行为,对应 Java 中的 方法

 在 Java 中,属性通常定义为类中的变量,而行为则通过方法来实现。例如:

public class Student {
    // 属性(成员变量)
    String name;
    int studentId;
    double score;

    // 行为(方法)
    public void study() {
        System.out.println(name + " 正在认真学习。");
    }

    public void takeExam() {
        System.out.println(name + " 参加了考试,成绩为:" + score);
    }
}public class Student {
    // 属性(成员变量)
    String name;
    int studentId;
    double score;

    // 行为(方法)
    public void study() {
        System.out.println(name + " 正在认真学习。");
    }

    public void takeExam() {
        System.out.println(name + " 参加了考试,成绩为:" + score);
    }
}

每个 Student 对象在创建后都会拥有自己独立的 namestudentIdscore 值,但它们共享相同的 study()takeExam() 方法逻辑。这种设计既保证了数据的独立性,又实现了代码的复用。 

1.2.3 比喻说明:月饼模具与月饼、建筑图纸与房屋
  1. 月饼模具与月饼
    月饼模具是用来制作月饼的工具,它规定了月饼的形状和花纹。这个“模具”就是类 。用模具压出来的每一个月饼,就是根据这个模板生成的具体产品,它们就是对象 。同一个模具可以做出很多个月饼,每个月饼外观相同,但馅料、重量可能不同。这就像程序中用一个类创建多个对象,每个对象有独立的属性值。

  2. 建筑图纸与房屋
    建筑师画的住宅设计图,规定了房子的结构、房间布局等信息。这张“图纸”就是类 。开发商根据图纸建起来的一栋栋真实房屋,就是对象 。虽然所有房屋外观一致,但每栋的住户、装修、使用状态都可能不同。图纸不占物理空间,而房屋是实际存在的实体,对应程序中类不占内存、对象才占用内存

1.2.4 类的实例化过程:通过new关键字创建对象

 在 Java 中,从类创建对象的过程称为 实例化 ,必须使用 new 关键字来完成。其基本语法为:

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

 例如:

Student s1 = new Student();

这条语句的执行过程包括以下几个步骤:

  1. 在堆内存中为对象分配空间;
  2. 调用类的构造函数(默认或自定义)初始化对象的属性;
  3. 返回该对象在堆中的内存地址;
  4. 将地址赋值给栈中的引用变量 s1

需要注意的是,Java 中的对象始终存储在堆内存中,而变量 s1 只是一个指向该对象的“引用”,存储在栈内存中。因此,多个引用可以指向同一个对象,也可以一个对象被多个引用共享。

通过这种机制,Java 实现了高效的内存管理和灵活的对象操作,也为后续的封装、继承、多态等面向对象特性奠定了基础。

二、类的定义与对象的实例化

2.1 Java中类的定义

在Java中,类 是面向对象编程的核心单元,它是对一类具有相同属性和行为的事物的抽象描述。我们可以将类理解为一个模板 或蓝图 ,它定义了这类事物“是什么”以及“能做什么”。只有通过这个模板创建出具体的对象 ,才能在程序中实际使用。 

2.1.1 使用class关键字定义类

 在Java中,我们使用 class 关键字来声明和定义一个类。

class Class {
    // 类的主体:包含字段(成员变量)和方法(成员方法)
}
  • class :是Java中的一个关键字,用于告诉编译器接下来要定义一个类。
  • Class:是您为这个类起的名字。类名的命名遵循大驼峰命名法 ,即每个单词的首字母大写,例如 Student, BankAccount, Carclass :是Java中的一个关键字,用于告诉编译器接下来要定义一个类。

注: 一个.java源文件中可以定义多个类,但有且仅有一个类可以被 public 修饰 ,并且这个 public 类的名称必须与 .java 文件的名称完全一致 。例如,如果文件名为 Person.java,那么其中的 public 类必须是 public class Person{...}。


2.1.2 类的主体包含字段和方法

 一个类的主体(即 {} 内部)主要由两大部分构成:字段(Field) 和 方法(Method) 。它们共同描述了类的状态 和行为 。

1.字段:

  • 定义 :字段,也被称为属性 或成员变量 ,是用来描述对象状态或特征的数据。它们是定义在类中、方法外部的变量。
  • 作用 :存储对象的数据。例如,在 Person 类中,name(姓名)、age(年龄)、sex(性别)就是它的字段。

注:如果字段在声明时没有被显式初始化,Java会为其赋予一个默认值

  • 整数类型 (byteshortintlong) 的默认值为 0

  • 浮点类型 (floatdouble) 的默认值为 0.0

  • 字符类型 (char) 的默认值为一个空字符,即 '\u0000',其数值为 0

  • 布尔类型 (boolean) 的默认值为 false

class Person {
    public String name; // 引用类型字段,默认值为 null
    public int age;     // 数字类型字段,默认值为 0
    public boolean isStudent; // boolean类型字段,默认值为 false
}

 (2)方法:

  • 定义 :方法,也被称为成员方法 ,是用来描述对象能够执行的操作或功能的代码块。
  • 作用 :封装对象的行为。例如,在 Person 类中,eat()(吃饭)、sleep()(睡觉)、show()(展示信息)就是它的方法。
class Person {
    public String name;
    public int age;

    // 成员方法:展示个人信息
    public void show() {
        System.out.println("我叫" + name + ",今年" + age + "岁。");
    }

    // 成员方法:改变名字
    public void setName(String newName) {
        name = newName;
    }
}

注:核心思想 :一个类通过字段 来“记住”它的数据,通过方法 来“做”事情。这种将数据和操作数据的方法捆绑在一起的设计,正是面向对象“封装”思想的体现 


2.1.3 示例代码展示一个简单类的定义

(1)定义Person类:

// Person.java
class Person {
    // --- 字段(成员变量) ---
    public String name; // 姓名
    public int age;     // 年龄
    public String sex;  // 性别

    // --- 方法(成员方法) ---
    // 行为1:吃饭
    public void eat() {
        System.out.println(name + " 正在吃饭!");
    }

    // 行为2:睡觉
    public void sleep() {
        System.out.println(name + " 正在睡觉!");
    }

    // 行为3:展示个人信息
    public void show() {
        System.out.println("姓名:" + name + ",年龄:" + age + ",性别:" + sex);
    }
}

(2)创建测试并使用Person对象

// Main.java
public class Main {
    public static void main(String[] args) {
        // --- 实例化对象 ---
        // 使用 new 关键字创建 Person 类的两个具体实例
        Person person1 = new Person();
        Person person2 = new Person();

        // --- 为对象的字段赋值 ---
        person1.name = "张三";
        person1.age = 25;
        person1.sex = "男";

        person2.name = "李四";
        person2.age = 22;
        person2.sex = "女";

        // --- 调用对象的方法 ---
        System.out.println("=== 个人信息 ===");
        person1.show(); // 输出:姓名:张三,年龄:25,性别:男
        person2.show(); // 输出:姓名:李四,年龄:22,性别:女

        System.out.println("\n=== 行为调用 ===");
        person1.eat();  // 输出:张三 正在吃饭!
        person2.sleep(); // 输出:李四 正在睡觉!
    }
}

(3)执行结果:

=== 个人信息 ===
姓名:张三,年龄:25,性别:男
姓名:李四,年龄:22,性别:女

=== 行为调用 ===
张三 正在吃饭!
李四 正在睡觉!

(4)

  • class Person {...} 定义了一个名为 Person 的类。
  • name, age, sex 是该类的三个字段 ,用于存储数据。
  • eat(), sleep(), show() 是该类的三个方法 ,用于定义行为。
  • 在 Main 类的 main 方法中,new Person() 创建了两个 Person 类的对象 person1 和 person2。
  • 通过 对象名.字段名 的方式为每个对象的字段赋值。
  • 通过 对象名.方法名() 的方式调用每个对象的方法。

2.2 对象的实例化

2.2.1 使用new关键字创建对象

在 Java 中,要从一个类“变”出一个真实可用的对象,必须使用一个非常关键的关键词:new

你可以把 new 想象成一个“对象制造机 ”。你给它一张图纸(类名),它就会立刻帮你造出一个实物(对象)。

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

注:new 不仅创建了对象,还会自动调用类的构造方法 来给对象的属性设置初始值。 


2.2.2 对象的引用与内存分配

 1.核心概念:

  • 对象 :是真实存在的实体,它存储在内存的堆(Heap) 区域。堆空间大,专门用来存放各种对象。
  • 引用 :是一个“指针”或“遥控器”,它存储在内存的栈(Stack) 区域。栈空间小但访问快,存放的是局部变量和方法调用信息。

想象你租了一间仓库(堆内存)来存放你的钢琴(对象)。你手里拿着一把钥匙(引用),这把钥匙能打开仓库的门。钥匙本身不是钢琴,但它能让你找到并使用钢琴。如果你把钥匙弄丢了(引用为 null),你就再也找不到那架钢琴了,它虽然还在仓库里,但对你来说已经“消失”了。 

2.代码示例:

Person person = new Person();

 new Person()在堆中创建了一个person对象,当前对象中有着题对应的属性

person是一个引用,他储存在栈中,相当于一个指针指向堆中对象的地址

person.name="张三"
person.age=25 ;
//当进行这个操作的时候,person就会对指向的堆中的对象中的属性进行修改,
//类似于person是把钥匙,堆中的对象相当于仓库,里面的成员属性就相当于仓库里的物品,例如钢琴等
//进行这个操作就相当于拿着钥匙打开仓库进去对钢琴进行操作

2.2.3 多个对象的创建与独立性

1.一个类可以创建无数个对象,就像一套图纸可以盖无数栋楼。但每一栋楼都是独立的,一栋楼漏水不会导致另一栋楼也漏水。

在 Java 中,每个通过 new 创建的对象都是相互独立 的。它们拥有自己独有的属性值,互不影响。

Person person1 = new Person();
person1.name = "张三";
person1.age = 25;

Person person2 = new Person();
person2.name = "李四";
person2.age = 20;

person1.age = 26; // 只修改了张三的年龄

System.out.println(person2.age); // 输出:20,李四的年龄没变!

 注:person1 和 person2 是两个不同的引用,它们分别指向堆内存中两个不同的 Person 对象。修改其中一个对象的属性,完全不会影响另一个对象。 相当于张三李四用同一张图纸改了两座房子,但是张三盖的房子是张三的,李四盖的房子是李四的。

2.静态成员

用 static 修饰的成员(如 static int count)是“全班共享”的。它不属于任何一个对象,而是属于整个“类”。所有对象都共用同一个 count 值。这就像一个班级的“总人数”,不是某个学生独有的。


2.2.4 图表说明:类与对象的关系、内存布局
[ 栈内存 (Stack) ]        [ 堆内存 (Heap) ]          [ 方法区 (Method Area) ]
                         ┌─────────────────┐
  person1 ──────────────→│  Person 对象 #1 │          ┌──────────────┐
                         │  name: "张三"   │          │   Person类    │
                         │  age:  25      │          │ - age (int)  │
                         └─────────────────┘          │ - name (String)│
                         ┌─────────────────┐          │ - eat()      │
  person2 ──────────────→│  Person 对象 #2 │          │ - sleep()    │
                         │  name: "李四"   │          │ - count (static)│
                         │  age:  20      │          └──────────────┘
                         └─────────────────┘

类是模板,对象是实物;引用是钥匙,堆是仓库;一个钥匙开一扇门,每扇门后是独立的世界。 

2.3 类与对象的关系

2.3.1 类是抽象的,对象是具体的
  • 类 是一种抽象的概念 ,它不占用实际的内存空间,也不参与程序的运行。它只是一套“规则”或“模板”,告诉你“这类东西应该有什么、能做什么”。
  • 而 对象 是具体的实体 ,它是类在内存中的真实存在。当你创建一个对象时,Java 才会为它分配实实在在的内存空间,存储它的数据。

在代码中,这种抽象与具体的关系体现在:

class Dog { ... } 是抽象的,它存在于 .java 文件中

Dog dog = new Dog(); 创建的是具体的,它存在于电脑的内存里。


2.3.2 一个类可以创建多个对象

这是面向对象编程(java)的强大之处:一次定义,无限使用 。

你可以把类想象成一个“自动生产机”,只要按下按钮(new),它就能源源不断地生产出结构相同但内容各异的对象。

Person p1=new Person()
Person p2=new Person()
Person p3=new Person()

 这三行代码创建了 3 个独立的 Person 对象 。它们都遵循 Person 类的定义(都有 name、age 属性),但每个对象都有自己独立的数据空间。修改 p1 的名字,不会影响 p2 或 p3。(前文也已经提起过)


2.3.3 类的实例化过程详解

 实例化的概念:实例化是指根据一个类创建具体对象的过程。类是抽象的模板,定义了对象的属性和行为;而实例化就是使用 new 关键字,按照这个模板在内存中生成一个真实的、可操作的对象。每个通过实例化产生的对象都拥有独立的数据空间,可以单独调用类中定义的方法。简单说,实例化就是“从图纸造出房子”的过程。

通俗易懂的说就是用图纸盖房子的全过程,当我们写下 new Person() 时,Java 背后发生了三件大事:

  1. 分配内存(在堆中)
    JVM 在堆内存中开辟一块新空间,用于存放这个新对象的所有实例变量 (如 name, age)。就像为新房子打地基、砌墙。

  2. 执行初始化(构造方法)
    系统自动调用类的构造方法 (Constructor)。如果程序员没写,Java 会提供一个“默认构造方法”,把数字设为0,布尔设为false,引用设为null。这一步就像给新房子通水、通电、做基础装修。

  3. 返回引用(在栈中)
    new 操作完成后,会返回一个“地址”(引用),这个地址指向堆中的对象。然后把这个地址赋值给左边的变量(如 p1)。这就像把新房的钥匙交给了业主。


2.3.4 类型与引用的关系:类是模板,对象是实体
  1.  类(Class) :是一种数据类型 ,就像 int、String 一样。Person 就是一个自定义的引用类型。
  2. 引用(Reference) :是一个“指针”或“遥控器”,它存储的是对象在内存中的地址。
  3. 对象(Object) :是存储在堆内存中的真实数据。

用比喻来说就是:

在一个停车场(堆内存)之中

类 就像是“轿车”这种车型的定义。
对象 就是停在车位上的一辆辆真实的轿车(如一辆红色宝马、一辆黑色奔驰)。
引用 就是车主手中的“车钥匙”。钥匙上写着“宝马”(类型),它能打开特定车位上的那辆车(对象)

Person p = new Person(); 
  • Person 是类型 ,说明 p 这把“钥匙”只能用来开“Person”这种“车”。
  • p 是引用变量 ,它存放在栈中,值是堆中对象的地址。
  • new Person() 创建的是对象实体 ,存放在堆中。

2.3.5图表说明:类与对象的关系、内存布局

[ 栈内存 (Stack) ]       [ 堆内存 (Heap) ]          [ 方法区 (Method Area) ]
  p1 ────────────────→  ┌─────────────────┐          ┌──────────────┐
                         │   Person 对象 1   │          │   Person类    │
  p2 ────────────────→  │   name: "张三"   │          │ - name: String│
                         │   age:  25      │          │ - age: int    │
                         └─────────────────┘          │ - eat(): void │
                         ┌─────────────────┐          └──────────────┘
  p3 ────────────────→  │   Person 对象 2   │
                         │   name: "李四"   │
                         │   age:  22      │
                         └─────────────────┘

  • :存放引用变量 p1, p2, p3
  • :存放两个独立的 Person 对象,每个对象都有自己的属性。
  • 方法区 :存放 Person 类的结构信息(如方法代码),被所有对象共享

三、类的成员详解

3.1 成员变量(字段)

3.1.1 字段的定义方式与命名规范

 成员变量是定义在类中、方法之外 的变量。它是类的组成部分

class Person {
    // 以下都是成员变量(字段)
    public String name;
    public int age;
    public boolean isStudent;
    public double height;
}

 命名规范 :

  • 一般采用小驼峰用于增加代码的可读性
  • 小驼峰命名法:首单词的小写,第二个单词开始每个单词首字母大写
  • 避免采用大驼峰命名法以及Java中的关键字

3.1.2 默认值规则:数字为0,布尔为false,引用为null

 这是Java为开发者提供的一个重要“安全网”。当你定义了一个成员变量但没有给它赋值时,Java会自动为其赋予一个默认初始值 。这与局部变量 (定义在方法内部的变量)形成鲜明对比,局部变量必须手动初始化才能使用,否则编译会报错。

变量类型默认值
整数类型 (byte" "short"
浮点类型 (float" "double)
字符类型 (char)&#39;\u0000&#39; (空字符)
布尔类型 (boolean)FALSE
引用类型 (String" 数组

class Person {
    public int age;        // 默认值为 0
    public double salary;  // 默认值为 0.0
    public boolean isEmployed; // 默认值为 false
    public String name;    // 默认值为 null
}

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        System.out.println(p.age);        // 输出:0
        System.out.println(p.salary);     // 输出:0.0
        System.out.println(p.isEmployed); // 输出:false
        System.out.println(p.name);       // 输出:null
    }
}

注:这个默认值规则仅适用于成员变量 !局部变量没有默认值 


3.1.3 就地初始化 vs 构造函数初始化

为成员变量设置初始值有多种方式,最常用的是“就地初始化”和“构造函数初始化”。

 1.就地初始化:

在声明变量的同时就给它赋值

class Person {
    public String name = "林夕";  // 就地初始化
    public int age = 22;          // 就地初始化
}
  •  优点 :简单直观,代码清晰。
  •  缺点 :灵活性差。

2.构造函数初始化:

在类的构造方法中为成员赋值,在new新的对象的时候构造方法会被自动调用

class Person {
    public String name;
    public int age;

    // 构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 25); // p1 的初始值是张三, 25
        Person p2 = new Person("李四", 30); // p2 的初始值是李四, 30
    }
}
  • 优点 极其灵活 。你可以在创建每个对象时,动态地传入不同的初始值。
  • 核心思想 :构造方法是对象“出生”时的“洗礼”,它决定了这个对象一“诞生”就拥有什么样的状态。

注: 

一个类可以同时拥有:

  • 无参构造方法
  • 带一个参数的构造方法
  • 带两个参数的构造方法
  • 带不同参数类型的构造方法

 如果一个字段同时有就地初始化和构造函数初始化,那么构造函数的赋值会覆盖就地初始化的值 。因为就地初始化的代码实际上会被“移动”到构造函数的最前面执行。


3.1.4 null的含义与使用注意事项

null 是Java中一个非常特殊且重要的值

1.null的含义:

null 表示一个空引用 (null reference)。它意味着这个引用变量不指向堆内存中的任何对象 。

对于引用类型的成员变量,如果没有显式初始化,其默认值就是 null。
你可以主动将一个引用变量赋值为 null,表示“我现在不指向任何对象了”

2.代码示例:

class Person {
    public String name; // 默认值为 null
}

public class Main {
    public static void main(String[] args) {
        Person p = new Person(); // p 指向一个 Person 对象
        System.out.println(p.name); // 输出:null

        p = null; // 将 p 置为 null,它不再指向任何对象
        // System.out.println(p.name); // 千万不要这样做!
    }
}class Person {
    public String name; // 默认值为 null
}

public class Main {
    public static void main(String[] args) {
        Person p = new Person(); // p 指向一个 Person 对象
        System.out.println(p.name); // 输出:null

        p = null; // 将 p 置为 null,它不再指向任何对象
        // System.out.println(p.name); // 千万不要这样做!
    }
}

 注:注意区分引用变量和基本变量之间的区别:

引用变量:如 String name;、数组、自定义类等

2.对一个值为 null 的引用变量进行 . 操作 (即访问其字段或调用其方法),会引发一个非常常见的运行时错误:NullPointerException(空指针异常)

p.name = "张三"; // 如果 p 是 null,这里会报错!
p.eat();         // 如果 p 是 null,这里会报错!

代码优化: 先进行判断该引用变量是否为null,如果是则再次进行操作

if (p != null) {
    p.eat();
}

 合理初始化 :尽量在创建对象时就通过构造函数为引用类型的字段赋予一个合理的初始值(如空字符串 "" 而不是 null),除非 null 本身具有特殊的业务含义(比如表示“未婚”)。

用生活比喻就是:null 就像一把“坏钥匙”或一张“空地图”。你拿着它去找东西,但根本找不到入口,程序就会“迷路”并报错

3.2 成员方法

3.2.1 方法的定义与调用方式

1.定义方式 : 成员方法是定义在类中、其他方法之外 的函数。它描述了该类所有实例可以执行的操作。 

class Person {
    public String name;

    // 成员方法:打招呼
    public void greet() {
        System.out.println("你好,我是" + name + "。");
    }
}

 public :访问修饰符,表示该方法可以被任何其他类调用。
void :返回值类型,表示该方法不返回任何值。
greet :方法名,遵循小驼峰命名法。
() :参数列表,这里为空,表示该方法不需要外部输入。
{} :方法体,包含该方法要执行的具体代码。

2.调用方式:

public class Main {
    public static void main(String[] args) {
        Person p = new Person(); // 创建对象
        p.name = "张三";         // 为对象的属性赋值

        p.greet(); // 调用对象的行为
        // 输出:你好,我是张三。
    }
}

成员方法必须要通过对象的引用来调用,通过.操作符来进行操作。 

 3.谁拥有数据,谁对外提供操作这些数据(私有)的方法。

(1)谁拥有数据?
数据(即类的成员变量)应该被类的内部 所拥有。
为了保护数据不被随意修改,这些数据通常用 private 关键字修饰,变成私有成员。这意味着外部代码(其他类)不能直接访问或修改这些数据。
(2)谁提供操作方法?
正是因为这个类拥有数据,所以它也必须提供一套公开的(public)方法来让外部代码安全地 操作这些数据。

在这个例子中,name 是 Person 对象“拥有”的数据。greet() 方法是 Person 类提供的一个“对外接口”,它允许外界通过 p.greet() 这个指令,让 Person 对象“主动”地展示自己的信息。这体现了“被动的一方是数据的拥有者 ”的设计思想——数据是被动的,而方法是主动的,它封装了对数据的操作。 


3.2.2 方法的返回值与参数传递

 1.返回值:

方法可以执行一个任务,并将结果“返回”给调用者。

有返回值的方法 :需要在方法签名中指定返回值类型,并在方法体内使用 return 语句。

class Person {
    public int age;

    // 方法返回一个 int 类型的值
    public int getAgeInDays() {
        return age * 365; // 将年龄转换为天数
    }
}

// 调用
Person p = new Person();
p.age = 25;
int days = p.getAgeInDays(); // 接收返回值
System.out.println("年龄(天):" + days);

 无返回值的方法 :使用 void 关键字,表示不返回任何值。

class Person {
    public int age;

    // 原有的方法:有返回值
    public int getAgeInDays() {
        return age * 365; // 将年龄转换为天数
    }

    // 新增的方法:没有返回值 (void)
    public void introduce() {
        System.out.println("我今年 " + age + " 岁了。");
        // 这个方法执行完就结束,不向调用者传递任何数据
    }
}

// 调用示例
Person p = new Person();
p.age = 25;

// 调用有返回值的方法
int days = p.getAgeInDays();
System.out.println("年龄(天):" + days);

// 调用没有返回值的方法
p.introduce(); // 直接执行方法,不接收返回值

注:构造方法是不需要返回值或者void的加上会变成普通的方法

2.参数传递:

方法可以接收外部输入的“参数”,以实现更灵活的功能。

class Person {
    public String name;
    public int age;

    // 接收一个 String 类型的参数
    public void setName(String newName) {
        name = newName; // 使用传入的参数值
    }

    // 接收两个 int 类型的参数
    public void setAgeAndShow(int newAge, int showTimes) {
        age = newAge;
        for (int i = 0; i < showTimes; i++) {
            System.out.println("年龄已设置为:" + age);
        }
    }
}

// 调用
Person p = new Person();
p.setName("李四");               // 传入 "李四"
p.setAgeAndShow(30, 2);          // 传入 30 和 2

 注:Java是值传递 ,对于基本类型(int, boolean等),传递的是值的副本 ;对于引用类型(如 String),传递的是引用的副本 。这意味着在方法内部修改参数,不会影响外部的原始变量(对于基本类型),但可以通过引用修改对象内部的状态。


3.2.3 this关键字的使用与理解

 this 是Java中一个非常重要的关键字,它代表当前对象的引用 。

核心作用:

1.区分同名变量 :当方法的参数名与成员变量名相同时,使用 this 来指明操作的是成员变量。

public void setName(String name) {
    this.name = name; // this.name 指成员变量,name 指参数
}

注:如果不用this可能会造成自身赋值自身的错误。

 2.在构造方法中调用其他构造方法 :可以使用 this() 在一个构造方法中调用本类的另一个构造方法,实现代码复用。

class Person {
    public String name;
    public int age;

    // 默认构造方法
    public Person() {
        this("未知", 18); // 调用下面的构造方法
    }

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

注:this 不是“当前对象本身”,而是“指向当前对象的引用”。在 p.setName("张三") 被调用时,this 就等价于 p,它让我们可以在方法内部操作 p 这个对象的成员。  通俗易懂的说就是谁调用谁就是this这个值


3.2.4 示例:show方法展示对象行为
class Person {
    private String name; // 使用 private 封装数据
    private int age;

    // 构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter方法,提供对私有字段的读取
    public String getName() {
        return name;
    }

    // Setter方法,提供对私有字段的修改
    public void setName(String name) {
        this.name = name;
    }

    // 核心行为方法:展示个人信息
    public void show() {
        System.out.println("我叫" + name + ",今年" + age + "岁。");
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建两个对象
        Person p1 = new Person("张三", 25);
        Person p2 = new Person("李四", 30);

        // 调用 show 方法
        p1.show(); // 输出:我叫张三,今年25岁。
        p2.show(); // 输出:我叫李四,今年30岁。

        // 修改 p1 的名字
        p1.setName("张三丰");
        p1.show(); // 输出:我叫张三丰,今年25岁。
    }
}

3.3 构造方法

3.3.1 构造方法的作用与定义规则

1.作用:
构造方法是用于初始化新创建对象的特殊方法。它的核心任务是:

  1. 为成员变量分配内存空间。
  2. 为成员变量赋予初始值,使其处于一个稳定、可用的状态。
  3. 没有构造方法,我们的对象可能带着“默认值”(如 age=0, name=null)开始运行,这在业务逻辑上往往是不合理的。

定义规则:
构造方法的定义遵循一套严格的规则,使其与普通方法区分开来:

1.方法名必须与类名完全相同。

 

class Person {
    public Person() { // 构造方法,名称与类名Person相同
        // 初始化代码
    }
}

2. 没有返回值类型。注意,这里不是 void,而是完全不写返回值类型。

public void Person() { ... } // 错误!这不是构造方法,这是一个普通方法

3.在对象创建时自动调用。当我们执行 new Person() 时,JVM会自动寻找并调用 Person 类的构造方法。 

注:

默认构造方法:

  • 如果一个类没有定义任何构造方法,Java编译器会自动提供一个无参的默认构造方法。
  • 一旦类中定义了任意一个构造方法(无论有参还是无参),编译器将不再提供默认构造方法。
  • 因此,如果你需要一个无参构造方法,就必须手动定义它。 

3.3.2 构造方法的重载

一个类可以有多个构造方法,只要它们的参数列表不同(参数的类型、个数或顺序不同),这被称为构造方法的重载。

为什么需要重载?
因为现实世界中的对象,其初始化方式往往是多样的。我们可能需要创建一个“默认的”Person,也可能需要创建一个“姓名和年龄都已知的”Person。

class Person {
    private String name;
    private int age;

    // 1. 无参构造方法:创建一个“默认”对象
    public Person() {
        this.name = "未知";
        this.age = 18;
    }

    // 2. 带一个参数的构造方法:只知道名字
    public Person(String name) {
        this.name = name;
        this.age = 18; // 默认年龄
    }

    // 3. 带两个参数的构造方法:知道名字和年龄
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

 调用方式:

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person();           // 调用无参构造
        Person p2 = new Person("张三");     // 调用带一个String参数的构造
        Person p3 = new Person("李四", 25); // 调用带两个参数的构造
    }
}

注:重载使得对象的创建更加灵活,可以根据不同的输入信息,选择最合适的初始化方案。 


3.3.3 使用this()调用其他构造方法

 

当一个类有多个重载的构造方法时,它们之间往往有代码重复。例如,上面的三个构造方法都需要为 name 和 age 赋值。为了避免重复,我们可以使用 this() 语句在一个构造方法中调用本类的另一个构造方法。

语法规则:

  • 使用 this(参数列表) 来调用本类的其他构造方法。
  • this() 调用必须是构造方法中的第一条语句。
  • 一个构造方法中只能调用一次 this(),并且不能成环(例如 A 调用 B,B 又调用 A)。
class Person {
    private String name;
    private int age;

    // 1. 无参构造:调用带参构造,提供默认值
    public Person() {
        this("未知", 18); // 调用下面的构造方法
    }

    // 2. 带一个参数的构造:调用带两个参数的构造,提供默认年龄
    public Person(String name) {
        this(name, 18); // 调用下面的构造方法
    }

    // 3. 带两个参数的构造:最终的初始化逻辑
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

 注:这种方式实现了代码复用,将最复杂的初始化逻辑集中在参数最全的构造方法中,其他构造方法只需调用它并传入默认值即可。

 如果构成环路会造成死循环:

class Example {
    // 直接自己调用自己
    public Example() {
        this(); // ❌ 编译错误:递归构造器调用
    }
    
    // 或者两个构造方法互相调用
    public Example(int x) {
        this(); // 调用无参构造方法
    }
    
    public Example() {
        this(5); // 调用带参构造方法 → 形成环路
    }
}

 修改后的结果:

class Person {
    private String name;
    private int age;

    // 正确的构造方法链(无环路)
    public Person() {
        this("未知", 18); // 调用最终构造方法
    }

    public Person(String name) {
        this(name, 18); // 调用最终构造方法
    }

    // 最终构造方法:不调用其他构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

3.3.4 构造方法与对象初始化的关系

 

构造方法是对象初始化过程的核心,但它并不是唯一的参与者。一个对象的完整初始化过程,是就地初始化、实例代码块和构造方法共同协作的结果。

完整的初始化顺序:

  • 就地初始化:在类中直接为成员变量赋值的语句(如 private String name = "未知";),会被“移动”到所有构造方法的最前面执行。
  • 实例代码块:用 {} 定义的代码块,也会被“移动”到所有构造方法的最前面执行,位于就地初始化之后。

构造方法:最后执行构造方法中的代码。

class Person {
    private String name = "就地初始化"; // 1. 就地初始化
    private int age;

    // 2. 实例代码块
    {
        System.out.println("实例代码块执行:name = " + name); // name已是"就地初始化"
        this.age = 10;
        System.out.println("实例代码块设置 age = " + age);
    }

    // 3. 构造方法
    public Person() {
        System.out.println("构造方法执行:name = " + name + ", age = " + age);
        this.name = "构造方法";
    }
}

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
    }
}

执行结果:

实例代码块执行:name = 就地初始化
实例代码块设置 age = 10
构造方法执行:name = 就地初始化, age = 10

 注:对象的初始化是一个“三步走”的过程:就地初始化 → 静态代码块→实例代码块 → 构造方法。它们共同确保了对象在被使用前,其内部状态是完整且一致的。构造方法是这个过程的最后一步,也是我们最常用来进行复杂初始化逻辑的地方。

3.4 静态成员(static关键字)

3.4.1 static修饰字段与方法的特性

static 修饰的成员,我们称之为静态成员,它独立于对象而存在,是类级别的“共享资源” 

1. static 字段(静态变量)
  • 共享性:被 static 修饰的字段,不依赖于任何对象。该类的所有实例都共享同一个静态变量。修改一个,所有对象看到的都会改变。
  • 存储位置:存放在JVM的方法区(Method Area),而不是堆内存中。
  • 生命周期:从类被加载时创建,到类被卸载时销毁,其生命周期与类相同,远长于任何单个对象。
2. static 方法(静态方法)
  • 类方法:静态方法也被称为“类方法”,因为它属于类本身,而不是类的某个实例。
  • 独立性:静态方法可以在不创建任何对象的情况下,直接通过类名调用。
  • 用途:常用于定义工具方法(如 Math.sqrt())、工厂方法或与类相关的全局操作。
class Person {
    public String name;           // 实例字段 - 每个对象独有
    public static int count = 0;  // 静态字段 - 所有对象共享

    // 静态方法:统计并返回总人数
    public static void showTotalCount() {
        System.out.println("当前总人数:" + count);
    }
}

 

3.4.2 静态成员与实例成员的区别
特性静态成员 (static)实例成员 (普通成员)
所属属于类本身属于类的每一个实例
内存位置方法区堆内存(在对象内部)
访问方式类名.成员名对象名.成员名
创建时机类加载时创建,只有一份创建对象时创建,每个对象一份
能否访问非静态成员不能直接访问可以访问
典型应用全局计数器、工具方法对象的姓名、年龄等个性化数据

 注:static 成员是“全局的”,而实例成员是“个体的”。就像一个班级的“总人数”是全班共享的(static),而每个学生的“姓名”是个人独有的(实例成员)


3.4.3 静态方法的调用方式与限制

1.调用方式:

 静态方法的调用非常简单,无需创建对象:

// 正确且推荐的调用方式
Person.showTotalCount(); 

// 虽然可以通过对象调用,但不推荐,容易造成误解
Person p = new Person();
p.showTotalCount(); // 不推荐,它本质上还是调用 Person.showTotalCount()

2.使用限制:

由于静态方法不依赖于任何对象,因此在设计上存在一些严格的限制:

不能直接访问实例成员:静态方法内部不能直接使用非静态的字段或调用非静态的方法。

public static void invalidMethod() {
    // System.out.println(name); // 编译错误!name是实例字段,不知道是哪个对象的
    // this.eat(); // 编译错误!this代表当前对象,但静态方法没有"当前对象"
}

不能使用 this 和 super:因为 this 是当前对象的引用,而静态方法不属于任何对象。
不能被重写(Override):静态方法属于类,不参与多态。子类可以定义同名的静态方法,但这称为“隐藏”(Hiding),而非“重写”。 


3.4.4 静态代码块与实例代码块的执行顺序

代码块是另一种初始化成员的方式,static 关键字也用于定义静态代码块。

  • 静态代码块:用 static {} 定义。在类被加载到JVM时执行,且只执行一次。常用于初始化静态资源(如数据库连接、配置文件读取)。
  • 实例代码块:用 {} 定义(不加 static)。在每次创建对象时执行,在构造方法之前执行。常用于为所有对象提供统一的初始化逻辑。

完整的执行顺序:

  1. 一个类的初始化和对象的创建,遵循一个严格的顺序:
  2. 静态代码块:类加载时执行一次。
  3. 实例代码块:创建对象时执行,每次创建都执行。
  4. 构造方法:创建对象时执行,完成对象的最终初始化。
class Person {
    {
        System.out.println("实例代码块执行");
    }
    static {
        System.out.println("静态代码块执行");
    }
    public Person() {
        System.out.println("构造方法执行");
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("开始创建第一个对象");
        Person p1 = new Person();
        
        System.out.println("\n开始创建第二个对象");
        Person p2 = new Person();
    }
}

 执行结果:

静态代码块执行
开始创建第一个对象
实例代码块执行
构造方法执行

开始创建第二个对象
实例代码块执行
构造方法执行

注 :静态代码块只在类第一次被使用时执行一次,而实例代码块和构造方法每次创建对象都会执行


3.4.5 示例:count变量的共享机制

 我们来用一个完整的例子,展示 static 字段 count 的共享机制。

class Person {
    private String name;
    public static int count = 0; // 静态字段:记录总人数

    public Person(String name) {
        this.name = name;
        count++; // 每创建一个新对象,总人数加一
        System.out.println("创建了 " + name + ",当前总人数:" + count);
    }

    public static void showTotalCount() {
        System.out.println("【统计】当前总人数:" + count);
    }
}

public class Main {
    public static void main(String[] args) {
        Person.showTotalCount(); // 输出:【统计】当前总人数:0

        Person p1 = new Person("张三"); // 创建张三,count=1
        Person p2 = new Person("李四"); // 创建李四,count=2

        Person.showTotalCount(); // 输出:【统计】当前总人数:2

        // 直接修改静态字段(不推荐,但可行)
        Person.count = 999;
        Person.showTotalCount(); // 输出:【统计】当前总人数:999
    }
}

执行结果:

【统计】当前总人数:0
创建了 张三,当前总人数:1
创建了 李四,当前总人数:2
【统计】当前总人数:2
【统计】当前总人数:999

 注:count 变量是所有 Person 对象共享的“全局计数器”。它不存储在任何一个 p1 或 p2 对象内部,而是独立地存在于方法区,由 Person 这个类本身所拥有。这完美体现了 static 成员“属于类,不依赖于对象”的核心特性。

四、封装与访问控制

4.1 封装的概念与意义

封装的概念:在软件开发中,我们面对的往往是极其复杂的系统。如果让每个使用者都了解系统内部的所有细节,学习成本和维护成本将变得不可承受。封装(Encapsulation) 正是解决这一问题的核心思想。它是面向对象编程(OOP)的四大基石之一,其本质是“信息隐藏”和“接口暴露”。 

4.1.1 数据隐藏与行为封装

封装的核心是将一个对象的内部实现细节(尤其是数据)隐藏起来,只对外提供一个清晰、稳定的接口(即公共方法)来与之交互。

1. 数据隐藏
如何实现:使用 private 关键字修饰成员变量。

class Person {
    private String name; // 私有化,外部无法直接访问
    private int age;
}

 为什么需要:直接暴露数据(如 public String name;)是危险的。调用者可能会随意修改数据,导致对象进入非法状态。例如,将年龄设置为 -5 或将姓名设为 null。

2.行为封装:

如何实现:提供 public 的 getter 和 setter 方法来间接访问和修改私有数据。

class Person {
    private String name;
    private int age;

    // Getter:获取name的值
    public String getName() {
        return name;
    }

    // Setter:设置name的值,并进行合法性检查
    public void setName(String name) {
        if (name != null && !name.trim().isEmpty()) {
            this.name = name;
        } else {
            System.out.println("姓名不能为空!");
        }
    }
}

好处:
控制访问:在 setName 方法中,我们可以加入逻辑来验证输入的有效性,防止非法数据。
灵活修改:类的实现者可以在不改变 setName 接口的前提下,修改内部实现。例如,未来可以增加“记录姓名修改日志”的功能,而调用者代码无需修改。 


4.1.2 降低复杂度,提高可维护性

封装是管理软件复杂度最有效的手段之一。封装是管理软件复杂度最有效的手段之一。 

1. 降低使用者的学习成本
对类的调用者(User)而言:他们不需要关心 Person 类内部是如何存储和验证姓名的。他们只需要知道“调用 setName() 可以设置姓名,调用 getName() 可以获取姓名”即可。
类比:就像你使用手机,你不需要知道芯片是如何工作的,你只需要知道“按电源键开机”、“滑动屏幕解锁”这些接口。手机制造商(类的实现者)隐藏了内部复杂的电路和操作系统。
2. 提高代码的可维护性
对类的实现者(Implementer)而言:一旦将数据私有化,他们就获得了“自由”。他们可以放心地修改类的内部实现,只要不改变 public 方法的签名(方法名、参数列表、返回值),就不会影响到任何调用者。
示例:假设最初 Person 类用 String 存储姓名,后来为了支持国际化,改为用 Map<String, String> 存储不同语言的姓名。只要 setName() 和 getName() 方法的接口不变,所有使用 Person 类的代码都无需修改。

注:封装在类的实现者和调用者之间建立了一道“防火墙”。这道墙保护了内部实现,使得系统各部分可以独立演化,极大地提升了软件的可维护性和可扩展性。


4.1.3 类的实现者与调用者的关系

理解封装,必须明确两个角色:类的实现者和类的调用者。

  • 类的实现者:负责设计和编写类的内部代码。他们关注“如何实现”这个类的功能。
  • 类的调用者:负责使用已经写好的类来完成自己的任务。他们关注“如何使用”这个类。 封装如何影响两者的关系:
  1. 实现者:通过 private 保护数据,通过 public 方法提供服务。他们的目标是稳定接口,灵活实现。
  2. 调用者:通过 public 方法与对象交互。他们的目标是正确使用接口,不关心内部细节。

4.2 访问修饰符

4.2.1 public与private的区别

 public 与 private 的区别
public 和 private 是Java中用于控制类成员(字段和方法)访问权限的关键字。它们的权限范围截然不同。

访问修饰符同一个类内同一个包内不同包的子类不同包的非子类访问范围
public可以访问可以访问可以访问可以访问最大访问范围
protected可以访问可以访问可以访问不可以访问包内加子类
默认(无修饰符)可以访问可以访问不可以访问不可以访问仅包内
private可以访问不可以访问不可以访问不可以访问仅类内

详细说明
1. public
访问范围:任何地方都可以访问
使用场景:对外提供的公共接口、主方法等
2. protected
访问范围:同一包内加不同包的子类
使用场景:允许子类继承的字段和方法
3. 默认访问权限(包访问权限)
访问范围:仅在同一包内可以访问
使用场景:包内共享,但不对外公开
4. private
访问范围:仅在定义它的类内部可以访问
使用场景:类的内部实现细节,隐藏实现 

 

package com.example.package1

public class AccessExample {
    public int publicField = 1        // 任何地方都能访问
    protected int protectedField = 2  // 包内加子类能访问
    int defaultField = 3              // 仅包内能访问
    private int privateField = 4      // 仅类内能访问
    
    public void testAccess() {
        // 类内可以访问所有字段
        System.out.println(publicField)
        System.out.println(protectedField)
        System.out.println(defaultField)
        System.out.println(privateField)
    }
}
// 同一包内的另一个类
package com.example.package1

class SamePackageClass {
    void test() {
        AccessExample obj = new AccessExample()
        System.out.println(obj.publicField)     // 可以
        System.out.println(obj.protectedField)  // 可以
        System.out.println(obj.defaultField)    // 可以
        // System.out.println(obj.privateField) // 编译错误
    }
}
// 不同包的子类
package com.example.package2
import com.example.package1.AccessExample

class SubClass extends AccessExample {
    void test() {
        System.out.println(publicField)     // 可以
        System.out.println(protectedField)  // 可以
        // System.out.println(defaultField) // 编译错误
        // System.out.println(privateField) // 编译错误
    }
}

注:private 并不是“禁止访问”,而是“禁止直接访问”。我们通过 public 的 getter 和 setter 方法,为外部提供了一条“安全通道”来间接访问私有数据。


4.2.2 如何通过封装保护数据安全

封装的核心就是利用 private 关键字来“隐藏数据”,并提供 public 的方法来“暴露行为”。这是一种主动的、有策略的保护,而非简单的隔绝。

1. 防止非法数据
这是封装最直接的好处。通过将字段设为 private,我们可以确保所有对数据的修改都必须经过我们预设的“安检通道”(即 setter 方法)。

// 如果 age 是 public 的,外部可以随意设置
person.age = -5; // 合法的Java代码,但业务上是荒谬的

// 但如果 age 是 private 的,外部只能通过 setAge() 方法
person.setAge(-5); // 在setAge方法内部,我们会拦截这个非法值

2. 实现数据的“只读”或“只写”
只读:只提供 getter 方法,不提供 setter 方法。

private final String id; // 身份证号一旦设定,不可更改
public String getId() { return id; } // 只读

只写:只提供 setter 方法,不提供 getter 方法(较少见,例如密码字段)。

3. 在访问时加入额外逻辑
封装允许我们在获取或设置数据时,执行额外的业务逻辑,而调用者对此完全无感。 

private String name;
private int loginCount = 0;

public void setName(String name) {
    this.name = name;
    // 额外逻辑:记录用户信息修改日志
    log("用户姓名已修改为:" + name);
}

public String getName() {
    loginCount++; // 额外逻辑:每次读取姓名,登录次数加一
    return name;
}

4.2.3 封装对代码维护的长远影响

 封装带来的好处,不仅仅体现在数据安全上,更深远地影响了软件的可维护性和可扩展性。

1. 实现与接口的分离
封装在类的实现者和调用者之间建立了一道“防火墙”。

对调用者(User):他们只需要知道“setName() 可以设置名字”就够了,不需要关心名字是如何存储的(是 String 还是 char[]),也不需要关心设置时做了哪些验证。
对实现者(Implementer):他们可以放心地修改内部实现。例如,未来可以将 name 从 String 改为 StringBuilder,或者将验证逻辑从 if 判断改为正则表达式,只要 setName() 的接口不变,调用者的代码就无需修改。
2. 降低耦合度,提高内聚性
低耦合:因为调用者不依赖于实现细节,所以当一个类的内部发生变化时,不会“牵一发而动全身”地影响到大量其他代码。
高内聚:一个类将相关的数据和操作这些数据的方法集中在一起,逻辑更加清晰。
3. 便于团队协作和长期维护
在一个大型项目中,不同的人负责不同的模块。封装确保了每个人只需要了解自己模块的“接口”,而无需深入理解所有模块的内部实现。这大大降低了沟通成本和出错概率。

 

4.3 Getter与Setter方法

4.3.1 提供对私有字段的可控访问

我们将一个字段(如 name)设为 private 后,类的外部就无法直接读取或修改它。Getter 和 Setter 方法就是我们主动提供的、用于间接访问这些私有字段的 public 接口。

Getter方法(取值方法):
命名:通常以 get 开头,后跟字段名(首字母大写),例如 getName()。
作用:读取私有字段的值。
返回值:与字段类型相同。
参数:无。

public String getName() {
    return name;
}

 Setter方法(赋值方法):
命名:通常以 set 开头,后跟字段名(首字母大写),例如 setAge()。
作用:修改私有字段的值。
返回值:void。
参数:一个与字段类型相同的参数。

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

为什么是“可控”的访问?
这正是 Getter/Setter 的精髓所在。我们不是简单地“开门”,而是在门后设置了一个“安检站”。

在 setAge() 方法中,我们可以加入逻辑来验证输入的有效性,防止非法数据(如负数年龄)进入。
在 getName() 方法中,我们可以加入日志记录,追踪是谁在什么时候读取了姓名。
我们甚至可以实现“只读”(只有 Getter)或“只写”(只有 Setter)的字段。


4.3.2 this关键字在Setter方法中的作用

 在编写 Setter 方法时,我们经常会遇到一个命名冲突:方法的参数名通常与成员变量名相同(如 setAge(int age))。此时,编译器无法区分你指的是参数 age 还是成员变量 age。

public void setAge(int age) {
    // age = age; // 错误!这是将参数赋值给它自己,无意义
    this.age = age; // 正确!this.age 指向当前对象的成员变量,age 指向方法的参数
}

 this:代表当前对象的引用。
this.age:明确地指代当前对象的成员变量 age。
age:在方法内部,指代传入的参数。

注:this 是解决同名变量冲突的“身份证”,它明确地告诉编译器:“我指的是我自己的那个成员变量,而不是你传进来的参数”。 还是那句话,谁调用谁就是this


4.3.3 自动生成Getter/Setter的方法(IDE操作建议)

 1. IntelliJ IDEA
将光标放在类的任意位置。
按下 Alt + Insert (Windows/Linux) 或 Cmd + N (Mac)。
在弹出的菜单中选择 Getter and Setter。
在对话框中勾选你想要生成方法的字段。
点击 OK,IDE 会自动生成所有选中字段的 Getter 和 Setter 方法。
2. Eclipse
右键点击类名。
选择 Source -> Generate Getters and Setters...。
在对话框中选择字段,点击 Generate。
3. Visual Studio Code (VS Code)
右键点击编辑器。
选择 Source Action...。
选择 Generate Getters and Setters。


4.3.4 示例:name和age字段的封装与访问

 

class Person {
    private String name; // 私有化,外部无法直接访问
    private int age;

    // Getter: 获取name
    public String getName() {
        return name;
    }

    // Setter: 设置name,并进行合法性检查
    public void setName(String name) {
        if (name != null && !name.trim().isEmpty()) {
            this.name = name;
        } else {
            System.out.println("姓名不能为空!");
        }
    }

    // Getter: 获取age
    public int getAge() {
        return age;
    }

    // Setter: 设置age,并进行合法性检查
    public void setAge(int age) {
        if (age >= 0 && age <= 150) {
            this.age = age;
        } else {
            System.out.println("年龄必须在0-150之间!");
        }
    }

    // 展示信息的方法
    public void show() {
        System.out.println("姓名:" + name + ",年龄:" + age);
    }
}

public class Main {
    public static void main(String[] args) {
        Person p = new Person();

        // 通过Setter方法设置数据
        p.setName("张三");     // 合法,设置成功
        p.setAge(25);         // 合法,设置成功
        p.setAge(-5);         // 非法,会打印错误信息
        p.setName("");        // 非法,会打印错误信息

        // 通过Getter方法获取数据
        String name = p.getName();
        int age = p.getAge();
        System.out.println("获取到的姓名:" + name);
        System.out.println("获取到的年龄:" + age);

        // 调用show方法
        p.show(); // 输出:姓名:张三,年龄:25
    }
}

 执行结果:

年龄必须在0-150之间!
姓名不能为空!
获取到的姓名:张三
获取到的年龄:25
姓名:张三,年龄:25

注:通过 Getter/Setter 方法,我们成功实现了对 name 和 age 字段的安全、可控的访问。类的使用者(main 方法)无需了解内部的验证逻辑,就能正确地使用这个类,这正是封装带来的巨大便利 

五、代码块与初始化顺序

5.1 代码块的类型

5.1.1 普通代码块、实例代码块、静态代码块

 1. 普通代码块

  • 定义:定义在方法内部的代码块。
  • 作用:用于限制变量的作用域。在 {} 内部定义的局部变量,其生命周期仅限于该代码块内。
  • 执行时机:在它所属的方法被调用时执行。
public class Main {
    public static void main(String[] args) {
        System.out.println("外部代码块开始");
        {
            int x = 10; // x 的作用域仅限于此代码块
            System.out.println("x = " + x);
        }
        // System.out.println(x); // 编译错误!x 已超出作用域
        System.out.println("外部代码块结束");
    }
}

 特点:使用频率较低,主要用于控制局部变量的生命周期。

2. 实例代码块

  • 定义:定义在类中、方法外,并且没有 static 修饰符的代码块。
  • 作用:用于初始化实例成员变量。它为所有构造方法提供了一套公共的初始化逻辑。
  • 执行时机:在每次创建对象时,在构造方法执行之前执行。无论调用哪个构造方法,实例代码块都会先执行。
class Person {
    private String name;
    private int age;

    // 实例代码块
    {
        System.out.println("实例代码块执行:为新对象设置默认值");
        this.name = "未知";
        this.age = 18;
    }

    public Person() {
        System.out.println("无参构造方法执行");
    }

    public Person(String name) {
        this.name = name; // 可以覆盖实例代码块的设置
        System.out.println("带参构造方法执行");
    }
}

特点:它是“就地初始化”的补充,可以执行更复杂的初始化逻辑。

3. 静态代码块
  • 定义:定义在类中、方法外,并且有 static 修饰符的代码块。
  • 作用:用于初始化静态成员变量。它常用于加载配置文件、建立数据库连接等只需要执行一次的全局初始化操作。
  • 执行时机:在类被加载到JVM时执行,且只执行一次。它在任何对象被创建之前就已经执行完毕。
class Person {
    public static int count = 0; // 静态变量

    // 静态代码块
    static {
        System.out.println("静态代码块执行:初始化全局资源");
        count = 100; // 初始化静态变量
        // 可以在这里加载配置、连接数据库等
    }

    public Person() {
        count++;
    }
}

 特点:生命周期与类相同,是进行全局、一次性初始化的最佳选择。


5.1.2 不同代码块的定义位置与作用
代码块类型定义位置修饰符执行时机执行次数主要作用
普通代码块方法内部所属方法被调用时每次方法调用都执行限制局部变量的作用域
实例代码块类中,方法外创建对象时,构造方法之前每次创建对象都执行为所有对象提供统一的实例变量初始化逻辑
静态代码块类中,方法外static类被加载时只执行一次初始化静态变量,执行全局一次性操作

5.2 初始化顺序分析

5.2.1 静态代码块 → 实例代码块 → 构造方法

当一个类被加载并开始创建对象时,其内部的初始化代码会按照一个精确的、不可更改的顺序执行。这个顺序是:

1. 静态代码块 (Static Block)
2. 实例代码块 (Instance Block / 构造块)
3. 构造方法 (Constructor)

让我们通过一个具体的例子来观察这个过程:

class Person {
    // 1. 静态字段(类属性)
    public static int count = 0;

    // 2. 就地初始化的实例字段
    private String name = "就地初始化";
    private int age;

    // 3. 静态代码块:在类加载时执行,只执行一次
    static {
        System.out.println("静态代码块执行:初始化全局资源");
        count = 100; // 初始化静态变量
    }

    // 4. 实例代码块:在每次创建对象时,构造方法之前执行
    {
        System.out.println("实例代码块执行:name = " + name); // name已是"就地初始化"
        this.age = 10;
        System.out.println("实例代码块设置 age = " + age);
        count++; // 每创建一个对象,总人数加一
    }

    // 5. 构造方法:对象初始化的最后一步
    public Person() {
        System.out.println("构造方法执行:name = " + name + ", age = " + age);
        this.name = "构造方法";
    }
}
public class Main {
    public static void main(String[] args) {
        System.out.println("开始创建第一个对象");
        Person p1 = new Person();

        System.out.println("\n开始创建第二个对象");
        Person p2 = new Person();
    }
}

执行结果: 

静态代码块执行:初始化全局资源
开始创建第一个对象
实例代码块执行:name = 就地初始化
实例代码块设置 age = 10
构造方法执行:name = 就地初始化, age = 10

开始创建第二个对象
实例代码块执行:name = 就地初始化
实例代码块设置 age = 10
构造方法执行:name = 就地初始化, age = 10
  1.  静态代码块:在 main 方法执行前,Person 类被JVM加载时,静态代码块立即执行,且只执行一次。它负责初始化 count 这个全局变量。
  2. 实例代码块:每当 new Person() 被调用时,实例代码块都会在构造方法之前执行。它为所有新对象提供了一套统一的初始化逻辑(如设置默认的 age)。
  3. 构造方法:最后执行,完成对象的最终定制。在这个例子中,它将 name 从“就地初始化”改为了“构造方法”。

5.2.2 图表展示类加载与对象创建过程
[ JVM启动 ]
     ↓
[ 类加载阶段 ]
     ↓
[ 执行静态代码块 ]  ← 只执行一次
     ↓
[ 创建对象阶段 (第一次) ]
     ↓
[ 执行就地初始化 ]  ← 为每个对象执行
     ↓
[ 执行实例代码块 ]  ← 为每个对象执行
     ↓
[ 执行构造方法 ]    ← 为每个对象执行
     ↓
[ 对象p1创建完毕 ]
     ↓
[ 创建对象阶段 (第二次) ]
     ↓
[ 执行就地初始化 ]  ← 再次执行
     ↓
[ 执行实例代码块 ]  ← 再次执行
     ↓
[ 执行构造方法 ]    ← 再次执行
     ↓
[ 对象p2创建完毕 ]
  •  类加载阶段:发生在程序启动时,或首次使用该类时。此阶段只执行一次静态代码块。
  • 创建对象阶段:每调用一次 new,就进入一次该阶段。此阶段会重复执行就地初始化、实例代码块和构造方法。

静态代码块会在以下情况下执行:

会执行的情况:

  1. 创建类的实例:new Person()
  2. 访问静态成员:Person.count 或 Person.staticMethod()
  3. 使用反射:Class.forName("Person")
  4. 初始化子类:new Student()(如果Student继承Person)

不会执行的情况:

  1. 类从未被使用过
  2. 只是定义了类但没有任何操作

5.2.3 示例:Person类的实例化过程
class Person {
    public static int totalCount = 0; // 全局计数器
    private String name;
    private int age;

    // 静态代码块:在类加载时初始化全局配置
    static {
        System.out.println("【系统】正在加载Person类...");
        System.out.println("【系统】数据库连接已建立");
        totalCount = 0; // 确保初始值为0
    }

    // 实例代码块:为所有新对象设置默认状态
    {
        System.out.println("【初始化】正在为新对象设置默认值...");
        this.name = "未知";
        this.age = 18;
    }

    // 构造方法:根据传入参数完成个性化设置
    public Person(String name, int age) {
        if (name != null && age >= 0) {
            this.name = name;
            this.age = age;
        }
        System.out.println("【完成】对象 " + this.name + " 创建成功!");
    }

    // 展示信息
    public void show() {
        System.out.println("姓名:" + name + ",年龄:" + age);
    }

    // 显示总人数
    public static void showTotal() {
        System.out.println("当前总人数:" + totalCount);
    }
}
public class Main {
    public static void main(String[] args) {
        // 类加载:执行静态代码块
        Person.showTotal(); // 输出:当前总人数:0

        // 创建第一个对象
        Person p1 = new Person("张三", 25);
        p1.show(); // 输出:姓名:张三,年龄:25

        // 创建第二个对象
        Person p2 = new Person("李四", 30);
        p2.show(); // 输出:姓名:李四,年龄:30

        // 显示总人数
        Person.showTotal(); // 输出:当前总人数:2
    }
}

执行结果:

【系统】正在加载Person类...
【系统】数据库连接已建立
当前总人数:0
【初始化】正在为新对象设置默认值...
【完成】对象 张三 创建成功!
姓名:张三,年龄:25
【初始化】正在为新对象设置默认值...
【完成】对象 李四 创建成功!
姓名:李四,年龄:30
当前总人数:2

六、常用方法与对象操作

6.1 toString方法

6.1.1 默认行为与重写实现

1.默认行为

如果你不为自己的类定义 toString() 方法,Java会使用从 Object 类继承而来的默认实现。

默认的 toString() 行为是: 返回一个由 类名 和 对象的哈希码(Hash Code) 组成的字符串。

class Person {
    private String name;
    private int age;

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

public class Main {
    public static void main(String[] args) {
        Person p = new Person("张三", 25);
        System.out.println(p); // 直接打印对象
    }
}

 执行结果为该对象哈希码的十六进制表示

Person@1c168e5
//Person 是类名。
//@ 是分隔符。
//1c168e5 是该对象哈希码的十六进制表示。

 2.重写实现:

为了解决这个问题,我们需要重写(Override) toString() 方法,提供一个更有意义的字符串表示。

class Person {
    private String name;
    private int age;

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

    // 重写 Object 的 toString 方法
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Main {
    public static void main(String[] args) {
        Person p = new Person("张三", 25);
        System.out.println(p); // 再次打印对象
    }
}

执行结果: 

Person{name='张三', age=25}

6.1.2 打印对象时的自动调用机制

toString() 方法有一个非常重要的特性:在打印对象时会被自动调用。

当你在 System.out.println() 中直接传入一个对象时,Java会自动在后台调用该对象的 toString() 方法。

// 这两行代码是等价的
System.out.println(p);
System.out.println(p.toString()); // 等价于上面的写法

这使得 toString() 成为调试和日志记录的利器。你不需要每次都手动调用 toString(),只需打印对象本身,就能看到其内部状态。 


6.1.3 IDEA中自动生成toString方法的快捷方式
  •  在 IntelliJ IDEA 中生成 toString() 方法:
  • 将光标放在类的任意位置。
  • 按下 Alt + Insert (Windows/Linux) 或 Cmd + N (Mac)。
  • 在弹出的菜单中选择 toString...。
  • 在对话框中勾选你想要包含在输出中的字段(如 name, age)。
  • 点击 OK,IDE会自动生成一个格式良好、可读性强的 toString() 方法。
@Override
public String toString() {
    return "Person{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
}

6.1.4 示例:重写toString实现自定义输出

通过例子说明:

class Student {
    private String name;
    private int id;
    private double score;

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

    // 使用IDE自动生成的toString方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", score=" + score +
                '}';
    }

    // 自定义一个更人性化的展示方法
    public String toFriendlyString() {
        return "学生姓名:" + name + ",学号:" + id + ",成绩:" + score + "分";
    }
}

public class Main {
    public static void main(String[] args) {
        Student s = new Student("李四", 1001, 95.5);

        // 1. 使用默认的toString (由IDE生成)
        System.out.println("【调试信息】" + s);
        // 输出:【调试信息】Student{name='李四', id=1001, score=95.5}

        // 2. 使用自定义的toFriendlyString
        System.out.println("【成绩单】" + s.toFriendlyString());
        // 输出:【成绩单】学生姓名:李四,学号:1001,成绩:95.5分
    }
}
  • toString() 是为调试和日志设计的,通常包含所有字段,格式为 类名{字段=值}。
  • 你可以创建额外的自定义方法(如 toFriendlyString())来生成面向用户的、更友好的字符串。
  • 无论何时,当你需要将对象转换为字符串,toString() 都是首选方法,因为它被Java的许多内置功能(如 println, String.format)所支持。 

6.2 匿名对象

6.2.1 定义与使用场景

 1. 定义
匿名对象(Anonymous Object)是指没有引用变量指向它的对象。它只存在于内存中,但没有一个“名字”或“变量”来引用它。

// 有名字的对象(有名对象)
Person p = new Person();

// 匿名对象:new Person() 创建了一个对象,但没有变量来引用它
new Person();

2. 使用场景
匿名对象的典型使用场景是:

对象只使用一次:创建后立即调用其方法或作为参数传递,之后就不再使用。
作为方法的参数:将一个临时对象作为参数传入另一个方法。
作为返回值:在方法内部创建一个对象并直接返回,而不将其赋值给中间变量。

 

class B {
    void print() {
        System.out.println("Hi");
    }
}

public class Main {
    public static void main(String[] args) {
        new B().print(); // 输出: Hi
    }
}

6.2.2 适用于只使用一次的对象

匿名对象的核心价值在于“一次性使用”。它非常适合那些“即用即抛”的场景。

为什么使用匿名对象?

  1. 简化代码:省去了声明引用变量的步骤,让代码更简洁。
  2. 减少内存占用(理论上):由于没有引用指向它,一旦使用完毕,该对象就成为“垃圾”,可以被垃圾回收器(GC)更快地回收。
  3. 避免命名污染:不需要为一个只用一次的对象起名字,保持了代码的清爽。

什么时候不该使用?

  • 当你需要多次使用同一个对象时,必须使用有名对象。
  • 当你需要将对象存储起来以备后用时,必须使用有名对象。

注:匿名对象和有名对象在功能上没有区别,它们都是通过 new 在堆内存中创建的真实对象。唯一的区别是,匿名对象没有栈中的引用变量来指向它,因此它无法被再次访问。  


6.2.3 示例:匿名对象调用show方法
class Person {
    private String name;
    private int age;

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

    // 展示个人信息
    public void show() {
        System.out.println("我叫" + name + ",今年" + age + "岁。");
    }
}

 1.使用有名对象:

public class Main {
    public static void main(String[] args) {
        // 创建有名对象
        Person p = new Person("张三", 25);
        p.show(); // 调用方法
        // p 变量仍然存在,可以再次使用
        // p.show(); // 可以再次调用
    }
}

2.使用匿名对象:

public class Main {
    public static void main(String[] args) {
        // 创建匿名对象并立即调用其方法
        new Person("李四", 30).show(); // 输出:我叫李四,今年30岁。
        // 对象使用完毕后,由于没有引用,它将成为垃圾
    }
}

3.匿名对象作为方法参数:

 匿名对象常用于作为方法的参数传递。

class Person {
    private String name;
    private int age;

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

    public void introduce() {
        System.out.println("大家好,我是" + name + "!");
    }
}

public class Main {
    // 接收一个 Person 对象作为参数
    public static void welcome(Person person) {
        System.out.print("欢迎 ");
        person.introduce();
    }

    public static void main(String[] args) {
        // 将匿名对象作为参数传递
        welcome(new Person("王五", 28));
        // 输出:欢迎 大家好,我是王五!
    }
}

七、综合案例与实践

7.1 实现一个Calculator类

7.1.1 定义两个属性num1和num2

 我们首先定义两个私有的成员变量来存储参与计算的两个数字。

class Calculator {
    // 使用 private 封装数据,防止外部随意修改
    private double num1;
    private double num2;

    // ... 其他代码
}

注:用private是为了不让外部随意修改num1,2。 


7.1.2 实现加减乘除方法

为 Calculator 类添加四个 public 方法,分别实现基本的算术运算。

 

class Calculator {
    private double num1;
    private double num2;

    // 加法
    public double add() {
        return num1 + num2;
    }

    // 减法
    public double subtract() {
        return num1 - num2;
    }

    // 乘法
    public double multiply() {
        return num1 * num2;
    }

    // 除法(需要考虑除零)
    public double divide() {
        if (num2 == 0) {
            System.out.println("错误:除数不能为零!");
            return Double.NaN; // 返回“非数字”
        }
        return num1 / num2;
    }

    // ... 其他代码
}

注:方法设计上每个方法只进行一个操作增强代码的可读性。 


7.1.3 不允许在定义时初始化字段

根据要求,我们不能在声明时就给 num1 和 num2 赋值(即不能 private double num1 = 0;)。这意味着我们必须通过构造方法或setter方法来为它们赋值。

我们可以提供一个无参构造方法和一个带参构造方法:

class Calculator {
    private double num1;
    private double num2;

    // 无参构造方法:创建一个空的计算器
    public Calculator() {
        // num1 和 num2 会获得默认值 0.0
    }

    // 带参构造方法:创建时就设置两个数
    public Calculator(double num1, double num2) {
        this.num1 = num1;
        this.num2 = num2;
    }

    // Getter 和 Setter 方法,提供对私有字段的可控访问
    public double getNum1() {
        return num1;
    }

    public void setNum1(double num1) {
        this.num1 = num1;
    }

    public double getNum2() {
        return num2;
    }

    public void setNum2(double num2) {
        this.num2 = num2;
    }

    // 加减乘除方法... (如上)
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        // 使用无参构造
        Calculator calc1 = new Calculator();
        calc1.setNum1(10);
        calc1.setNum2(5);
        System.out.println("10 + 5 = " + calc1.add()); // 输出:15.0

        // 使用带参构造
        Calculator calc2 = new Calculator(20, 4);
        System.out.println("20 / 4 = " + calc2.divide()); // 输出:5.0
    }
}

注: 这个 Calculator 类完美体现了封装。数据被保护,通过 getter/setter 进行安全访问,通过 add()、subtract() 等方法提供行为。构造方法的重载也使得创建对象更加灵活。

7.2 构造函数的多态性

7.2.1 设计多个构造函数

 我们以一个 Person 类为例,设计多种方式来创建一个人。

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

    // 1. 无参构造:创建一个“空白”对象
    public Person() {
        this.name = "未知";
        this.age = 18;
        this.address = "地球";
    }

    // 2. 只知道名字
    public Person(String name) {
        this(name, 18); // 调用下面的构造方法,代码复用
    }

    // 3. 知道名字和年龄
    public Person(String name, int age) {
        this(name, age, "地球"); // 调用下面的构造方法,代码复用
    }

    // 4. 知道名字、年龄和地址(最终的初始化逻辑)
    public Person(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    // toString 方法,便于打印
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}

7.2.2 通过不同构造函数实例化对象

 

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person();                    // 调用无参构造
        Person p2 = new Person("张三");              // 调用带一个String参数的构造
        Person p3 = new Person("李四", 30);          // 调用带两个参数的构造
        Person p4 = new Person("王五", 25, "北京"); // 调用带三个参数的构造

        System.out.println(p1); // Person{name='未知', age=18, address='地球'}
        System.out.println(p2); // Person{name='张三', age=18, address='地球'}
        System.out.println(p3); // Person{name='李四', age=30, address='地球'}
        System.out.println(p4); // Person{name='王五', age=25, address='北京'}
    }
}

7.2.3 构造函数重载的使用技巧
  1. 使用 this() 实现代码复用:如上例所示,通过在一个构造方法中调用 this(参数) 来调用本类的其他构造方法,避免了代码重复。
  2. 将最全的构造方法作为“最终”构造方法:通常,参数最多的构造方法包含最完整的初始化逻辑,其他构造方法通过 this() 调用它。
  3. 避免成环:确保构造方法的调用链是单向的,不能形成循环(A 调用 B,B 又调用 A),否则会导致编译错误。
  4. 提供有意义的默认值:无参构造方法应该为对象提供一个合理的、可用的默认状态。 

注:构造函数重载让对象的创建方式更加灵活,适应不同的业务场景。

7.3 交换两个变量的值

7.3.1 使用对象引用实现交换

 我们创建一个简单的 IntWrapper 类,用它来包装一个 int 值。

class IntWrapper {
    public int value; // 使用 public 简化示例,实际中应为 private + getter/setter

    public IntWrapper(int value) {
        this.value = value;
    }
}

7.3.2 展示如何通过方法修改实参值

 现在,我们可以写一个 swap 方法,它接收两个 IntWrapper 对象的引用,并交换它们内部的 value。

public class Main {
    // 交换两个 IntWrapper 对象内部的 value
    public static void swap(IntWrapper a, IntWrapper b) {
        int temp = a.value;
        a.value = b.value;
        b.value = temp;
        System.out.println("在 swap 方法内:a.value=" + a.value + ", b.value=" + b.value);
    }

    public static void main(String[] args) {
        IntWrapper num1 = new IntWrapper(10);
        IntWrapper num2 = new IntWrapper(20);

        System.out.println("交换前:num1.value=" + num1.value + ", num2.value=" + num2.value);
        // 输出:交换前:num1.value=10, num2.value=20

        swap(num1, num2);
        // 输出:在 swap 方法内:a.value=20, b.value=10

        System.out.println("交换后:num1.value=" + num1.value + ", num2.value=" + num2.value);
        // 输出:交换后:num1.value=20, num2.value=10
    }
}

注: 

  • num1 和 num2 是两个引用,它们分别指向堆内存中的两个 IntWrapper 对象。
  • 当我们调用 swap(num1, num2) 时,参数 a 和 b 接收的是 num1 和 num2 的副本(即它们的地址)。
  • 虽然 a 和 b 是副本,但它们指向的对象是同一个。
  • 因此,在 swap 方法内部通过 a.value 和 b.value 修改的是堆内存中对象的实际数据。
  • 这种修改是全局可见的,所以 main 方法中的 num1 和 num2 指向的对象的值也被改变了。

八、总结与进阶建议

8.1核心概念回顾

类和对象是Java编程的基石。类是对象的模板,它定义了对象的属性和行为。对象是类的具体实例,是程序运行时的实体。我们用 class 关键字来定义一个类。类名使用大驼峰命名法。一个类包含属性和方法。属性描述对象的状态,方法描述对象的行为。

8.2对象的创建与内存

我们用 new 关键字来创建对象。new 操作会完成三件事:在堆内存中为对象分配空间,调用类的构造方法进行初始化,然后将对象的地址返回给一个引用变量。这个引用变量存放在栈内存中,就像一把“钥匙”,通过它可以找到并操作堆内存中的“实物”(对象)。

8.3封装与数据安全

封装是面向对象的核心思想。它通过 private 关键字将数据隐藏起来,只提供 public 的 getter 和 setter 方法来访问和修改。这就像一个“安全通道”,我们可以在通道里加入检查逻辑,防止非法数据进入。例如,在 setAge() 方法中检查年龄是否为负数。

8.4构造方法与初始化

构造方法用于初始化新创建的对象。它与类同名,没有返回类型。一个类可以有多个构造方法,只要它们的参数不同,这称为重载。我们可以用 this() 在一个构造方法中调用另一个,实现代码复用。对象的完整初始化过程是:静态代码块 → 就地初始化 → 实例代码块 → 构造方法。

8.5静态成员与代码块

static 修饰的成员属于类本身,而不是某个具体对象。所有对象共享同一个静态成员。我们通过 类名.成员名 的方式来访问。静态代码块在类加载时执行一次,用于初始化静态资源。实例代码块在每次创建对象时执行,用于为所有对象提供统一的初始化逻辑。

8.6常用方法与实践

toString() 方法用于返回对象的字符串表示。默认的输出不直观,我们通常会重写它,让打印对象时能清晰地看到其内部状态。IDE 提供了自动生成此方法的快捷方式。当一个对象只使用一次时,我们可以使用匿名对象,它没有引用变量,创建后立即使用,用完即弃。

8.7进阶学习方向

掌握了这些基础知识后,就可以继续深入学习。可以学习继承,让一个类复用另一个类的代码。可以学习多态,让同一个操作在不同对象上有不同的行为。可以学习接口,定义更灵活的规范。这些高级特性能让程序设计更优雅,更容易维护。通过不断实践,可以成为更优秀的Java程序员。

 补:上课做的笔记:

import java.util.Scanner;
import java.util.Date;
import java.util.ArrayList;
//可以用import java.util.*来导入所有类;
//静态导入:import static java.lang.Math.*;
//静态导入后,可以直接使用Math.abs()等方法。
public class _7101 {
    //什么是类?什么是对象?
    //面向程序:c
    //面向对象:java,python,c++(简称oop)
    //类名一定要用大驼峰
    //类中有,1.属性,2.行为。
    //成员属性:是定义在类的里面,方法的外面
    //行为/成员方法
    //public修饰的跟文件名有关,private修饰的跟类名有关。
    //多个类中有多个方法,可以重载。
    //方法的调用:对象.方法名()
    //方法的重载:方法名相同,参数不同。
    //最好的操作是一个文件一个类
    //类的实例化:Dog dog = new Dog();
    //对象的成员属性在没有赋值的时候,默认值是null。
    //通过new关键字可以开辟一块新的内存空间,并将这个空间的地址赋值给一个变量。
    //this关键字:指向当前对象的引用。
    //tongguothis可以访问当前对象的成员属性/成员变量(静态的成员变量不支持)
   //this只能在方法名、属性名、构造器中使用。
    //成员变量:定义在方法的外部,类的内部
    //当成员变量没有被初始化的时候引用类型一般为null基本类型默认值是自己的0值
    //int->0,boolean->false,double->0.0,char->'\u0000',String->null
    //构造方法:类名和方法名相同,在创建对象后自动调用。
    //当类中没有任何一个构造方法的时候,系统会自动添加一个默认的不带参数的构造方法。
    //当有一个构造方法的时候,系统不会再添加默认的构造方法。如果没有不带参数的构造方法,系统会报错。
    //构造方法之间的重载:构造方法的名字相同,参数不同
    // 完成一个对象的构造分为两部。
    //第一步:创建对象,调用构造方法,为成员变量分配内存空间。
    //第二步:初始化成员变量,为成员变量赋值。
    //构造方法的调用:对象名=new 类名(参数列表);
    //this():调用本类或父类构造方法。
    //只能在构造方法中调用,不能在其他方法中调用。
    //只能在第一行使用
    //防止成环:构造方法不能调用其他构造方法,否则会成环。
    //this.data访问当前对象的属性
    //this.method()调用当前对象的成员方法(方法名)
    //默认初始化:当成员变量没有被初始化的时候,系统会自动初始化为默认值。
    //封装:将数据和操作数据的方法封装在一起,对外隐藏内部实现。
    //继承:子类继承父类,子类可以获得父类的所有属性和方法,并可以增加自己的属性和方法。
    //多态:父类引用指向子类对象,调用子类的方法,实际上调用的是子类的方法。
    //当属性被private修饰时,只能在类的内部访问。
    //可以通过过方法来对属性进行修改和操作
    //通过生成快捷键里面的set和get进行快速的生成方法(不带参数的不行)
    //包的名称全部为小写,尽量避免使用下划线。
    //包的作用:为了避免命名冲突,将类分组。
    //import:导入类,可以省略包名。
    //权限就是包访问权限。
    //java.util包:包含了常用的工具类。
    //static修饰的成员:静态成员,不依赖于对象,可以直接通过类名调用。
    //static修饰的成员变量,操作方式:
    //类名.成员变量名
    //用public 和private修饰的成员是普通变量,可以直接通过对象名调用。
    //用static修饰的成员是静态变量,可以直接通过类名调用。
    //用final修饰的成员变量,只能赋值一次,不能被修改。
    //static方法:不依赖于对象,可以直接通过类名调用。
    //使用方法:类名.方法名(参数列表)
    //在静态方法的内部,不能直接访问非静态成员变量和方法。
    //必须要创建对象后进行访问。
    //静态方法不能用this关键字。
    //静态方法无法被修改,不能实现多态
    //静态代码块,只执行一次,在类被加载的时候执行。
    //都是静态代码块,按照顺序执行
    //代码块:在类中定义的代码,可以有参数,可以有返回值。
    //代码块的调用:类名.代码块名(参数列表)
    //代码块的作用:可以对类的成员变量进行初始化。
    //代码块的执行顺序:按照代码块的顺序执行。
    //代码块的位置:类中,方法中,构造方法中。
    //实例代码块:在构造方法外定义的代码块,只执行一次。
    //实例代码块:只有在实例对象的构造方法执行的时候才会执行。
    //静态代码块:在类中定义的代码块,只执行一次,在类被加载的时候执行。
    //静态代码块:在类中定义的代码块,只执行一次,在类被加载的时候执行。



    public static void main(String[] args) {
        Dog dog = new Dog();
        Dog duy=new Dog();
    }



}
class Dog{
    private  String name;

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

    private   int age;
    private String color;
    public void barks(){
        System.out.println("汪汪叫");
    }

    public static void main(String[] args) {

    }
    public Dog(){
//        this("小狗", 1, "黑色");
        System.out.println("小狗被创建了");
     this.show();
}
public Dog(String name, int age, String color) {}
    public void show(){
        System.out.println("名字:" + this.name + "年龄:" + this.age + "颜色:" + this.color);
    }
class WashMachine{

}
class Student {
    public String name;
    public int age;

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

    public String gender;}

    public void study() {
        System.out.println(this.name + "学习");

    }

}

特别感谢比特的课件资料帮我完善这个框架

感谢您阅读这篇关于Java面向对象编程的博客。希望这些内容能帮助您更好地理解类和对象的概念。如果您有任何问题或建议,欢迎随时交流。祝您学习愉快,编程之路越走越远。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值