一、面向对象思想
面向对象思想(Object-Oriented Programming,OOP)是一种以 "对象" 为核心的编程范式,它将现实世界中的事物抽象为程序中的对象,通过对象之间的交互来实现程序功能,旨在解决传统面向过程编程中代码复用性低、维护性差、扩展性弱等问题。
简单来说,面向对象的核心是把 "数据" 和 "操作数据的行为" 打包在一起,而不是像面向过程那样把程序拆成一系列线性的步骤和函数。
1. 封装(Encapsulation):"数据隐藏" 与 "接口暴露"
将对象的属性(数据)和方法(行为)捆绑在一起,同时隐藏对象内部的实现细节,只对外提供统一的访问接口。
目的:
保护数据安全:避免外部直接修改对象的属性,防止数据被非法篡改;
降低耦合度:外部只需关注对象的功能,无需关心内部如何实现。
举例 :一个 "学生" 对象的成绩属性(score)可以被私有化,外部不能直接修改,只能通过setScore()方法修改(可在方法中增加分数合法性校验),通过getScore()方法获取。
2. 继承(Inheritance):"代码复用" 与 "层次划分"
子类(派生类)可以继承父类(基类 / 超类)的属性和方法,同时可以根据需要扩展自己的属性和方法。
目的:
实现代码复用:避免重复编写相同的代码;
构建类的层次结构:体现事物的 "一般与特殊" 关系。
举例:定义 "动物" 作为父类(属性:名字、年龄;方法:吃、睡),"猫" 和 "狗" 作为子类,继承父类的属性和方法,同时新增自己的方法(猫:抓老鼠;狗:看门)。
3. 多态(Polymorphism):"一个接口,多种实现"
同一行为(方法)在不同对象上有不同的表现形式,即父类的引用可以指向子类的对象,调用方法时会根据对象的实际类型执行对应的实现。
目的:
提高代码的灵活性和扩展性;
降低代码的冗余,符合 "开闭原则"(对扩展开放,对修改关闭)。
举例 :父类 "动物" 有方法makeSound(),子类 "猫" 实现为 "喵喵叫",子类 "狗" 实现为 "汪汪叫"。当用Animal animal = new Cat()调用animal.makeSound()时,会执行猫的叫声;换成new Dog()则执行狗的叫声。
二类与对象:面向对象的核心载体
类(Class)和对象(Object)是面向对象思想的基本构成单元 ,二者是 "抽象模板" 与 "具体实例" 的关系,简单来说:类是对一类事物的抽象定义,对象是这类事物的具体存在。
1.类与对象的概念
类是对现实世界中同一类事物的共同属性和行为的抽象概括,它定义了这类事物有什么特征(属性)、能做什么动作(方法),但本身并不是具体的事物,只是一个 "蓝图" 或 "模板"。
对象是类的实例化结果 ,是根据类的模板创建出来的具体个体,拥有类中定义的所有属性和方法,是实实在在存在的 "实体"。
2.类的定义
在java中类的定义格式如下:
java
class 类名{
成员变量;
成员方法;
}
注意:在Java中,定义在类中的变量成为成员变量,定义在方法中的变量称为局部变量。如下示例:
java
class Student {
int age = 30;
void read(){
int age = 50;
System.out.println("大家好,我" + age + "岁了,我在看书!");
}
}
上述代码中,再Student类的read()方法中有一条打印语句,访问了变量age,此时访问的是局部变量age,也就是说当有一个程序调用read()方法时,输出的age值为50,而不是30。
3.对象的创建与使用
在java程序中可以使用new关键字创建对象,格式如下:
java
类名 对象名 = new 类名();
使用类创建对象具体实例:
Phone.java
java
// 定义类:手机类(作为创建对象的模板)
class Phone {
// 成员变量(属性):描述手机的特征
String brand; // 品牌
int price; // 价格
// 成员方法(行为):描述手机的功能
public void call() {
System.out.println(brand + "手机正在打电话");
}
public void sendMessage() {
System.out.println(brand + "手机正在发短信");
}
}
Test01.java
java
public class Test01 {
public static void main(String[] args) {
// 2. 创建对象:通过new关键字+构造方法实例化
// 方式1:无参构造创建对象(Java默认提供)
Phone phone1 = new Phone();
// 3. 使用对象:为属性赋值
phone1.brand = "苹果";
phone1.price = 5999;
// 4. 使用对象:调用方法
System.out.println("手机品牌:" + phone1.brand + ",价格:" + phone1.price + "元");
phone1.call();
phone1.sendMessage();
}
}

上述代码在main()方法中实例化了一个Phone对象,名称为Phone1。在java中,使用new关键字创建的对象在堆内分配空间。如下图:
栈内存 :存储对象的引用变量(即对象的地址)。
堆内存 :存储对象的实际数据(属性值)。

创建对象后,使用"."访问对象的属性与方法,我们可以改写一下Test01.java的内容:
java
public class Test01 {
public static void main(String[] args) {
// 2. 创建对象:通过new关键字+构造方法实例化
// 方式1:无参构造创建对象(Java默认提供)
Phone phone1 = new Phone();
// 3. 使用对象:为属性赋值
phone1.brand = "苹果";
phone1.price = 5999;
// 4. 使用对象:调用方法
System.out.println("手机品牌:" + phone1.brand + ",价格:" + phone1.price + "元");
phone1.call();
phone1.sendMessage();
phone1.brand="华为";
phone1.price=6999;
System.out.println("手机品牌:" + phone1.brand + ",价格:" + phone1.price + "元");
}
}

4.对象的引用传递
在 Java 中,一切变量的传递都是值传递 ,但对于对象类型 ,变量存储的是对象在堆内存中的地址(引用),因此传递的是 "引用的副本"------ 这就是常说的 "引用传递"(本质是值传递的一种特殊形式)。
引用传递的本质 :方法传参时,将栈中 "引用变量的地址值" 复制一份传给方法的形参,此时实参和形参指向堆中同一个对象。
定义一个简单的类:Student.java
java
// 学生类(包含姓名属性)
class Student {
String name; // 对象的属性(存储在堆内存)
public Student(String name) {
this.name = name;
}
}
测试引用传递:Test02.java
java
public class Test02 {
public static void main(String[] args) {
// 创建对象,引用变量s指向堆中的Student对象
Student stu1 = new Student("张三");
Student stu2 = null;
stu2 = stu1; // 引用变量stu2指向stu1指向的堆中的Student对象
stu2.name = "李四";
System.out.println(stu1.name);
}
}

方法内通过修改stu2的name属性,本质是修改堆中的数据,因此对象stu1的name属性也会被改变。
5.访问控制
Java 的访问控制(访问修饰符) 用于限定类、属性、方法、构造方法的可见性范围,核心目的是实现封装 、控制代码的访问权限,降低程序耦合度。Java 提供了 4 种访问修饰符,按访问范围从宽到窄依次为:public、protected、default(缺省,无关键字)、private。
4 种访问修饰符的核心特性
| 修饰符 | 同一类内 | 同一包内的类 | 不同包的子类 | 不同包的非子类 | 访问范围总结 |
|---|---|---|---|---|---|
private |
✅ | ❌ | ❌ | ❌ | 最严格,仅自身可见 |
default |
✅ | ✅ | ❌ | ❌ | 包级访问,仅同包可见 |
protected |
✅ | ✅ | ✅ | ❌ | 子类可访问(跨包也可)+ 同包可见 |
public |
✅ | ✅ | ✅ | ✅ | 最宽松,所有类可见 |
三、封装性
1.为什么要封装
封装是面向对象的核心特性之一,简单来说,封装的本质是 "隐藏内部细节,暴露统一接口" 。在 Java 中使用封装,核心目的是为了保证数据安全、降低代码耦合、提高程序的可维护性和可扩展性,解决直接访问对象属性 / 方法带来的各种问题。
比如,一个Order类有一个calculatePrice()方法用于计算订单总价,内部实现可能包含折扣、税费等复杂逻辑:
java
class Order {
private double price;
private double discount;
// 封装的计算方法(外部只需调用,无需关心内部逻辑)
public double calculatePrice() {
// 内部逻辑:原价 - 折扣 + 税费
return price * (1 - discount) + price * 0.09;
}
// set方法用于赋值
public void setPrice(double price) {
this.price = price;
}
public void setDiscount(double discount) {
this.discount = discount;
}
}
2.如何进行封装
在 Java 中,封装的核心实现步骤是:
(1)将类的属性(成员变量)用 private 修饰,使其仅在类内部可见;
(2)提供公共的 getter/setter 方法,用于外部访问和修改属性(可在方法中添加数据校验);
(3)按需封装方法(如内部工具方法用 private,对外功能用 public)。
java
// 封装的学生类
public class Student {
// 1. 私有属性:用private修饰,外部无法直接访问
private String name; // 姓名
private int age; // 年龄
// 2. 公共的getter方法:获取属性值
public String getName() {
return name;
}
// 3. 公共的setter方法:修改属性值(可添加数据校验)
public void setName(String name) {
// 简单校验:姓名不为空
if (name != null && !name.trim().isEmpty()) {
this.name = name;
} else {
System.out.println("姓名不能为空!");
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 简单校验:年龄在合理范围
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("年龄不合法!");
}
}
// 4. 对外提供的方法:展示学生信息(封装行为)
public void showInfo() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
// 测试类
public class TestEncapsulation {
public static void main(String[] args) {
// 创建学生对象
Student stu = new Student();
// 无法直接访问私有属性(编译报错)
// stu.name = "张三";
// stu.age = -18;
// 通过setter方法赋值(触发数据校验)
stu.setName(""); // 姓名为空,提示错误
stu.setName("张三");
stu.setAge(-18); // 年龄非法,提示错误
stu.setAge(20);
// 通过getter方法获取属性值
System.out.println("获取的姓名:" + stu.getName());
System.out.println("获取的年龄:" + stu.getAge());
// 调用封装的方法
stu.showInfo();
}
}
四、构造方法
构造方法(Constructor)是 Java 中用于创建并初始化对象的特殊方法,它是对象实例化过程中自动执行的核心方法,保证对象被创建时就具备合理的初始状态。
1.定义构造方法
构造方法的特征:
- 名称与类名完全一致 :比如类名是
Student,构造方法名也必须是Student,大小写敏感。 - 无返回值 :区别于普通方法,不需要写
return类型(连void都不能写)。 - 自动调用 :通过
new关键字创建对象时,JVM 会自动调用对应的构造方法,无需手动调用。 - 可重载 :一个类可以定义多个构造方法,只要参数列表(个数、类型、顺序)不同即可。
无参构造方法
- 如果类中没有显式定义任何构造方法,Java 会自动提供一个默认的无参构造方法;
- 若定义了有参构造,默认无参构造会失效,需手动声明。
java
class Cat {
String name;
int age;
// 手动声明无参构造
public Cat() {
name = "小白"; // 初始化默认值
age = 1;
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat(); // 创建对象时自动调用无参构造
System.out.println(cat.name); // 输出:小白
}
}
有参构造方法
创建对象时直接为属性赋值,避免后续手动调用setter方法,更高效。
java
class Cat {
String name;
int age;
// 有参构造
public Cat(String name, int age) {
this.name = name; // this区分成员变量和局部变量
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑", 2); // 调用有参构造赋值
System.out.println(cat.name + "," + cat.age); // 输出:小黑,2
}
}
2.构造方法的重载
构造方法的重载(Constructor Overloading)是 Java 中多态 的一种体现,指的是一个类中定义多个构造方法,这些方法名称与类名一致,但参数列表(参数个数、类型、顺序)不同,从而满足不同的对象初始化需求。
核心规则
- 方法名必须相同:所有构造方法的名称都和类名一致。
- 参数列表必须不同 :
- 参数个数不同(比如一个无参,一个单参);
- 参数类型不同(比如一个参数是
int,一个是String); - 参数顺序不同(比如
(String name, int age)和(int age, String name))。
- 返回值无要求(因为构造方法本身无返回值):无需考虑返回值的差异。
java
class Cat {
String name;
int age;
// 无参构造
public Cat() {
this.name = "未知";
this.age = 0;
}
// 单参构造(仅姓名)
public Cat(String name) {
this.name = name;
this.age = 0;
}
// 双参构造(姓名+年龄)
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
}
3.案例:银行取款机
对于银行存取款的流程,人们非常熟悉,用户可在银行对自己的资金账户进行存款、取款、查询余额等操作,极大地方便了人们对资金的管理。本案例要求使用所学的知识编写一个程序,实现银行存取款功能。案例要求具体如下:
(1)创建账户,初始存款为 0 元。
(2)向账户存入 100 元。
(3)从账户取出 80 元。
整体采用 "循环菜单展示 + 分支指令处理" 的核心思路实现存款、取款、查询余额和退出的基础功能。首先初始化账户余额为 0.0,创建 Scanner 对象用于接收用户输入,并定义布尔变量isRunning控制程序的持续运行;接着通过while循环持续展示 ATM 功能菜单(存款、取款、显示余额、退出),每次循环中获取用户输入的功能选择项;然后利用switch分支语句对用户的不同选择进行针对性处理:选择 1 时接收存款金额,校验金额大于 0 后更新余额并提示存款成功,否则提示输入错误;选择 2 时接收取款金额,先校验金额大于 0,再判断余额是否充足,满足则扣除金额并提示取款成功,否则分别提示金额错误或余额不足;选择 3 时直接打印当前账户余额;选择 4 时将isRunning设为 false 以终止循环,实现程序退出;若用户输入非 1-4 的数字,通过default分支提示无效选择。
java
import java.util.Scanner;
public class ATM {
public static void main(String[] args) {
// 初始化余额为0
double balance = 0.0;// 余额
Scanner scanner = new Scanner(System.in);
boolean isRunning = true;
while (isRunning) {
// 显示菜单
System.out.println("=========ATM========");
System.out.println("1、存款");
System.out.println("2、取款");
System.out.println("3、显示余额");
System.out.println("4、退出");
System.out.print("请选择(1-4):");
// 获取用户选择
int choice = scanner.nextInt();
switch (choice) {
case 1: // 存款
System.out.print("请输入存款金额:");
double deposit = scanner.nextDouble();
if (deposit > 0) {
balance += deposit;
System.out.println("存款成功!当前余额:" + balance + "元");
} else {
System.out.println("存款金额输入错误,请重新操作!");
}
break;
case 2: // 取款
System.out.print("请输入取款金额:");
double withdraw = scanner.nextDouble();
if (withdraw > 0) {
if (withdraw <= balance) {
balance -= withdraw;
System.out.println("取款成功!当前余额:" + balance + "元");
} else {
System.out.println("余额不足,无法取款!");
}
} else {
System.out.println("取款金额必须大于0,请重新操作!");
}
break;
case 3: // 显示余额
System.out.println("当前账户余额:" + balance + "元");
break;
case 4: // 退出
isRunning = false;
System.out.println("感谢使用,再见!");
break;
default:
System.out.println("无效选择,请输入1-4之间的数字!");
}
System.out.println(); // 输出空行,分隔每次操作
}
scanner.close();
}
}

五、this关键字
1.使用this关键字调用本类中的属性
this 调用本类属性的核心场景是局部变量(如构造方法参数、方法参数)与类的成员变量重名 时,通过 this.属性名 明确指向成员变量,避免命名冲突。
java
// 定义一个学生类
class Student {
// 类的成员变量(属性)
String name;
int age;
// 构造方法:参数与成员变量重名
public Student(String name, int age) {
// this.name 指向本类的name属性,name指向构造方法的参数
this.name = name;
// this.age 指向本类的age属性,age指向构造方法的参数
this.age = age;
}
// 普通方法:参数与成员变量重名
public void setInfo(String name, int age) {
this.name = name;
this.age = age;
}
// 展示属性值
public void show() {
// 此处可省略this(无重名时),但加上也可明确指向成员变量
System.out.println("姓名:" + this.name + ",年龄:" + this.age);
}
}
java
public class ThisTest {
public static void main(String[] args) {
// 创建对象时调用构造方法,通过this为属性赋值
Student stu1 = new Student("张三", 20);
stu1.show(); // 输出:姓名:张三,年龄:20
// 调用普通方法,通过this修改属性值
stu1.setInfo("李四", 22);
stu1.show(); // 输出:姓名:李四,年龄:22
}
}

2.使用this关键字调用成员方法
利用 this 返回当前对象,可实现成员方法的链式调用。
java
class Person {
String name;
int age;
// 设置姓名,返回当前对象
public Person setName(String name) {
this.name = name;
return this; // 返回当前对象
}
// 设置年龄,返回当前对象
public Person setAge(int age) {
this.age = age;
return this; // 返回当前对象
}
// 展示信息,调用本类成员方法
public void show() {
this.printInfo(); // 调用本类的printInfo()方法
}
// 私有成员方法(也可通过this调用)
private void printInfo() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
// 测试链式调用
public class ThisChainTest {
public static void main(String[] args) {
// 链式调用:通过this返回对象,连续调用方法
new Person().setName("李四").setAge(25).show();
}
}

3.使用this关键字调用本类中的构造方法
在 Java 中,可通过this(参数)在一个构造方法中调用本类的另一个构造方法,必须将其作为构造方法的第一行代码,这样能减少初始化代码的冗余,统一初始化逻辑。
java
// 定义一个学生类
class Student {
String name;
int age;
String id;
// 1. 无参构造方法:调用三参构造方法,传入默认值
public Student() {
this("未知姓名", 0, "000000"); // 调用本类的三参构造,必须是第一行
System.out.println("无参构造方法执行");
}
// 2. 双参构造方法:调用三参构造方法,传入姓名、年龄和默认学号
public Student(String name, int age) {
this(name, age, "000000"); // 调用本类的三参构造,必须是第一行
System.out.println("双参构造方法执行");
}
// 3. 三参构造方法:核心初始化逻辑
public Student(String name, int age, String id) {
this.name = name; // this调用属性
this.age = age;
this.id = id;
System.out.println("三参构造方法执行");
}
}
// 测试类
public class ThisConstructorTest {
public static void main(String[] args) {
// 调用无参构造
System.out.println("=====创建第一个学生对象=====");
Student stu1 = new Student();
System.out.println("姓名:" + stu1.name + ",年龄:" + stu1.age + ",学号:" + stu1.id);
// 调用双参构造
System.out.println("\n=====创建第二个学生对象=====");
Student stu2 = new Student("张三", 20);
System.out.println("姓名:" + stu2.name + ",年龄:" + stu2.age + ",学号:" + stu2.id);
}
}
代码中定义了无参、双参、三参 三个构造方法,它们名称与类名一致,仅参数列表不同,这正是构造方法重载的体现。通过重载,实现了不同初始化场景的支持:
- 无参构造:给属性赋默认值,适合创建 "默认状态" 的对象;
- 双参构造:仅传入姓名和年龄,学号用默认值;
- 三参构造:传入全部属性值,支持自定义初始化。
参和双参构造通过this(...)调用三参构造,避免了在多个构造方法中重复写this.name = name;这类赋值代码。

六、代码块
Java 中的代码块 是指用{}包裹的一段代码,根据定义位置和关键字修饰的不同,可分为普通代码块、构造代码块、静态代码块、同步代码块四类,其中前三者是日常开发中最常用的,核心作用是实现代码的复用和对象 / 类的初始化。
1.普通代码块
定义在方法内部的代码块,主要作用是限定变量的作用域,避免变量名冲突(实际开发中使用较少,更多是语法层面的存在)。
java
public class BlockTest {
public static void main(String[] args) {
// 普通代码块
{
int a = 10;
System.out.println("普通代码块:a = " + a);
}
// 此处无法访问a,因为a的作用域仅限于上面的代码块
// System.out.println(a); // 编译报错
}
}
2.构造块
定义在类中、方法外的代码块,每次创建对象时都会执行,且执行顺序在构造方法之前,常用于提取所有构造方法的公共初始化逻辑,实现代码复用。
java
class Person {
String name;
// 构造代码块
{
System.out.println("构造代码块执行:初始化公共逻辑");
name = "默认姓名"; // 所有对象创建时都会执行的初始化
}
// 无参构造
public Person() {
System.out.println("无参构造方法执行");
}
// 有参构造
public Person(String name) {
this.name = name;
System.out.println("有参构造方法执行");
}
}
public class BlockTest {
public static void main(String[] args) {
System.out.println("=====创建第一个对象=====");
Person p1 = new Person();
System.out.println("=====创建第二个对象=====");
Person p2 = new Person("张三");
}
}

七、static关键字
static(静态)是 Java 中的一个重要关键字,用于修饰成员变量 、成员方法 、代码块 和内部类 ,其核心特征是:被static修饰的成员属于类本身,而非类的某个对象,可直接通过类名访问,无需创建对象。
简单来说,static的作用是将成员 "共享" 给该类的所有对象使用,不再与单个对象绑定。
1.静态属性
用static修饰的成员变量,存储在方法区的静态域中,属于类的全局变量。
特点:所有对象共享同一个静态变量,修改一个对象的静态变量,会影响所有对象;类加载时初始化,生命周期与类一致。
java
class Student {
// 静态变量:所有学生共享同一个学校名称
static String school = "北京大学";
// 实例变量:每个学生有自己的姓名
String name;
public Student(String name) {
this.name = name;
}
}
public class StaticTest {
public static void main(String[] args) {
Student s1 = new Student("张三");
Student s2 = new Student("李四");
// 直接通过类名访问静态变量(推荐)
System.out.println(Student.school);
// 通过对象访问(不推荐,易混淆)
System.out.println(s1.school);
// 修改静态变量,所有对象都受影响
Student.school = "清华大学";
System.out.println(s2.school);
}
}
2.静态方法(类方法)
用static修饰的成员方法,属于类本身。
特点 :可直接通过类名调用,无需创建对象;静态方法中不能直接访问非静态成员(实例变量 / 实例方法),因为静态方法属于类,执行时可能还没有对象。
java
class MathUtil {
// 静态方法:计算两数之和
public static int add(int a, int b) {
return a + b;
}
// 实例方法:静态方法中无法直接调用
public void show() {
System.out.println("实例方法");
}
}
public class StaticTest {
public static void main(String[] args) {
// 直接通过类名调用静态方法
int sum = MathUtil.add(10, 20);
System.out.println(sum); // 输出:30
// 静态方法中调用实例方法(需先创建对象)
MathUtil util = new MathUtil();
util.show();
}
}
3.静态代码块
用static修饰的代码块(static {}),位于类中、方法外。
- 特点 :类加载时执行,且仅执行一次;常用于初始化静态变量、加载配置文件、连接数据库等全局初始化操作。
java
class Config {
static String configPath;
// 静态代码块:初始化静态变量
static {
configPath = "/config/app.properties";
System.out.println("静态代码块执行,初始化配置路径");
}
}
public class StaticTest {
public static void main(String[] args) {
// 类加载时静态代码块已执行
System.out.println(Config.configPath);
}
}
静态代码块 (static {})是static修饰的代码块,其核心特征是:在Config类被 JVM 加载时自动执行,且整个程序运行期仅执行一次 。这里的逻辑是给静态变量configPath赋值为配置文件的路径,并打印初始化提示。