面向对象二

146 阅读11分钟

面向对象二

1. this关键字的使用

我们在声明一个属性对应的setXxx方法时,通过形参给对应的属性赋值。如果形参名和属性名同名了,那么该如何在方法内区分这两个变量呢?

解决方案:使用this。具体来讲,使用this修饰的变量,表示的是属性。没有用this修饰的,表示的是形参。

  • this调用的结构:属性、方法;构造器

  • this调用属性或方法时,理解为:当前对象或当前正在创建的对象。

    public void setName(String name){ //当属性名和形参名同名时,必须使用this来区分
    	this.name = name;
    }
    
    public Person(String name){
        this.name = name;
    }
    
  • this(形参列表)的方式,表示调用当前类中其他的重载的构造器。

【针对于方法内的使用情况:(准确的说是非static修饰的方法)】

一般情况:我们通过对象a调用方法,可以在方法内调用'当前对象a'的属性或其他方法。 此时,我们可以在属性和其他方法前使用"this.",表示当前属性或方法所属的对象a。 但是,一般情况下,我们都选择省略此"this."结构。 特殊情况:如果方法的形参与对象的属性同名了,我们必须使用"this."进行区分。 使用this.修饰的变量即为属性(或成员变量),没有使用this.修饰的变量,即为局部变量。

【针对于构造器内的使用情况:】

一般情况:我们通过构造器创建对象时,可以在构造器内调用'当前正在创建'的对象的属性或方法。 此时,我们可以在属性和方法前使用"this.",表示当前属性或方法所属的对象。 但是,一般情况下,我们都选择省略此"this."结构。 特殊情况:如果构造器的形参与正在创建的对象的属性同名了,我们必须使用"this."进行区分。 使用this.修饰的变量即为属性(或成员变量),没有使用this.修饰的变量,即为局部变量。

this调用构造器

格式:"this(形参列表)"

我们可以在类的构造器中,调用当前类中指定的其它构造器

要求:"this(形参列表)"必须声明在当前构造器的首行

结论:"this(形参列表)"在构造器中最多声明一个

如果一个类中声明了n个构造器,则最多有n-1个构造器可以声明有"this(形参列表)"的结构

2. 面向对象的特征二:继承性

Java中声明的类,如果没有显式的声明其父类时,则默认继承于java.lang.Object

自上而下:定义了一个类A,在定义另一个类B时,发现类B的功能与类A相似,考虑类B继承于类A

自下而上:定义了类B,C,D等,发现B、C、D有类似的属性和方法,则可以考虑将相同的属性和方法进行抽取,封装到类A中,让类B、C、D继承于类A,同时,B、C、D中的相似的功能就可以删除了。

  • 继承性的好处
    • 继承的出现减少了代码冗余,提高了代码的复用性。
    • 继承的出现,更有利于功能的扩展。
    • 继承的出现让类与类之间产生了is-a的关系,为多态的使用提供了前提。
      • 父类更通用、更一般,子类更具体
  • Java中继承性的特点
    • 局限性:类的单继承性。后续我们通过类实现接口的方式,解决单继承的局限性。
    • 支持多层继承,一个父类可以声明多个子类。
    • 子类就获取到了父类中声明的所有的属性和方法。
    • 但是,由于封装性的影响,可能子类不能直接调用父类中声明的属性或方法。
    • 子类在继承父类以后,还可以扩展自己特有的功能(体现:增加特有的属性、方法) extends:延展、扩展、延伸
    • 子类和父类的理解,要区别于集合和子集
    • 不要为了继承而继承。在继承之前,判断一下是否有is a的关系。
  • 基础:class A extends B{}
  • 理解:子类就获取了父类中声明的全部的属性、方法。可能受封装性的影响,不能直接调用。

补充

Java是支持多层继承。

概念:直接父类、间接父类

Java中的子父类的概念是相对的。

Java中一个父类可以声明多个子类。反之,一个子类只能有一个父类(Java的单继承性)

3. 方法的重写(override / overwrite)

子类在继承父类以后,就获取了父类中声明的所有的方法。但是,父类中的方法可能不太适用于子类,换句话说,子类需要对父类中继承过来的方法进行覆盖、覆写的操作。

方法的重载与重写的区别?

  • 方法的重载:“两同一不同”

    • 父类被重写的方法子类重写的方法方法名和形参列表必须相同。
  • 方法的重写:

    • 前提:类的继承关系
    • 子类对父类中同名同参数方法的覆盖、覆写。
  • 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符

    • 子类不能重写父类中声明为private权限修饰的方法。
  • 关于返回值类型:

    • 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型必须是void
    • 父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须与被重写的方法的返回值类型相同。
    • 父类被重写的方法的返回值类型是引用数据类型(比如类),则子类重写的方法的返回值类型可以与被重写的方法的返回值类型相同 或 是被重写的方法的返回值类型的子类
  • 子类重写的方法抛出的异常类型可以与父类被重写的方法抛出的异常类型相同,或是父类被重写的方法抛出的异常类型的子类。

4. super关键字的使用

  • super可以调用的结构:属性、方法;构造器

    • 子类继承父类以后,我们就可以在子类的方法或构造器中,调用父类中声明的属性或方法。(满足封装性的前提下)
    • 需要使用"super."的结构,表示调用父类的属性或方法。
  • super:父类的

  • super调用父类的属性、方法:

    • 如果子父类中出现了同名的属性,此时使用super.的方式,表明调用的是父类中声明的属性。
    • 子类重写了父类的方法。如果子类的任何一个方法中需要调用父类被重写的方法时,需要使用super.
  • super调用构造器:

    • 在子类的构造器中,首行要么使用了"this(形参列表)",要么使用了"super(形参列表)"。

一般情况下,我们可以考虑省略"super."的结构。但是,如果出现子类重写了父类的方法或子父类中出现了同名的属性时,则必须使用"super."的声明,显式的调用父类被重写的方法或父类中声明的同名的属性。

总结一下:

① 子类继承父类时,不会继承父类的构造器。只能通过“super(形参列表)”的方式调用父类指定的构造器。

② 规定:“super(形参列表)”,必须声明在构造器的首行。

③ 我们前面讲过,在构造器的首行可以使用"this(形参列表)",调用本类中重载的构造器, 结合②,结论:在构造器的首行,"this(形参列表)" 和 "super(形参列表)"只能二选一。

④ 如果在子类构造器的首行既没有显示调用"this(形参列表)",也没有显式调用"super(形参列表)", 则子类此构造器默认调用"super()",即调用父类中空参的构造器。

⑤ 由③和④得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。只能是这两种情况之一。

⑥ 由⑤得到:一个类中声明有n个构造器,最多有n-1个构造器中使用了"this(形参列表)", 则剩下的那个一定使用"super(形参列表)"。

5. 子类对象实例化的全过程

代码举例:

class Creature{ //生物类
    //声明属性、方法、构造器
}

class Animal extends Creature{ //动物类

}

class Dog extends Animal{ //狗类

}

class DogTest{
    public static void main(String[] args){
        Dog dog = new Dog();
        dog.xxx();
        dog.yyy = ...;
    }
}
  • 结果上来说:体现为类的继承性

    • 当我们创建子类对象后,子类对象就获取了其父类中声明的所有的属性和方法,在权限允许的情况下,可以直接调用
  • 过程上来说:子类调用构造器创建对象时,子类的构造器一定会直接或间接的调用其父类的构造器,以及父类的父类的构造器,...,直到调用到Object()的构造器。

    • 正因为我们调用过子类所有的父类的构造器,所以我们就会将父类中声明的属性、方法加载到内存中,供子类的对象使用

6. 面向对象的特征三:多态性

理解为一个事物的多种形态

体现:

  • 子类对象的多态性:父类的引用指向子类的对象。(或子类的对象赋给父类的引用)

    • 比如:Person p2 = new Man();
  • 广义上的理解:子类对象的多态性、方法的重写;方法的重载

    狭义上的理解:子类对象的多态性。

  • 多态性的使用前提:① 要有类的继承关系 ② 要有方法的重写

  • 格式:Object obj = new String("hello"); 父类的引用指向子类的对象。

  • 多态的好处:减少了大量的重载的方法的定义;开闭原则

    • 举例:public boolean equals(Object obj)
    • 多态,无处不在!
  • 多态的弊端:

    • 在多态的场景下,我们创建了子类的对象,也加载了子类特有的属性和方法。但是由于声明为父类的引用,导致我们没有办法直接调用子类特有的属性和方法。
  • 多态的使用:虚拟方法调用。“编译看左边,运行看右边”。属性,不存在多态性。

  • 多态的逆过程:向下转型,使用强转符()。

    • 为了避免出现强转时的ClassCastException,建议()之前使用instanceOf进行判断。
  • 多态的适用性:适用于方法,不适用于属性。

instanceof的使用

    1. 建议在向下转型之前,使用instanceof进行判断,避免出现类型转换异常
    1. 格式: a instanceOf A : 判断对象a是否是类A的实例。
    1. 如果a instanceOf A 返回true,则:

      a instanceOf superA 返回也是true。其中,A 是superA的子类。

7. Object类的使用

  1. Object类的说明

明确:java.lang.Object

任何一个Java类(除Object类)都直接或间接的继承于Object类

Object类称为java类的根父类

Object类中声明的结构(属性、方法等)就具有通用性。

Object类中没有声明属性

Object类提供了一个空参的构造器

重点关注:Object类中声明的方法

  • 根父类
  • equals()的使用

java.lang.Object类中equals()的定义: public boolean equals(Object obj) { return (this == obj); }

  • 重写和不重写的区别
    • 实际开发中,针对于自定义的类,常常会判断两个对象是否equals(),而此时主要是判断两个对象的属性值是否相等。所以:我们要重写Object类的equals()方法。

如何重写:

手动自己实现

调用IDEA自动实现(推荐)

  • == 和 equals()

    • == 运算符 ①使用范围:基本数据类型、引用数据类型 ②基本数据类型:判断数据值是否相等

    • equals():方法

      使用范围:只能使用在引用数据类型上。 具体使用:对于类来说,重写equals()和不重写equals()的区别。

  • toString()的使用

    • Object中toString()调用后,返回当前对象所属的类和地址值。
    • 开发中常常重写toString(),用于返回当前对象的属性信息。