

✨博客主页: https://blog.csdn.net/m0_63815035?type=blog
💗《博客内容》:大数据、AI开发、Java、测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识
📢博客专栏: https://blog.csdn.net/m0_63815035/category_11954877.html
📢欢迎点赞 👍 收藏 ⭐留言 📝
📢本文为学习笔记资料,如有侵权,请联系我删除,疏漏之处还请指正🙉
📢大厦之成,非一木之材也;大海之阔,非一流之归也✨

目录
-
- 一、语言发展主线:为什么需要对象?
- 二、类与对象
-
- [1. 类的定义](#1. 类的定义)
- [2. 对象的创建和使用](#2. 对象的创建和使用)
- [3. 构造器(Constructor)](#3. 构造器(Constructor))
- [4. this 关键字](#4. this 关键字)
- [5. 内存分析(简略)](#5. 内存分析(简略))
- 三、封装
-
- [1. 为什么要封装?](#1. 为什么要封装?)
- [2. 访问控制符](#2. 访问控制符)
- [3. getter / setter](#3. getter / setter)
- [4. JavaBean 规范](#4. JavaBean 规范)
- 四、继承
-
- [1. 继承语法和特点](#1. 继承语法和特点)
- [2. 方法重写(Override)](#2. 方法重写(Override))
- [3. super 关键字](#3. super 关键字)
- [4. 构造器调用顺序](#4. 构造器调用顺序)
- [5. Object 类常用方法](#5. Object 类常用方法)
- 五、多态
-
- [1. 多态的定义](#1. 多态的定义)
- [2. 注意点](#2. 注意点)
- [3. 向下转型](#3. 向下转型)
- [4. 动态绑定机制](#4. 动态绑定机制)
- 六、抽象类与接口
-
- [1. 抽象类](#1. 抽象类)
- [2. 接口](#2. 接口)
- [3. 接口 vs 抽象类(Java 8 以前)](#3. 接口 vs 抽象类(Java 8 以前))
- [4. Java 8 之后的接口变化](#4. Java 8 之后的接口变化)
- [5. 面向接口编程](#5. 面向接口编程)
- 七、内部类
-
- [1. 成员内部类](#1. 成员内部类)
- [2. 静态内部类](#2. 静态内部类)
- [3. 局部内部类](#3. 局部内部类)
- [4. 匿名内部类](#4. 匿名内部类)
- 八、类和类之间的关系
- [九、设计原则(SOLID 简版)](#九、设计原则(SOLID 简版))
- 十、综合练习
本篇是面向对象的完整讲解,从类和对象开始,到封装、继承、多态、接口、内部类,最后涉及设计原则。每个概念都有例子和常见注意事项。
一、语言发展主线:为什么需要对象?
计算机语言发展本质上是为了更自然地表达人的思维,同时管理越来越复杂的数据和操作。
- 只有基本变量 → 能存单个数字、字符,但数据一多就乱。
- 数组 → 把同一类型的数据连续存放,可以批量处理。但数组要求所有元素类型相同,且只能存数据,没有操作。
- 结构体(struct) → 允许把不同类型的数据(如人的姓名、年龄、身高)组合成一个整体。但数据和方法还是分开的。
- 类和对象 → 把数据(属性)和操作(方法)绑在一起。一个对象就像现实中的一个实体,有自己的状态和行为。
面向过程 :数据和方法分离,程序 = 数据结构 + 算法,以函数为中心。
面向对象:数据和方法合一,程序 = 对象 + 对象之间的消息传递,以类/对象为中心。
对于复杂系统(比如一个电商平台),宏观上需要用面向对象分析业务,拆成用户、商品、订单等类;但每个方法内部的具体实现(如计算价格、校验库存)仍然是面向过程的。

二、类与对象
1. 类的定义
类是模板,描述了一类事物共有的属性和方法。
java
class Student {
// 属性(成员变量,实例变量)
int id;
String name;
int age;
// 方法
void study() {
System.out.println(name + "正在学习");
}
}
- 属性可以设默认值,不设则系统给默认值:
0、0.0、false、\u0000、null。 - 方法定义和之前的函数一样,只不过它属于类。
2. 对象的创建和使用
java
Student s1 = new Student();
s1.id = 1001;
s1.name = "张三";
s1.study();
new Student()在堆内存中开辟空间,并返回地址给栈中的变量s1。- 每个对象有自己的属性副本,互相独立。
- 方法代码只存一份,所有对象共享。
3. 构造器(Constructor)
构造器是创建对象时自动调用的特殊方法,用来初始化对象。
特点:
- 名字和类名完全相同
- 没有返回值(也不能写
void) - 不能被
static、final、abstract修饰 - 可以重载
java
class Student {
int id;
String name;
// 无参构造
Student() {
System.out.println("调用无参构造");
}
// 有参构造
Student(int id, String name) {
this.id = id; // this 区分成员变量和局部变量
this.name = name;
}
}
重要:
- 如果你不写任何构造器,编译器会自动生成一个无参构造(空实现)。
- 只要你写了任何带参构造,编译器就不再自动生成无参构造。为了避免子类继承时出错,建议手动把无参构造也写上。
构造器之间可以用 this(...) 互相调用,但必须放在第一行。
java
Student() {
this(0, "无名"); // 调用有参构造
}
4. this 关键字
- 在构造器中,表示正在初始化的对象。
- 在实例方法中,表示调用该方法的对象。
- 不能出现在静态方法中(因为静态方法不属于任何对象)。
常用场景:
- 区分成员变量和参数。
- 调用另一个构造器(必须第一行)。
- 返回当前对象(链式调用)。
java
class Calculator {
Calculator add(int x) {
// ... 运算
return this; // 返回自身
}
}
Calculator c = new Calculator().add(5).add(3);
5. 内存分析(简略)
- 栈:存放局部变量(包括基本类型和对象引用),方法调用时会压栈。线程私有,速度较快。
- 堆 :存放所有
new出来的对象(包括数组)。线程共享,速度稍慢。 - 方法区:存放类信息(字节码)、静态变量、字符串常量等。线程共享。
java
Student s = new Student();
- 加载
Student.class到方法区。 - 栈中创建
s变量(引用)。 - 堆中开辟一块空间存 Student 对象,成员变量取默认值。
- 执行构造器代码(如果存在)。
- 将堆中对象的地址赋给
s。
三、封装
1. 为什么要封装?
- 防止外部代码随意修改内部状态(例如年龄不能为负数)。
- 隐藏实现细节,外部只需知道调用什么方法,不必关心内部如何实现。
- 提高代码的可维护性:修改内部逻辑不影响调用方。
2. 访问控制符
| 修饰符 | 同类 | 同包 | 子类(不同包) | 任何地方 |
|---|---|---|---|---|
private |
√ | |||
| (default) | √ | √ | ||
protected |
√ | √ | √ | |
public |
√ | √ | √ | √ |
- 属性通常用
private隐藏。 - 方法一般用
public向外提供服务。 - 仅本类内部调用的辅助方法可以用
private。
3. getter / setter
java
public class Person {
private String name;
private int age;
private boolean married;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
} else {
throw new IllegalArgumentException("年龄非法");
}
}
// 注意:boolean 的 getter 习惯用 isXxx
public boolean isMarried() {
return married;
}
}
4. JavaBean 规范
一个标准的 JavaBean 需要:
- 类公开,有无参构造
- 属性私有
- 提供 getter/setter
- 实现
Serializable(可序列化,后面再学)
封装不是简单的私有化,而是提供合理的访问接口。
四、继承
1. 继承语法和特点
java
class Animal {
String name;
void eat() {
System.out.println("吃东西");
}
}
class Dog extends Animal {
void bark() {
System.out.println("汪汪");
}
}
- Java 只支持单继承(一个类只能有一个直接父类),但可以实现多接口。
- 所有类(除了
Object)都直接或间接继承Object。 - 子类拥有父类所有的成员,但
private成员不能直接访问(可以通过public或protected方法间接访问)。
2. 方法重写(Override)
子类重新实现父类的方法。
规则:
- 方法名、参数列表必须完全相同。
- 返回值类型:子类返回值类型可以是父类返回值类型的子类(协变返回类型,了解即可)。
- 访问权限不能更严格(可以扩大,不能缩小)。
private方法不能被重写,static方法不能被重写(但是可以重新声明)。- 可以用
@Override注解,让编译器帮你检查。
java
class Animal {
protected void shout() {
System.out.println("动物叫");
}
}
class Cat extends Animal {
@Override
public void shout() { // protected -> public 允许
System.out.println("喵喵");
}
}
3. super 关键字
- 访问父类的属性(当子类隐藏了父类同名属性时):
super.name - 调用父类的方法:
super.shout() - 调用父类的构造器:
super(...),必须出现在子类构造器的第一行。
4. 构造器调用顺序
创建子类对象时,会先创建父类部分(递归直到 Object),然后再执行子类构造器体的代码。
- 子类构造器默认第一行有
super()(调用父类无参构造)。 - 如果父类没有无参构造,子类构造器必须显式调用父类的有参构造。
java
class Father {
Father(int x) {
System.out.println("Father " + x);
}
}
class Son extends Father {
Son() {
super(100); // 必须写,否则编译错误
System.out.println("Son");
}
}
5. Object 类常用方法
toString():返回对象的字符串表示,通常重写。equals(Object obj):判断对象是否相等,默认比较地址,通常需要重写。hashCode():返回对象的哈希码,重写equals时必须重写hashCode。getClass():返回运行时类信息。
java
@Override
public String toString() {
return "Person{name=" + name + ", age=" + age + "}";
}
五、多态
1. 多态的定义
同一个类型的变量,调用同一个方法,实际执行的行为取决于运行时具体的对象类型。
必要条件:
- 继承
- 方法重写
- 父类引用指向子类对象
java
Animal a = new Dog(); // 向上转型(自动)
a.shout(); // 调用的是 Dog 的 shout()
2. 注意点
- 属性没有多态:访问属性看编译时类型,不是运行时类型。
java
Animal a = new Dog();
System.out.println(a.age); // 还是 Animal 的 age
- 静态方法没有多态:静态方法属于类,调用时看编译时类型。
java
Animal a = new Dog();
a.staticMethod(); // 调用的是 Animal 的静态方法
3. 向下转型
当你想调用子类特有的方法时,需要把父类引用转回子类类型。
java
Animal a = new Dog();
if (a instanceof Dog) { // 安全检查,避免 ClassCastException
Dog d = (Dog) a;
d.bark(); // Dog 独有的方法
}
instanceof 关键字:判断对象是否是某个类(或其子类)的实例。
4. 动态绑定机制
- 编译时,编译器只检查引用变量的类型中是否有该方法。
- 运行时,JVM 会找到对象实际类型的方法表,执行真正的方法。
- 这就是多态的核心原理。
六、抽象类与接口
1. 抽象类
当某个方法无法在父类中给出合理实现时,可以声明为抽象方法,要求子类必须实现。
java
abstract class Shape {
abstract double area(); // 抽象方法,无方法体
void show() {
System.out.println("这是一个形状");
}
}
- 有抽象方法的类必须声明为
abstract。 - 抽象类不能实例化(不能
new)。 - 抽象类可以有构造器(供子类调用)、普通属性、普通方法。
- 子类必须实现所有抽象方法,除非子类也是抽象类。
2. 接口
接口是完全抽象的规范,强调"能做什么"。
java
interface Flyable {
int MAX_HEIGHT = 1000; // public static final
void fly(); // public abstract
}
- 接口中的变量默认是
public static final,方法默认是public abstract。 - 接口不能有构造器,不能有实例属性(但可以有静态常量)。
- 一个类可以实现多个接口(多实现)。
- 实现类必须重写所有接口中的抽象方法,且方法必须是
public。
java
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("鸟飞");
}
}
3. 接口 vs 抽象类(Java 8 以前)
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 多继承 | 不支持 | 支持(类可多实现) |
| 实例属性 | 可以有 | 只能有静态常量 |
| 构造器 | 可以有 | 不能有 |
| 访问权限 | 可以任意 | 方法默认 public |
| 使用场景 | 表示"是什么"(is-a) | 表示"能做什么"(can-do) |
4. Java 8 之后的接口变化
- 默认方法 :用
default修饰,可以有方法体,实现类可以继承或重写。 - 静态方法 :用
static修饰,属于接口,通过接口名调用。
java
interface Vehicle {
default void run() {
System.out.println("交通工具在跑");
}
static void honk() {
System.out.println("嘀嘀");
}
}
这些变化允许接口在不破坏已有实现类的情况下增加方法。
5. 面向接口编程
优先使用接口类型声明变量,而不是具体类。这样更换实现不影响调用代码。
java
List<String> list = new ArrayList<>(); // 好
// ArrayList<String> list = new ArrayList<>(); // 不好,耦合太强
七、内部类
内部类定义在另一个类内部,可以更好地封装,也能访问外部类的所有成员(包括私有)。
1. 成员内部类
java
class Outer {
private int x = 10;
class Inner {
void print() {
System.out.println(x); // 可以访问外部类私有成员
}
}
}
// 创建方法
Outer out = new Outer();
Outer.Inner in = out.new Inner();
- 成员内部类不能有静态成员(除了静态常量)。
- 内部类中可以使用
Outer.this访问外部类当前对象。
2. 静态内部类
java
class Outer {
static class Inner { }
}
// 创建
Outer.Inner in = new Outer.Inner();
- 不持有外部类对象的引用,只能访问外部类的静态成员。
- 可以像普通类一样有静态成员。
3. 局部内部类
定义在方法内部,作用域仅限该方法。
java
void method() {
class Local {
void work() { }
}
Local l = new Local();
}
- 局部内部类可以访问外部类的成员,也可以访问方法中的局部变量,但局部变量必须是
final或"实际上 final"(JDK 8 后不需要显式写 final,但不能修改)。
4. 匿名内部类
最常用,一次性使用,无需命名。
java
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("run");
}
};
- 匿名内部类继承某个类或实现某个接口,不能同时做两件事。
- 不能定义构造器。
- 常用于事件监听、线程、回调等场景。
java
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// 处理点击
}
});
八、类和类之间的关系
| 关系 | 描述 | UML 表示 | 代码示例 |
|---|---|---|---|
| 依赖 | 一个类临时使用另一个类(方法参数) | 虚线箭头 | 方法参数、局部变量 |
| 关联 | 一个类长期持有另一个类的引用 | 实线箭头 | 成员变量 |
| 聚合 | 整体与部分,部分可独立存在 | 空心菱形+实线箭头 | 成员变量(弱) |
| 组合 | 整体与部分,同生共死 | 实心菱形+实线箭头 | 成员变量(强) |
| 继承 | is-a | 空心三角+实线(子类→父类) | extends |
| 实现 | 类实现接口 | 空心三角+虚线(类→接口) | implements |
- 聚合:汽车和轮胎,轮胎可以拆下来装在另一辆车上。
- 组合:人和心脏,人没了心脏也就没了意义。
九、设计原则(SOLID 简版)
- 单一职责:一个类只负责一项职责。
- 开闭原则:对扩展开放,对修改关闭。增加功能尽量新增代码,而不是改原有代码。
- 里氏替换:子类必须能替换父类并且程序行为正确。
- 接口隔离:接口应该小而专,不要做"胖接口"。
- 依赖倒置:依赖抽象(接口/抽象类),不依赖具体实现。
- 迪米特法则:一个对象应尽可能少地了解其他对象。
这些原则不是死的,但遵循它们能让代码更容易维护和扩展。
十、综合练习
- 定义一个
Point3D类 :三个坐标x,y,z,提供构造器,计算到原点的距离平方,计算到另一个Point3D对象的距离平方。 - 定义一个
Circle类 :包含圆心(Point)和半径,提供面积方法,以及判断一个Point是否在圆内的方法。 - 动物多态 :
Animal抽象类,shout()抽象方法。Dog、Cat继承并重写。用Animal引用调用shout()。 - 接口练习 :定义
USB接口,含work()方法。实现Mouse和Keyboard类,模拟将 USB 设备插入计算机。
学习面向对象时,多思考现实中的事物如何抽象成类,类之间有哪些关系。代码写多了,自然就会明白为什么需要封装、继承和多态。遇到不确定的就多翻 API 或写个小例子验证。
csharp
今天这篇文章就到这里了,大厦之成,非一木之材也;大海之阔,非一流之归也。感谢大家观看本文
