Java SE 学习笔记 · 面向对象基础篇(二)
前言
在上一篇文章中,我们学习了 Java 面向对象的基本概念,包括类与对象、成员变量、成员方法、
this关键字以及构造方法。但是随着项目规模逐渐增大,仅仅会创建对象还远远不够。我们还需要解决以下问题:
如何将不同功能的类分类管理?
如何限制外部代码对对象内部数据的访问?
多个对象需要共享相同的数据怎么办?
静态方法与普通成员方法有什么区别?
创建对象时,静态代码块、实例代码块和构造方法按照什么顺序执行?
本文结合代码示例,系统整理这些知识。
一、Java 包
1.1 什么是包?
在java中,包是一种组织和管理类,接口(这个可以先不用理解,后面再说)等类型的机制
简单理解为:文件夹
例如我们做了一个项目
JavaProject ├── Student.java ├── Teacher.java ├── Computer.java └── Main.java可以看到这个项目包含了很多类,但是如果类多了起来,我们这个项目就会变得难以管理
这时候就需要包,对这些负责不同功能的类进行分类管理
比如
src └── com └── example ├── student │ └── Student.java ├── computer │ └── Computer.java └── main └── Main.java那么说了那么多,包有什么作用呢?
包主要有三个作用:
1:组织和管理不同功能的类。★★★
2:避免不同包之间的类名冲突。
3:配合访问修饰符,实现访问权限控制。
1.2 package关键字
例如
package com.example.student; public class Student { String name; int age; }package声明必须位于 Java 源文件的前部,在普通import和类型声明之前。
包名通常使用小写字母,常见命名方式为反向域名。
例如
package com.example.demo;对应的文件目录为
com/example/demo/1.3 import关键字
当我们需要使用其他包中的类时,可以通过import导入。
例如
import java.util.Date; public class Main { public static void main(String[] args) { Date date = new Date(); System.out.println(date.getTime()); } }当然,这里特殊介绍一种
import java.util.*;这样子,可以访问这个包中所有的类
但是实际项目,我们更应该明确导入需要使用的类,不仅是为了更加清晰,更是为了减少类名冲突
二 java的封装
2.1 什么是封装
封装是 Java 面向对象编程的三大基本特性之一。
另外两个分别是继承和多态(后面再说)。
封装的核心思想可以概括为:
将对象的数据和操作数据的方法组织在一起,隐藏内部实现细节,只提供必要的对外访问接口。
可以用电脑来理解。
对于电脑使用者而言,只需要知道:
1:如何开机
2:如何关机
3:如何使用键盘
4:如何通过屏幕查看信息
至于 CPU 内部如何工作、主板线路如何设计,普通使用者并不需要了解。
在 Java 编程中,也可以采用类似的设计思想。这就是封装
2.2 为什么需要封装
假设定义一个学生类:
class Student { public String name; public int age; }外部可以直接修改成员变量:
Student s = new Student(); s.name = "张三"; s.age = -100;虽然可以,但是这个年龄显然不符合实际要求
所以我们更希望限制外部代码对成员变量的直接修改
2.3 private关键字
使用private修饰成员变量:
class Student { private String name; private int age; }此时,在其他类中不能直接访问这些字段:
Student s = new Student(); s.age = 18; // 编译错误因为age是私有成员,只可以在student这个类内部中直接访问,不可以在其他地方访问
需要注意,private限制的是类级别的访问,而不是只能由某一个对象访问。
同一个类中的方法,可以通过其他同类对象的引用访问其私有字段。
2.4 getter 与 setter 方法
为了让外部能够合理访问属性,我们可以提供公共方法。
class Student { private String name; private int age; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } }在其他类中
public class Main { public static void main(String[] args) { Student s = new Student(); s.setName("张三"); s.setAge(18); System.out.println(s.getName()); System.out.println(s.getAge()); } }输出:
张三 18这里有两个重要概念:
getter:读取对象属性。
setter:修改对象属性。
2.5 setter 的真正价值
或许有人问,这样是不是脱裤子放屁--多此一举,其实并不是,通过setter调用
更重要的是:我们可以在公共方法中控制数据的合法性。
例如:
public void setAge(int age) { if (age >= 0 && age <= 150) { this.age = age; } else { System.out.println("年龄不合法!"); } }这样可以避免随意修改对象状态。
对于某些不允许外部修改的数据,甚至可以只提供 getter,而不提供 setter。
2.6 封装的优点
| 优点 | 说明 |
|---|---|
| 降低耦合性 | 外部不需要了解类的内部实现 |
| 降低复杂性 | 只暴露必要的操作接口 |
| 提高安全性 | 防止对象状态被随意修改 |
| 提高可维护性 | 内部实现改变时,可以尽量保持公共接口稳定 |
需要注意,封装的主要目的并不是提高程序运行速度,而是改善代码设计和维护性。
三 java访问修饰符
Java 提供四种常见的成员访问权限:
1:public2:protected3:默认访问权限(不写修饰符)
4:private
下面我们来列表介绍区别
3.1 访问权限对比
| 访问范围 | public | protected | 默认权限 | private |
|---|---|---|---|---|
| 同一个类 | ✓ | ✓ | ✓ | ✓ |
| 同包其他类 | ✓ | ✓ | ✓ | ✗ |
| 不同包的子类 | ✓ | 有条件 | ✗ | ✗ |
| 不同包的普通类 | ✓ | ✗ | ✗ | ✗ |
这里,我们只了解public,默认权限,private即可
我们可以类比,在这里,类指自己,包指家人
public相当于你对外界公开的信息,比如你的性别等等
默认权限,就是指你和你的家人都知道的信息
private,就是指只有你自己知道的信息
3.2 默认访问权限
例如:
class Student { String name; }name没有显式指定访问修饰符。
此时,它使用默认访问权限,也叫包访问权限(package-private)。
同包中的其他类可以访问,其他包中的普通类不能直接访问。
3.3 跨包访问示例
看了那么多理论性的东西,下面我们给出一个简单的例子方便大家更好的理解
假设项目结构如下:
com.example ├── model │ └── Computer.java └── app └── Main.javaComputer.java
package com.example.model; public class Computer { private String cpu; public String screen; String brand; public Computer(String cpu, String screen, String brand) { this.cpu = cpu; this.screen = screen; this.brand = brand; } public String getCpu() { return cpu; } }Main.java
package com.example.app; import com.example.model.Computer; public class Main { public static void main(String[] args) { Computer computer = new Computer("M4", "Retina", "Apple"); System.out.println(computer.screen); // System.out.println(computer.cpu); // System.out.println(computer.brand); System.out.println(computer.getCpu()); } }问题:
Q1:为什么 Main.java 要使用 import?
Q2:为什么 computer.screen 可以访问,而 computer.cpu 不能?
Q3:为什么 computer.brand 不能访问?
Q4:为什么 private 修饰的 cpu 可以通过 getCpu() 获取?
Q5:new Computer("M4", "Retina", "Apple") 有什么作用?
大家可以尝试做做这个5个问题,答案会在文末提出,谢谢!
四:static关键字
4.1 static 是什么?
static可以修饰成员变量和成员方法。
被static修饰的成员称为静态成员,也叫类成员。
静态成员属于类,而不是某一个具体对象。
4.2 普通成员变量和静态成员变量
假设定义一个学生类:
class Student { String name; static String classRoom = "1-101"; }假如我们要创建多个对象
Student s1 = new Student(); Student s2 = new Student(); Student s3 = new Student(); s1.name = "张三"; s2.name = "李四"; s3.name = "王五";此时,每个对象有自己的name,但是classRoom是被整个Student类共享
4.3 静态变量的访问
可以通过类名直接访问
System.out.println(Student.classRoom);输出:
1-101如果修改成1-114514,
那么将会输出 1-114514.
4.4 static 的实际用途
一个常见用途是统计对象创建次数。
class Student { private static int count = 0; public Student() { count++; } public static int getCount() { return count; } }调用
public class Main { public static void main(String[] args) { Student s1 = new Student(); Student s2 = new Student(); Student s3 = new Student(); System.out.println(Student.getCount()); } }输出
3通过静态变量,我们可以记录这个类累计创建了多少个对象。
需要注意,这段示例只统计构造方法执行的次数,不等于当前仍然存活的对象数量。
五:static静态方法
5.1 静态方法的定义
public static 返回值类型 方法名() { // 方法体 }例如
class MyMath { public static int max(int a, int b) { return a > b ? a : b; } }使用时
int result = MyMath.max(10, 20); System.out.println(result);静态方法通常适合实现不需要依赖某个具体对象状态的功能。
注意:静态方法不可以直接访问非静态成员
5.2静态方法能否使用对象?
class Student { String name; public static void show() { Student student = new Student(); student.name = "张三"; System.out.println(student.name); } }这是合法的。
因为静态方法虽然没有自己的this,但可以创建对象,并通过明确的对象引用访问实例成员。
因此应该准确理解为:
静态方法不能直接访问实例成员,但可以通过对象引用访问允许访问的实例成员。
同时,静态方法一样不可以用this,注意!!!
5.3 静态成员和实例成员对比
| 比较 | 静态成员 | 实例成员 |
|---|---|---|
| 所属 | 类 | 对象 |
| 推荐访问方式 | 类名.成员 | 对象.成员 |
| 是否由对象分别拥有 | 否 | 是 |
| 是否需要当前对象 | 不需要 | 需要 |
| 静态方法中直接访问 | 可以访问可见的静态成员 | 不能直接访问 |
| 使用 this | 静态方法中不允许 | 实例方法中允许 |
六 代码块
6.1 什么是代码块
我们称{}包围起来的一段代码称为代码块
可以分为以下几类
1:普通代码块
2:实例代码块
3:静态代码块
4:同步代码块
在本篇文章只介绍前3种
6.2 普通代码块
普通代码块可以定义在方法内部
例如
public class Main { public static void main(String[] args) { { int x = 10; System.out.println(x); } int x = 100; System.out.println(x); } }输出
10 100可以看见,普通代码块可以形成一个局部作用域
在上面的例子中,第一个x的作用域随着内部代码块结束而结束,因此后面可以重新声明x。
6.3 实例代码块
例如
class Student { String name; { name = "张三"; System.out.println("实例代码块执行"); } public Student() { System.out.println("构造方法执行"); } }创建对象:
Student s = new Student();输出
实例代码块执行 构造方法执行他有一个重要特点,就是:
每创建一个对象,实例代码块就会执行一次。
6.4 静态代码块
静态代码块通过static定义:
class Student { static String school; static { school = "机密大坝"; System.out.println("静态代码块执行"); } }静态代码块通常用于初始化静态成员。
他只会执行一次
6.5 静态代码块与实例代码块的区别
| 比较 | 静态代码块 | 实例代码块 |
|---|---|---|
| 定义形式 | static {} | {} |
| 执行时机 | 类初始化时 | 每次对象初始化时 |
| 执行次数 | 每次类初始化一次 | 每创建一个对象执行一次 |
| 是否可以使用 this | 不可以 | 可以 |
| 常见用途 | 初始化静态数据 | 初始化实例数据 |
下面介绍一下这三种代码块的执行顺序
大家可以把这一段代码运行一下
class Student { static { System.out.println("1. 静态代码块"); } { System.out.println("2. 实例代码块"); } public Student() { System.out.println("3. 构造方法"); } } public class Main { public static void main(String[] args) { Student s1 = new Student(); System.out.println("=========="); Student s2 = new Student(); } }输出的是
1. 静态代码块 2. 实例代码块 3. 构造方法 ========== 2. 实例代码块 3. 构造方法可以看见,静态的代码块只会执行一次,第二次创建新的对象时,不会再执行
通常三种代码块的执行顺序是
静态字段显式初始化 / 静态代码块 ↓ 实例字段显式初始化 / 实例代码块 ↓ 构造方法主体最后的总结
本章主要学习了 Java 类与对象的进一步知识。
其中最重要的内容可以归纳为五点:
1. package 与 import 用于组织和使用不同包中的类。
合理划分包能够提升项目结构的清晰度。
2. 封装的重点是隐藏实现细节、控制对外访问。
private 与公共方法只是实现封装的常见手段。
3. 访问权限决定不同位置的代码能否访问成员。
需要理解 public、private、默认访问权限和 protected 的区别(先不用理解protected)。
4. static 成员属于类,而不是某个具体对象。
静态变量适合保存共享数据,静态方法适合实现不依赖实例状态的功能。
5. 静态代码块、实例代码块和构造方法具有不同的初始化时机。
理解它们的执行顺序,有助于阅读代码和排查初始化问题。
我们下节再见!
2026.10.26 11:09