Java 面向对象(全程详细)

一、简介

面向对象(Object Oriented)是一种软件开发方法、编程范式。 面向对象是相对于面向过程来讲的,面向对象是指将事物相关的数据和方法组织为一个整体来看待,这种编程方式相较于面向过程,更贴近人的思维习惯。

面向对象的三大基本特征是:封装、继承、多态

  • 封装:隐藏了类的内部实现,可以在不影响使用者的前提下改变类的内部结构,同时保护了数据。
  • 继承:产生了子类和父类的关系,子类可以复用或扩展父类的代码,另外继承是实现多态的前提。
  • 多态:指的是一个行为有多种形态,让对象在运行期自动决定执行何种行为,通过将子类对象引用赋值给父类对象来实现动态方法调用。

二、面向过程和面向对象

在编程语言中,面向过程和面向对象是两种不同的编程范式,在 Java 等编程语言中均有广泛应用,它们具有以下特点:

2.1 面向过程

面向过程编程的核心关注点在实现功能的步骤上,强调的是解决问题的步骤和顺序,把程序看作是一系列函数的集合,通过依次调用这些函数来完成任务。


注重程序的执行流程,按照顺序一步一步地执行代码。数据和操作数据的函数通常是分离的,数据在不同的函数之间传递。对于简单的任务,编程效率可能较高,因为代码直接围绕问题的解决步骤展开。


2.2 面向对象

面向对象编程的核心关注点在实现功能需要哪些对象的参与,将数据和操作数据的方法封装在对象中。把程序看作是一组相互交互的对象的集合,通过对象之间的消息传递来完成任务。

强调对象的封装性、继承性和多态性。

  • 封装性使得对象的内部状态和行为被隐藏起来,外部只能通过特定的接口进行访问,提高了代码的安全性和可维护性。
  • 继承性允许子类继承父类的属性和方法,实现代码复用。
  • 多态性使得同一操作可以作用于不同类型的对象,产生不同的行为,提高了代码的灵活性和可扩展性。

2.3 总结


代码组织方式

面向过程编程通常以函数为单位组织代码,函数之间的调用关系较为明确。
面向对象编程以类为单位组织代码,类之间通过继承、组合等关系相互关联。

可维护性

面向对象编程由于封装性和继承性等特点,使得代码的可维护性更高。当需要修改代码时,只需要在相应的类中进行修改,而不会影响其他不相关的部分。
面向过程编程中,如果一个函数的修改可能会影响到其他多个函数,维护起来相对困难。

可扩展性

面向对象编程的继承性和多态性使得代码的可扩展性更强。可以通过继承现有类并添加新的功能来扩展程序,而不需要修改已有代码。
面向过程编程在扩展功能时,可能需要修改多个函数,容易引入新的错误。

适用场景

面向过程编程适用于简单的、流程较为固定的任务,例如,操作系统内核、嵌入式系统等。
面向对象编程适用于复杂的、需要不断扩展和维护的系统,例如,企业级应用、游戏开发等。


三、类和对象

在编程语言中,类和对象是面向对象编程的基本组成单元,它们是抽象和具体的关系,具体如下:

3.1 类

类指的是在现实世界中,将事物与事物之间的共同的状态和行为提取出来,形成了一个模版,这个模版就叫做类。

// 定义学生类
public class Student {
    // 成员属性
    public String name;

    // 成员方法
    public void printName() {
        System.out.println("姓名:" + this.name);
    }
}

3.2 对象

对象是通过类的实例化创建出来的,对象是类的具体个体,对象又叫做实例。

public class Student {
    // 成员属性
    public String name;

    // 成员方法
    public void printName() {
        System.out.println("姓名:" + this.name);
    }

    // 测试
    public static void main(String[] args) {
        // 创建学生类
        Student student = new Student();

        // 调用属性
        student.name = "张三";

        // 调用方法
        student.printName();
    }
}

运行结果:

姓名:张三

四、扩展内容

4.1 成员变量

定义在方法的外部,类的内部,成员变量的作用范围是整个类,不需要初始值,在创建对象时,存储在堆内存当中。


4.2 局部变量

定义在方法的内部或者某一个语句块的内部,局部变量的作用范围仅限于方法内或者语句块内,必须有初始值,否则编译器报错,存储在栈内存中。


4.3 成员方法

成员方法也被称为方法,定义在类的内部,可以将实现的细节封装起来,方便调用,提高代码的复用性,成员方法存储在方法区中。


4.4 this

代表的是一个引用,该引用保存的是被引用对象的内存地址,通过这个内存地址可以访问成员变量和成员方法,在大部分的情况下都可以省略,但是在用于区分局部变量和成员变量时不能省略,不然编译器报错。

public class Student {
    //成员变量
    public String name;
    public int age;

    public Student(String name, int age) {
        //s1调用了构造方法,this存的是s1的引用
        this.name = name;
        this.age = age;
    }

    // 测试
    public static void main(String[] args) {
        // 创建对象
        Student s1 = new Student("张三", 18);
        System.out.println("姓名:" + s1.name + "\n年龄:" + s1.age);
    }
}

运行结果:

姓名:张三
年龄:18
  • 还可以用来调用同一类中的其他构造方法,可以实现代码复用,只能在构造方法的第一行
public class Student {
    //成员变量
    public String name;

    // 无参构造
    public Student() {
        System.out.println("无参构造执行了");
    }

    // 有参构造
    public Student(String name) {
        // 调用无参构造
        this();
        this.name = name;
    }

    // 测试
    public static void main(String[] args) {
        // 创建对象,这里是用有参构造初始化对象的
        Student s1 = new Student("张三");
        System.out.println("姓名:" + s1.name);
    }
}

运行结果:

无参构造执行了
姓名:张三

4.5 构造方法

构造方法用于创建对象并初始化对象的状态。当创建对象时,如果没有有参构造方法的话,默认是直接调用对象的无参构造方法,并且会将该对象的成员属性初始化,具体特点如下:

  • 方法名必须和类名相同;
  • 没有返回值;
  • 不能被 static 修饰;
  • 可以具有不同的访问修饰符;
  • 可以方法重载;
  • 当一个类中没有写构造方法时,系统会默认给该类一个默认的无参构造方法。当自己定义构造方法后默认的构造方法就不存在了。
public class Student {
    // 成员属性
    public String name;
    public int age;

    // 无参构造方法
    public Student() {
        System.out.println("------无参构造方法------");
        name = "张三";
        age = 18;

    }

    // 有参构造方法
    public Student(String name, int age) {
        System.out.println("------有参构造方法------");
        this.name = name;
        this.age = age;
    }

    // 测试
    public static void main(String[] args) {
        //创建对象
        Student s1 = new Student();
        System.out.println("姓名:" + s1.name + "\n年龄:" + s1.age);

        Student s2 = new Student("李四", 20);
        System.out.println("姓名:" + s2.name + "\n年龄:" + s2.age);
    }
}

运行结果:

------无参构造方法------
姓名:张三
年龄:18
------有参构造方法------
姓名:李四
年龄:20

五、封装

封装是 Java 面向对象编程的三大特性之一,它强调将数据和操作数据的方法封装在一个类中,对外隐藏内部实现细节。私有化属性是实现封装的关键步骤,同时限制这些属性的访问权限,只通过特定的方法来进行访问和修改。

5.1 实现步骤

  1. 将对象的属性私有化,隐藏内部的实现细节,防止外部类恶意的访问对象的属性;
  2. 由于上述属性被私有化了,所以必须提供一个对外的方法,来修改这些被私有化的属性,又因为是方法,所以可以在方法内添加访问逻辑和数据验证,这样即可以保证数据的完整性,也可以降低代码耦合度。

5.2 样例代码

对于下面的样例代码,希望大家认真思考,想想有封装和没有封装的区别,以及如何理解保证数据的完整性、降低代码耦合度。

public class Person {
    // 第一步:将属性私有化
    private String name;
    private int age;

    // 第二步:提供对外的方法,可以按 alt + insert 快速生成 getter 和 setter 方法
    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        // 年龄要在 0 - 120 岁区间
        if (age > 0 && age <= 120) {
            this.age = age;
        } else {
            System.out.println("年龄不合法");
        }
    }
	
	// 测试
    public static void main(String[] args) {
        // 创建对象p1
        Person p1 = new Person();
        p1.setName("张三");
        p1.setAge(20);
        System.out.println("姓名:" + p1.getName() + "\n年龄:" + p1.getAge());

        System.out.println("==========");
        
        // 创建对象p2
        Person p2 = new Person();
        p2.setName("李四");
        p2.setAge(-20);
        System.out.println("姓名:" + p2.getName() + "\n年龄:" + p2.getAge());
    }
}

运行结果:

姓名:张三
年龄:20
==========
年龄不合法
姓名:李四
年龄:0

六、继承

继承是 Java 面向对象编程的三大特性之一,继承是一种机制,允许一个类(子类)继承另一个类(父类)的属性和方法,并且子类可以在父类的基础上进行扩展和特化,添加新的属性和方法,或者重写父类的方法以实现不同的行为。在 Java 中,通过 extends 关键字,实现继承关系,采用的是单继承。而在 C++ 中,采用的是多继承。

6.1 优缺点


继承的优点

  • 代码复用:子类可以继承父类的属性和方法,避免了重复编写相同的代码。这大大提高了开发效率,减少了代码量。

  • 层次结构清晰:继承可以建立清晰的类层次结构,使代码更易于理解和维护。相关的类可以组织在一起,形成一个有层次的体系。

  • 多态性:继承是实现多态的前提之一,通过继承,不同的子类对象可以对同一方法有不同的实现,从而实现多态性。

  • 扩展性强:子类可以在父类的基础上进行扩展,添加新的属性和方法,满足特定的需求。这种扩展性使得代码能够适应不断变化的需求。

继承的缺点

  • 复杂性:当继承层次过多时,代码的复杂性会增加,理解和维护起来会变得困难。而且,深层次的继承关系可能会导致性能问题。

  • 限制了灵活性:一旦一个类继承了另一个类,就受到了父类的约束。在某些情况下,这可能会限制子类的灵活性,使得子类难以进行独立的扩展和修改。

  • 使用场景:并不是所有的类之间的关系都适合用继承来表示。


6.2 this 和 super

super 关键字体现了 Java 中类的继承层次结构。它使得子类能够在自身的实现中,有选择地调用父类的功能,同时进行扩展和定制。

关键字对象不同
this指代当前对象,可以使用 this 来访问该对象的成员变量和成员方法。
super指代父类对象,在子类中使用 super 可以访问父类的成员变量、成员方法和调用父类的构造方法。
关键字用法不同
this主要用于区分局部变量和成员变量,当方法中的局部变量与成员变量同名时。
super主要用于在子类中调用父类的构造方法,访问父类被重写的方法,访问父类的成员变量。

总之,this 和 super 在 Java 中分别用于指代当前对象和父类对象,它们在不同的场景下发挥着重要的作用,帮助开发者实现面向对象编程中的封装、继承等特性。

⚠️需要注意的是 super 并不是严格意义上指代父类对象的,它只是父类对象存储空间的一个标识。由于它不能像使用对象引用一样,来独立地调用父类中所有的方法或访问所有的成员,只能在特定的上下文中,如调用父类构造方法、访问被重写的方法或父类的成员变量。但为了方便理解,你可以认为 super 指代的是父类对象,因为大多情况,使用 super 的效果类似于在操作父类对象,但要清楚它的本质是在子类中用于特定目的的关键字,与真正的父类对象引用有很大的区别。


6.3 访问变量特点

在 Java 中,访问成员变量都是遵循就近原则,谁离得近就调用谁,如果子类没有定义 name 属性,就会调用父类的 name,以此类推。

// 父类:Animal
public class Animal {
    public String name = "动物";
}
// 子类:Dog,通过 extends 关键字,实现继承关系
public class Dog extends Animal{
    public String name = "中华田园犬";

    public void show() {
        name = "小土狗";
        System.out.println(name);
    }
}
// 创建测试类:Test,测试结果
public class Test {
    public static void main(String[] args) {
    	// 创建对象
        Dog dog = new Dog();
        // 调用对象的方法
        dog.show();
    }
}

运行结果:

小土狗

6.4 访问成员方法特点

在 Java 中,访问成员方法都是遵循就近原则,谁离得近就调用谁,如果子类没有定义 show 方法,就会调用父类的 show,以此类推。

// 父类:Animal
public class Animal {

    public void show() {
        System.out.println("Animal 父类的 show() 执行了...");
    }
}
// 子类:Dog
public class Dog extends Animal {

    public void show() {
        System.out.println("Dog 子类的 show() 方法执行了...");
    }
}
// 测试类:Test
public class Test {
    public static void main(String[] args) {
        // 创建对象
        Dog d1 = new Dog();
        // 调用对象的成员方法
        d1.show();
    }
}

运行结果:

Dog 子类的 show() 方法执行了...

6.5 访问构造方法特点

在子类中,所有的构造方法都会隐式通过 super() 访问父类中无参的构造方法,并且必须在构造方法的第一行。因为子类可能会访问父类的属性,如果在父类还没有初始化就去访问的话,编译器就会报错,所以在子类初始化前必须先完成父类属性的初始化,这也是为什么要在子类构造方法第一行去调用父类的无参构造,目的就是为了初始化父类的数据。

// 父类:Animal
public class Animal {

    public Animal() {
        System.out.println("Animal 父类的无参构造被调用了...");
    }
}
// 子类:Dog,通过 extends 关键字,实现继承关系
public class Dog extends Animal {
    public String name;

    public Dog() {
        System.out.println("Dog 子类的无参方法被调用了...");
    }

    public Dog(String name) {
        System.out.println("Dog 子类的有参方法被调用了...");
    }
}

// 测试类:Test,测试结果
public class Test {
    public static void main(String[] args) {
    	// 调用子类的无参构造创建对象
        Dog d1 = new Dog();

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

		// 调用子类的有参构造创建对象
        Dog d2 = new Dog("小土狗");
    }
}

运行结果:

Animal 父类的无参构造被调用了...
Dog 子类的无参方法被调用了...
==========
Animal 父类的无参构造被调用了...
Dog 子类的有参方法被调用了...

6.6 方法重写

当子类中出现了和父类中一模一样的方法签名(返回值、方法名、参数列表),用于当子类需要父类的方法时,而父类方法不满足子类的需求,就可以在子类中使用方法重写,对父类的方法进行扩展。

⚠️@Override:这是一个用于标识当前方法是被重写的方法的一个注解,可以帮助我们检查重写方法的方法声明的正确性。

// 父类:Animal
public class Animal {

    public void show() {
        System.out.println("我是一只动物");
    }
}
// 子类:Dog
public class Dog extends Animal {

    @Override
    public void show() {
        System.out.println("我是一只小狗");
    }
}
// 测试类:Test
public class Test {
    public static void main(String[] args) {
        // 创建对象
        Dog d1 = new Dog();
        // 调用对象的成员方法
        d1.show();
    }
}

运行结果:

我是一只小狗

⚠️注意:私有方法不能被重写,子类方法访问权限不能更低比父类的访问权限更低。


6.7 继承中的注意事项

单一职责原则:在设计继承关系时,应遵循单一职责原则,即一个类应该只有一个引起它变化的原因。避免让一个类承担过多的职责,导致继承层次过于复杂。

避免过度继承:过度使用继承可能会导致代码的复杂性增加,并且可能会出现继承层次过深、难以理解和维护的问题。在某些情况下,可以考虑使用组合或接口来替代继承。

方法重写的规则:当子类重写父类的方法时,需要遵循方法签名一致,并且父类方法不能被私有化、子类方法的访问修饰符不能比父类方法更严格。

构造方法的调用顺序:在创建子类对象时,会先调用父类的构造方法,然后再调用子类的构造方法。这确保了父类的属性得到正确的初始化。


七、多态

多态是 Java 面向对象编程的三大特性之一,即同一操作对于不同的对象可以有不同的表现形式,简单来说,就是允许不同类型的对象对同一消息作出不同的响应。

7.1 优缺点

多态的优点

  • 提高代码的可扩展性和可维护性:当需要增加新的子类时,只需要实现相应的方法,而不需要修改现有的代码。这使得代码更加灵活,能够适应不断变化的需求。

  • 增强代码的通用性:可以使用父类类型的引用来处理不同子类的对象,提高了代码的通用性,这样可以减少代码的重复,提高开发效率。

  • 降低代码的耦合度:多态使得代码依赖于抽象而不是具体的实现,减少了不同部分代码之间的依赖关系。这有助于提高代码的可维护性和可测试性。

  • 实现开闭原则:开闭原则是面向对象编程中的一个重要原则,即对扩展开放,对修改关闭。多态可以帮助实现开闭原则,因为可以通过添加新的子类来扩展功能,而不需要修改现有的代码。

多态的缺点

  • 运行时类型检查可能导致性能开销:由于多态是在运行时根据对象的实际类型来决定调用哪个方法,这可能会导致一些运行时类型检查的开销。特别是在频繁调用多态方法的情况下,可能会对性能产生一定的影响。不过,在大多数情况下,这种性能开销是可以忽略不计的,而且当前版本的 Java 虚拟机也在不断优化多态的实现,以减少性能损失。

  • 代码理解和调试可能变得复杂:多态使得代码的行为更加动态和灵活,但也可能使得代码的理解和调试变得更加困难。特别是当代码中存在复杂的多态关系时,可能需要花费更多的时间来理解代码的执行流程。在调试多态代码时,需要注意对象的实际类型和方法的调用关系,以确保代码的正确性。

  • 可能导致意外的行为:如果不恰当地使用多态,可能会导致意外的行为。例如,如果子类没有正确地重写父类的方法,或者在多态方法中没有正确地处理不同的对象类型,可能会导致程序出现错误或异常。


7.2 实现前提

实现多态的前提是,类与类之间存在继承关系,然后子类重写父类的方法,最后父类引用指向子类对象,当通过父类引用调用这个被重写的方法时,实际执行的是子类中的方法实现,这就是多态


7.3 访问成员变量

在多态中访问成员变量遵循编译看左边,运行也看左边。当代码被编译时,会看父类中有没有这个变量,如果有则编译成功,没有则编译失败,运行时,实际返回的是父类中的成员变量。

// 测试类
public class Test {
    public static void main(String[] args) {
        // 多态方式创建对象
        Animal d = new Dog();
        Animal c = new Cat();
        // 访问成员变量
        System.out.println(d.name);
        System.out.println(c.name);
    }
}

// 父类:Animal
class Animal {
    String name = "动物";
}

// 子类:Dog
class Dog extends Animal {
    String name = "小狗";
}

// 子类:Cat
class Cat extends Animal {
    String name = "小猫";
}

运行结果:

动物
动物

7.4 访问成员方法

在多态中访问成员方法遵循编译看左边,运行也看右边。当代码被编译时,会看父类中有没有这个方法,如果有则编译成功,没有则编译失败,运行时,实际返回的是子类中的成员方法。

// 测试类
public class Test {
    public static void main(String[] args) {
        // 多态方式创建对象
        Animal d = new Dog();
        Animal c = new Cat();
        // 访问成员方法
        d.show();
        c.show();

    }
}

// 父类:Animal
class Animal {
    public void show() {
        System.out.println("我是一只动物");
    }
}

// 子类:Dog
class Dog extends Animal {
    @Override
    public void show() {
        System.out.println("我是一只小狗");
    }
}

// 子类:Cat
class Cat extends Animal {
    @Override
    public void show() {
        System.out.println("我是一只小猫");
    }
}

运行结果:

我是一只小狗
我是一只小猫

7.5 多态的转型

向上转型

向上转型指的是子类对象赋值给父类引用,通过向上转型,可以实现多态。在运行时,根据实际对象的类型来决定调用哪个方法,向上转型是安全的,因为子类总是包含父类的所有成员和方法。但是,这种转型方式,只能访问父类中已有的成员,不能直接访问子类特有的成员。

// 测试类
public class Test {
    public static void main(String[] args) {
        // 多态方式创建对象
        Animal d = new Dog();
        Animal c = new Cat();
        // 访问成员变量
        System.out.println(d.name);
        System.out.println(c.name);
    }
}

// 父类:Animal
class Animal {
    String name = "动物";
}

// 子类:Dog
class Dog extends Animal {
    String name = "小狗";
}

// 子类:Cat
class Cat extends Animal {
    String name = "小猫";
}

运行结果:

动物
动物

向下转型

向下转型指的是父类引用转换为子类引用,在某些情况下,我们需要调用子类特有的方法或访问子类特有的属性,这时就需要进行向下转型。但是向下转型是不安全的,因为父类引用不一定总是指向子类对象。如果进行了错误的向下转型,会在运行时抛出 ClassCastException 异常,在进行向下转型之前,应该先进行类型检查,以确保父类引用实际指向的是子类对象,使用 instanceof 运算符进行类型检查。

// 测试类
public class Test {
    public static void main(String[] args) {
        // 多态方式创建对象
        Animal d = new Dog();
        Animal c = new Cat();
        // 访问子类特有成员方法
        if (d instanceof Dog) {
            Dog dog = (Dog) d;
            dog.eat();
        } else if (c instanceof Cat) {
            Cat cat = (Cat) c;
            cat.eat();
        }
    }
}

// 父类:Animal
class Animal {
    public void show() {
        System.out.println("我是一只动物");
    }
}

// 子类:Dog
class Dog extends Animal {
    @Override
    public void show() {
        System.out.println("我是一只小狗");
    }

    public void eat() {
        System.out.println("狗狗吃骨头");
    }
}

// 子类:Cat
class Cat extends Animal {
    @Override
    public void show() {
        System.out.println("我是一只小猫");
    }

    public void eat() {
        System.out.println("猫猫吃小鱼");
    }
}

运行结果:

狗狗吃骨头

在 Java 14 中,instanceof 有一个新特性,建议简化上述代码,具体如下:

// 测试类
public class Test {
    public static void main(String[] args) {
        // 多态方式创建对象
        Animal d = new Dog();
        Animal c = new Cat();
        // 访问子类特有成员方法
        if (d instanceof Dog dog) {
            dog.eat();
        } else if (c instanceof Cat cat) {
            cat.eat();
        }
    }
}

// 父类:Animal
class Animal {
    public void show() {
        System.out.println("我是一只动物");
    }
}

// 子类:Dog
class Dog extends Animal {
    @Override
    public void show() {
        System.out.println("我是一只小狗");
    }

    public void eat() {
        System.out.println("狗狗吃骨头");
    }
}

// 子类:Cat
class Cat extends Animal {
    @Override
    public void show() {
        System.out.println("我是一只小猫");
    }

    public void eat() {
        System.out.println("猫猫吃小鱼");
    }
}

运行结果:

狗狗吃骨头

7.6 多态的绑定

静态绑定

静态多态也叫编译时多态 ,通常指的是方法重载(Overload),在编译时就能够确定要调用的具体方法。

public class Test {
    public int sum(int a, int b) {
        return a + b;
    }

    public double sum(double a, double b) {
        return a + b;
    }

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

动态绑定

动态多态也叫运行时多态,通常指的是方法重写(Override),在运行时根据对象的实际类型来决定调用哪个具体的方法实现,通过方法重写和父类引用指向子类对象来实现。

// 父类:Animal
class Animal {
    public void show() {
        System.out.println("Animal 父类的 show() 执行了...");
    }
}

// 子类:Dog
class Dog extends Animal {
    @Override
    public void show() {
        System.out.println("Dog 子类的 show() 执行了...");
    }
}

// 子类:Cat
class Cat extends Animal {
    @Override
    public void show() {
        System.out.println("Cat 子类的 show() 执行了...");
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        // 创建Dog实现类
        Animal dog = new Dog();
        dog.show();

        // 创建Cat实现类
        Animal cat = new Cat();
        cat.show();
    }
}

运行结果:

Dog 子类的 show() 执行了...
Cat 子类的 show() 执行了...

尽管在编译时,dog 和 cat 的引用类型都是 Animal ,但在运行时,实际执行的 show() 方法分别是 Dog 类和 Cat 类中实现的方法,这就是动态多态的体现。


  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野生派蒙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值