面向对象
面向对象思想
面向过程强调解决问题的步骤和执行顺序;面向对象强调把数据和操作数据的行为组织到对象中,再由对象之间协作完成任务。
类是对一类对象共同特征和行为的抽象,对象是类的具体实例。
面向对象编程通常包含以下过程:
-
分析业务中的对象及其关系。
-
根据对象的共同特征定义类。
-
创建对象并设置初始状态。
-
调用对象的方法完成业务功能。
类的定义
类使用 class 关键字定义,类中可以包含成员变量、构造器和方法。
public class Person {
private String name;
private int age;
public void introduce() {
System.out.println("我叫 " + name);
}
}
成员变量
定义在类中、方法外的变量称为成员变量,也常称为字段或属性。定义在方法、构造器或代码块中的变量称为局部变量。
public class Person {
String name;
int age;
char sex;
double height;
}
成员变量可以使用基本类型,也可以使用引用类型。
public class School {
String name;
String address;
}
public class Person {
String name;
School school;
}
创建 Person 对象后,引用类型字段 school 的默认值是 null。在访问 school.name 前,必须先让它指向一个 School 对象。
Person person = new Person();
person.school = new School();
person.school.name = "哈工大";
person.school.address = "南岗区";
下面的代码会抛出 NullPointerException,因为 person.school 仍然是 null。
Person person = new Person();
person.school.name = "哈工大";

当一个对象不再被任何有效引用访问时,它可能成为垃圾回收器的回收对象。垃圾回收的具体时机由 JVM 决定,程序不能假定对象会立刻被回收。
成员变量的默认值
创建对象时,成员变量会获得默认值。
| 类型 | 默认值 |
|---|---|
| 整数类型 | 0 |
| 浮点类型 | 0.0 |
char | \u0000 |
boolean | false |
| 引用类型 | null |
局部变量没有自动可用的默认值,必须在读取前完成赋值。
对象的内存结构
从概念上看,对象通常包含对象头、实例数据和对齐填充;数组对象还需要记录长度。

对象头、引用大小和对齐方式属于 JVM 实现细节,会受到 JVM、压缩指针和运行平台影响,不应在业务代码中依赖固定字节数。
方法
方法用于封装一段可重复调用的行为。
访问修饰符 返回类型 方法名(参数列表) {
方法体
return 返回值;
}
返回值
返回类型不是 void 时,所有正常结束的执行路径都必须返回与声明类型兼容的值。执行 return 后,当前方法立即结束。
public int add(int a, int b) {
return a + b;
}
返回类型为 void 时,不需要返回结果,也可以使用不带值的 return 提前结束方法。
public void printPositive(int value) {
if (value <= 0) {
return;
}
System.out.println(value);
}
形参与实参
方法声明中的参数称为形参,调用方法时传入的值称为实参。
public boolean eat(String food) {
return "肉".equals(food);
}
boolean full = person.eat("大米饭");
比较字符串内容时应使用 equals(),不能使用 ==。
在方法中调用其他方法
同一个对象的方法之间可以直接调用,也可以显式使用 this。
public void introduce() {
System.out.println("我叫 " + name);
}
public void play() {
introduce();
// 等价于 this.introduce();
}
调用其他类的实例方法时,需要先获得对应对象。
School school = new School();
school.showInfo();
方法重载
在同一个类中,方法名相同、参数列表不同的方法构成重载。参数列表不同可以表现为参数数量、参数类型或参数顺序不同。
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b) {
return a + b;
}
public double add(int a, double b) {
return a + b;
}只有返回类型不同不能构成重载。
编译器根据实参类型选择最匹配的方法。存在基本类型提升、装箱、可变参数等多个候选时,可能出现调用歧义,因此重载设计应保持清晰。
public class Calculator {
double add(double a, double b) {
return a + b;
}
int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
Calculator calculator = new Calculator();
calculator.add(10, 20); // 调用 int 版本
calculator.add('a', 'b'); // char 提升为 int
calculator.add(10L, 20L); // long 提升为 double
}
}创建对象
使用 new 表达式可以创建对象并调用构造器。
Person person1 = new Person();
person1.name = "Tom";
person1.age = 20;
Person person2 = new Person();
person2.name = "Jack";
person2.age = 21;
变量 person1 和 person2 保存的是两个不同对象的引用。
构造器
构造器用于在创建对象时完成初始化。构造器名称必须与类名相同,并且没有返回类型。
public class Person {
String name;
int age;
double height;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
}如果类中没有声明任何构造器,编译器会提供一个与类访问级别相关的无参构造器。一旦显式声明了构造器,编译器就不再自动补充无参构造器。
一个类可以通过不同参数列表重载构造器。
this 关键字
this 表示当前对象。
访问当前对象的成员
当成员变量与局部变量同名时,使用 this 区分成员变量。
public Person(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
调用当前对象的方法时可以省略 this。
public void introduce() {
System.out.println("我叫 " + name);
}
public void sing() {
this.introduce();
}
调用其他构造器
this() 用于调用本类的其他构造器,并且必须是当前构造器的第一条语句。
public Person() {
System.out.println("创建 Person 对象");
}
public Person(String name, int age) {
this();
this.name = name;
this.age = age;
}
public Person(String name, int age, double height) {
this(name, age);
this.height = height;
}构造器之间不能形成循环调用。
封装
封装是把数据和操作数据的行为组织在类中,并隐藏不应由外部直接控制的实现细节。
属性封装
通常把字段声明为 private,再通过方法提供受控访问。
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄不合法");
}
this.age = age;
}
}行为封装
方法应尽量只承担清晰、单一的职责。高内聚可以提升代码的可读性、复用性和可测试性。
包(Package)
包用于组织类并避免类名冲突,其目录结构通常与包名一致。
package com.hyxy.system;
import com.hyxy.model.Student;
public class Test {
public static void main(String[] args) {
Student student = new Student();
}
}包名通常使用小写,常见命名方式是公司域名倒写加项目名和模块名。java.lang 包会被自动导入,例如 String、Object 和 System。
不建议在正式项目中使用默认包,因为默认包中的类难以被命名包中的代码规范引用和管理。
继承
子类使用 extends 继承父类。继承用于表达“子类是一种父类”的关系,并复用父类可继承的状态和行为。
public class Animal {
protected String name;
public void eat() {
System.out.println("动物正在进食");
}
}
public class Person extends Animal {
private String id;
public void study() {
System.out.println("学习");
}
}
Java 类只支持单继承,但可以形成多层继承。未显式继承其他类的类,默认直接继承 Object。
构造器不会被继承,父类的 private 成员也不能被子类直接访问,但它们仍然属于父类对象状态的一部分。
访问权限修饰符
| 修饰符 | 当前类 | 同包类 | 不同包子类 | 其他位置 |
|---|---|---|---|---|
private | 可以 | 不可以 | 不可以 | 不可以 |
| 默认权限 | 可以 | 可以 | 不可以 | 不可以 |
protected | 可以 | 可以 | 可以 | 不可以 |
public | 可以 | 可以 | 可以 | 可以 |
protected 在跨包子类中的访问还受到引用表达式类型限制,实际使用时应优先通过受保护的方法封装行为。
方法重写
子类重新实现父类可继承的实例方法称为重写。
public class Person extends Animal {
@Override
public void eat() {
System.out.println("这个人正在吃饭");
}
}
重写需要满足以下主要规则:
- 方法名和参数列表相同。
- 返回类型相同,或者是父类返回类型的子类型。
- 子类方法的访问权限不能比父类方法更严格。
- 子类声明的受检异常不能比父类方法更宽泛。
final方法不能重写,private方法不可见,静态方法属于隐藏而不是重写。
建议添加 @Override,让编译器检查重写是否正确。
重写与动态绑定
public class Shape {
public double area() {
return 0;
}
public static double sumArea(Shape[] shapes) {
double sum = 0;
for (Shape shape : shapes) {
sum += shape.area();
}
return sum;
}
}public class Rectangle extends Shape {
private final int length;
private final int width;
public Rectangle(int length, int width) {
this.length = length;
this.width = width;
}
@Override
public double area() {
return length * width;
}
}Shape[] shapes = {
new Rectangle(10, 20),
new Rectangle(15, 10)
};
System.out.println(Shape.sumArea(shapes));
即使数组元素的声明类型是 Shape,运行时仍会调用实际对象重写后的 area() 方法。
super 关键字
super 用于访问父类成员或调用父类构造器。
调用父类方法
@Override
public void eat() {
super.eat();
System.out.println("这个人正在吃饭");
}
调用父类构造器
子类构造器必须直接或间接调用父类构造器。没有显式编写时,编译器会尝试在第一行加入 super()。
public class Person extends Animal {
private final String id;
public Person(String name, String id) {
super(name);
this.id = id;
}
}
如果父类没有可访问的无参构造器,子类必须显式调用其他父类构造器。super() 和 this() 都必须位于构造器第一行,因此二者不能在同一构造器中同时直接出现。
多态
父类类型的引用可以指向子类对象,这种写法体现了多态。
Animal animal = new Person();
Object object = new Person();
Animal[] animals = {
new Person(),
new Dog()
};
多态使方法参数和集合能够接收多种具体子类。
public void feed(Animal animal) {
animal.eat();
}
调用重写的实例方法时,运行结果由实际对象类型决定;可以访问哪些成员则由引用的声明类型决定。
多态引用的访问范围
父类引用不能直接调用子类新增的方法。



Animal animal = new Person();
animal.eat();
// animal.study(); // 编译错误
字段访问不具备与实例方法相同的动态绑定效果,因此不建议在父类和子类中定义同名字段。
向上转型与向下转型
子类对象转换为父类类型称为向上转型,通常可以自动完成。
Animal animal = new Person();
父类引用转换为子类类型称为向下转型,需要显式转换,并且实际对象必须确实属于目标类型。
Person person = (Person) animal;
person.study();
错误的向下转型会抛出 ClassCastException。
instanceof 运算符
instanceof 用于判断对象是否可以安全转换为指定类型。对象为 null 时,判断结果为 false。
public void friend(Animal animal) {
if (animal instanceof Person) {
Person person = (Person) animal;
person.study();
} else if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchDoor();
}
}JAR 包导出与导入
JAR 是基于 ZIP 格式的归档文件,可以包含字节码、资源和元数据。发布类库时通常把编译结果打包为 JAR,也可以根据需要附带源代码和文档。
在 IntelliJ IDEA 中导出
-
打开
File、Project Structure、Artifacts。 -
新建 JAR Artifact 并设置输出内容和位置。
-
通过
Build、Build Artifacts构建 JAR。
导入第三方 JAR
-
在工程中创建
lib目录并复制 JAR 文件。 -
打开
File、Project Structure、Libraries。 -
把
lib中的 JAR 添加到工程类路径。
使用 Maven 或 Gradle 的项目通常通过依赖配置管理第三方类库,不建议手工复制大量 JAR。
Object 常用方法
toString() 方法
toString() 返回对象的字符串表示。System.out.println(object) 会间接调用对象的 toString()。
Object.toString() 的默认结果通常包含类名和哈希码的十六进制形式,不应解释为可靠的内存地址。
public class StudentCard {
private final String id;
private final String name;
public StudentCard(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "学号:" + id + ",姓名:" + name;
}
}equals() 和 hashCode() 方法
Object.equals() 默认比较引用是否指向同一个对象。业务对象通常根据关键字段重写 equals()。
public class StudentCard {
private final String id;
private final String name;
public StudentCard(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof StudentCard)) {
return false;
}
StudentCard other = (StudentCard) object;
return Objects.equals(id, other.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}重写 equals() 时必须保持自反性、对称性、传递性和一致性,并正确处理 null。同时应重写 hashCode(),保证相等对象具有相同哈希码。
字符串内容比较必须使用 equals() 或其他明确的内容比较方法。
JVM 运行时数据区概述
JVM 运行时数据区通常包括以下部分:
- 程序计数器:记录当前线程执行到的字节码位置,每个线程独立拥有。
- Java 虚拟机栈:每次方法调用会创建栈帧,用于保存局部变量、操作数栈和返回信息等。
- 本地方法栈:为本地方法调用提供支持,具体实现可能与虚拟机栈合并。
- 堆:主要存放对象和数组,由垃圾回收器管理,通常由所有线程共享。
- 方法区:保存类结构、运行时常量池、静态字段等逻辑信息;具体实现可能使用元空间等机制。
不要把逻辑运行时区域与某个 JVM 版本的物理内存实现完全等同。
Java 的参数传递
Java 只有值传递。调用方法时,实参的值会复制给形参。
基本类型参数
public void modifyValue(int value) {
value++;
System.out.println("方法内部:" + value);
}
int value = 3;
modifyValue(value);
System.out.println("方法外部:" + value);
方法内部修改的是形参副本,不会改变外部变量。

引用类型参数
传递对象时,复制的是引用值。形参和实参最初指向同一个对象,因此通过形参修改对象属性,调用方可以看到变化。
public void rename(Person person) {
person.name = "李四";
}
Person person = new Person();
person.name = "张三";
rename(person);
System.out.println(person.name); // 李四
但把形参重新赋值为 null 或另一个对象,只会改变形参保存的引用副本。
public void clearReference(Person person) {
person = null;
}
调用后,外部变量仍然指向原对象。
修改数组内容
如果方法的目标是清空原数组,应修改数组元素,而不是只让形参指向新数组。
public void clear(int[] array) {
Arrays.fill(array, 0);
}
如果方法要创建并返回一个新数组,应由调用方接收返回值。
public int[] createEmptyCopy(int[] array) {
return new int[array.length];
}
喜欢的话,留下你的评论吧~