Java与生活:3.OOP下半部分

目录

2-1-2 继承 (extends)

展示了 继承extends)的基本用法。继承是面向对象编程中的一个重要概念,它使得子类能够继承父类的属性和方法,从而实现代码复用和扩展。

关键概念

  1. 继承
    • 子类可以继承父类的属性和方法,使用 extends 关键字来定义继承关系。
    • 子类可以访问父类的 publicprotected 成员,但不能直接访问父类的 private 成员。
  2. 父类(Super Class)
    • 在这个例子中,Animal 类是父类,它包含了 namevarietyagefood 等属性以及 eatsleep 等方法。
  3. 子类(Sub Class)
    • DogsCats 类是子类,它们通过 extends 关键字继承了 Animal 类的所有公共和保护成员。这样,子类就可以复用父类的代码,并且可以根据需要扩展或覆盖父类的方法。

代码分析

1. Animal 类(父类)
@Getter
@Setter
@ToString
public class Animal {
    private String name;
    private String variety;
    private int age;
    private String food;
    private static String plot = "NanG";  // 静态变量,所有实例共享

    public static String getPlot() {
        return plot;
    }

    public Animal() { }

    public Animal(String name, String variety, int age, String food) {
        this.name = name;
        this.variety = variety;
        this.age = age;
        this.food = food;
    }

    public static void goplot() {
        System.out.println("所有狗都进了小区");
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        if (age < 0 || age > 100) {
            this.age = 0;
        } else {
            this.age = age;
        }
    }

    public void eat() {
        System.out.println(this.name + " 正在吃饭");
    }

    public void sleep() {
        System.out.println(this.name + " 正在睡觉");
    }
}
  • 构造方法
    • Animal 类有无参构造方法和有参构造方法,允许你创建 Animal 类的对象并设置其属性。
  • 静态变量和静态方法
    • plot 是一个静态变量,所有 Animal 类的实例共享这个值。goplot 是一个静态方法,它输出所有狗都进了小区的消息。
  • 普通方法
    • eatsleep 是实例方法,可以在实例对象上调用,打印出狗的行为。
2. DogsCats 类(子类)
// Dogs 子类
public class Dogs extends Animal {
    // 继承 Animal 类的属性和方法
}
// Cats 子类
public class Cats extends Animal {
    // 继承 Animal 类的属性和方法
}
  • 继承 Animal

    • DogsCats 都继承了 Animal 类,因此它们自动获得了 Animal 类的所有属性(如 nameagefood)和方法(如 eatsleep)。
  • 无新功能

    • 在这个例子中,DogsCats 类没有定义新的属性或方法,它们完全继承了 Animal 类的行为。
3. Application 类(测试)
import com.nix.demo.Cats;
import com.nix.demo.Dogs;

public class Application {
    public static void main(String[] args) {
        // 创建 Dog 类实例
        Dogs zhangDog = new Dogs();
        zhangDog.setName("Jerry");
        zhangDog.setAge(2);
        System.out.println("zhangDog = " + zhangDog);

        // 创建 Cat 类实例
        Cats liCat = new Cats();
        liCat.setName("Tom");
        liCat.setAge(1);
        System.out.println("liCat = " + liCat);

        // 调用继承自 Animal 类的方法
        zhangDog.eat();
        liCat.sleep();
        
        // 调用静态方法 goplot
        Cats.goplot();

        // 访问静态变量 plot
        System.out.println("Cat.getPlot() = " + Cats.getPlot());
    }
}
  • 创建子类对象
    • 创建了 Dogs 类和 Cats 类的实例,并设置它们的属性,如 nameage
  • 继承方法的调用
    • zhangDog.eat()liCat.sleep() 调用了从 Animal 类继承来的 eatsleep 方法。
  • 调用静态方法和访问静态变量
    • Cats.goplot() 调用了 Animal 类中的静态方法 goplot()
    • Cats.getPlot() 输出静态变量 plot 的值。

输出结果

zhangDog = Dogs(name=Jerry, variety=null, age=2, food=null)
liCat = Cats(name=Tom, variety=null, age=1, food=null)
Jerry 正在吃饭
Tom 正在睡觉
所有狗都进了小区
Cat.getPlot() = NanG

总结

  • 继承DogsCats 继承了 Animal 类,复用了 Animal 类的属性和方法。
  • 静态变量和方法plotgoplot() 是静态的,所有 Animal 类及其子类的实例共享这些静态成员。
  • 方法重用:子类无需重新实现 Animal 类中的 eatsleep 方法,直接通过继承使用。
  • 静态方法和变量访问:即使子类 Cats 继承自 Animal,它也可以直接访问静态方法和变量。

2-2-2 饿狼传说之多层继承

提到的概念是 多层继承,以及 Java 不支持多重继承 的特性。在 Java 中,一个类只能继承一个父类,但它可以通过 多层继承 的方式间接继承多个类的属性和方法。

1. 单继承 vs 多继承

  • 单继承:Java 是一个单继承的语言,即一个子类只能有一个直接父类。你不能写类似于 class A extends B, C 这样的代码。
  • 多层继承:虽然 Java 不支持多重继承,但可以通过多层继承来实现类似的功能。这意味着一个类可以继承另一个类,而这个父类本身又继承自另一个类,从而间接获得多个父类的属性和方法。

2. 多层继承的示例

在你的代码中,Dogs 类继承自 Animal 类,而 Animal 类又是你的父类。因此,Dogs 类通过继承 Animal 间接继承了 Animal 的所有成员。

示例解析

1. Animal 类(父类)
public class Animal {
    private String name;
    private String variety;
    private int age;
    private String food;
    
    public void eat() {
        System.out.println(this.name + " 正在吃饭");
    }
    
    public void sleep() {
        System.out.println(this.name + " 正在睡觉");
    }
    
    // 其他成员
}
2. Dogs 类(子类)
public class Dogs extends Animal {
    // Dogs 类继承了 Animal 类的所有成员,包括 eat() 和 sleep()
}

在这个例子中,Dogs 类直接继承了 Animal 类,因此 Dogs 类将会拥有 Animal 类中定义的属性和方法,像是 nameage 以及 eat()sleep() 方法。

3. Cats 类(另一个子类)
public class Cats extends Animal {
    // 同样继承了 Animal 类的所有成员
}

这里 Cats 类也继承了 Animal 类,因此它也具备了 Animal 类中的所有成员。

3. 多层继承的效果

public class Application {
    public static void main(String[] args) {
        // Dogs 类对象
        Dogs zhangDog = new Dogs();
        zhangDog.eat();  // 继承自 Animal 类

        // Cats 类对象
        Cats liCat = new Cats();
        liCat.sleep();  // 继承自 Animal 类
    }
}

4. 为什么 Java 不支持多重继承?

  • 复杂性:多重继承会导致继承链复杂,如果父类之间有相同的方法或者属性,可能会导致二义性问题(即无法确定使用哪个父类的方法)。

  • 钻石问题(Diamond Problem):如果类 C 继承了类 A 和类 B,而 AB 都有相同的方法,类 C 无法明确选择调用 A 还是 B 中的那个方法。

    举个例子:

    class A {
        void speak() {
            System.out.println("A speaks");
        }
    }
    
    class B extends A {
        void speak() {
            System.out.println("B speaks");
        }
    }
    
    class C extends A, B {  // 不能直接写多继承
        // 这里就会出现二义性,C 不知道该调用 A 还是 B 的 speak 方法
    }
    

5. 如何解决多继承问题?

Java 通过 接口interface)来解决多继承的问题。一个类可以实现多个接口,这样可以在不引入多重继承复杂性的情况下,获取多个不同类的功能。

通过接口实现多继承
interface CanFly {
    void fly();
}

interface CanSwim {
    void swim();
}

class Duck implements CanFly, CanSwim {
    public void fly() {
        System.out.println("Duck is flying");
    }

    public void swim() {
        System.out.println("Duck is swimming");
    }
}

在这个例子中,Duck 类同时实现了 CanFlyCanSwim 接口,获得了两个接口的功能。

总结

  1. 单继承:Java 只允许一个类继承另一个类,但通过多层继承,子类可以间接继承多个类的功能。
  2. 多继承问题:Java 不支持类的多继承,避免了复杂性和钻石问题。
  3. 接口:如果需要多继承的功能,可以通过实现多个接口来实现。接口为类提供了多种行为的能力。

2-2-3 方式的重写与 2-2-4 super啃老

现在正在介绍的是 方法重写(Override)和 super关键字。这些是继承中的重要概念。

1. 方法重写(Method Overriding)

方法重写是子类对父类方法的重新实现。当子类需要改变或增强父类的方法功能时,可以通过方法重写来实现。方法重写有以下特点:

  • 子类的方法名、参数列表和返回类型必须和父类被重写的方法完全相同。
  • 重写方法时,子类可以提供不同的实现。

在你的例子中,Dogs 类和 Cats 类都重写了父类 Animal 中的 barking 方法。

@Override
public void barking() {
    System.out.println("汪汪汪~~~");
}
  • Dogs 类中的 barking 方法重写了父类 Animal 中的 barking 方法,打印的是 “汪汪汪~~~”。
  • Cats 类中的 barking 方法重写了父类 Animal 中的 barking 方法,打印的是 “喵喵喵~~~”。

这样,通过重写,子类可以根据需要改变父类的方法行为。

2. super 关键字(啃老)

super 关键字用于访问父类的成员(属性、方法)或调用父类的构造方法。在子类中,可以通过 super 来调用父类被重写的方法。如果你在子类的重写方法中调用 super,就会执行父类的原始方法,而不是子类的重写版本。

示例:

@Override
public void barking() {
    super.barking();  // 调用父类的 barking 方法
    System.out.println("汪汪汪~~~");  // 然后再执行子类的 barking 方法
}
  • 如果不调用 super.barking(),那么 DogsCats 类中的 barking 方法会完全替代父类 Animal 的实现。
  • 如果调用 super.barking(),则在子类方法中先执行父类的 barking 方法,然后再执行子类的实现。

在你的 DogsCats 类中,barking 方法如果注释掉了 super.barking(),则不调用父类的 barking 方法。否则,会先调用父类的 barking() 方法。

3. 代码分析

你在代码中的重写示例如下:

// Dogs 类重写了 barking 方法
@Override
public void barking() {
    // 如果注释掉 super.barking(),则不会调用父类的方法
    // super.barking();
    System.out.println("汪汪汪~~~");
}

// Cats 类重写了 barking 方法
@Override
public void barking() {
    // 如果注释掉 super.barking(),则不会调用父类的方法
    // super.barking();
    System.out.println("喵喵喵~~~");
}
4. 测试代码(Application 类)
public class Application {
    public static void main(String[] args) {
        // 创建 Dogs 对象
        Dogs zhangDog = new Dogs();
        zhangDog.setName("Jerry");
        zhangDog.setAge(1);
        zhangDog.setFood("狗粮");

        // 创建 Cats 对象
        Cats liCat = new Cats();
        liCat.setName("Tom");
        liCat.setAge(3);
        liCat.setFood("猫粮");

        // 调用 eat 和 sleep 方法
        zhangDog.eat();
        liCat.sleep();

        // 调用 barking 方法
        zhangDog.barking();  // 输出: 汪汪汪~~~
        liCat.barking();     // 输出: 喵喵喵~~~
    }
}
  • Dogs 类 会输出 “汪汪汪~~~”,这是重写的 barking 方法。
  • Cats 类 会输出 “喵喵喵~~~”,这是重写的 barking 方法。
5. 总结
  • 方法重写:子类可以重写父类的方法,以实现不同的行为。
  • super 关键字super 用于调用父类的方法或构造函数,常常用于在子类中调用父类的行为,或在重写方法时使用。
  • 重写的应用:通过重写,可以根据实际需要在子类中定义不同的实现,改变父类的默认行为。

2-2-5 启发性构造方法与继承

在 Java 中,继承是面向对象编程的一个重要特性,但继承的同时有一些关键点需要注意,比如父类构造函数的继承和调用。Java 是 单继承 的语言,子类不能直接继承父类的构造函数,但子类可以通过 super 关键字调用父类的构造函数。

1. 父类的构造方法

在代码中,Animal 类提供了多个构造函数(重载的构造函数)。这些构造函数允许初始化动物的不同属性。

public Animal(String name, String variety, int age, String food) {
    this.name = name;
    this.variety = variety;
    this.age = age;
    this.food = food;
}

public Animal(String name, int age) {
    this.name = name;
    this.age = age;
}
  • Animal 类提供了两个构造函数,一个用于初始化所有属性,另一个只需要 nameage
2. 子类调用父类的构造方法

在你的 Dogs 类中,子类调用了父类的构造函数:

public Dogs(String name, String variety, int age, String food) {
    super(name, variety, age, food);  // 调用父类构造函数
}

public Dogs(String name, int age) {
    super(name, age);  // 调用父类构造函数
}
  • Dogs 类使用 super 关键字调用父类 Animal 的构造方法。通过 super(),子类可以初始化父类的属性。
  • 如果你不显式调用父类构造方法,Java 编译器会隐式地调用父类的无参构造函数(super()),这就是为什么你在 Dogs 类中可以有一个无参构造函数(public Dogs())。
3. 子类重写父类的方法

你重写了 barking 方法,子类 DogsCats 都有不同的实现。

@Override
public void barking() {
    System.out.println("汪汪汪~~~");  // Dogs 类中重写 barking 方法
}
@Override
public void barking() {
    super.barking();  // 调用父类的 barking 方法
    System.out.println("喵喵喵~~~");  // Cats 类中重写 barking 方法
}
  • Dogs 类中,barking 方法完全重写了父类的方法,输出 “汪汪汪~~~”。
  • Cats 类中,barking 方法调用了父类 Animalbarking 方法(通过 super.barking()),然后输出 “喵喵喵~~~”。
4. 总结 - 启发性构造方法与继承
  • 父类构造方法:子类不能继承父类的构造方法,但可以通过 super 调用父类的构造方法。这是为了确保父类的初始化可以在子类中进行。
  • 构造函数重载:通过多个构造函数,子类可以根据需要调用父类不同的构造方法。
  • 方法重写:子类可以重写父类的方法,提供自己特有的实现,或者通过 super 调用父类的方法并扩展它。
5. 改进建议

你的代码已经实现了继承和方法重写的核心概念。为了增强代码的可读性和维护性,建议考虑以下几点:

  • 确保每个类的职责单一。例如,可以将 barking 方法抽象到一个更适合的名字,避免使用过于通用的名称。
  • 如果有多个构造函数,确保每个构造函数都有明确的用途并加以注释,帮助其他开发者理解为何需要这些构造函数。

完整代码示例

package com.nix.demo;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Animal {
    private String name;
    private String variety;
    private int age;
    private String food;

    // 默认构造函数
    public Animal() {}

    // 参数化构造函数
    public Animal(String name, String variety, int age, String food) {
        this.name = name;
        this.variety = variety;
        this.age = age;
        this.food = food;
    }

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

    public void eat() {
        System.out.println(this.name + " 正在吃饭..正在吃:" + this.food);
    }

    public void sleep() {
        System.out.println(this.name + " 正在睡觉");
    }

    public void barking() {
        System.out.println("我是你爹 父类");
    }
}

public class Dogs extends Animal {
    // 默认构造函数
    public Dogs() {}

    public Dogs(String name, String variety, int age, String food) {
        super(name, variety, age, food);
    }

    public Dogs(String name, int age) {
        super(name, age);
    }

    @Override
    public void barking() {
        System.out.println("汪汪汪~~~");
    }
}

public class Cats extends Animal {
    @Override
    public void barking() {
        super.barking();  // 调用父类的 barking 方法
        System.out.println("喵喵喵~~~");
    }
}

import com.nix.demo.Animal;
import com.nix.demo.Cats;
import com.nix.demo.Dogs;

public class Application {
    public static void main(String[] args) {
        Dogs zhangDog = new Dogs("Jerry", "null", 1, "狗粮");
        Cats liCat = new Cats();
        liCat.setName("Tom");
        liCat.setAge(3);
        liCat.setFood("猫粮");

        zhangDog.eat();
        liCat.sleep();

        zhangDog.barking();
        liCat.barking();
    }
}

在这个完整的例子中:

  • Dogs 类和 Cats 类都继承自 Animal 类,并重写了 barking 方法。
  • 通过 super 调用父类的构造方法和方法。

你可以根据需要继续扩展和修改类的实现,增加新的特性或行为。


2-2-6 final 关键字

在 Java 中,final 关键字用于表示“最终”的意思,用来修饰类、方法、变量等,禁止它们被修改、继承或重写。

1. final 修饰类

当一个类被声明为 final 时,它不能被继承。也就是说,无法创建该类的子类。例如:

public final class Labrador extends Dogs {
    @Override
    public boolean isGuideBlindness() {
        return true;
    }
}
  • Labrador 类被声明为 final,这意味着它不能再被其他类继承。尝试继承 Labrador 会导致编译错误。
  • 在你的例子中,Dogs 类是可以继承的,但 Labrador 类一旦定义为 final,任何其他类都不能继承它。
2. final 修饰方法

当一个方法被声明为 final 时,表示该方法不能被子类重写。这是为了防止子类改变父类的方法实现。例如:

public final boolean isGuideBlindness() {
    return false;
}
  • 如果在 Dogs 类中的 isGuideBlindness 方法被声明为 final,则无法在任何子类(如 Labrador)中重写它。这确保了该方法的行为在所有子类中都保持一致。
3. final 修饰变量

当一个变量被声明为 final 时,表示该变量的值一旦被初始化,就不能再被改变。通常用于常量的定义。例如:

private static final String TEXT_COMMUNITY_NAME = "新正苑";
  • TEXT_COMMUNITY_NAME 是一个常量,其值在初始化时设定之后不能更改。
  • 常量通常使用大写字母和下划线分隔(如 TEXT_COMMUNITY_NAME)来提高代码的可读性。
4. 总结 final 关键字的用途
  • 修饰类:禁止类被继承,确保该类是最终的,不允许其他类扩展它。
  • 修饰方法:禁止方法被重写,确保方法的实现不可变。
  • 修饰变量:定义常量,确保变量的值在初始化后不可改变。

完整代码分析

以下是完整的代码实现,展示了 final 关键字的不同用法:

package com.nix.demo;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Animal {
    private String name;
    private String variety;
    private int age;
    private String food;

    public Animal() {}

    public Animal(String name, String variety, int age, String food) {
        this.name = name;
        this.variety = variety;
        this.age = age;
        this.food = food;
    }

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

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        if (age < 0 || age > 100) {
            this.age = 0;
        } else {
            this.age = age;
        }
    }

    public void eat() {
        System.out.println(this.name + " 正在吃饭..正在吃:" + this.food);
    }

    public void sleep() {
        System.out.println(this.name + " 正在睡觉");
    }

    // final修饰变量,不能更改
    private static final String TEXT_COMMUNITY_NAME = "新正苑";

    public static String getTextCommunityName() {
        return TEXT_COMMUNITY_NAME;
    }

    public void barking() {
        System.out.println("我是你爹 父类");
    }
}

public class Cats extends Animal {
    @Override
    public void barking() {
        super.barking();  // 调用父类的 barking 方法
        System.out.println("喵喵喵~~~");
    }
}

public class Dogs extends Animal {
    // alt + insert 生成构造函数
    public Dogs() {}

    public Dogs(String name, String variety, int age, String food) {
        super(name, variety, age, food);
    }

    public Dogs(String name, int age) {
        super(name, age);
    }

    @Override
    public void barking() {
        System.out.println("汪汪汪~~~");
    }

    // 这个方法没有被声明为 final,因此子类可以覆盖
    public boolean isGuideBlindness() {
        return false;
    }
}

// Labrador 类是 final 类,不能被继承
public final class Labrador extends Dogs {
    @Override
    public boolean isGuideBlindness() {
        return true;
    }
}

import com.nix.demo.Cats;
import com.nix.demo.Dogs;
import com.nix.demo.Labrador;

public class Application {
    public static void main(String[] args) {
        Dogs zhangDog = new Dogs("Jerry", "null", 1, "狗粮");

        Cats liCat = new Cats();
        liCat.setName("Tom");
        liCat.setAge(3);
        liCat.setFood("猫粮");

        zhangDog.eat();
        liCat.sleep();

        zhangDog.barking();
        liCat.barking();

        // 使用 final 修饰的常量
        System.out.println("猫的小区名:" + Cats.getTextCommunityName());

        // Labrador 是 final 类,不能再被继承
        Labrador labrador = new Labrador();
        System.out.println(labrador.isGuideBlindness());
    }
}

关键点总结:

  1. final 修饰类Labrador 类是 final,它不能被继承。
  2. final 修饰方法isGuideBlindness 方法可以被子类重写,除非它被声明为 final
  3. final 修饰变量TEXT_COMMUNITY_NAME 是常量,一旦赋值后不能再修改。

通过使用 final 关键字,你可以确保一些关键的类、方法和变量不被修改,从而确保代码的安全性和可维护性。


2-2-7 引出抽象类的概念

在你的问题中,确实存在一个非常重要的设计原则:“Animal 类是否能被直接实例化?”

1. Animal 类不能被实例化
  • Animal 类是一个基类,用于继承,不是一个具体的类。我们通常不会直接创建 Animal 类型的对象。
  • 类似地,你提到的 new Animal() 实际上在实际应用中是没有意义的。Animal 是一个通用的类,表示所有动物的共性特征(如名字、年龄、食物等),但它不能直接创建实例。
2. 为什么不可以实例化 Animal
  • Animal 类有很多具体的行为,但也有很多行为是所有动物通用的,这些通用行为可以由具体的子类去继承和实现。因此,Animal 类更多的是作为一个 父类 来存在。
  • 比如,Animal 类中定义了 barking()(动物叫)的行为,但是每个具体的动物(如狗、猫)都应该有不同的叫声(比如狗叫、猫叫),这时候就需要用到 抽象类
3. 什么是抽象类?
  • 抽象类 是一个 不能被实例化的类。它通常包含一些 抽象方法(没有具体实现的方法),这些方法必须在 子类 中实现。
  • 通过抽象类,我们可以确保所有子类都有相同的接口,但具体的实现细节是由每个子类来提供的。
4. 如何使用抽象类来解决问题?

假设我们要为所有动物定义一个 叫声,但是每个动物的叫声都不一样,我们可以将 barking() 方法定义为 抽象方法,这样子类必须重写它并提供具体实现。

修改 Animal 类,使用抽象类

package com.nix.demo;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

// Animal 是一个抽象类
@Getter
@Setter
@ToString
public abstract class Animal {  // 关键字 abstract 标明这是一个抽象类
    private String name;
    private String variety;
    private int age;
    private String food;

    public Animal() {}

    public Animal(String name, String variety, int age, String food) {
        this.name = name;
        this.variety = variety;
        this.age = age;
        this.food = food;
    }

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

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        if (age < 0 || age > 100) {
            this.age = 0;
        } else {
            this.age = age;
        }
    }

    public void eat() {
        System.out.println(this.name + " 正在吃饭..正在吃:" + this.food);
    }

    public void sleep() {
        System.out.println(this.name + " 正在睡觉");
    }

    // 这是一个抽象方法,必须在子类中实现
    public abstract void barking();  // 这里不提供实现,交给具体的动物去实现
}

修改 DogsCats 类,继承并实现抽象方法

package com.nix.demo;

// Dogs 是具体类,继承自抽象类 Animal
public class Dogs extends Animal {
    public Dogs() {}

    public Dogs(String name, String variety, int age, String food) {
        super(name, variety, age, food);
    }

    @Override
    public void barking() {
        System.out.println("汪汪汪~~~");  // 具体实现狗的叫声
    }
}
package com.nix.demo;

// Cats 是具体类,继承自抽象类 Animal
public class Cats extends Animal {
    public Cats() {}

    public Cats(String name, String variety, int age, String food) {
        super(name, variety, age, food);
    }

    @Override
    public void barking() {
        System.out.println("喵喵喵~~~");  // 具体实现猫的叫声
    }
}

具体使用

import com.nix.demo.Dogs;
import com.nix.demo.Cats;

public class Application {
    public static void main(String[] args) {
        // 不能创建 Animal 的实例,因为它是抽象类
        // Animal animal = new Animal(); // 编译错误

        Dogs zhangDog = new Dogs("Jerry", "Golden Retriever", 2, "狗粮");
        Cats liCat = new Cats("Tom", "Siamese", 3, "猫粮");

        zhangDog.eat();
        liCat.sleep();

        zhangDog.barking();  // 狗叫
        liCat.barking();  // 猫叫
    }
}

关键点解释:

  1. 抽象类 (abstract class):
    • Animal 类定义为抽象类,不能直接实例化。
    • 抽象类可以包含 抽象方法(没有具体实现的方法),这些方法在子类中必须重写。
  2. 抽象方法 (abstract method):
    • barking() 方法被声明为抽象方法,子类(如 DogsCats)必须提供具体实现。
    • barking()Dogs 类中实现为 汪汪汪~~~,在 Cats 类中实现为 喵喵喵~~~
  3. 不能实例化抽象类
    • Animal 类是抽象类,不能直接使用 new Animal() 创建对象。
    • 你只能通过它的具体子类(DogsCats)来创建对象。

结论:

  • 通过将 Animal 类定义为抽象类,你确保了它只能作为一个基类,不能直接实例化。
  • DogsCats 类继承自 Animal 类,并实现了 barking() 方法的具体行为。
  • 这样设计可以避免在某些情况下创建不完整的对象,并且让每个具体的动物提供自己独特的行为实现。

通过 抽象类抽象方法,我们实现了对不同行为的具体实现,同时确保了通用的接口一致性。这是面向对象设计中常用的技巧之一。


2-3-3 接口(Interface)

在 Java 中,接口(Interface)是一种特殊的类,它完全由 抽象方法 组成,并且类通过实现(implements)接口来提供这些方法的具体实现。接口本质上定义了一些行为,而不关心具体的实现。

1. 接口的基本特征
  • 接口中不能有具体的方法实现(Java 8 后可以有默认方法和静态方法,但通常接口方法是抽象的)。
  • 接口不能有构造方法
  • 类可以通过 implements 关键字来实现一个或多个接口,并且必须实现接口中定义的所有抽象方法。
2. 如何使用接口:

你定义了一个接口 Human,其中包含了两个方法 eat()run(),它们是 抽象方法。然后你创建了两个类 ChineseWesterner,它们都实现了 Human 接口,并为接口方法提供了具体实现。

代码示例解析

package com.nix.demo;

// 定义接口 Human
public interface Human {
    public void eat();
    public void run();
}
  • Human 接口定义了两个抽象方法:eat()run()
  • 接口本身不能实例化,因此你不能直接创建 Human 类型的对象。
package com.nix.demo;

// 实现 Human 接口的 Chinese 类
public class Chinese implements Human {
    @Override
    public void eat() {
        System.out.println("吃中餐");
    }

    @Override
    public void run() {
        System.out.println("小跑");
    }
}
  • Chinese 类实现了 Human 接口,并提供了 eat()run() 方法的具体实现。
  • 这里使用了 @Override 注解,表示我们在子类中重写了接口中的方法。
package com.nix.demo;

// 实现 Human 接口的 Westerner 类
public class Westerner implements Human {
    @Override
    public void eat() {
        System.out.println("吃西餐");
    }

    @Override
    public void run() {
        System.out.println("大跑");
    }
}
  • 同样,Westerner 类也实现了 Human 接口,提供了自己版本的 eat()run() 方法。
import com.nix.demo.Chinese;
import com.nix.demo.Westerner;
import com.nix.demo.Human;

public class Application {
    public static void main(String[] args) {
        // 创建 Chinese 类型的对象
        Human chinese = new Chinese();
        chinese.eat();  // 输出: 吃中餐
        chinese.run();  // 输出: 小跑

        // 创建 Westerner 类型的对象
        Human westerner = new Westerner();
        westerner.eat();  // 输出: 吃西餐
        westerner.run();  // 输出: 大跑
    }
}
  • Application 类中,创建了 ChineseWesterner 类型的对象,并通过接口 Human 类型来引用它们。
  • 这展示了接口的多态性:你可以通过接口类型来操作不同的具体实现。

总结

  1. 接口的定义和实现
    • 接口定义了一些方法,类通过 implements 关键字来实现这些接口,提供具体的行为实现。
  2. 接口的多态性
    • 通过接口引用对象,你可以实现不同类的多态性。例如,在 Application 中,chinesewesterner 都是 Human 类型,可以通过相同的接口方法(eat()run())来操作它们,尽管它们是不同的类。
  3. 接口和抽象类的区别
    • 抽象类可以有具体方法(有方法实现),而接口中所有的方法通常是抽象的。
    • 一个类可以实现多个接口,而只能继承一个抽象类。

扩展

你还可以使用接口来解决多个类共享行为但不具有共同父类的情况。例如,如果你希望让 Hamsters 也实现 Human 接口,虽然 Hamsters 并不是 Human 类的子类,但它可以通过实现接口来拥有 eat()run() 方法。

package com.nix.demo;

// 实现 Human 接口的 Hamsters 类
public class Hamsters implements Human {
    @Override
    public void eat() {
        System.out.println("吃坚果");
    }

    @Override
    public void run() {
        System.out.println("小步跑");
    }
}

这样,你的 Hamsters 类也实现了 Human 接口,具有了 eat()run() 方法,从而在程序中可以与其他实现了 Human 接口的类保持一致的接口使用方式。


2-3-4 classinterface 的区别:哲学气息

哲学 的角度来看,抽象类接口 其实代表了不同的 抽象化层次设计理念

1. 抽象类:针对具体事物的抽象

抽象类像是 一个模板,它定义了某种事物(例如 “动物”)的通用特征和行为。抽象类是用来描述 事物的本质属性,它更加偏向于 事物的存在,而不仅仅是 行为

  • 存在:抽象类试图捕捉事物的共同特性,例如所有“动物”都应该有“名字”、“年龄”、“吃饭”的功能。
  • 抽象:抽象类在描述事物的特征时,可以有部分方法的实现(即具体方法)来提供默认行为,也可以有一些抽象方法(即没有具体实现的方法)来要求子类实现。

举个例子

abstract class Animal {
    String name;
    int age;
    
    // 这是抽象类的行为,所有动物都可以吃饭
    public void eat() {
        System.out.println(name + " 正在吃饭");
    }
    
    // 这是抽象方法,要求子类实现具体的叫声
    abstract void makeSound();
}
  • 哲学层面:抽象类代表了事物的 本质属性(如“吃饭”)。它是对事物的一个整体抽象,给出了事物共有的行为规范,但也允许事物的具体行为(如“叫声”)根据不同的实例来变动。
2. 接口:针对动作、行为的抽象

接口则完全不同,它代表了 事物能做的事情。它并不关心事物的具体存在形式,而是专注于 事物的行为。接口更多的是 描述一系列行为动作的集合,而且通常避免使用名词,更多是动词。

  • 行为:接口定义了事物能做的事情,像是“吃饭”、“跑步”或“飞行”等动作。
  • 抽象:接口中没有具体的实现方法,所有的方法都是 抽象的,它强调的是 能力和动作,不关心事物的内在属性和具体状态。

举个例子

interface AnimalActions {
    void eat();  // 动作:吃
    void run();  // 动作:跑
}
  • 哲学层面:接口代表了事物 所能表现出的行为。它不关心“事物是什么”,而关心“事物做什么”。例如,不同的动物(鸟、鱼、狗)都可以有相同的“吃”和“跑”行为,它们通过实现接口来表明自己具有这些行为能力。
3. 本质区别总结:
  • 抽象类:针对具体事物进行抽象,它关心的是事物的结构,即事物有哪些属性以及部分行为如何实现。抽象类是事物的抽象定义,它可能包含具体的实现
  • 接口:针对行为和动作进行抽象,专注于描述事物能做什么而非事物本身。接口像是行动的契约,定义了事物应该具备的行为,但不关心这些行为的具体实现方式。
4. 举例对比:
  • 抽象类Animal 类作为一个抽象类,关心的是所有动物的基本属性和行为,如“吃”。

    abstract class Animal {
        abstract void makeSound();  // 声音
    }
    
  • 接口AnimalActions 接口关心的是行为,如动物是否能“吃”和“跑”。

    interface AnimalActions {
        void eat();
        void run();
    }
    
5. 类比:
  • 抽象类的类比:可以想象成一个大类的类别或种类,例如“车”类。它描述了所有车的共同属性(如车的颜色、型号)以及可以有部分共同的行为(如启动车)。
  • 接口的类比:可以想象成一个工具包或能力列表,例如“驾驶能力”。它仅仅列出一个驾驶者可以执行的动作,而不关心他们驾驶的是哪种车。每个驾驶者都可能有这个能力,但驾驶的具体细节则由车辆类型决定。

总结

  • 抽象类像是对事物的整体抽象,它有自己的属性和部分实现。它描述了事物的一些共性特征,同时也可以为子类提供一些默认行为。
  • 接口则像是对事物行为的抽象,它只关心事物能做什么,而不关心事物本身。它提供了一种行为契约,任何实现了接口的类都能执行这些行为。

从哲学角度来说,抽象类更像是对事物“存在”的抽象,而接口则是对事物“行动”的抽象。


2-4-1 多态 — 花木兰替父从军

1. 什么是多态?

多态是面向对象编程中的一个重要特性,它使得同一操作在不同的对象上产生不同的效果。换句话说,多态允许同一个方法调用在不同的类中表现出不同的行为

多态的实现需要满足三个必要条件:

  1. 继承:子类继承父类,或者实现接口。
  2. 重写:子类重写父类的方法,提供具体的实现。
  3. 父类引用指向子类对象:父类引用可以指向子类对象,从而动态调用子类的实现。
2. 代码分析:

你的代码展示了 向上转型向下转型 的例子。

向上转型

向上转型是指把子类对象赋值给父类引用变量。这种情况下,虽然父类引用指向子类对象,但是调用的方法仍然是子类重写的方法(如果子类有重写的话)。这种机制称为 动态绑定运行时绑定

// 向上转型:父类引用指向子类对象
HuaHu huaHu = new HuaMuLan();

这里,huaHu 是父类 HuaHu 类型的引用,但它指向的是子类 HuaMuLan 的对象。

向下转型

向下转型是指将父类引用强制转换为子类引用。向下转型之前,必须保证父类引用实际指向的是子类对象,否则会抛出 ClassCastException

// 向下转型:父类引用强制转换为子类引用
HuaMuLan huaMuLan = (HuaMuLan) huaHu;

这里,huaHu 是父类 HuaHu 类型的引用,但它指向的是子类 HuaMuLan 的对象,因此可以通过强制转换将 huaHu 转回 HuaMuLan 类型。

3. 程序执行的过程:
  1. 向上转型后,父类 HuaHu 引用 huaHu 指向了子类 HuaMuLan 的对象。调用 huaHu.fight() 时,实际调用的是 HuaMuLan 类中的重写方法(如果有重写)。
  2. 向下转型后,我们把 huaHu 转回 HuaMuLan 类型,然后访问 HuaMuLan 中的属性和方法(如 dressing())。
4. 多态的表现:
  • 父类引用调用子类重写的方法:

    HuaHu huaHu = new HuaMuLan();
    huaHu.fight();  // 调用 HuaMuLan 中重写的 fight 方法
    

    即使 huaHu 的声明类型是 HuaHu,它实际指向的是 HuaMuLan 对象,因此调用的是 HuaMuLan 中的 fight() 方法(如果有重写的话)。

  • 父类引用指向子类对象时,动态绑定: 在运行时,JVM 会根据对象的实际类型(而不是引用类型)来决定调用哪个方法,这就是多态的核心。

5. 输出结果:
HuaMuLan huaMuLan = (HuaMuLan) huaHu;
System.out.println(huaMuLan.name);  // 输出:HuaMuLan
System.out.println(huaMuLan.age);   // 输出:19
huaMuLan.dressing();  // 输出:HuaMuLan化妆...
HuaMuLan.sayMe();     // 输出:大家好!我叫HuaMuLan,我今年19!
6. 总结:
  • 多态的关键在于:父类引用指向子类对象,从而使得调用的方法在运行时表现出不同的行为。
  • 向上转型和向下转型是多态的一部分。向上转型简化了代码,但有时需要进行向下转型来访问子类特有的成员。

7. 扩展:

多态使得代码更加灵活和可扩展。例如,如果你要添加一个新的类 HuaZhong 继承自 HuaHu,并实现自己的 fight() 方法,你只需要通过向上转型和多态调用,即可无缝地扩展代码。


2-4-2 匿名内部类

1. 什么是匿名内部类?

匿名内部类(Anonymous Inner Class)是没有名字的内部类。它是一种在方法内临时创建类的机制,通常用于只需要一次的类定义。匿名内部类可以用来简化代码,特别是当需要创建一个实现接口或继承抽象类的类时。

匿名内部类的特点:

  • 它没有类名。
  • 它继承一个类或实现一个接口。
  • 通过实例化对象并直接提供接口或类的实现代码。
2. 匿名内部类的用法:
2.1 实现接口

匿名内部类的常见用途之一是实现接口。你可以在创建接口实例时,直接提供实现代码,而不需要显式创建一个类。

Human jerry = new Human() {
    @Override
    public void eat() {
        System.out.println("吃中国菜");
    }

    @Override
    public void sleep() {
        // 实现 sleep 方法,留空
    }
};
jerry.eat();  // 输出:吃中国菜
  • 解释

    • new Human() 创建了一个匿名内部类实例,这个类实现了 Human 接口。
    • 接着,匿名内部类中的 eat() 方法被重写,输出 吃中国菜
    • 匿名内部类没有类名,而是在创建实例时直接定义其实现。
2.2 继承抽象类

匿名内部类也可以继承一个抽象类,并实现其抽象方法。

Human jerry = new Human() {
    @Override
    public void eat() {
        System.out.println("吃中国菜");
    }

    @Override
    public void sleep() {
        // 可以选择是否重写 sleep 方法
    }
};
jerry.eat();

在此,Human 是一个接口,匿名类实现了该接口并提供了 eat() 方法的具体实现。

3. 匿名内部类的使用场景:
  1. 临时实现接口或抽象类:当你只需要某个接口或类的实现一次时,使用匿名内部类可以简化代码并提高可读性。

  2. 事件监听器:在GUI编程中,事件监听器常常使用匿名内部类来实现。例如在 Java Swing 中,按钮的点击事件通常使用匿名内部类来处理。

    button.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("按钮被点击了!");
        }
    });
    
  3. 回调机制:在多线程编程或异步编程中,回调函数通常使用匿名内部类来定义。

4. 优缺点:
优点:
  • 简洁:匿名内部类不需要额外创建类文件,代码更简洁。
  • 临时性:适用于仅使用一次的类实例,避免了类的定义。
  • 提高可读性:使得代码结构更紧凑,避免冗余。
缺点:
  • 可读性差:匿名内部类没有名字,可能使得调试和理解代码时稍显困难。
  • 不可复用:匿名内部类不能被复用,只能在当前上下文中使用。
5. 总结:

匿名内部类是用来临时创建类并实现接口或继承抽象类的方便方式。当你需要一个类的实例,但只在一个地方用到它时,使用匿名内部类会让代码更简洁。然而,对于复杂的类实现或需要多次使用的情况,使用普通类会更合适。


3-1-1 权限修饰符

在 Java 中,权限修饰符(Access Modifiers)用于控制类、方法、字段等成员的访问权限。Java 提供了四种主要的权限修饰符,它们控制了程序中不同类或对象对类、方法和成员变量的访问权限。

1. 四种权限修饰符:
  • public
  • protected
  • default(无修饰符,包级访问权限)
  • private
2. 权限修饰符的作用:
  • public: 最高权限,可以被任何类访问。
  • protected: 可以在同一个包中的类访问,或者在不同包中的子类访问。
  • default (包级别,未指定修饰符):只在同一个包中的类可以访问。
  • private: 只能在当前类内部访问,无法在外部访问。
3. 权限修饰符的具体说明:
访问修饰符类的访问权限同一包中的其他类子类中的访问权限其他包中的类
public可以访问可以访问可以访问可以访问
protected可以访问可以访问可以访问只有子类可以访问
default只在同包内可见只在同包内可见不能访问不能访问
private只在本类内可见不能访问不能访问不能访问
4. public 访问修饰符:
  • public 修饰的类可以被任何其他类访问。
  • 字段/方法public 修饰的字段或方法可以被任何其他类直接访问。
public class MyClass {
    public int number;  // 任何类都能访问这个字段

    public void display() {  // 任何类都能调用这个方法
        System.out.println("Hello, World!");
    }
}
5. protected 访问修饰符:
  • :不能修饰类,只能修饰字段和方法。
  • 字段/方法protected 修饰的字段或方法可以在同一包中访问,也可以在不同包中的子类中访问。
class Animal {
    protected String name;

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

class Dog extends Animal {
    public void speak() {
        System.out.println("Dog barks");
    }
}
6. default(包级访问权限):
  • :默认情况下,类在没有指定访问修饰符时是default
  • 字段/方法:没有指定修饰符时,也是default。即只能在同一包内访问。
class MyClass {
    int number;  // 默认访问权限,只能在同包中访问

    void display() {  // 默认访问权限,只能在同包中访问
        System.out.println("Hello from default");
    }
}
7. private 访问修饰符:
  • private 不能修饰类。
  • 字段/方法private 修饰的字段或方法只能在当前类内部访问,外部类无法访问。
class MyClass {
    private int number;  // 只能在本类内部访问

    private void display() {  // 只能在本类内部调用
        System.out.println("Hello from private method");
    }

    public void accessPrivate() {
        // 可以在本类内部访问 private 成员
        display();
    }
}
8. privatedefaultprotectedpublic 的使用建议:
  • public:如果类、字段或方法需要在整个项目中广泛访问,可以使用public修饰符。
  • private:为了信息隐藏和封装,应该尽量将字段和方法设置为private,并通过公共的getter和setter方法来访问。
  • protected:当字段或方法仅在子类或同一包内可见时,可以使用protected。但是尽量避免过多使用 protected,以防止设计不当。
  • default:如果字段或方法只在同一包中使用,可以使用default,通常用于实现内部的共享方法。
9. 示例代码:
// 父类
public class Parent {
    public String publicField = "Public Field";
    protected String protectedField = "Protected Field";
    String defaultField = "Default Field";
    private String privateField = "Private Field";

    public void publicMethod() {
        System.out.println("Public Method");
    }

    protected void protectedMethod() {
        System.out.println("Protected Method");
    }

    void defaultMethod() {
        System.out.println("Default Method");
    }

    private void privateMethod() {
        System.out.println("Private Method");
    }
}

// 子类
class Child extends Parent {
    public void accessParentFields() {
        System.out.println(publicField);   // 可以访问 public
        System.out.println(protectedField); // 可以访问 protected
        // System.out.println(defaultField);  // 错误,无法访问 default
        // System.out.println(privateField);  // 错误,无法访问 private
    }

    public void accessParentMethods() {
        publicMethod();   // 可以访问 public
        protectedMethod(); // 可以访问 protected
        // defaultMethod();  // 错误,无法访问 default
        // privateMethod();  // 错误,无法访问 private
    }
}
10. 总结:
  • public:最开放,任何地方都可以访问。
  • protected:子类和同包类可以访问。
  • default(包级):同包中的类可以访问。
  • private:只有当前类内部可以访问。

正确使用权限修饰符可以帮助我们进行更好的封装和代码设计,确保类、方法、字段的访问权限符合设计需求。


3-1-2 Object

在 Java 中,Object 是所有类的父类。所有的类都直接或间接继承自 Object 类。如果一个类没有显式继承父类,那么它默认继承自 Object 类。这个类提供了一些常用的方法,所有继承自 Object 的类都能使用这些方法。

1. Object 类的作用:
  • 所有类的基类:每一个类都直接或间接地继承自 Object 类。
  • 提供基础的方法Object 类定义了一些基础方法,如 equals()hashCode()toString() 等,所有类可以重写这些方法来满足特定需求。
2. Object 类的常用方法:
  • toString():返回对象的字符串表示形式,通常需要重写。

    默认情况下,toString() 方法返回的是对象的类名和哈希值,但我们通常会重写它以便输出更有意义的信息。

    class Person {
        String name;
        int age;
    
        @Override
        public String toString() {
            return "Person{name='" + name + "', age=" + age + "}";
        }
    }
    
    public class Application {
        public static void main(String[] args) {
            Person p = new Person();
            p.name = "John";
            p.age = 25;
            System.out.println(p.toString()); // 输出: Person{name='John', age=25}
        }
    }
    
  • equals(Object obj):判断当前对象与另一个对象是否相等。默认实现是比较对象的内存地址(即是否是同一个对象),但通常会重写此方法来比较对象的内容。

    class Person {
        String name;
        int age;
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true; // 判断是否是同一个对象
            if (obj == null || getClass() != obj.getClass()) return false; // 类型检查
            Person person = (Person) obj; // 强制类型转换
            return name.equals(person.name) && age == person.age; // 内容比较
        }
    }
    
    public class Application {
        public static void main(String[] args) {
            Person p1 = new Person();
            p1.name = "John";
            p1.age = 25;
    
            Person p2 = new Person();
            p2.name = "John";
            p2.age = 25;
    
            System.out.println(p1.equals(p2)); // 输出: true
        }
    }
    
  • hashCode():返回对象的哈希码,通常需要与 equals() 一起重写。如果两个对象相等(equals() 返回 true),那么它们的哈希码也必须相等。

    class Person {
        String name;
        int age;
    
        @Override
        public int hashCode() {
            return 31 * name.hashCode() + age; // 根据对象的字段计算哈希值
        }
    }
    
    public class Application {
        public static void main(String[] args) {
            Person p1 = new Person();
            p1.name = "John";
            p1.age = 25;
    
            Person p2 = new Person();
            p2.name = "John";
            p2.age = 25;
    
            System.out.println(p1.hashCode() == p2.hashCode()); // 输出: true
        }
    }
    
  • getClass():返回当前对象的类类型。此方法常用于反射。

    class Person {}
    
    public class Application {
        public static void main(String[] args) {
            Person p = new Person();
            System.out.println(p.getClass()); // 输出: class Person
        }
    }
    
  • clone():创建并返回当前对象的一个副本(浅拷贝)。该方法是 Object 类中的 protected 方法,因此需要在子类中重写并实现 Cloneable 接口才能使用。

    class Person implements Cloneable {
        String name;
        int age;
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    public class Application {
        public static void main(String[] args) throws CloneNotSupportedException {
            Person p1 = new Person();
            p1.name = "John";
            p1.age = 25;
    
            Person p2 = (Person) p1.clone();
            System.out.println(p1 == p2); // 输出: false,说明是不同的对象
        }
    }
    
  • finalize():在对象被垃圾回收前调用,用于清理资源。通常不推荐使用,因为垃圾回收器并不保证什么时候调用 finalize() 方法。

    class Person {
        @Override
        protected void finalize() throws Throwable {
            System.out.println("对象被垃圾回收之前调用");
        }
    }
    
    public class Application {
        public static void main(String[] args) {
            Person p = new Person();
            p = null; // 设置对象为null,可能会被垃圾回收
            System.gc(); // 强制进行垃圾回收
        }
    }
    
3. Object 类的继承与重写
  • 继承:所有的 Java 类默认继承自 Object 类,所以每个类都可以使用 Object 类中的方法。
  • 重写方法:如上所述,Object 类提供了很多方法,可以在自定义类中根据需要进行重写,来实现自定义行为。例如,重写 toString() 来输出自定义的对象信息,重写 equals() 来比较两个对象的内容是否相同。
4. Object 类中的常用方法小结:
  • toString():提供对象的字符串表示,常常被重写以提供更加有意义的信息。
  • equals():用于比较对象是否相等,通常需要重写。
  • hashCode():返回对象的哈希码,常常与 equals() 一起重写。
  • getClass():返回当前对象的类类型。
  • clone():复制当前对象,需实现 Cloneable 接口才能使用。
  • finalize():在对象被垃圾回收前执行,用于清理资源。
5. 总结:
  • Object 类是 Java 中所有类的父类,所有类默认继承它,提供了许多方法。
  • toString()equals()hashCode() 等方法提供了基础的对象操作能力,可以根据需要进行重写。
  • 通过继承 Object 类,Java 为我们提供了标准的行为(如比较、字符串表示等),使得我们在编写代码时能够实现更一致的接口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值