Java笔记第四章 面向对象

第四章 面向对象

一、Java面向对象学习的三条主线

  1. Java类及类的成员:属性、方法、构造器(重点);代码块、内部类
  2. 面向对象的三大特征:封装性、继承性、多态性 如果有四个再加一个抽象性
  3. 其他关键字:this、super、static、final、abstract、interface、package、import等

二、面向过程(POP)与面向对象(OOP)

面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象,将功能封装进对
, 强调具备了功能的对象以类/对象为最小单位,考虑谁来做。


三、Java基本元素:类和对象是面向对象的核心概念

  1. 类:是对一类事物的描述,是抽象的、概念上的定义 。
  2. 对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)。
  3. 面向对象程序设计的重点是类的设计。 类的设计, 其实就是类的成员的设计
  4. 万事万物皆对象:
    • 在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化来调用具体的功能结构。
    • 涉及到Java语言与前端html、后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类、对象

四、类的成员之一:属性=成员变量=field=域、字段

  1. 对应类中的成员变量

  2. 属性(成员变量)vs 局部变量:

    • 不同点:

      (1)在类中声明的位置不同:

      ​ 属性:直接定义在类的一对{ }内

      ​ 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量

      ​ 方法内是在方法内({ }内)声明的变量。

      ​ 方法形参是指在类中定义方法时需要传入的参数。

      (2)关于权限修饰符的不同(声明变量的数据类型前面可以加权限修饰符)

      ​ 常用的权限修饰符:private、public、缺省、protected (封装性时会详细说)

      ​ 权限修饰符目的是定义结构被调用时的可见性大小,public出了类还可以调用,public就不可以了

      ​ 属性:可以在声明属性时,指明其权限,使用权限修饰符。一般都是使用缺省(不写)

      ​ 局部变量:不可以使用权限修饰符。他跟方法的权限保持一致

      (3)默认初始化值的情况:

      ​ 属性:类的属性,根据其类型,都有默认初始化值。

      ​ 整型(byte、short、int、long):0

      ​ 浮点型(float、double):0.0

      ​ 字符型(char):0 (或’\u0000’)

      ​ 布尔型(boolean):false

      ​ 引用数据类型(类、数组、接口):null

      ​ 局部变量:没有默认初始化值。意味着,我们在调用局部变量之前,一定要显式赋值。

      ​ 特别地:形参在调用时,我们赋值即可。

      (4)在内存中加载的位置不同:

      ​ 属性:加载在堆空间中(非static的属性,static的属性放在方法区)

      ​ 局部变量:加载在栈空间中

    • 相同点:

      (1)定义变量的格式相同:数据类型 变量名 = 变量值

      (2)先声明后使用

      (3)变量都有其对应的作用域


五、类的成员之二:方法=成员方法=函数=method

  1. 对应类中的成员方法

    例如:人是一个类,人的身高、体重、年龄可以看作属性,人会做饭、打球属于方法。

  2. 类和对象的使用:

    • 创建类,设计类的成员

      class Person{    //创建类
          int age;
          String name;
          boolean isMale;  //属性
          //方法
          public void eat(){
              System.out.println("人可以吃饭");
          }
          public void talk(String language){  //String language使方法形参
              System.out.println(name+"可以说话,说的是:"+language);
          }
      }
      
    • 创建类的对象

    • 通过“对象.属性“或”对象.方法“调用对象的结果

      public class class_object {
          public static void main(String[] args) {
             Person person = new Person();//用这种new的方法创建Person类的对象实例
             person.age = 10;       //调用对象的属性,对象.属性
             person.isMale = true;  //调用对象的属性,对象.属性
             person.name = "Tom";   //调用对象的属性,对象.属性
             person.eat();          //调用对象的方法,对象.方法
             person.talk("Chinese");//调用对象的方法,对象.方法
          }
      }
      
  3. 如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(该属性在类中的声明必须是非static的)意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。

    注意:这里的创建多个对象必须是用new创建的,这样才会在堆空间中新建一个对象的空间,如果只是通过Person p1 = p3的方式,改变p1中的属性,p3也会随之改变,因为他们指向的是同一地址值。

  4. 内存解析:

    JVM的内存区域分为方法区、虚拟机栈、堆、本地方法栈、程序计数器,类与对象涉及的主要是前三个,引用类型的变量,只可能存储两类值:null 或者 地址值(含变量的类型)

    • 堆:唯一目的就是存放对象实例,几乎所有的对象都在这里分配内存。规范的描述是:所有的对象实例以及数组都要在堆上分配。注意:类中的属性是跟随对象存储在堆空间中的,如果属性的值是String,值存放在方法区中的常量池中
    • 栈:通常是指虚拟机栈,用于存储局部变量,方法中变量都是局部变量,如变量定义在main方法中,他就是局部变量
    • 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 可分为常量池(String存放在常量池)和静态域。
  5. 类中方法的声明

    方法:描述类应该具有的功能。比如:Math类:sqrt() \ random()……

    • 方法的声明:

      • 一般格式:

        权限修饰符 返回值类型 方法名(形参列表){

        ​ 方法体

        }

        **权限修饰符:**四种:public、private、缺省、protected(封装性再细说,目前先可以都用public)

        **返回值类型:**有返回值时:定义变量的那些类型都可以写,无返回值时用void

        ​ (1)如果有返回值,必须在方法声明时指定返回值的类型,同时在方法中需要用return返回指定返回值类型的变量或者常量。且必须确保return必须有一个执行

        ​ (2)没有返回值时用void,通常没有return,如果有只能用return;表示结束此方法

        **形参列表:**形参可有可无,方法可以声明0个、1个或多 个形参

        ​ 格式:数据类型1 形参1,数据类型2,形参2,……

        **方法名:**属于标识符,遵循标识符的规则和规范

      **注意:**在权限修饰符前可能还有一些关键字来修饰方法如static、final、abstract(后面再说)

  6. 方法的使用:

    • 方法中可以调用当前类的方法和属性。

    • 方法中不能再定义方法

  7. 构建对象数组(拿一个名为Student的类为列)

    • 声明一个类的数组,Student[ ] s = new Student[20];

    • 接着需要遍历这个数组依次为数组中的元素声明为类的对象,然后给对象的属性赋值,*否则会空指针异常

      Student[] stu = new Student[20];
      for (int i=0;i<stu.length;i++){
          stu[i] = new Student();
      }
      
  8. 匿名对象的使用:new 类名();

    • 理解:我们创建的对象,没有显式的赋给一个变量名。即为匿名对象
    • 特征:匿名对象只能调用一次。
    • 使用:将匿名对象当做方法的参数传入方法,相当于方法的形参会指向匿名对象的地址

六、再谈方法

  1. 方法的重载

    • 概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。

    • 特点:与返回值类型无关,只看参数列表,且参数列表必须不同。 (参数个数或参数类 型)。调用时, 根据方法参数列表的不同来区别。

    • 示例:

      //返回两个整数的和
      int add(int x,int y){return x+y;}
      //返回三个整数的和
      int add(int x,int y,int z){return x+y+z;}
      //返回两个小数的和
      double add(double x,double y){return x+y;}
      
    • 判断是否是重载:

      跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系

    • 通过对象调用方法时,如何确定某一个指定的方法:方法名+参数列表

  2. 可变形参的方法

    JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。

    • 格式:数据类型 变量名

    • 当调用可变形参的方法时,传入的参数的个数可以是0个、1个、2个……类型必须和形参类型相同

    • 当可变个数形参的方法与本类中方法名相同,形参不同的方法构成重构

    • 当可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载,不能共存

      public void method(String...str){}
      public void method(String[] str){}
      
    • 可变个数形参在方法的形参中,必须声明在末尾

    • 可变个数形参在方法的形参中,最多只能声明一个可变形参

  3. 方法参数的值传递机制(形参的传递机制:值传递)

    • 变量的赋值:

      如果变量是基本数据类型,此时赋值的是变量所保存的数据值

      如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值

    • 方法形参的传递机制:值传递

      如果参数是基本数据类型,此时实参赋给形参的是实参实际存储的数据值,因此形参的改变不影响实参。

      如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值

      因此想要通过形参改变实参存储的值,如果是基本数据类型,需要先包装成引用数据类型,然后传入形参

  4. 递归方法

    方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。

    斐波那契数列 : 规律:一个数等于前两个数之和

    汉诺塔

    快排


七、OOP特征一:封装与隐藏

隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说, 把该隐藏的隐藏起来,该暴露的暴露出来。 这就是封装性的设计思想。

  1. 问题的引入:

    当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private).此时,针对于属性就体现了封装性。

  2. 封装性的体现:(不是说这就是封装性)

    我们将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值

    其他的体现:① 如上 ② 不对外暴露的私有的方法 ③ 单例模式(构造器里说) …

  3. 封装性的体现,需要权限修饰符来配合。

    • Java规定的4种权限(权限从小到大排列):private、缺省、protected 、public

      修饰符类内部同一个包不同包的子类同一个工程
      privateYes
      (缺省)YesYes
      protectedYesYesYes(涉及到继承性)
      publicYesYesYesYes
    • 具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部

    • 对于class的权限修饰只可以用public和default(缺省)。
      public类可以在任意地方被访问。
      default类只可以被同一个包内部的类访问。

    • 总结封装性:

      Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。

    • 以后一般属性都设置成私有的


八、类的成员之三:构造器Constructor(任何一个类都有构造器)

  1. 构造器的作用:

    Person p = new Person(); //Person()就称为构造器

    • 创建对象
    • 初始化对象的属性(在new的时候传入参数,对属性进行赋值,然后创建的对象的属性就被赋值了)
  2. 说明:

    • 如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器,权限和类的权限相同
    • 定义构造器的格式:权限修饰符 类名(形参列表){}
    • 一个类中可以调用多个构造器(构造器的重载)
    • 一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
    • 一个类中,至少会有一个构造器,如果没有显示定义,系统会默认一个空参的构造器
  3. JavaBean

    • JavaBean是一种Java语言写成的可重用组件。

    • 所谓javaBean,是指符合如下标准的Java类:

      • 类是公共的

      • 有一个无参的公共的构造器

      • 有属性,且有对应的get、 set方法

    • 用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、 Servlet、其他JavaBean、 applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。

  4. UML类图
    在这里插入图片描述


九、关键字:this

  1. this可以用来修饰、调用:属性、方法、构造器

  2. this修饰属性和方法:

    • this理解为:当前对象或当前正在创建的对象

    • 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。

    • 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的用"this.变量"的方式,表明此变量是属性,而非形参。

  3. this调用构造器

    • ① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
    • ② 构造器中不能通过"this(形参列表)"方式调用自己
    • ③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"
    • ④ 规定:"this(形参列表)"必须声明在当前构造器的首行
    • ⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器

十、关键字:package、import

  1. package关键字的使用:

    ①为了更好地实现项目中类的管理,提供包的概念

    ②使用package声明类或接口所属的包,声明在源文件的首行

    ③包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”

    ④每"."一次,就代表一层文件目录。

    ⑤补充:同一个包下,不能命名同名的接口、类。不同的包下,可以命名同名的接口、类。

  2. JDK中主要的包介绍

    ① java.lang----包含一些Java语言的核心类, 如String、 Math、 Integer、 System和Thread, 提供常用功能
    ② java.net----包含执行与网络相关的操作的类和接口。
    ③ java.io ----包含能提供多种输入/输出功能的类。
    ④ java.util----包含一些实用工具类, 如定义系统特性、 接口的集合框架类、 使用与日期日历相关的函数。
    ⑤ java.text----包含了一些java格式化相关的类
    ⑥ java.sql----包含了java进行JDBC数据库编程的相关类/接口
    ⑦ java.awt----包含了构成抽象窗口工具集(abstract window toolkits) 的多个类, 这些类被用来构建和管理应用程序的图形用户界面(GUI)。 B/S C/S

  3. MVC设计模式:

    • MVC是常用的设计模式之一,将整个程序分为三个层次: 视图模型层,控制器层,与数据模型层。 这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。

    • 模型层 model 主要处理数据:

      数据对象封装 model.bean/domain

      数据库操作类 model.dao

      数据库 model.db

    • 视图层 view 显示数据 :

      相关工具类 view.utils

      自定义view view.ui

    • 控制层 controller 处理业务逻辑

      应用界面相关 controller.activity
      存放fragment controller.fragment
      显示列表的适配器 controller.adapter
      服务相关的 controller.service
      抽取的基类 controller.base

  4. import 关键字的使用

    ① 在源文件中显示的使用import结构导入指定包下的类、接口

    ② 声明在包的声明和类的声明之间

    ③ 可以使用import xxx.*的方式,表示导入xxx包下的所有结构

    ④ 如果使用的类或接口是java.lang包下定义的,则可以省略import结构

    ⑤ 如果使用的类或接口是本包下定义的,也可以省略import结构

    ⑥ 如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名(包.类)的方式显示。

    ⑦ 使用"xxx.*"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入

    ⑧ import static:导入指定类或接口中的静态结构:属性或方法。不能只写到类,要写到结构


十一、OOP特征二:继承性

  1. 继承性的好处:

    ① 减少了代码的冗余,提高了代码的复用性

    ② 便于功能的扩展

    ③ 为之后多态性的使用,提供了前提

  2. 继承性的格式

    class A extends B{}

    A:子类、派生类、subclass B:父类、超类、基类、superclass

  3. 体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。可以通过其他方法间接调用,如对私有的属性可以使用get/set方法来间接调用

  4. 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。

    • 子类和父类的关系,不同于子集和集合的关系。子类的功能会更丰富。
    • extends:延展、扩展
  5. Java中关于继承性的规定:

    • 一个类可以被多个子类继承。
    • Java中类的单继承性:一个类只能有一个父类
    • 子父类是相对的概念。
    • 类可以被多层继承,子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
    • 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
  6. 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类

    所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类,意味着,所有的java类具有java.lang.Object类声明的功能。

  7. Debug:

    step over F8:一条一条代码的执行

    step into F7:进入待执行的代码调用的方法内部

    Drop Frame :返回调用方法的那一行

    step out shift+F8:跳出方法,并进入下一行

    Run to Cursor Alt+F9:执行到下一个断点处,如果没有执行到结束


十二、方法的重写(override)

  1. 定义:子类继承父类以后,可以对父类中同名同参数的方法进行改造,在程序执行时,子类对象中与父类同名重写后的子类的方法将覆盖父类的方法。

  2. 区分方法的重载与重写:

    重载可以发生在一个类中,重写发生在继承类之间。

    重载是方法名相同参数的类型或个数不同,且对权限和返回值类型没有要求,重写是方法子类中的方法与父类中的方法同名同参数,但是方法体不同,且子类的访问权限不小于父类的权限,返回值类型必须与父类相同或为父类返回值类型的子类

  3. 重写的规定:

    ① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同

    ② 子类重写的方法的权限修饰符的权限不小于父类被重写的方法的权限修饰符,特别地,子类不能重写父类中权限为private的方法

    ③ 返回值类型:

    ​ 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void

    ​ 父类被重现的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类型或者A类的子类

    ​ 父类被重现的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型只能是相同的数据类型

    ④ 异常的类型:子类重写的方法抛出的异常类型不大于父类被重写的方法爆出的异常类型(具体在异常时再说)

    ⑤ 子类和父类中同名同参数的方法要么都声明是非static的(考虑重写),要么是static的(不是重写)


十三、四种访问权限修饰符

Java规定的4种权限(权限从小到大排列):private、缺省、protected 、public

修饰符类内部同一个包不同包的子类(继承不同包的父类)同一个工程下不同包非子类
privateYes
(缺省)YesYes
protectedYesYesYes
publicYesYesYesYes

十四、关键字:supper

  1. supper理解为:当前对象的父类的

  2. supper可以用来调用属性、方法构造器

  3. supper的使用:

    • 我们可以在子类的方法或构造器中,通过使用supper.属性或supper.方法,显式地调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."

    • 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。supper不能直接调用private的属性

    • 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的

      使用"super.方法"的方式,表明调用的是父类中被重写的方法。

    • super关键字搜索的顺序是先去直接父类搜索没找到再去间接父类去找

  4. supper调用构造器

    • 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
    • "super(形参列表)"的使用,必须声明在子类构造器的首行!
    • 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
    • 在构造器的首行,没有显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的是父类中空参的构造器:super(),因此父类必须有一个空参构造器
    • 在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器,最多有n-1个构造器使用了this(),至少有一个类使用了"super(形参列表)

十五、 子类对象实例化过程

  1. 从结果上来看:(继承性)

    子类继承父类以后,就获取了父类中声明的属性或方法。

    创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。

  2. 从过程上来看:

    当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。

  3. 明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。


十六、OOP特征三:多态性

  1. 多态性:对象的多态性:父类的引用指向子类的对象(父类 对象名 = new 子类(); )

  2. 多态的使用:虚拟方法的调用

    • 子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。

    • 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,如果该方法在子类中被重写,我们实际执行的是子类重写父类的方法。

    • 总结:

      编译时:要查看引用变量所声明的类中是否有所调用的方法。因此不能调用子类所特有的方法
      运行时: 调用实际new的对象所属的类中的重写方法。

      编译看左边,运行看右边(父类 对象名 = new 子类(); )

  3. 多态性使用的前提:

    ① 具有类的继承关系,② 要有方法的重写

  4. 对象的多态性只适用于方法,不适用于属性(编译和运行都看左边)

  5. 从编译和运行的角度看重载和多态情况下的重写:

    • 重载,是指允许存在多个同名方法,而这些方法的参数不同。 编译器根据方法不同的参数表, 对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。 它们的调用地址在编译期就绑定了。 Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。

      所以: 对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定” ;

    • 而对于多态,只有等到方法调用的那一刻, 解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定” 。

  6. 多态性的作用: 提高了代码的通用性,常称作接口重用

  7. instanceof 的使用:

    有了对象的多态性以后,内存的堆空间中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致在编译时,只能识别父类中声明的属性和方法,子类特有的属性和方法不能调用。

    因为变量存储的地址的首端是存储了所声明类型的字段的,因此只能调用所声明类型的方法

    如何才能调用子类特有的属性和方法?

    • 向下转型:使用强制类型转换符 子类名 对象名 = (子类名)被声明为父类的多态子类对象,使用强转时可能出现ClassCastException的异常

    • 采用instanceof 关键字:

      a instanceof A:判断对象a是否是类A的实例,如果是,返回true,如果不是,返回false

      使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。

      如果 a instanceof A返回true,则 a instanceof B也返回true.其中,类B是类A的父类。
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jWxY2MCB-1589898243658)(C:\Users\29457\AppData\Roaming\Typora\typora-user-images\1588929309315.png)]


十七、Object类的使用

  1. 一个类中没有显式指明父类,则默认他的直接父类为Object类,是所有类的根父类

  2. 如果在类的声明中未使用extends关键字指明其父类, 则默认父类为java.lang.Object类

  3. Object类中的功能(属性、方法)具有通用性

    属性:没有 构造器:有一个空参构造器

  4. 方法可以看官方文档

    equals() / toString() / getClass() /hashCode() / clone() / finalize()(内存垃圾自动回收) / wait() / notify() / notifyAll()

  5. ==和equals()的区别:

    • ==的使用

      ① 可以使用在基本数据类型变量和引用数据类型变量中

      ② 比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同,比如整型和浮点型)

      ③ 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体,对于String如果没有写成new的形式,而是直接赋值,则两个变量==返回为true,因为String存放在常量池里,在常量池总中会复用值相等的字符串,即他们在常量池里是同一个字符串。

      ④ 用“==”进行比较时, 符号两边的数据类型必须兼容(可自动转换的基本数据类型除外), 否则编译出错

    • equals()的使用:

      ① Object类中定义的equals()和的作用是相同的,比较两个对象的地址值是否相同.即两个引用是否指向同一个 对象实体 源码中用比较两个对象是否相等

      ② 特例:当用equals()方法进行比较时, 对类File、 String、 Date及包装类(Wrapper Class) 来说, 是比较类 型及内容而不考虑引用的是否是同一个对象; 原因是在这些类中重写了Object类的equals()方法。

      ③ 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写。重写的原则:比较两个对象的实体内容是否相同。在IDEA中可以通过generate自动生成重写的equals()方法

      //手动重写equals()方法;
      	public boolean equals(Object obj) {		
      		System.out.println("Customer equals()....");
      		if (this == obj) {
                 return true;
             }		
      		if(obj instanceof Customer){
      			Customer cust = (Customer)obj;
      //			//比较两个对象的每个属性是否都相同
      		     return this.age == cust.age && this.name.equals(cust.name);
      	     }else{
      			return false;}
      
  6. toString()方法的使用:

    ① toString()方法在Object类中定义, 其返回值是String类型, 返回类名和它的引用地址。当我们输出一个对象的引 用时,实际上就是调用当前对象的toString()

    ② Object类中toString()的定义:

    public String toString() {
    return getClass().getName() + “@” + Integer.toHexString(hashCode()); toHexString()转换为16进制
    }

    ③ 像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回"实体 内容"信息

    ④ 自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容",IDEA的generate可以自动生成,且 此时的toString()也可以省略

    ⑤ 在进行String与其它类型数据的连接操作时, 自动调用toString()方法, 基本类型数据转换为String类型时, 调用 了对应包装类的toString()方法


十八、包装类(Wrapper)的使用

  1. 单元测试方法的使用:可以分单元测试代码

    Idea进行Junit单元测试:

    • 创建Java类,进行单元测试。

      此时的Java类要求:① 此类是public的 ②此类提供公共的无参的构造器

    • 此类中声明单元测试方法。

      此时的单元测试方法:方法的权限是public,没有返回值,没有形参

    • 此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;

    • 声明好单元测试方法以后,就可以在方法体内测试相关的代码。

    • 写完代码以后,可以进行右键运行代码

    • 说明:

      如果执行结果没有任何异常:绿条

      如果执行结果出现异常:红条

  2. 针对八种基本数据类型定义相应的引用类型—包装类(封装类)

    基本数据类型包装类
    byteByte
    shortShort
    intInteger
    longLong
    floatFloat
    doubleDouble
    booleanBoolean
    charCharacter
    • 基本数据类型包装成包装类的实例 —装箱:

      ① 通过包装类的构造器实现: int i = 500; Integer t = new Integer(i);

      ② 还可以通过字符串参数构造包装类对象:Float f = new Float(“4.56”); 这种方法必须使括号里的字符串的值符合声明的变量类型,boolean类型的不一定非得是"true"或"false",其他任意的都可以只是返回的默认为false

    • 获得包装类对象中包装的基本类型变量 —拆箱:

      调用包装类的.xxxValue()方法:boolean b = bObj.booleanValue();

    • JDK1.5之后,支持自动装箱,自动拆箱。但类型必须匹配。前面两种不需要掌握使用自动装拆箱就可以

      自动装箱是只需要int i = 500; Integer t = i; 不用再手动写new了

      **注意:**Integer内部定义了IntegerCache结构,它内部定义了一个保存 -128127范围的整数的数组,如果我们使用自动装箱的方式给Integer赋值的范围在-128127范围内时,可以直接使用数组中的元素,而不用直接去new

      自动拆箱是:boolean b = bObj;直接赋值

  3. 基本数据类型和包装类----->String类型:

    ① 与“”(一个空的字符串)进行连接运算

    ② 调用String中重载的valueOf(Xxx xxx)

    float f1 = 12.3f; Double d1 = 12.4; String str1 = String.valueOf(f1); String str2 = String.valueOf(d1);

  4. String类型----->基本数据类型和包装类

    调用包装类的paraseXxx(String s); 除了boolean外,其他类型在转换时候要确保能转,否则会出现异常


十九、关键字:static

  1. static静态的,可以用来修饰:属性、方法、代码块、内部类,不能修饰构造器

  2. 使用static修饰属性:静态变量或类变量

    ① 属性按是否使用static修饰,分为:静态属性 vs 非静态属性(实例变量)

    • 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
    • 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。

    ② 使用static修饰属性的其他说明:

    • 静态变量随类的加载而加载,即静态变量的加载要早于对象的创建,可以通过 ”类.静态变量“的方式调用

    • 由于类只会加载一次,则静态变量在内存中只会存在一份,存在方法区的静态域中

    • 静态属性举例:System.out Math.PI

    • 静态变量、静态方法实例变量、非静态方法
      可以调用不能调用
      对象可以调用可以调用
  3. 使用static修饰方法:称为类方法、静态方法

    ① 随着类的加载而加载,可以通过“类.静态方法”的方式调用

    ② 静态方法中,只能调用静态的方法或属性,非静态方法既可以调用静态的方法或属性,也可调非静态的

  4. **注意:**在静态的方法内,不能使用this关键字或super关键字

  5. 开发中,如何确定一个属性是否要声明为static的?

    • 属性不会随着对象的不同而不同
    • 类中的常量也常常声明为static

    开发中,如何确定一个属性是否要声明为static的?

    • 操作静态属性的方法,通常设置为static的
    • 工具类中的方法,习惯上声明为static,比如Math、Arrays
  6. 单例(Singleton)设计模式:

    • 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。 设计模免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。 ”套路”

    • 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,类内部产生的该类对象的变量也必须定义成静态的。 这样在类外部创建对象都是同一个对象,即多个变量指向同一对象。

    • 实现方式:饿汉式 和 懒汉式

      public class SingletonTest1 {
      	public static void main(String[] args) {
      		Bank bank1 = Bank.getInstance();
      		Bank bank2 = Bank.getInstance();
      		
      		System.out.println(bank1 == bank2);
      	}
      }
      //饿汉式
      class Bank{
      	//1.私有化类的构造器
      	private Bank(){		
      	}	
      	//2.内部创建类的对象
      	//4.要求此对象也必须声明为静态的
      	private static Bank instance = new Bank();	
      	//3.提供公共的静态的方法,返回类的对象
      	public static Bank getInstance(){
      		return instance;
      	}
      }
      //懒汉式
      class Order{	
      	//1.私有化类的构造器
      	private Order(){	
      	}	
      	//2.声明当前类对象,没有初始化
      	//4.此对象也必须声明为static的
      	private static Order instance = null;	
      	//3.声明public、static的返回当前类对象的方法
      	public static Order getInstance(){		
      		if(instance == null){		
      			instance = new Order();		
      		}
      		return instance;
      	}	
      }
      
    • 区分饿汉式和懒汉式:

      饿汉式:坏处:一开始就创建了,对象加载时间过长 好处:线程安全的

      懒汉式:好处:延迟对象的创建 目前写法的坏处:线程不安全 —>讲多线程时修改

    • 单例模式的优点:

      由于单例模式只生成一个实例, 减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

    • 单例模式的应用场景:

      ① 网站的计数器,一般也是单例模式实现,否则难以同步。
      ② 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作, 否则内容不好追加。
      ③ 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
      ④ 项目中, 读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
      ⑤ Application 也是单例的典型应用
      ⑥ Windows的Task Manager (任务管理器)就是很典型的单例模式
      ⑦ Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。


二十、理解main方法的语法

  1. main()方法的使用说明:

    ① main()方法作为程序的入口;

    ② main()方法也是一个普通的静态方法,所以它在所在类中只能直接调用所在类中静态的方法或属性;其他类也可以通过main方法所在的类来调用main方法

    ③ main()方法可以作为我们与控制台交互的方式。IDEZ通过右键有个"类名.main()",里面有个program argumments,里面可以设置main方法中传入的字符串数组,在cmd中可以先通过javac 类.java 编译,然后通过

    java 类名+空格+所要传入的一系列字符串


二十一、类的成员之四:代码块

  1. 代码块的作用:用来初始化类、对象。

  2. 代码块如果有修饰的话,只能使用static

  3. 静态代码块:static{ }

    • 内部可以有输出语句,只能调用静态的属性和方法
    • 随着类的加载而执行,而且只会执行一次
    • 作用:初始化类的信息,如静态属性
    • 一个类中定义了多个静态代码块,按照声明的先后顺序执行
    • 静态代码块的执行优先于非静态代码块的执行
  4. 非静态代码块 { }

    • 内部可以有输出语句,既可以调用静态的属性和方法,也可以调用非静态的属性和方法
    • 随着对象的创建而执行
    • 每创建一个对象,就执行一次非静态代码块
    • 作用:可以在创建对象时,对对象的属性进行初始化。
    • 一个类中定义了多个非静态代码块,按照声明的先后顺序执行
  5. 对属性可以赋值的位置:

    ① 默认初始化

    ② 显式初始化

    ③ 构造器中初始化

    ④ 有了对象以后,可以通过“对象.属性”或者“对象.方法”的方式进行赋值

    ⑤ 在代码块中赋值

    执行顺序:①->②/⑤->③->④

  6. 总结:由父及子、静态先行,当构造器中继承父类的构造器,且子父类中都有代码块,只要加载了类就会执行静态代码块


二十二、关键字:final(最终的)

  1. final可以用来修饰的结构:类、方法、变量

  2. final 用来修饰一个类:此类不能被其他类所继承。比如:String类、System类、StringBuffer类

  3. final 用来修饰方法:表明此方法不可以被重写。比如:Object类中getClass();

  4. final 用来修饰变量:此时的"变量"就称为是一个常量,常量一般所有字母都大写

    ① final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化

    ② final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。

  5. static final 用来修饰属性:全局常量


二十三、抽象类与抽象方法

abstract(抽象的)关键字的使用

  1. abstract可以用来修饰的结构:类、方法

  2. abstract修饰类:抽象类

    • 此类不能实例化
    • 抽象类中一定有构造器,便于子类实例化时调用(涉及到的点:子类对象实例化的全过程)
    • 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
  3. abstract修饰方法:抽象方法

    • 抽象方法只有方法的声明,没有方法体
    • 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
    • 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
    • 若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
  4. abstract使用上的注意点:

    • abstract不能用来修饰:属性、构造器等结构
    • abstract不能用来修饰私有方法、静态方法、final的方法、final的类
  5. 匿名对象和抽象类的匿名子类

    匿名对象:在调用方法时,直接传入new 类名()

    抽象类的匿名子类:Person p = new Person(){ //里面写重写的抽象类的抽象方法 } Person是一个抽象类

  6. 模板方法的设计模式(抽象的应用)

    在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。


二十四、接口(interface)

  1. 接口的理解: 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。 继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。

  2. 接口的特点:

    ① 接口使用interface来定义 public interface 接口名{}

    ② 接口和类是并列关系, 或者可以理解为一种特殊的类。 从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前), 而没有变量和方法的实现。

    ③ 如何定义接口:定义接口中的成员

    • JDK7及以前:只能定义全局常量和抽象方法,全局常量:public static final的.但是书写时,可以省略不写,抽象方法:public abstract的

    • JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法

    静态方法:public static void 方法名(){} 接口中定义的静态方法,只能通过接口调用,实现类调用不了

    默认方法:public default void 方法名(){} 需要通过构造实现类的对象来调用,实现类可以重写接口中的默认方法,调用时会调用重写后的方法。

    • 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下,默认调用的是父类中同名同参数的方法。(类优先原则)

    • 如果实现类实现的多个接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下,则会报错,接口冲突,就必须在实现类中重写此方法

    • 如何在实现类的方法中调用接口中被重写的方法:接口名.super.方法名();

    ④ 接口中不能定义构造器的!意味着接口不可以实例化

    ⑤ Java开发中,接口通过让类去实现(implements)的方式来使用.

    ​ 定义Java类的语法格式: 先写extends,后写implements,

    ​ class SubClass extends SuperClass implements InterfaceA{ }

    ​ 如果实现类实现了接口中所有的抽象方法,则此实现类就可以实例化(类似于抽象)

    ​ 如果实现类没有全部实现接口中所有的抽象方法,则此实现类仍为一个抽象类。

    ⑥ Java类可以实现多个接口 —>弥补了Java单继承性的局限性

    ​ 格式:class AA extends BB implements CC,DD,EE

    ⑦ 接口与接口之间可以继承,而且可以多继承,继承方式和类相同,使用extends,且extends后面可以写多个接口

    ⑧ 与继承关系类似,接口与实现类之间存在多态性,接口的引用指向实现类的对象

    ⑨ 接口的主要用途就是被实现类实现。(面向接口编程)

    public class USBTest {
    	public static void main(String[] args) {		
    		Computer com = new Computer();
    		//1.创建了接口的非匿名实现类的非匿名对象
    		Flash flash = new Flash();
    		com.transferData(flash);
    		//2. 创建了接口的非匿名实现类的匿名对象
    		com.transferData(new Printer());	
    		//3. 创建了接口的匿名实现类的非匿名对象
    		USB phone = new USB(){
    			@Override
    			public void start() {
    				System.out.println("手机开始工作");
    			}
    			@Override
    			public void stop() {
    				System.out.println("手机结束工作");
    			}
    		};
    		com.transferData(phone);
    		
    		//4. 创建了接口的匿名实现类的匿名对象
    		com.transferData(new USB(){
    			@Override
    			public void start() {
    				System.out.println("mp3开始工作");
    			}
    			@Override
    			public void stop() {
    				System.out.println("mp3结束工作");
    			}
    		});
    	}
    }
    
    class Computer{
    	public void transferData(USB usb){//USB usb = new Flash();多态性的体现
    		usb.start();
    		System.out.println("具体传输数据的细节");
    		usb.stop();
    	}	
    }
    
    interface USB{
    	//常量:定义了长、宽、最大最小的传输速度等
    	void start();
    	void stop();	
    }
    
    class Flash implements USB{
    
    	@Override
    	public void start() {
    		System.out.println("U盘开启工作");
    	}
    	@Override
    	public void stop() {
    		System.out.println("U盘结束工作");
    	}	
    }
    
    class Printer implements USB{
    	@Override
    	public void start() {
    		System.out.println("打印机开启工作");
    	}
    	@Override
    	public void stop() {
    		System.out.println("打印机结束工作");
    	}
    }
    
  3. 代理模式:

    首先有一个接口,然后定义两个类来实现这个接口Object,其中一个为被代理类ObjectImpl,另一个为代理类ProxyObject,然后通过代理类的对象来对被代理类的对象进行控制

  4. 接口与抽象类的对比:

    No.区别点抽象类接口
    1定义包含抽象方法的类主要是抽象方法和全局常量的集合
    2组成构造方法、抽象方法、普通方法、 常量、变量常量、抽象方法、 (jdk8.0:默认方法、静态方法)
    3使用子类继承抽象类(extends)子类实现接口(implements)
    4关系抽象类可以实现多个接口接口不能继承抽象类,但允许继承多个接口
    5常见设计模式模板方法简单工厂、工厂方法、代理模式
    6对象都通过对象的多态性产生实例化对象
    7局限抽象类有单继承的局限接口没有此局限
    8实际作为一个模板是作为一个标准或是表示一种能力
    9选择如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限

二十五、类的成员之五:内部类

  1. Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类

  2. 内部类的分类:成员内部类(静态和非静态)和局部内部类(方法内、代码块内、构造器内)

  3. 成员内部类:

    • 一方面,作为外部类的成员

      可以外部类的结构,在调用时省略了 外部类名.this.

      可以被static修饰

      可以被四种不同的权限修饰

    • 另一方面,作为一个类。

      在类内可以定义属性、方法、构造器等

      可以被final修饰,修饰后不能继承

      可以被abstract修饰,修饰后不能被实例化

  4. 关注如下问题:

    ① 如何实例化成员内部类的对象

    创建静态的成员内部类:Person.Dog dog = new Person.Dog(); 其中Person是外部类,Dog是静态成员内部类

    创建非静态的成员内部类:因为是非静态的不会随类的加载而加载,因此作为类的成员首先需要创建一个外部类的对象,具体操作如下:

    Person p = new Person();

    Person.Bird bird = p.new Bird();

    其中Person是外部类,Bird 是非静态成员内部类

    ② 如何在成员内部类中区分调用外部类的结构

    Person.this.name Person为外部类类名,name为外部类与内部类同名的属性

    ③ 开发中局部内部类的使用

    public class InnerClassTest1 {
        //返回一个实现了Comparable接口的类的对象
    	public Comparable getComparable(){
    		//创建一个实现了Comparable接口的类:局部内部类
    		//方式一:
    		class MyComparable implements Comparable{
    			@Override
    			public int compareTo(Object o) {
    				return 0;
    			}	
    		}
    		return new MyComparable();
    		//方式二:
    		return new Comparable(){
    			@Override
    			public int compareTo(Object o) {
    				return 0;
    			}	
    		};	
    	}
    }
    

a中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类

  1. 内部类的分类:成员内部类(静态和非静态)和局部内部类(方法内、代码块内、构造器内)

  2. 成员内部类:

    • 一方面,作为外部类的成员

      可以外部类的结构,在调用时省略了 外部类名.this.

      可以被static修饰

      可以被四种不同的权限修饰

    • 另一方面,作为一个类。

      在类内可以定义属性、方法、构造器等

      可以被final修饰,修饰后不能继承

      可以被abstract修饰,修饰后不能被实例化

  3. 关注如下问题:

    ① 如何实例化成员内部类的对象

    创建静态的成员内部类:Person.Dog dog = new Person.Dog(); 其中Person是外部类,Dog是静态成员内部类

    创建非静态的成员内部类:因为是非静态的不会随类的加载而加载,因此作为类的成员首先需要创建一个外部类的对象,具体操作如下:

    Person p = new Person();

    Person.Bird bird = p.new Bird();

    其中Person是外部类,Bird 是非静态成员内部类

    ② 如何在成员内部类中区分调用外部类的结构

    Person.this.name Person为外部类类名,name为外部类与内部类同名的属性

    ③ 开发中局部内部类的使用

    public class InnerClassTest1 {
        //返回一个实现了Comparable接口的类的对象
    	public Comparable getComparable(){
    		//创建一个实现了Comparable接口的类:局部内部类
    		//方式一:
    		class MyComparable implements Comparable{
    			@Override
    			public int compareTo(Object o) {
    				return 0;
    			}	
    		}
    		return new MyComparable();
    		//方式二:
    		return new Comparable(){
    			@Override
    			public int compareTo(Object o) {
    				return 0;
    			}	
    		};	
    	}
    }
    

    在局部内部类的方法中如果调用局部内部类所声明的方法中的局部变量的话,要求此局部变量声明为final的。 jdk 7及之前版本:要求此局部变量显式的声明为final的。jdk 8及之后的版本:可以省略final的声明

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值