面向对象

发布于 2026-07-29 08:35 更新于 2026-07-29 08:35 3926 字 20 min read ... 访问量

本文系统介绍了面向对象编程的核心概念与实现细节,包括类与对象的定义、成员变量与方法的特性、构造器与this关键字的使用、封装、继承、多态、super与this的调用、JAR包管理以及JVM运行时数据区结构。重点强调了面向对象的“数据与行为封装”思想,通过类的抽象和对象的实例化实现代码的可复用与可维护性,并详细说明了方法调用、参数传递、访问控制、重写规则及内存管理机制,为Java编程提供了完整的理论与实践基础。

面向对象

面向对象思想

面向过程强调解决问题的步骤和执行顺序;面向对象强调把数据和操作数据的行为组织到对象中,再由对象之间协作完成任务。

类是对一类对象共同特征和行为的抽象,对象是类的具体实例。

面向对象编程通常包含以下过程:

  1. 分析业务中的对象及其关系。

  2. 根据对象的共同特征定义类。

  3. 创建对象并设置初始状态。

  4. 调用对象的方法完成业务功能。

类的定义

类使用 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 = "哈工大";
image-001
image-001

当一个对象不再被任何有效引用访问时,它可能成为垃圾回收器的回收对象。垃圾回收的具体时机由 JVM 决定,程序不能假定对象会立刻被回收。

成员变量的默认值

创建对象时,成员变量会获得默认值。

类型默认值
整数类型0
浮点类型0.0
char\u0000
booleanfalse
引用类型null

局部变量没有自动可用的默认值,必须在读取前完成赋值。

对象的内存结构

从概念上看,对象通常包含对象头、实例数据和对齐填充;数组对象还需要记录长度。

image-002
image-002

对象头、引用大小和对齐方式属于 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;

变量 person1person2 保存的是两个不同对象的引用。

构造器

构造器用于在创建对象时完成初始化。构造器名称必须与类名相同,并且没有返回类型。

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 包会被自动导入,例如 StringObjectSystem

不建议在正式项目中使用默认包,因为默认包中的类难以被命名包中的代码规范引用和管理。

继承

子类使用 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();
}

调用重写的实例方法时,运行结果由实际对象类型决定;可以访问哪些成员则由引用的声明类型决定。

多态引用的访问范围

父类引用不能直接调用子类新增的方法。

image-003
image-003
image-004
image-004
image-005
image-005
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 中导出

  1. 打开 FileProject StructureArtifacts

  2. 新建 JAR Artifact 并设置输出内容和位置。

  3. 通过 BuildBuild Artifacts 构建 JAR。

导入第三方 JAR

  1. 在工程中创建 lib 目录并复制 JAR 文件。

  2. 打开 FileProject StructureLibraries

  3. 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);

方法内部修改的是形参副本,不会改变外部变量。

image-006
image-006

引用类型参数

传递对象时,复制的是引用值。形参和实参最初指向同一个对象,因此通过形参修改对象属性,调用方可以看到变化。

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

喜欢的话,留下你的评论吧~

... 访问量
© 2026 跨越星轨的客 @Hoshiumi
Powered by theme astro-koharu · Inspired by Shoka