JAVA基础09- 多态,引用类型转换,接口,抽象类,内部类与单例模式

目录

final关键字

作用

注意

多态

定义

如何产生

多态出现前提

练习

练习1

练习2

引用类型的转换

向上转型

向下转型

instanceof关键字

编译时类型与运行时类型

静态类型

动态类型

单例模式

定义

类别

创建步骤

懒汉式

饿汉式

使用场景

初始化以及初始化块

初始化块的作用

静态与非静态初始化

抽象类

定义

抽象方法

抽象类

特点、注意事项

为什么要用抽象类

使用场景

练习

接口

定义

注意事项

抽象类与接口关系

区别

使用场景

练习

回调方法

模板方法类型

定义

工厂模式

场景解释

内部类

定义

分类

非静态内部类

静态内部类(static)

匿名内部类


final关键字

作用

用于修饰类、变量、常量、方法

注意

1.final修饰最终类 ,则能给子类继承

2.final修饰方法,则该方法能被重写

3.final修饰变量,要先进行赋值,赋值后该变量的值能被修改

4.类型为基本数据类型,赋值后该变量的值能被修改

5.类型为引用类型,则赋值一个新的对象,但是可以修改对象中成员变量的数值

6.final修饰局部变量,可以先不初始化,但是赋值后该变量的值不能被修改

7.使用final修饰成员变量,(包括静态/非静态)则需要先初始化(否则会编译出错),则变量值是不能被修改的


多态

定义

在Java中多态指的是行为的多态性

如何产生

多态是由于编译时类型和运行时类型不一致而出现的情况

编译时类型是引用变量的类型

运行时类型是new出来的对象类型

	//	--当在编译时,编译的语法校验是按照编译时的类型来决定
	//	--当运行时,调用方法时本质是调用运行时类型的方法

		//编译时类型       运行时类型
		Person p    =    new Doctor();
		// 当引用变量调用该方法时,则显示出子类的行为特征
		p.eat();

		//由于p编译时类型是Person,所以在编译期间没有能在Person类中找到work方法,编译出错
		p.work();//The method work() is undefined for the type Person
		

多态出现前提

1.父类的引用指向子类的对象

2.子类要重写父类方法

结果:当引用变量调用方法会出现行为的多态,显示子类的行为特征。

注意:变量没有多态性


练习

练习1

- -宠物商店,有寄养宠物的方法

- -宠物Pet,有喂食的方法 - - 猫类- - 狗类

public class PetShop {
	// 寄养宠物的方法[宠物]
	public void adoptPet(Pet pet) {//父类的引用   指向  子类对象
		pet.feed();//当调用方法时,显示子类的行为特征
	}
}
//宠物的父类
public class Pet {
	// 有喂食的方法
	public void feed() {
		System.out.println("喂食宠物。");
	}
}
//不同宠物的类
class Cat extends Pet {
	public void feed() {
		System.out.println("喂食小鱼干。");
	}
}

class Dog extends Pet {
	public void feed() {
		System.out.println("喂食骨头。");
	}
}

class Raby extends Pet {
	public void feed() {
		System.out.println("喂食萝卜。");
	}
}
//测试类
public class PetMain {

	public static void main(String[] args) {
		// 宠物商店寄养宠物
		PetShop shop = new PetShop();
		Dog dog = new Dog();
		Cat cat = new Cat();
		shop.adoptPet(cat);//输出喂食小鱼干
	}
}

练习2

--ElectronicShop电子维修店,有维修电子设备方法repair(),repair调用设备中的openAndCheck方法

public class ElectronicShop {
	// 有维修电子设备方法repair(),repair调用设备中的openAndCheck方法
	public void repair(ElectronicEquipment equipment) {
		equipment.openAndCheck();// 检测维修
	}
	
	//编写测试代码
	public static void main(String[] args) {
		//电子设备维修店维修手机
		ElectronicShop shop = new ElectronicShop();
		
		shop.repair(new MobilePhone("华为", "重庆", "麒麟"));
		shop.repair(new Flat("苹果", "深圳", "M1"));
	}
}

--电子设备ElectronicEquipment

拥有属性:name[名称]\produtionAddress[生产点]\cpu[处理器]

拥有方法:openAndCheck();方法【输出name正在启动,输出设备的信息】

电子设备子类:--手机MobilePhone 重写openAndCheck();方法  --平板Flat 重写openAndCheck();方法  --电脑Computer 重写openAndCheck();方法

public class ElectronicEquipment {
	// name[名称]\produtionAddress[生产点]\cpu[处理器]
	public String name;
	public String produtionAddress;
	public String cpu;

	// :openAndCheck();方法【输出name正在启动,输出设备的信息】
	public void openAndCheck() {
		System.out.println(name + "正在启动,设备的地址信息:" + produtionAddress + "    cpu:" + cpu);
	}
//构造器,有参与无参
	public ElectronicEquipment(String name, String produtionAddress, String cpu) {
		super();
		this.name = name;
		this.produtionAddress = produtionAddress;
		this.cpu = cpu;
	}

	public ElectronicEquipment() {
		super();
	}

}

class MobilePhone extends ElectronicEquipment {
	public void openAndCheck() {
		System.out.println("正在启动手机,准备进行检测" + name + "设备的地址信息:" + produtionAddress + "    cpu:" + cpu);
	}

	public MobilePhone(String name, String produtionAddress, String cpu) {
		super();
		this.name = name;
		this.produtionAddress = produtionAddress;
		this.cpu = cpu;
	}

	public MobilePhone() {
		super();
	}
}

class Flat extends ElectronicEquipment {
	public void openAndCheck() {
		System.out.println("正在启动平板,准备进行检测" + name + "设备的地址信息:" + produtionAddress + "    cpu:" + cpu);
	}

	public Flat(String name, String produtionAddress, String cpu) {
		super();
		this.name = name;
		this.produtionAddress = produtionAddress;
		this.cpu = cpu;
	}

	public Flat() {
		super();
	}
}

class Computer extends ElectronicEquipment {
	public void openAndCheck() {
		System.out.println("正在启动电脑,准备进行检测" + name + "设备的地址信息:" + produtionAddress + "    cpu:" + cpu);
	}

	public Computer(String name, String produtionAddress, String cpu) {
		super();
		this.name = name;
		this.produtionAddress = produtionAddress;
		this.cpu = cpu;
	}

	public Computer() {
		super();
	}
}

引用类型的转换

向上转型

类赋值给父,子类型可以赋值给父类的引用变量

向下转型

类赋值给子,当引用变量进行向下转换时,要注意类型是否兼容,可以通过instanceof关键字进行判别。

报错提醒:com.day0120.Doctor cannot be cast to com.day0120.Student【类型不兼容】ClassCastException

instanceof关键字

- - 判断变量中的对象是否为某个类或子类的对象判断变量中存储的对象类型

  instanceof后面的类型要和前面变量的编译时类型要继承关系【子类、父类、本类】

		Person p1 = new Doctor();//person是doctor和student的父类。
		//instanceof关键字【判断变量中存储的对象类型】   
		//instanceof后面的类型要和前面变量的编译时类型要继承关系【子类、父类、本类】
		if (p1 instanceof Student) {//判断为false
			System.out.println("是Student");
			Student d = (Student) p1;
			d.eat();
		}
		if (p1 instanceof Doctor) {//判断为true
			System.out.println("是Doctor");
			Doctor d = (Doctor) p1;
			d.eat();
		}//结果输出:是Doctor Doctor 吃饭

编译时类型与运行时类型

Person p = new Student()

引用变量定义的类型Person 为编译时类型

new 出来的对象类型Student 为运行时类型

1、方法调用时要注意是按照编译时类型来决定(编译时类型是决定你编程时是否会出现编译错误【语法】)

2、在运行程序时,是通过运行时类型来决定是否出错(对象转换时,对象类型是否兼容---即可用instanceof判断

举例:动物里面有狗 、 猫;狗不能赋值给猫

静态类型

- -根据入参变量编译时类型决定调用哪个方法

(即谁入参到这个方法就根据谁的编译时类型来决定

//静态类型:确定在同一个类中,调用了哪个方法
public class StaticTest {
	//在同一个类中,如何调用哪个方法,根据方法签名【根据入参的变量的编译时类型】
	public void test(A a) {
		System.out.println("test(A a)-----------------");
	}
	public void test(B b) {
		System.out.println("test(B b)-----------------");
	}
	public void test(C c) {
		System.out.println("test(C c)-----------------");
	}
	//main
	public static void main(String[] args) {
		//
		StaticTest test = new StaticTest();
		//
		A a = new C();
		B b = new C();
		//在同一个类中,调用哪个方法在编译时已经确定了【方法签名】【入参的数据编译时类型】
		test.test(a);// A a  \入参为A类型,即匹配 test(A a)
		test.test(b);// B b \入参为B类型,即匹配 test(B b)
	}
}
class A {
}
class B extends A {
}
class C extends B {
}

动态类型

- -根据调用方法的引用变量的运行时类型决定调用哪个类

(即谁调用了这个方法就根据谁的运行时类型来决定)

public class DynamicTest {
	//1、确定调用哪个类【调用方法的变量的运行时类型确定】   
    //2、确定调用哪个方法【入参的变量的编译时类型确定】
	public static void main(String[] args) {
		A a = new A();
		B b = new B();
		C c = new C();
		a.test(b);// a--b \a为A类型,其中b为b方法
		A a_c = new C();
		a_c.test(b);// c--b \a_c为C类型,b为b方法
		B b_c = new C();
		b_c.test(a);// c--a \b_c为C类型,a为a方法
		//
		a_c = a;
		b = b_c;
		b.test(a_c);// c--a  \b为b_c也即C类型,a_c为a方法
		b_c.test(b_c);//c--b  \b_c为c类型,b_c为b方法
	}
}
class A {
	public void test(A a) {
		System.out.println("A ---------- a");}
	public void test(B b) {
		System.out.println("A ---------- b ");}
	public void test(C c) {
		System.out.println("A ---------- c");}}

class B extends A {
	public void test(A a) {
		System.out.println("B ---------- a");}
	public void test(B b) {
		System.out.println("B ---------- b ");}
	public void test(C c) {
		System.out.println("B ---------- c");}}

class C extends B {
	public void test(A a) {
		System.out.println("C ---------- a");}
	public void test(B b) {
		System.out.println("C ---------- b ");}
	public void test(C c) {
		System.out.println("C---------- c");}}

单例模式

定义

用于限定当前类只能生成一个实例对象,则这个类称为单例类。保证类在内存中只有一个对象的存在。

类别

创建步骤

1、设置构造器为私有化 不允许外部类来创建对象

2、提供一个静态的公有化的方法来获取唯一的对象

3、判断laz是否为空,目的是确保对象是唯一

懒汉式

天生非线权安全,只有用到了才会new一个对象

//单例类:懒汉式【在用的时候才创建】
public class SingleLaz {
	// 3、定义一个变量用于存放单例的对象
	static SingleLaz laz = null;
	// 1、将构造器进行私有化
	private SingleLaz() {
	}
	// 2、提供一个静态方法来获取单例的对象
	public static SingleLaz getInstance() {
		//如果对象为null,则创建唯一的对象
		if(laz == null) {
			laz = new SingleLaz();
		}
		return laz;
	}
}

饿汉式

饿汉式天生属于线程安全,一开始就会new一个对象占内存。

//单例类:饿汉式【一开始的时候就创建了】
public class SingleHug {
	// 3、定义一个变量用于存放单例的对象
	final static SingleHug laz = new SingleHug();

	// 1、将构造器进行私有化
	private SingleHug() {
	}

	// 2、提供一个静态方法来获取单例的对象
	public static SingleHug getInstance() {
		//返回唯一的对象
		return laz;
	}
}

使用场景

【多线程、频繁操作、资源消耗大】

1、在多线程的情况下,生成唯一的序列号  【确保只有唯一的生成器

2、IO流操作、频繁的创建新的对象会造成性能损耗,固定唯一的io对象

3、数据库链接、获取唯一的数据库链接对象


初始化以及初始化块

初始化块的作用

用于在类加载或者创建对象时,可以设置一些固定的初始化操作(初始化成员变量、执行其他类的加载)

静态与非静态初始化

区别

静态初始化块只执行一次(类加载时),静态初始化块每创建一个对象就执行一次。

顺序

静态初始化块-->非静态初始化块-->构造器

初始化在继承链中顺序

- -父类静态初始化块-->子类静态初始化块-->父类非静态初始化块-->父类构造器-->子类非静态初始化块-->子类构造器


抽象类

定义

抽象类:模板[给子类继承](普通类能定义的东西,抽象类都可以定义)

抽象方法

不知道具体实现的方法,是没有方法体的,使用abstract修饰。

Person -->eat(){System.out.println("人吃饭");}【普通类】
Person -->eat();【抽象类】//以;结尾

抽象类

抽象类的作用是作为一个模板给子类继承。

特点、注意事项

1.抽象类本身不能被创建对象的【实例化的】即不能new创建对象,但是可以将子类的对象赋值给抽象类的引用变量。

		People p = new People();//不能这样,抽象类不可以实例化!
		People p = new Doctor();//doctor为people的子类对象
		p.eat();

2.子类继承抽象类,如果子类不实现抽象方法,则该类必须为抽象类。

注意:抽象类可以继承抽象类、可以不实现抽象方法

为什么要用抽象类

1、抽象类本身是一种概念

2、作为一个模板给子类继承,让子类必须实现抽象方法

使用场景

如果需要子类重写的方法,则使用普通类继承即可;但是如果要求子类一定要实现方法,则需要使用抽象类,让子类继承并重写方法的实现。


练习

多边形抽象类,定义求取周长和面积的抽象方法

子类如下:--四边形--三角形

public abstract class Polygon {//多边形
	public abstract double getArea();// 面积
	public abstract double getPerimeter() ;	// 周长
}
public class Rectangle extends Polygon {//四边形

	public double length;//长
	public double width;//宽

	@Override
	public double getArea() {
		return length * width;
	}
	@Override
	public double getPerimeter() {
		return (length + width) * 2;
	}
	public Rectangle(double length, double width) {//构造器
		super();
		this.length = length;
		this.width = width;
	}
	public Rectangle() {
		super();
	}
}
//三角形
public class Triangle extends Polygon {

	double bottom;
	double height;
	double side1;
	double side2;
	double side3;
    
	@Override
	public double getArea() {//子类不为
		return (bottom * height) / 2.0;// (底*高)/2
	}
	@Override
	public double getPerimeter() {
		return side1 + side2 + side3;
	}
    //全参构造器
	public Triangle(double bottom, double height, double side1, double side2, double side3) {
		super();
		this.bottom = bottom;
		this.height = height;
		this.side1 = side1;
		this.side2 = side2;
		this.side3 = side3;
	}
	public Triangle() {
		super();
	}
}
public class PolygonMain {//测试类

	public static void main(String[] args) {
		Polygon p = new Triangle(10, 5, 10, 10, 10);
		System.out.println(p.getArea());
		System.out.println(p.getPerimeter());
	}

}

接口

定义

接口就是特殊的抽象类,接口类是作为一种规范;接口就是用于定义抽象方法(定义规范)

接口的作用是解耦[定义和实现分离]

使用场景?一般情况下都是使用接口

注意事项

1.不能定义变量、普通方法、初始化块、构造器;

2.可以定义默认方法、常量

//不能定义普通实现的方法\但是可以定义默认方法
	public void demo() {//这种为普通方法,不能如此定义
		System.out.println("sdfdsf");
	}
	//默认方法
	default void test() {//在接口中,default 表示默认,和修饰符中的default作用不同
		System.out.println("sdfdsf");
	}

3.抽象方法默认是使用public abstarct修饰的

// 在接口中也能定义抽象方法 \默认 public abstract
	public abstract void devilyData();//即该语句与 void devilyData(); 作用相同

4.常量默认是使用public final static修饰的

5.接口不能被实例化的

6.接口中可以定义默认方法,但是一定要使用default关键字修饰

7.接口可以多实现(一个类可以实现多个接口


抽象类与接口关系

区别

抽象类是作为一个模板

  • 可以定义抽象方法,并且要求子类必须要重写实现。
  • 可以定义变量,普通方法,构造器,常量,初始化块。

接口作为一个规范

  • 可以定义抽象方法
  • 不可以定义常量,普通方法,构造器,初始化块。
  • 只能定义常量和默认方法

使用场景

要给子类继承共通性的属性以及方法时(有东西继承的)用抽象类,不用给子类继承东西  的用接口。原因:java只能单继承,但接口可以多实现(解耦)


练习

分析题意:类(共有三个类,警局(普通类--实现传话器类),警察(抽象类),传话器(接口))-->属性-->方法-->测试

//传话器Phone【接口】
public interface Phone {
	// 报告的抽象方法
	void report(String msg);
}
//警局PoliceHost【普通类】
public class PoliceHost implements Phone {
	@Override
	public void report(String msg) {
		System.out.println(msg);
	}
	// 派遣出警
	public void detach() {
		// 1、新建一个警察对象
		Police gao = new Gao();
		//给高警官设置传话器
		gao.setPhone(this);//new Phone()[不能实例化接口];-->new PoliceHost()[只能实例化子类] --> this【相当于PoliceHost的对象】
		// 2、输出一点信息
		System.out.println("派遣高警官出镜");
		// 3、侦查
		gao.scout();
	}
}
//警官Police【抽象类】
public abstract class Police {
	public Phone phone;	// 传话器
	public abstract void scout();// 侦查的抽象方法
	public Phone getPhone() {//get,set方法
		return phone;
	}
	public void setPhone(Phone phone) {
		this.phone = phone;
	}
}

// 李警官
class Li extends Police {

	@Override
	public void scout() {
		System.out.println("李警官脚踏哈雷,火速到达现场进行侦查");
		phone.report("李警官反馈:现场惨不忍睹!重要线索丢失!");// 侦查结束,反馈情况
	}

}

// 高警官
class Gao extends Police {
	@Override
	public void scout() {
		System.out.println("高警官搭乘喷气机,火速到达现场进行侦查");
		phone.report("高警官反馈:现场留下了蛛丝马迹!继续侦查!");// 侦查结束,反馈情况
	}
}
public class PoliceTest {
	public static void main(String[] args) {
		PoliceHost host = new PoliceHost();	// 警局派遣出警,高警官前去现场侦查
		host.detach();	
	}
}

回调方法

解释:把A【警局】入参给B【警官】,B再调用A的方法,这个过程就叫做回调方法

举例:把警局对象(implements 于phone接口,即传话机)赋值给警官,警官回调警局(传话机)中的report方法。

警官调用report方法的操作称为方法的回调。


模板方法类型

定义

用于作为一个模板给子类继承、进行扩展实现(抽象类、接口实现)属性-功能模板


例子

//这是悍马的生产模型
public abstract class HummerModel {
	// 模板方法
	public abstract void start();
	public abstract void stop();
	public abstract void alarm();
	public abstract void engineBoom();
	public abstract void run();
}
//根据模板生产了一个车型
public class HummerM1 extends HummerModel {
	@Override
	public void start() {
		System.out.println("HummerM1 start..");}
	@Override
	public void stop() {
		System.out.println("HummerM1 stop..");}
	@Override
	public void alarm() {
		System.out.println("HummerM1 alarm..");}
	@Override
	public void engineBoom() {
		System.out.println("HummerM1 engineBoom..");}
	@Override
	public void run() {
		System.out.println("HummerM1 run..");}
}
public class HummerMain {//测试类
	public static void main(String[] args) {
		HummerModel m1 = new HummerM1();//父类的引用指向子类对象
		m1.start();//调用子类的实现操作,体现不同的行为特征
	}
}

工厂模式

用于定义一个工厂类来生产某个东西。(工厂方法要静态

在多个类中,同样都使用某个类型时,用工厂类来生产这个类型。后期进行业务修改与更新换代,只用改工厂类。(实现解耦)

  • 在下面场景中,这个类型即为output类

场景解释

有一个场景:假设程序中有个Computer类需要组合一个输出设备,现在有两个选择:直接让Computer类组合一个Printer,或者让 Computer类组合一个Output,那么到底采用哪种方式更好呢?

假设让Computer类组合一个Printer对象,如果有一天系统需要重构,需要使用BetterPrinter来代替Printer,这就需要打开Computer类源代码进行修改。如果系统中只有一个Computer类组合了Printer还好,但如果系统中有100个类组合了Printer,甚至1000个、10000个……将意味着需要打开100个、1000个、10000个类进行修改,这是多么大的工作量啊!

为了避免这个问题,工厂模式建议让 Computer类组合一个Output类型对象,将Computer类与Printer类完全分离。Computer对象实际组合的是 Printer对象还是BetterPrinter对象,对Computer而言完全透明。当Printer对象切换到BetterPrinter对象时,系统完全不受影响。下面是这个Computer类的定义代码。

//电脑类
public class Computer {
	// 组合了一个输出设备
    // public Output output;
	//注释掉的代码是用Computer类组合一个Printer
	/*public Output getOutput() {
		return output;}
		public void setOutput(Output output) {
		this.output = output;}*/

	// 打印信息的方法
	public void printData(String msg) {
		OutputFactory.getOutput("led").show(msg);
        //效果与下面同
	}
/*  public Output output = OutputFactory.getOutput();
    public void printData(String msg) {
		output.show(msg);
	}*/
}
class Dell extends Computer {
	// 组合了一个输出设备
//	public Output output;
/*public void setOutput(Output output) {
		this.output = output;}*/

	// 打印信息的方法
	public void printData(String msg) {
		OutputFactory.getOutput("Output").show(msg);
	}
}
//输出设备[电子显示屏]
public class Output {
	// 显示信息的方法
	public void show(String msg) {
		System.out.println("显示屏显示信息:" + msg);
	}
}
class LedOutput extends Output{
	// 显示信息的方法
	public void show(String msg) {
		System.out.println("LED屏显示信息:" + msg);
	}
}
//输出设备的生产--工厂类
public class OutputFactory {
	//生产的方法
	public static Output getOutput(String type) {//return的是output对象即返回的是输出设备
		//根据类型来生产对象
		if(type.equals("led")) {
			return new LedOutput();
		}else {
			return new Output();
		}	
	}
}

内部类

定义

类中包含类,目的就为了更好隐藏类的细节【链表-节点】(内部类可以继承和实现接口)


分类

非静态内部类

1、由于非静态内部类是外部类的成员所以可以使用public、protected、private、static修饰类

2、在非静态内部类中只能定义静态常量,不能定义静态变量

3、创建非静态内部类时,要通过外部类的对象来调用内部类的构造器

		Clothes c = new Clothes();//创建非静态内部类时,要通过外部类的对象来调用内部类的构造器
		Button b = c.new Button();//纽扣是衣服的非静态内部类
		b.test();

4、非静态内部类可以访问外部类的属性和方法,包括私有化的

5、外部类中访问不了非静态内部类的非静态的属性、可以访问静态常量

6、外部类中只能使用public、abstract、final

7、内部类和外部类具有同名的属性时,按照最近原则调用


静态内部类(static)

1、静态内部类中可以定义静态的属性

2、静态内部类中不可以调用外部类静态属性和方法

3、通过外部类型,引用静态内部类的构造器来创建对象

	//通过外部类型,引用静态内部类的构造器来创建对象
		Collar collar = new Clothes.Collar();//衣领是衣服的静态内部类
		collar.demo_test();

匿名内部类

实现了某个接口或抽象类,没有类名内部类【比如:(一次性)线程的应用,函数式接口、事件监听操作】【结合抽象类和接口使用】

特点

1.只使用一次(一次性的)

2.不需要定义一个Java文件

3.没有名字的、所以称为匿名

public interface Phone {
	// 报告的抽象方法
	void report(String msg);
}
public class PhoneMain {

	public static void main(String[] args) {
		//匿名内部类:一次性【线程中】、函数式接口、事件监听操作
		//表达式右边的操作,实际上是创建一个实现了Phone接口的内部类的对象
		Phone p = new Phone() {
			@Override
			public void report(String msg) {
				System.out.println("收到数据:"+msg);
			}
		};
		p.report("nihao ");
		
		//举例:线程(一次性操作)
		Thread t = new Thread(
		new Runnable() {
			@Override
			public void run() {
				// 
				System.out.println("买汉堡!");
			}
		});	
		t.start();
	}
}

  • 52
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值