关于 java: 2. 面向对象编程(OOP)核心概念

一、类与对象

1.1 什么是“类”?

类(Class) 是一种模板或蓝图,描述了一组对象具有的属性(变量)和行为(方法)

可以把类想象成“制造对象的图纸”或“对象的设计说明书”。

类的组成

一个类主要由以下几个部分组成:

  • 属性(字段):用来描述对象的状态(例如名字、年龄)。

  • 方法(函数):用来描述对象的行为(例如说话、行走)。

示例:定义一个类

// 定义一个类
class Person {
    // 属性
    String name;
    int age;

    // 方法
    void sayHello() {
        System.out.println("Hi, I'm " + name + ", and I'm " + age + " years old.");
    }
}

此类定义了一个 Person,它有两个属性 nameage,以及一个方法 sayHello()

1.2 什么是“对象”?

对象(Object) 是类的一个具体实例,是实际在内存中创建的实体。

类就像模具(模板),而对象就像用模具生产出来的“具体物品”。

创建对象(实例化)

public class Main {
    public static void main(String[] args) {
        // 创建对象
        Person p1 = new Person();
        p1.name = "Alice";
        p1.age = 25;
        p1.sayHello(); // 输出:Hi, I'm Alice, and I'm 25 years old.
    }
}

解释:

  • new Person()在内存中开辟空间创建一个 Person 对象。

  • p1是对象的引用变量,指向这个新建的 Person

  • p1.name访问对象的属性。

  • p1.sayHello()调用对象的方法。

1.3 类和对象的内存结构(高级理解)

Person p1 = new Person();

这行代码背后发生了什么?

  • JVM 在堆内存中分配一块空间,用来存储 Person 对象。

  • 类的属性被初始化(name = null, age = 0)。

  • 返回对象地址(引用)赋值给变量 p1(在栈内存中)。

1.4 多个对象是相互独立的

Person p1 = new Person();
Person p2 = new Person();

p1.name = "Alice";
p2.name = "Bob";

System.out.println(p1.name); // Alice
System.out.println(p2.name); // Bob

每个对象都有自己独立的一份属性,互不影响。

1.5 类 vs 对象 的总结对比

比较项类(Class)对象(Object)
定义模板 / 设计图模板创建出的具体实例
是否占内存不直接占用堆内存占用内存(堆)
数量一个类可创建多个对象每个对象都独立存在
操作方式用来创建对象用来存储和操作数据

1.6 类中可以定义什么?

一个类中可以包含以下成员:

成员类型描述
属性(字段)用于存储数据
方法描述类对象的行为
构造函数用于初始化对象
内部类定义在类内部的类
静态块类加载时执行的初始化代码块
代码块每次创建对象时执行的匿名代码块

1.7 示例项目

项目结构

CarDemo/
├── Car.java           // 自定义类
└── Main.java          // 程序主入口

Car.java

// 文件:Car.java
public class Car {
    // 属性(封装)
    private String brand;
    private int speed;

    // 构造函数
    public Car(String brand) {
        this.brand = brand;
        this.speed = 0;
    }

    // 加速方法
    public void accelerate() {
        speed += 10;
        System.out.println(brand + " 加速,当前速度:" + speed + " km/h");
    }

    // 减速方法
    public void decelerate() {
        if (speed >= 10) {
            speed -= 10;
        } else {
            speed = 0;
        }
        System.out.println(brand + " 减速,当前速度:" + speed + " km/h");
    }

    // 显示信息方法
    public void displayStatus() {
        System.out.println("品牌:" + brand + ",当前速度:" + speed + " km/h");
    }
}

Main.java

// 文件:Main.java
public class Main {
    public static void main(String[] args) {
        // 创建两个 Car 对象
        Car car1 = new Car("特斯拉");
        Car car2 = new Car("保时捷");

        // 调用方法
        car1.accelerate();
        car1.accelerate();
        car1.decelerate();
        car1.displayStatus();

        System.out.println("---------------");

        car2.accelerate();
        car2.accelerate();
        car2.accelerate();
        car2.displayStatus();
    }
}

输出示例

特斯拉 加速,当前速度:10 km/h
特斯拉 加速,当前速度:20 km/h
特斯拉 减速,当前速度:10 km/h
品牌:特斯拉,当前速度:10 km/h
---------------
保时捷 加速,当前速度:10 km/h
保时捷 加速,当前速度:20 km/h
保时捷 加速,当前速度:30 km/h
品牌:保时捷,当前速度:30 km/h

项目重点回顾

  • Car 是类,car1car2 是对象(实例)。

  • 每个对象有自己的 brandspeed,互不干扰。

  • 使用了封装(private 属性 + public 方法)保护数据。

  • 使用构造函数初始化品牌名称。


二、构造函数

构造函数(Constructor) 是一种特殊的方法,用于在创建对象时初始化对象的状态(属性)

它在执行 new 类名() 时自动调用,用于给新对象赋初始值

2.1 构造函数的语法格式

class 类名 {
    类名(参数列表) {
        // 初始化代码
    }
}

特点

  • 方法名 必须和类名相同

  • 没有返回值类型(不能写 void/int/...)

  • 只能通过 new 创建对象时调用

  • 可以有多个构造函数(重载)

2.2 构造函数的分类

1)默认构造函数(无参)

没有显式写构造函数时,Java 自动提供一个无参构造函数。

class Person {
    String name;
    int age;
}

// 使用时
Person p = new Person(); // 系统自动调用默认构造函数

2)自定义构造函数(有参)

也可以手动定义构造函数,用来传递参数并初始化属性。

class Person {
    String name;
    int age;

    // 自定义构造函数
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

// 使用:
Person p = new Person("Alice", 25);

2.3 构造函数中的 this

构造函数中经常用 this 来区分成员变量与参数名重名的问题。

Person(String name) {
    this.name = name; // this.name 是成员变量,name 是参数
}

2.4 构造函数重载

可以定义多个构造函数,只要参数不同(数量或类型不同)即可构成重载。

class Person {
    String name;
    int age;

    // 无参构造
    Person() {
        this.name = "未命名";
        this.age = 0;
    }

    // 单参构造
    Person(String name) {
        this.name = name;
        this.age = 0;
    }

    // 双参构造
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

调用:

new Person(); // 使用无参构造
new Person("Bob"); // 使用单参构造
new Person("Alice", 25); // 使用双参构造

2.5 构造函数中的 this() 调用其它构造

可以在一个构造函数中调用另一个构造函数,必须放在第一行。

class Person {
    String name;
    int age;

    Person() {
        this("未命名", 0); // 调用双参构造
    }

    Person(String name) {
        this(name, 0); // 调用双参构造
    }

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

2.6 构造函数和对象创建过程

Person p = new Person("Tom", 20);

这行代码执行了三件事:

  • 分配内存,创建对象(在堆中)

  • 调用构造函数初始化属性

  • 返回对象引用到变量 p(在栈中)

2.7 构造函数 vs 普通方法

区别点构造函数普通方法
名称必须和类名一致任意合法名字
返回值类型没有返回值,不能写 void必须声明返回值类型(可为 void)
调用方式创建对象时自动调用(new手动调用(对象.方法())
功能初始化对象执行具体操作

2.8 示例项目

项目结构

BookConstructorDemo/
├── Book.java      // 自定义类,包含多个构造函数
└── Main.java      // 主类,用来测试

Book.java

// 文件:Book.java
public class Book {
    private String title;
    private String author;
    private double price;

    // 无参构造函数
    public Book() {
        this("未命名", "未知作者", 0.0);  // 调用带参构造函数
        System.out.println("调用了无参构造函数");
    }

    // 单参数构造函数
    public Book(String title) {
        this(title, "未知作者", 0.0);
        System.out.println("调用了单参数构造函数");
    }

    // 双参数构造函数
    public Book(String title, String author) {
        this(title, author, 0.0);
        System.out.println("调用了双参数构造函数");
    }

    // 三参数构造函数
    public Book(String title, String author, double price) {
        this.title = title;
        this.author = author;
        this.price = price;
        System.out.println("调用了三参数构造函数");
    }

    // 展示书籍信息
    public void displayInfo() {
        System.out.println("书名: " + title);
        System.out.println("作者: " + author);
        System.out.println("价格: ¥" + price);
        System.out.println("---------------------");
    }
}

Main.java

// 文件:Main.java
public class Main {
    public static void main(String[] args) {
        // 使用无参构造
        Book book1 = new Book();
        book1.displayInfo();

        // 使用单参数构造
        Book book2 = new Book("Java从入门到精通");
        book2.displayInfo();

        // 使用双参数构造
        Book book3 = new Book("Effective Java", "Joshua Bloch");
        book3.displayInfo();

        // 使用三参数构造
        Book book4 = new Book("算法导论", "Thomas H. Cormen", 128.00);
        book4.displayInfo();
    }
}

输出结果

调用了三参数构造函数
调用了无参构造函数
书名: 未命名
作者: 未知作者
价格: ¥0.0
---------------------
调用了三参数构造函数
调用了单参数构造函数
书名: Java从入门到精通
作者: 未知作者
价格: ¥0.0
---------------------
调用了三参数构造函数
调用了双参数构造函数
书名: Effective Java
作者: Joshua Bloch
价格: ¥0.0
---------------------
调用了三参数构造函数
书名: 算法导论
作者: Thomas H. Cormen
价格: ¥128.0
---------------------

项目要点回顾

技术点应用方式
构造函数重载定义多个构造函数,参数不同
this() 构造函数调用在构造函数中复用逻辑,避免重复代码
属性初始化构造函数中直接赋值
对象创建与测试Main.java 中通过多种方式 new 出对象测试

三、封装

封装(Encapsulation)就是将类的属性(数据)和方法(操作)包装在一起,隐藏内部的实现细节,只对外暴露必要的接口。

通俗理解:封装就像电饭煲,用户只需按按钮,不需要知道里面如何加热——内部复杂逻辑对外部屏蔽

3.1 封装的三个关键点

关键点说明
私有化属性使用 private 修饰成员变量,不允许外部直接访问
公开访问方法使用 public 提供 gettersetter 方法间接访问属性
控制访问权限可以在方法中加入校验逻辑,控制属性的有效性、安全性和可操作性

3.2 封装的 Java 示例

public class Person {
    // 封装:将属性设为 private
    private String name;
    private int age;

    // 提供 public 方法访问 private 属性
    public String getName() {
        return name;
    }

    public void setName(String name) {
        // 可以加入校验逻辑
        if (name == null || name.isEmpty()) {
            System.out.println("名字不能为空!");
            return;
        }
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        // 校验年龄合法性
        if (age < 0 || age > 150) {
            System.out.println("年龄不合法!");
            return;
        }
        this.age = age;
    }
}

3.3 封装的好处

好处说明
增强安全性外部不能随意修改对象的状态(如年龄为负)
提高代码可维护性修改内部实现不影响外部调用
增强可控性和规范性可在 set 方法中加入数据校验或转换逻辑
符合面向对象设计原则隐藏实现细节,提高模块独立性

3.4 使用封装的完整示例

public class Main {
    public static void main(String[] args) {
        Person p = new Person();

        p.setName("张三");
        p.setAge(25);

        System.out.println("姓名:" + p.getName());
        System.out.println("年龄:" + p.getAge());

        p.setAge(-10); // 年龄不合法!
    }
}

输出:

姓名:张三
年龄:25
年龄不合法!

3.5 封装 vs 非封装代码对比

非封装(属性暴露)

public class User {
    public String username;
    public String password;
}

别人可以随便改密码,没有控制逻辑:

user.password = "123";  // 不安全

封装后更安全:

public class User {
    private String password;

    public void setPassword(String pwd) {
        if (pwd.length() < 6) {
            System.out.println("密码太短!");
        } else {
            this.password = pwd;
        }
    }
}

3.6 常见模式:JavaBean 规范

JavaBean 是一种封装模式:类具有私有属性、公共的构造函数、getter/setter 方法。

public class Student {
    private String name;
    private int score;

    public Student() {} // 无参构造

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getScore() { return score; }
    public void setScore(int score) { this.score = score; }
}

3.7 小结

封装是通过“隐藏数据 + 公开方法”的方式来增强对象的安全性与规范性,是 Java 面向对象最基础的设计理念之一。


四、继承

继承(Inheritance)是指一个类(子类)自动拥有另一个类(父类)所有的非私有成员(属性和方法),是代码复用建模抽象的基础。

通俗理解:
子类像儿子,继承父亲的基因(属性和行为),也可以拥有自己特有的能力。

4.1 Java 中继承的语法

class 父类 {
    // 成员
}

class 子类 extends 父类 {
    // 子类扩展的成员
}

4.2 简单例子:动物继承

// 父类:Animal
public class Animal {
    public void eat() {
        System.out.println("动物在吃东西");
    }
}

// 子类:Dog
public class Dog extends Animal {
    public void bark() {
        System.out.println("狗在叫");
    }
}
// 测试类
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();  // 父类方法
        dog.bark(); // 子类方法
    }
}

输出:

动物在吃东西
狗在叫

4.3 继承的特性

特性描述
单继承Java 只支持单继承,一个子类只能继承一个父类
多级继承子类也可以作为父类,形成继承链
方法重写(Override)子类可以重写父类的方法,提供自己的实现
构造器不能继承子类不能继承父类构造器,但可通过 super() 调用

4.4 super 关键字的使用

  • super()调用父类构造器

  • super.属性super.方法()访问父类的成员

public class Animal {
    String name = "动物";

    public void info() {
        System.out.println("我是一个动物");
    }
}

public class Dog extends Animal {
    String name = "狗";

    public void show() {
        System.out.println(super.name);  // 动物
        super.info();                    // 我是一个动物
    }
}

4.5 构造函数的继承流程

子类构造器中默认会先调用父类的无参构造函数

class Animal {
    Animal() {
        System.out.println("Animal 构造函数");
    }
}

class Dog extends Animal {
    Dog() {
        System.out.println("Dog 构造函数");
    }
}

输出:

Animal 构造函数
Dog 构造函数

如果父类没有无参构造,子类必须用 super(...) 显式调用有参构造。

4.6 继承的好处

优点说明
提高代码复用性子类可以复用父类已有的属性和方法
便于维护和扩展修改父类可影响所有子类,修改成本小
支持多态子类对象可以被当作父类使用,增强系统灵活性

4.7 继承的注意点

  • 不能继承 private 成员(但可以通过 public 方法访问)

  • 不能继承构造器

  • Java 不支持多继承(类层面),但可以通过接口解决

  • 子类方法名与父类相同即为“重写”,需注意 @Override 注解规范

4.8 继承完整案例

// 父类
public class Person {
    protected String name;

    public Person(String name) {
        this.name = name;
    }

    public void introduce() {
        System.out.println("我是:" + name);
    }
}

// 子类
public class Student extends Person {
    private String school;

    public Student(String name, String school) {
        super(name);  // 调用父类构造器
        this.school = school;
    }

    // 方法重写
    @Override
    public void introduce() {
        System.out.println("我是学生:" + name + ",就读于:" + school);
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        Student s = new Student("小明", "xx大学");
        s.introduce();  // 调用子类重写的方法
    }
}

输出:

我是学生:小明,就读于:xx大学

4.9 继承 vs 组合(扩展理解)

技术方式描述推荐使用场景
继承is-a 关系,继承父类结构子类完全是一种父类,逻辑紧密
组合has-a 关系,通过引用其他类灵活组合功能,适合低耦合场景

4.10 小结

继承是代码复用和面向对象建模的重要手段,Java 中通过 extends 实现单继承,支持方法重写与父类访问控制,并是多态实现的基础。


五、多态

多态(Polymorphism),意思是“同一个行为在不同对象中表现不同的形式”。

在 Java 中,主要指:父类引用指向子类对象,通过父类引用调用的方法,在运行时决定执行哪个版本。

通俗类比:“动物都会叫”,但猫是“喵”、狗是“汪”——同一个行为(叫),具体对象不同,表现不同

5.1 实现多态的三个条件

条件说明
1. 继承关系子类继承父类
2. 方法重写(Override)子类重写父类中的方法(方法名、参数、返回值都一样)
3. 父类引用指向子类对象使用 父类 类型 = new 子类();

5.2 Java 示例:展示多态

class Animal {
    public void sound() {
        System.out.println("动物在叫");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("狗叫:汪汪!");
    }
}

class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("猫叫:喵喵!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal a1 = new Dog();  // 父类引用指向子类对象
        Animal a2 = new Cat();

        a1.sound();  // 输出:狗叫:汪汪!
        a2.sound();  // 输出:猫叫:喵喵!
    }
}

虽然变量是 Animal 类型,但调用的是 Dog Cat 各自的 sound() 方法。这就是多态

5.3 多态的原理:动态绑定(Dynamic Binding)

编译时 vs 运行时:

阶段绑定行为
编译阶段根据变量的引用类型(父类)决定是否允许调用某方法
运行阶段根据变量的实际对象类型决定执行哪一个方法(子类)

Java 的多态方法调用是 动态绑定 的,而非 static 方法,它们是 静态绑定

5.4 多态的好处

优势举例
提高程序扩展性新增一个 Bird 类继承 Animal,原有代码无需改动
提高代码可维护性写一个方法操作父类对象,可以支持所有子类
实现统一编程模型List 接口支持 ArrayListLinkedList,都是多态体现

5.5 多态的注意事项

1)多态只适用于方法重写,不适用于属性

class A { public int x = 1; }
class B extends A { public int x = 2; }
A obj = new B();
System.out.println(obj.x); // 输出 1,不是 2

2)子类新增方法,父类引用不能调用

A obj = new B();
obj.bOnlyMethod(); //  编译报错

3)如果要访问子类独有成员,需要强制类型转换(类型检查推荐用 instanceof

5.6 实际应用:数组或列表中使用多态

Animal[] animals = { new Dog(), new Cat(), new Dog() };

for (Animal animal : animals) {
    animal.sound();  // 调用的是各自子类的 sound 方法
}

5.7 演示统一接口调用的例子(非常常见)

public class AnimalOperator {
    public void makeSound(Animal a) {
        a.sound();
    }
}

public class Main {
    public static void main(String[] args) {
        AnimalOperator op = new AnimalOperator();
        op.makeSound(new Dog());  // 输出:狗叫:汪汪!
        op.makeSound(new Cat());  // 输出:猫叫:喵喵!
    }
}

这就是**“编程面向父类或接口,运行靠子类”**的核心思想!

5.8 小结

多态是一种接口统一、实现多样的机制,是面向对象三大特性(封装、继承、多态)中最核心的表现,依赖方法重写 + 父类引用指向子类对象 + 动态绑定来实现。


六、抽象类

抽象类(Abstract Class)是包含一个或多个抽象方法的类,它不能被实例化,只能被继承
抽象类是“不完全的类”,它用来作为其他类的模板或父类

通俗理解:抽象类就像“规则说明书”或“蓝图”,子类要按照它来实现自己的具体功能。

6.1 语法格式

// 声明一个抽象类
abstract class Animal {
    abstract void sound(); // 抽象方法:没有方法体
}
// 子类必须重写抽象方法,否则也必须声明为 abstract
class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("狗叫:汪汪");
    }
}

6.2 抽象类的特性总结

特性说明
使用 abstract 关键字声明类abstract class 类名 {}
抽象类中可以有 抽象方法和普通方法既可以定义方法体,也可以定义规范
抽象类不能实例化只能通过子类实例化
抽象方法没有方法体(结尾无 {}子类必须实现所有未实现的抽象方法,否则子类也必须是抽象类
构造器是允许的抽象类可以有构造函数供子类使用(不能直接 new
可以有成员变量也可以定义 static 方法、常量等

6.3 例子:抽象动物类

abstract class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }

    // 抽象方法(不写方法体)
    public abstract void makeSound();

    // 普通方法
    public void sleep() {
        System.out.println(name + " 睡觉了");
    }
}

class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + ":喵喵~");
    }
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + ":汪汪!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal a1 = new Cat("小猫");
        Animal a2 = new Dog("小狗");

        a1.makeSound(); // 多态调用
        a2.makeSound();

        a1.sleep();
        a2.sleep();
    }
}

输出:

小猫:喵喵~
小狗:汪汪!
小猫 睡觉了
小狗 睡觉了

6.4 抽象类 vs 普通类

项目抽象类普通类
是否能实例化 不能 可以
是否包含抽象方法 可以 不可以
目的提供“规范+通用功能”的父类模板具体实现类
使用场景设计层级模型、作为父类定义行为规范实际功能开发类

6.5 抽象类 vs 接口

项目抽象类接口 (interface)
是否能有成员变量 有,也可有构造函数变量必须是 public static final 常量
是否能有构造器 可以 不可以
是否能包含方法体 可以(普通方法) Java 8 起支持 default、static 方法
是否支持多继承 不支持类的多继承 接口支持多继承
使用 extends / implements子类使用 extends 继承抽象类类使用 implements 实现接口

6.6 实际应用场景

  • 当想为一组类定义通用的行为模板(有些实现留给子类),使用抽象类;

  • 比如:

    • Shape(图形)是抽象类,具体有 CircleRectangle

    • Animal 是抽象类,具体有 CatDog

    • HttpHandler 是抽象类,具体有 JsonHandlerHtmlHandler

6.7 小结

抽象类是定义通用“行为模板”的不完整类,只能被继承,不能被实例化。子类必须实现抽象方法,从而实现“规范与实现分离”。


七、接口

接口(Interface)是一种特殊的“抽象类型”,用来定义类应当具有哪些方法(规范),而不关心具体实现

接口 = 纯规范、无状态、无行为逻辑。

7.1 接口的语法结构

public interface 接口名 {
    // 默认都是 public abstract
    void 方法名();

    // Java 8+ 可以有 default 方法和 static 方法
    default void 默认方法() {
        System.out.println("默认实现");
    }

    static void 静态方法() {
        System.out.println("静态方法");
    }
}

类实现接口:

class 类名 implements 接口名 {
    @Override
    public void 方法名() {
        // 必须实现接口中的所有方法
    }
}

7.2 接口和类的区别

项目接口(interface)抽象类(abstract class)
是否可实例化 不能 不能
是否能有普通方法 Java 8 以后可以有 default 和 static 可以
是否有构造函数 不可以 可以
是否支持多继承 可以实现多个接口 只能继承一个抽象类
成员变量默认是 public static final 常量可定义任意修饰符的成员变量

7.3 接口示例:USB 接口

// 定义一个 USB 接口
interface USB {
    void connect();  // 抽象方法
    void disconnect();
}

// 实现类:U盘
class FlashDrive implements USB {
    public void connect() {
        System.out.println("U盘已连接");
    }

    public void disconnect() {
        System.out.println("U盘已断开");
    }
}

// 实现类:键盘
class Keyboard implements USB {
    public void connect() {
        System.out.println("键盘已连接");
    }

    public void disconnect() {
        System.out.println("键盘已断开");
    }
}
public class Main {
    public static void main(String[] args) {
        USB usb1 = new FlashDrive();
        USB usb2 = new Keyboard();

        usb1.connect();      // 输出:U盘已连接
        usb2.connect();      // 输出:键盘已连接

        usb1.disconnect();   // 输出:U盘已断开
        usb2.disconnect();   // 输出:键盘已断开
    }
}

接口统一了调用方式,增强了代码扩展性和灵活性。

7.4 接口的高级特性(Java 8+)

1)默认方法 default

interface A {
    default void print() {
        System.out.println("A 的默认方法");
    }
}

用途:给接口添加新方法而不破坏原有实现类。

2)静态方法

interface A {
    static void printInfo() {
        System.out.println("接口的静态方法");
    }
}

用途:工具方法,例如 Comparator.comparing()

7.5 接口的实际作用

功能描述
统一规范类似“协议”、“合同”,定义类必须实现的方法
解耦合编程面向接口而非具体类
多态父接口引用指向子类对象(即接口的多态)
支持多实现一个类可以实现多个接口,弥补 Java 单继承的不足
插件式开发 / 策略模式实现类可以自由替换,主程序逻辑无需修改

7.6 一个综合案例:动物行为接口

interface Speakable {
    void speak();
}

interface Runnable {
    void run();
}

class Dog implements Speakable, Runnable {
    public void speak() {
        System.out.println("狗叫:汪汪");
    }

    public void run() {
        System.out.println("狗跑得飞快");
    }
}
public class Main {
    public static void main(String[] args) {
        Speakable s = new Dog();
        s.speak(); // 多态调用

        Runnable r = new Dog();
        r.run();   // 多态调用
    }
}

7.7 小结

接口是“纯抽象”的规范,用于定义一组行为规范,不关心具体实现。接口是实现多态、解耦、模块化的关键武器。


八、this

在 Java 中,this 是一个 当前对象的引用,它在 类的内部方法或构造方法中使用,用于指代当前对象本身

 换句话说,this 指的是 哪个对象调用了这个方法this 就代表谁。

8.1 this 的常见用途

1)区分成员变量和局部变量同名冲突

class Person {
    String name;

    Person(String name) {
        this.name = name; // this.name 是成员变量,name 是参数
    }
}

如果不用 this.name = name;编译器会认为 name = name; 是给自己赋值,没有效果。

2)在类的方法中引用当前对象

public void printName() {
    System.out.println("我的名字是:" + this.name);
}

虽然 name 不加 this 也可以访问,但加 this 更清晰地表示它是“当前对象的属性”。

3)在构造方法中调用另一个构造方法(必须放在第一行)

class Student {
    String name;
    int age;

    Student(String name) {
        this(name, 0); // 调用另一个构造方法
    }

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

用途:构造器重载时避免重复代码

4)作为参数传递当前对象

class Person {
    String name;

    Person(String name) {
        this.name = name;
    }

    void introduce() {
        Helper.printPerson(this); // 传递当前对象
    }
}

class Helper {
    static void printPerson(Person p) {
        System.out.println("我是:" + p.name);
    }
}

用途:传递当前对象给其他方法/类使用

5)返回当前对象(实现链式调用)

class Counter {
    int count = 0;

    public Counter add() {
        this.count++;
        return this; // 返回当前对象
    }
}

public class Main {
    public static void main(String[] args) {
        Counter c = new Counter();
        c.add().add().add(); // 链式调用
        System.out.println(c.count); // 输出:3
    }
}

用途:链式调用模式(Builder模式等)

8.2 this 总结:

用途记忆口诀
区分成员变量和参数this.属性 = 参数
在方法中引用当前对象谁调用我,this 就是谁
构造器中调用构造器this(...) 仅限第一行
传参或返回当前对象this 可用于链式编程和对象传参

8.3 实际例子

class Book {
    String title;

    Book(String title) {
        this.title = title;
    }

    void printTitle() {
        System.out.println("书名:" + this.title);
    }

    Book rename(String newTitle) {
        this.title = newTitle;
        return this; // 实现链式调用
    }
}

public class Main {
    public static void main(String[] args) {
        Book b = new Book("Java入门");
        b.printTitle();  // 输出:Java入门
        b.rename("Java进阶").printTitle(); // 输出:Java进阶
    }
}

8.4 this 与静态(static)对比

关键字指代是否属于对象
this当前对象的引用 属于对象
static类本身相关(与对象无关) 属于类

静态方法中不能使用 this,因为没有“当前对象”。

8.5 关于 this 的常见问题

  • this 可以用于静态方法中吗?
     不可以,因为静态方法不属于任何对象。

  • 构造函数中 this(...) 的位置有限制吗?
     有限制,必须是构造函数的第一行。

  • this 是指当前类吗?
     是当前类的对象实例,不是类本身。

8.6 小结

this 是当前对象的引用,用于访问自己的属性、调用构造器或方法,并支持链式调用与对象传递,是面向对象编程中理解“对象自我”的核心。


九、super

super 是父类对象的引用,表示“当前对象的父类部分”。

通俗理解:如果 this 是“我自己”,那 super 就是“我的父类”。

9.1 super 的三大主要用途

1)调用父类的构造函数

class Parent {
    Parent(String msg) {
        System.out.println("父类构造函数:" + msg);
    }
}

class Child extends Parent {
    Child() {
        super("来自子类的调用"); // 调用父类的构造函数
        System.out.println("子类构造函数");
    }
}

注意:super(...) 必须是子类构造器中的第一行!

2)访问父类的成员变量(当子类成员变量与父类同名时)

class Parent {
    int x = 10;
}

class Child extends Parent {
    int x = 20;

    void print() {
        System.out.println("子类 x: " + x);
        System.out.println("父类 x: " + super.x); // 访问父类的 x
    }
}

3)调用父类被重写的方法

class Animal {
    void speak() {
        System.out.println("动物叫");
    }
}

class Dog extends Animal {
    void speak() {
        System.out.println("狗叫");
    }

    void show() {
        this.speak();      // 调用子类方法
        super.speak();     // 调用父类被重写的方法
    }
}

9.2 super vs this 区别对比

特性thissuper
指代对象当前类的对象当前对象的父类部分
调构造函数调用本类的其他构造器(this(...)调用父类的构造器(super(...)
调方法/变量当前类的方法或变量父类的方法或变量
使用限制可在非静态方法中使用可在子类中使用,super(...) 必须在首行

9.3 完整示例:演示 super 的三种用途

class Animal {
    String name = "动物";

    Animal() {
        System.out.println("Animal 构造函数");
    }

    void speak() {
        System.out.println("Animal 叫");
    }
}

class Dog extends Animal {
    String name = "狗";

    Dog() {
        super(); // 调用父类构造函数
        System.out.println("Dog 构造函数");
    }

    void printNames() {
        System.out.println("子类 name: " + name);
        System.out.println("父类 name: " + super.name); // 访问父类变量
    }

    void speak() {
        System.out.println("Dog 叫");
    }

    void showSpeak() {
        this.speak();    // 子类 speak
        super.speak();   // 父类 speak
    }
}

public class Main {
    public static void main(String[] args) {
        Dog d = new Dog();
        d.printNames();
        d.showSpeak();
    }
}

输出结果:

Animal 构造函数
Dog 构造函数
子类 name: 狗
父类 name: 动物
Dog 叫
Animal 叫

9.4 super 使用注意事项

注意点说明
super(...) 必须在构造器第一行不能和 this(...) 同时出现
父类无无参构造器,必须显式调用如果父类没有无参构造器,子类必须使用 super(...)
static 方法中不能用 super因为 static 属于类而不属于对象

9.5 小结

super 是子类对“父类部分”的访问桥梁,常用于调用父类构造函数、属性或被重写的方法,体现了继承的本质与多态的延伸。


十、final

final 表示“最终的、不可更改的”

在 Java 中,final 可以用来修饰:

使用位置含义
变量这个值只能被赋值一次,之后不能修改
方法这个方法不能被子类重写
这个类不能被继承

10.1 final 修饰变量

1)修饰基本类型变量

final int a = 10;
a = 20; // 编译错误:final 变量不能修改

一旦赋值,就不能再次赋值。

2)修饰引用类型变量

final Person p = new Person("Tom");
p.name = "Jerry"; // 可以修改对象的属性
p = new Person("Alice"); // 不可以修改引用本身

结论:final 引用不能换地址,但对象内容可以改。

3)用于常量定义(常与 static 一起使用)

public static final double PI = 3.14159;

推荐命名:常量一般用 全大写 + 下划线 命名。

10.2 final 修饰方法

class Animal {
    final void breathe() {
        System.out.println("动物呼吸");
    }
}

class Dog extends Animal {
    // void breathe() { ... } // 编译错误:不能重写 final 方法
}

用于阻止方法在子类中被重写(override),保护核心逻辑不被篡改

10.3 final 修饰类

final class Constants {
    public static final double PI = 3.14;
}

// class MyConstants extends Constants {} // 编译错误,final 类不能继承

用于创建“工具类”、“常量类”、“不可被扩展的类”。

10.4 为什么使用 final

用途作用/意义
修饰变量定义常量,防止被误修改
修饰方法防止被重写,确保逻辑安全
修饰类防止被继承,增强封装与安全
性能优化编译器能做更多优化(例如:方法内联)
线程安全多线程中确保对象引用不可变,提高安全性

10.5 关于 final 的几个注意点

场景是否允许
final 局部变量必须初始化 是(否则编译错误)
final 引用对象内容能变? 可以变(引用不可变)
final 方法能重载吗? 可以(不能重写)
构造方法能用 final 修饰? 不可以
abstract 和 final 同时用? 冲突:抽象是为了重写,final 是禁止重写

10.6 final 方法 vs 抽象方法(abstract)

特性final 方法abstract 方法
是否必须实现 已实现 未实现,必须重写
是否能被重写 不允许 子类必须重写
用途保护实现不被子类更改让子类自定义具体实现

10.7 小结

final 是 Java 中用于声明“不可变”的关键字,修饰变量可变为常量,修饰方法可防止重写,修饰类可防止继承,是编写安全、健壮、可维护代码的重要工具。


十一、static

static 表示“静态”,它修饰的成员属于类本身,而不是某个对象。

通俗理解:static 的成员是“所有对象共享的”,只会存在一份,不依赖实例。

11.1 static 的用途总结

用法位置作用
修饰变量类变量(共享变量)
修饰方法类方法(无需创建对象调用)
修饰代码块静态代码块(类加载时执行)
修饰内部类静态内部类(不依赖外部类对象)

11.2 static 修饰变量:静态变量

class Student {
    static String school = "xx大学"; // 所有学生共用
    String name;

    Student(String name) {
        this.name = name;
    }

    void print() {
        System.out.println(name + " 来自 " + school);
    }
}

特点:

  • 所有对象共享,类加载时创建。

  • 一处修改,处处生效。

  • 推荐使用类名访问:Student.school

11.3 static 修饰方法:静态方法

class MathUtil {
    static int add(int a, int b) {
        return a + b;
    }
}

// 使用
int result = MathUtil.add(3, 5); // 不需要 new 对象

特点:

  • 不依赖对象,可以直接通过类名调用。

  • 不能使用 this 和 super

  • 只能访问其他 static 成员。

11.4 static 代码块:静态初始化块

class Demo {
    static {
        System.out.println("类加载时执行:初始化静态资源");
    }
}

特点:

  • 类第一次被加载时执行一次。

  • 用于初始化静态变量或复杂资源(如数据库连接池等)。

11.5 static 修饰内部类:静态内部类

class Outer {
    static class Inner {
        void hello() {
            System.out.println("静态内部类方法");
        }
    }
}

// 使用
Outer.Inner inner = new Outer.Inner();
inner.hello();

特点:

  • 不依赖外部类对象,可以直接通过外部类名访问。

  • 与外部类的实例无关。

11.6 static 的限制与注意事项

限制原因/说明
static 方法不能用 this / super因为 static 不属于对象
static 方法只能访问 static 成员因为它可能在对象创建前被调用
static 不能修饰局部变量局部变量是栈上动态创建的
构造方法不能是 static 的因为构造函数是用于构造对象的

11.7 常见使用场景总结

场景用法示例
工具类方法Math.random()Arrays.sort()
常量定义public static final double PI
单例模式静态变量保存单例实例
计数器/ID生成器静态变量自增
静态内部类Map.EntryThread.State

11.8 对比:实例变量 vs 静态变量

对比点实例变量(无 static)静态变量(有 static)
所属对象每个对象一份所有对象共享一份
生命周期对象创建时生成类加载时生成
访问方式对象.变量类名.变量
常见使用场景描述对象特征描述公共信息

11.9 综合示例代码

class Counter {
    static int count = 0; // 静态变量:所有对象共享

    Counter() {
        count++; // 每创建一个对象,count +1
        System.out.println("当前对象数:" + count);
    }

    static void showCount() {
        System.out.println("总共创建了 " + count + " 个对象");
    }
}

public class Main {
    public static void main(String[] args) {
        new Counter();
        new Counter();
        Counter.showCount(); // 调用静态方法
    }
}

11.10 小结

static 关键字使变量或方法变成“属于类而非对象”,用于实现共享数据、工具方法、静态初始化等,是面向对象中“类层面编程”的核心工具。


十二、inner class(内部类)

内部类(Inner Class)是定义在另一个类内部的类。
它相对于外部类来说是“内部作用域”的类,具有更强的封装性和更紧密的逻辑关联。

12.1 内部类的分类(4种)

类型定义位置是否 static依赖外部类对象
成员内部类类中,方法外
静态内部类类中,方法外,加 static 修饰
局部内部类方法内部
匿名内部类方法中,用于快速实现接口/父类

12.2 成员内部类(最基本形式)

class Outer {
    private String msg = "Hello";

    class Inner {
        void show() {
            System.out.println("Inner: " + msg); // 访问外部类私有成员
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner(); // 注意创建方式
        inner.show();
    }
}

特点:

  • 可以访问外部类的所有成员(包括 private

  • 依赖外部类实例来创建(必须先创建 Outer 对象)

12.3 静态内部类(加 static)

class Outer {
    static class Inner {
        void show() {
            System.out.println("Static inner class");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer.Inner(); // 不需要外部类对象
        inner.show();
    }
}

特点:

  • 可以独立创建,不依赖外部类实例

  • 只能访问外部类的静态成员

  • 常用于工具类、常量类、封装模块

12.4 局部内部类(定义在方法中)

class Outer {
    void outerMethod() {
        final String name = "局部变量";

        class Inner {
            void print() {
                System.out.println("Inner uses: " + name);
            }
        }

        Inner inner = new Inner();
        inner.print();
    }
}

特点:

  • 类定义在方法中,作用域仅限该方法

  • 只能访问方法中的 final 或 “等同 final” 的局部变量

  • 常用于:线程任务、事件监听、数据封装等

12.5 匿名内部类(最常见)

abstract class Animal {
    abstract void sound();
}

public class Test {
    public static void main(String[] args) {
        Animal cat = new Animal() {
            void sound() {
                System.out.println("Meow~");
            }
        };

        cat.sound();
    }
}

特点:

  • 没有名字的类,定义和使用合并为一行

  • 通常用于:快速实现接口或抽象类

  • 常见于:线程、事件监听、回调函数

new Thread(new Runnable() {
    public void run() {
        System.out.println("线程启动");
    }
}).start();

12.6 使用场景总结

内部类类型常见用途
成员内部类封装外部类内部逻辑,表示强耦合关系
静态内部类工具类、Builder 模式、数据结构嵌套类
局部内部类临时封装类、回调或任务
匿名内部类一次性接口/抽象类实现、事件监听器

12.7 注意事项与限制

  • 内部类可以访问外部类的所有成员(包括私有)

  • 外部类访问内部类成员,需先创建内部类对象

  • 匿名类不能定义构造器,只能用代码块初始化

  • Java 8 后局部内部类可访问“等同 final”的变量

12.8 小结

内部类是类中类的实现形式,用于封装紧密相关的逻辑,可以增强代码结构的清晰性与安全性,在 GUI、回调、工具封装等场景非常常见。


十三、访问控制权限(private/protected/public/default)

访问控制权限(Access Modifiers)是 Java 中 封装性(Encapsulation)的核心体现,用于控制类的成员(变量、方法、构造器等)在不同作用范围内的访问权限。掌握它是写出安全、清晰、模块化代码的基础。

13.1 四种访问权限概览

访问修饰符同类(自己)同包(包内其他类)子类(包内外)外部类(不同包)
public
protected✘(非子类不可)
默认(default)
private

注意:**默认访问权限(default)**是“无修饰符”时的权限,也叫包访问权限。

13.2 各个访问修饰符

1)public —— 公开的,任何地方都能访问

public class Person {
    public String name;

    public void sayHi() {
        System.out.println("Hi, I'm " + name);
    }
}
  • 用于公共 API(如库函数、工具类)

  • 类、变量、方法、构造器都可以是 public

  • 类只能是 public 或 default,不能是 private/protected

2)private —— 私有的,只能在类内部访问

public class Account {
    private double balance; // 外部无法访问

    public void deposit(double amount) {
        if (amount > 0) balance += amount;
    }
}
  • 常用于封装属性,实现安全访问

  • 强制外部通过 getter/setter 间接访问

  • 不能修饰类(外部类)

3)protected —— 受保护的:包内 + 子类可访问

class Animal {
    protected void eat() {
        System.out.println("吃东西");
    }
}

class Dog extends Animal {
    void bark() {
        eat(); // 子类可访问
    }
}
  • 用于允许子类访问但隐藏给其他类

  • 继承结构中非常重要

  • 包外的非子类无法访问

4)default(无修饰符)—— 包内可访问

class Teacher { // 没有修饰符,即 default
    void teach() {
        System.out.println("授课中...");
    }
}
  • 只能在同一个包中的类之间访问

  • 适合将相关类组织在一个包内进行模块化

13.3 应用建议

目标建议修饰符
封装属性,控制访问private + getter/setter
公开使用的类或方法public
仅子类需要访问的方法protected
只在本包内使用的工具类方法default

13.4 访问控制常见例子

封装属性:

public class Student {
    private int age;

    public int getAge() { return age; }
    public void setAge(int age) {
        if (age >= 0) this.age = age;
    }
}

模块化包设计:

// 包内可见
class DaoHelper {
    // default 权限,仅限同包使用
}

13.5 编译器错误示例

package a;
class A {
    private void test() {}
}

package b;
class B {
    void call() {
        new A().test(); // 编译错误:test() 是 private
    }
}

13.6 小结

访问修饰符决定了类的“成员”能被谁看到、谁使用,是封装与模块化的基础。在设计类时,应合理控制暴露范围,隐藏实现细节,开放必要接口。

权限从高到低:

public > protected > default > private

十四、方法重写 vs 重载

方法重载(Overloading) vs 方法重写(Overriding)

比较项方法重载(Overloading)方法重写(Overriding)
所属关系同一个类内部父类与子类之间
方法名必须相同必须相同
参数列表必须不同(个数、类型、顺序)必须相同
返回类型可以不同必须相同(或协变返回类型)
修饰符无特别限制子类方法权限必须 ≥ 父类(不能更低)
static/final可用于 static/final 方法不能重写 final 或 static 方法
编译时 or 运行时编译时决定(静态绑定)运行时决定(动态绑定,多态)

14.1 方法重载(Overloading)—— 同一个类中参数不同

特点:

  • 方法名相同,参数不同

  • 和返回值无关

  • 编译器在编译期根据参数决定调用哪个方法(静态绑定)

示例:

public class Calculator {
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }

    int add(int a, int b, int c) {
        return a + b + c;
    }
}

应用场景:

设计统一操作(如打印、加法等)时,支持不同参数类型

14.2 方法重写(Overriding)—— 父类子类之间

特点:

  • 方法签名(方法名+参数)必须与父类一致

  • 返回值必须相同(或是父类返回类型的子类,即协变返回类型)

  • 子类不能缩小访问权限(如父类是 public,子类也必须是 public

  • 运行时决定调用哪个方法(多态/动态绑定)

示例:

class Animal {
    public void sound() {
        System.out.println("Some sound");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Bark");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog(); // 多态
        a.sound(); // 输出:Bark
    }
}

应用场景:

  • 父类定义规范(如抽象类或接口),子类根据需要实现

  • 用于实现多态(同一个接口,多种行为)

14.3 @Override 注解的作用

@Override
public void sound() { ... }

编译器检查是否真的在“重写”

  • 若方法名拼错或参数不对,会报错

  • 强烈建议加上

14.4 不能被重写的方法

  • final 方法:不能被重写

  • static 方法:静态方法不参与多态,属于类本身

  • private 方法:不是继承的,子类不能访问或重写

class A {
    private void hello() {}
    static void greet() {}
    final void bye() {}
}

14.5 重载 vs 重写对比

  • 重载是 一个类 方法名一样、参数不一样

  • 重写是 继承关系中 方法名、参数都一样,改写行为

  • 重载是 编译期绑定(静态)

  • 重写是 运行期绑定(动态)

class A {
    public void print(int a) {
        System.out.println("A:int");
    }

    public void print(String s) {
        System.out.println("A:String");
    }
}

class B extends A {
    @Override
    public void print(String s) {
        System.out.println("B:String");
    }
}

public class Test {
    public static void main(String[] args) {
        A obj = new B();
        obj.print(10);         // 输出?
        obj.print("hello");    // 输出?
    }
}

输出:

A:int       // 调用 A 中的重载方法
B:String    // 调用 B 重写后的方法(动态绑定)

14.6 小结

重载是方法名相同参数不同、编译期决定;重写是继承中行为替换、运行时决定,是实现多态的关键。


十五、多态的原理:方法调用的“动态绑定”

Java 中的 多态(Polymorphism) 是面向对象的三大特性之一,它允许「父类引用指向子类对象」,并在运行时调用真正子类中实现的方法。这一机制背后的原理就是 —— 方法调用的动态绑定(Dynamic Binding)

动态绑定(Dynamic Binding)指的是:在运行时(而非编译时)决定调用哪个方法的机制

也叫 后期绑定(Late Binding)

举例说明:

class Animal {
    void speak() {
        System.out.println("Animal speaks");
    }
}

class Dog extends Animal {
    void speak() {
        System.out.println("Dog barks");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog(); // 父类引用指向子类对象
        a.speak();            // 输出: Dog barks(运行时决定)
    }
}
  • 编译时只知道 aAnimal 类型

  • 运行时发现 a 实际是 Dog 类型 → 调用的是 Dog.speak() 方法

这就是 多态的体现,调用哪个方法,不是编译器决定的,而是 JVM 在运行时通过动态绑定来决定的

15.1 为什么需要动态绑定?

因为:

Java 支持多态 —— 一个方法可以根据对象的真实类型来表现不同行为。

也就是说:

  • 编译器只检查方法是否存在于父类或接口

  • 真正要调用谁,由对象的实际类型在运行时决定

15.2 工作原理底层解析

当我们写下:

Animal a = new Dog();
a.speak();

背后发生了什么?

  • 编译期:检查 Animal 类中是否有 speak() 方法 —— 有,编译通过。

  • 运行期

    • JVM 发现 a 实际上是 Dog 对象。

    • JVM 查找 Dog 类是否覆盖了 speak() 方法。

    • 如果有,则绑定并调用 Dog.speak()

实现机制:

JVM 会为每个类维护一个叫作「虚方法表」(vtable)的数据结构,里面记录了类中所有可被重写的方法。

  • 当通过引用调用方法时,JVM 会查 vtable,找到真正该调用的方法地址。

15.3 哪些方法支持动态绑定?

方法类型动态绑定?
static 方法 支持
private 方法 不支持,编译期静态绑定(不参与继承)
final 方法  不支持,编译期静态绑定(不能重写)
static 方法  不支持,编译期静态绑定(属于类,不属于对象)

15.4 静态绑定(Static Binding)对比

class Demo {
    static void hello() {
        System.out.println("Static Hello");
    }
}

public class Test {
    public static void main(String[] args) {
        Demo d = null;
        d.hello();  // 静态方法,编译期已决定,输出: Static Hello
    }
}

即使 d 是 null,仍然能调用,因为编译器已经绑定好了。

15.5 实际例子:多态 + 动态绑定

abstract class Shape {
    abstract void draw();
}

class Circle extends Shape {
    void draw() {
        System.out.println("画圆");
    }
}

class Square extends Shape {
    void draw() {
        System.out.println("画正方形");
    }
}

public class Test {
    public static void main(String[] args) {
        Shape s1 = new Circle();
        Shape s2 = new Square();
        s1.draw(); // 运行时决定,输出:画圆
        s2.draw(); // 输出:画正方形
    }
}

15.6 小结

多态的核心就是“动态绑定”方法调用在运行时根据对象真实类型来决定执行哪一个方法,是实现“同一接口,不同行为”的关键

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值