09、面向对象(继承、super、方法的重写、final关键字、多态、向下转型、抽象类概述)

继承

1、基本概述

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

通过extends关键字可以实现类与类的继承
class 子类名 extends 父类名 {}

单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。

继承的好处

  • 提高了代码的复用性
  • 提高了代码的维护性
  • 让类与类之间产生了关系,是多态的前提

继承的弊端

  • 类的耦合性增强了

开发的原则:高内聚、低耦合。

耦合:完成一个功能,需要依赖别的类;

内聚:自己完成某件事情的能力。

package org.westos.demo1;

/**
 * 1、继承的概述
 * Animal父类,将多个类的相同属性和行为抽取到父类中
 * @author lwj
 * @date 2020/4/17 13:42
 */
public class Animal {
    /** default缺省的权限修饰符,修饰成员变量,在本包下的类可以访问 */
    String name;
    int age;

    public void eat() {
        System.out.println("吃饭");
    }

    public void sleep() {
        System.out.println("睡觉");
    }
}

/**
 * 继承Animal类,继承了Animal所有的非私有成员
 */
class Cat extends Animal {
    public void catchMouse() {
        System.out.println("猫抓老鼠");
    }
}

/**
 * 继承Animal类,继承了Animal所有的非私有成员
 */
class Dog extends Animal {
    public void actAsDoorkeeper() {
        System.out.println("看家护院");
    }
}

class Demo {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.name = "小咪";
        cat.age = 2;
        System.out.println(cat.name);
        System.out.println(cat.age);
        cat.eat();
        cat.sleep();
        cat.catchMouse();
        System.out.println("=============");
        Dog dog = new Dog();
        dog.name = "旺财";
        dog.age = 3;
        System.out.println(dog.name);
        System.out.println(dog.age);
        dog.eat();
        dog.sleep();
        dog.actAsDoorkeeper();
    }
}

继承的特点

  • Java中的继承是单继承,不支持多继承;(一个类只能有一个直接父类)
  • Java支持多层继承(继承体系)

继承的注意事项

  • 子类只能继承父类的所有非私有成员(成员方法和成员变量)
  • 构造方法不参与继承,子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法
  • 不要为了父类的部分功能而去继承
  • 继承体现的是一种"is a"的关系

继承中成员变量的关系

  • 子类中的成员变量与父类中的成员变量名称不同
  • 子类中的成员变量和父类中的成员变量名称相同
    • 在子类中访问一个变量的查找顺序("就近原则")
      • 在子类的方法的局部范围找,有就使用
      • 在子类的成员范围找,有就使用
      • 在父类的成员范围找,有就使用
      • 如果还找不到,就报错。
package org.westos.demo2;

/**
 * @author lwj
 * @date 2020/4/17 14:34
 */
public class MyTest {
    public static void main(String[] args) {
        new SubClass().show();
    }
}

class SuperClass {
    int a = 10;
}

class SubClass extends SuperClass {
    int b = 20;

    public void show() {
        int c = 30;
        System.out.println(c);
        //30
        System.out.println(b);
        //20
        System.out.println(a);
        //10
    }
}

2、this与super

  • this代表本类的对象引用,访问本类成员(成员变量与成员方法)和本类构造方法;
  • super代表父类存储空间的标识,理解为父类的引用,可以操作父类的成员和父类构造方法;

使用

1、调用成员变量

  • this.成员变量 调用本类成员变量
  • super.成员变量 调用父类成员变量

2、调用构造方法

  • this(...) 调用本类构造方法
  • super(...) 调用父类构造方法

3、调用成员方法

  • this.成员方法() 调用本类成员方法
  • super.成员方法() 调用父类成员方法
package org.westos.interview;

/**
 * 继承的面试题一
 * @author lwj
 * @date 2020/4/15 17:04
 */
public class MyTest {
    public static void main(String[] args) {
        Son son = new Son();
        //初始化子类之前,必须先初始化父类,父类的构造方法在子类的构造方法之前执行
        son.show();
    }
}

class Father {
    public int num = 10;

    public Father() {
        System.out.println("父类无参构造方法执行了");
        //1
    }
}

class Son extends Father {
    public int num = 20;
    //子类的成员变量与父类的成员变量名相同

    public Son() {
        System.out.println("子类的无参构造方法执行了");
        //2
    }

    public void show() {
        int num = 30;
        System.out.println(num);
        //30,就近原则
        System.out.println(this.num);
        //20
        System.out.println(super.num);
        //10
        //super.父类成员变量
        //super.父类成员方法
        //super()父类构造方法
    }
}

3、继承中构造方法的关系

子类中所有的构造方法默认都会访问父类中空参的构造方法。

因为子类会继承父类中的数据,还有可能会使用父类中的数据,所以子类初始化之前一定要先完成父类数据的初始化。

其实,每一个构造方法的第一条语句默认都是:super()。

继承中构造方法的注意事项:

如果父类没有无参构造函数怎么办?

package org.westos.demo;

/**
 * 继承的构造方法关系
 * @author lwj
 * @date 2020/4/15 17:31
 */
public class SuperClass {
    private String name;
    private int age;

    //父类有一个有参构造,未设置无参构造方法,子类直接报错,没有默认的可利用的构造方法,子类所有的构造方法默认都会访问父类的无参构造方法

    /**
     * 解决方案三:终极解决方案,在父类增加一个无参构造方法(有了有参构造方法,默认的无参构造方法消失,如果想要使用,显式增加一个无参构造)
     */
    public SuperClass() {

    }

    public SuperClass(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
package org.westos.demo;

/**
 * @author lwj
 * @date 2020/4/15 17:31
 */
public class SubClass extends SuperClass {
    private String address;
    /**
     * 解决方案一:子类的构造方法显式调用父类其他带参构造方法
     */
    public SubClass() {
        super("张三", 12);
    }

    /**
     * 解决方案二:this关键字调用本类的其他构造方法,但是该构造方法必须super()调用了父类构造方法,(子类初始化之前,必须先初始化父类)
     * @param address 地址
     */
    public SubClass(String address) {
        this();
    }
}

super(…)或者this(….)必须出现在第一条语句上,并且只能存在一个。

出现在第一句:

因为子类的初始化必须在父类的初始化之后,所以必须首先super()或者this()初始化父类,然后再初始化本类。

只能存在一个:

如果super(...)和this(...)同时存在,那么会存在重复初始化父类的数据,因为this(...)调用的本类其他构造方法,也会super(...)调用父类的构造方法。

package org.westos.interview;

/**
 * 继承的面试题二
 * @author lwj
 * @date 2020/4/15 17:09
 */
public class MyDemo {
    public static void main(String[] args) {
        new SubClass();
        //初始化子类之前,如果发现父类还没有初始化,那么必须先初始化父类
        //初始化:执行静态变量的赋值语句和静态代码块,而且父类在子类之前
        //创建对象:执行父类的构造代码块,父类的构造方法,子类的构造代码块,子类的构造方法
    }
}

class SuperClass {
    static {
        System.out.println("Super Class static block");
        //1
    }

    {
        System.out.println("Super Class Constructor block");
        //3
    }

    public SuperClass() {
        System.out.println("Super Class Constructor Method");
        //4
    }
}

class SubClass extends SuperClass {
    static {
        System.out.println("SubClass static block");
        //2
    }

    {
        System.out.println("SubClass Constructor block");
        //5
    }

    public SubClass() {
        System.out.println("SubClass Constructor Method");
        //6
    }
}

4、继承中成员方法的关系

  • 子类的方法名和父类的方法名不同;
  • 子类的方法名与父类的方法名相同
    • 通过子类调用方法
      • 首先查找子类中有没有该方法,如果有就使用
      • 再看父类中有没有该方法,有就使用
      • 如果没有就报错。
package org.westos.demo3;

/**
 * @author lwj
 * @date 2020/4/17 15:51
 */
public class MyDemo {
    public static void main(String[] args) {
        new SubClass().eat();
        //子类的eat方法
    }
}

class SuperClass {
    public void eat() {
        System.out.println("父类的eat方法");
    }
}

class SubClass extends SuperClass {
    @Override
    public void eat() {
        System.out.println("子类的eat方法");
    }
}

5、方法重写

概述

方法重写:子类中出现了和父类中一模一样的方法声明(方法名,参数列表,返回值类型),也称为方法覆盖。

方法重写的应用:

当子类需要父类的功能,但是不想采用父类的实现,想要有自己的实现时,那么子类可以重写父类的方法。

package org.westos.demo4;

/**
 * 方法重写
 * @author lwj
 * @date 2020/4/17 15:55
 */
public class MyTest {
    public static void main(String[] args) {
        new Dog().eat();
        //我要吃骨头
        new Cat().eat();
        //我要吃鱼
    }
}

class Animal {
    public void eat() {
        System.out.println("吃饭");
    }

    public void sleep() {
        System.out.println("睡觉");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("我要吃骨头");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("我要吃鱼");
    }
}

在子类的重写方法中,可以使用super.成员方法(),来沿袭父类的实现。

@Override:注解,检测该方法是否是重写父类的;

方法重写的注意事项

  • 父类中的私有方法不能被重写,因为父类私有方法子类根本就无法继承
  • 子类抛出的异常不能大于父类抛出的异常
  • 子类的访问级别不能小于父类的访问级别
  • 子类的返回值类型不能大于父类的返回值类型
  • 静态方法不参与重写

6、final关键字

为什么会有final?

由于继承中有一个方法重写的现象,而有时候我们不想让子类去重写父类的方法,对于这种情况Java就给我们提供了一个关键字:final。

概述:

final关键字就是最终的意思,可以修饰类、变量(成员变量和局部变量)和成员方法。

final关键字修饰的特点:

  • 修饰类:被修饰类不能被继承
  • 修饰方法:被修饰方法不能被重写
  • 修饰变量:被修饰的变量不能被重写赋值,因为这个量其实是一个常量
    • 基本类型:值不能改变
    • 引用类型:地址值不能改变,该引用不能再指向其他对象
package org.westos.demo5;

/**
 * @author lwj
 * @date 2020/4/17 16:41
 */
public class MyDemo {
    /** final修饰的成员变量初始化时机,<init>方法执行时期,即成员变量的赋值、构造代码块或者构造方法 */
    final Person person;
    final int num = 20;
    
    {
        person = new Person();
    }

    public MyDemo() {
        //person = new Person();
        //person = new Person();
        //final修饰的引用变量地址值不能更改,引用不能再指向其他对象
        person.name = "小白";
        person.age = 12;
        //final修饰的引用变量所指向空间的值可以改变

        //num = 30;
        //final修饰的基本类型变量值不能改变
    }

    public static void main(String[] args) {
        MyDemo myDemo = new MyDemo();
        //myDemo.person = new Person();
        //同样也是不能修改地址值
        myDemo.person.name = "大佬";
        myDemo.person.age = 22;
        //final修饰的引用类型变量的内容可以改变
        System.out.println(myDemo.person.age);
        //22
        System.out.println(myDemo.person.name);
        //大佬

        final int num = 100;
        //num = 200;
        //final修饰的基本类型局部常量值不能改变
    }
}

class Person {
    String name;
    int age;
}

多态

1、概述

多态的前提:继承和方法的重写,父类引用指向子类对象。

多态:访问成员方法(编译看左,运行看右)

package org.westos.demo;

/**
 * 1、多态的概述
 * @author lwj
 * @date 2020/4/17 17:11
 */
public class MyTest {
    public static void main(String[] args) {
        Animal animal = new Cat();
        //多态:父类引用指向子类对象
        animal.eat();
        //猫吃鱼
        //编译器看左边(父类是否有该方法),运行时看右边(执行子类重写的方法)
    }
}

class Animal {
    public void eat() {
        System.out.println("吃饭");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

多态:访问成员变量(编译看左,运行看左)

package org.westos.demo2;

/**
 * @author lwj
 * @date 2020/4/17 17:16
 */
public class MyDemo {
    public static void main(String[] args) {
        SuperClass superClass = new SubClass();
        System.out.println(superClass.num);
        //20
        //采用多态形式访问成员变量,访问的是父类的成员变量
        //访问成员变量:编译看左边,运行看左边
    }
}

class SuperClass {
    int num = 20;
}

class SubClass extends SuperClass {
    int num = 200;
}

多态:构造方法(先初始化父类,再初始化子类)

package org.westos.demo2;

/**
 * @author lwj
 * @date 2020/4/17 17:16
 */
public class MyDemo {
    public static void main(String[] args) {
        SuperClass superClass = new SubClass();
        System.out.println(superClass.num);
        //20
        //采用多态形式访问成员变量,访问的是父类的成员变量
        //访问成员变量:编译看左边,运行看左边
        
        //多态形式访问构造方法
    }
}

class SuperClass {
    int num = 20;

    public SuperClass() {
        System.out.println("父类的构造方法执行了");
        //1
    }
}

class SubClass extends SuperClass {
    int num = 200;

    public SubClass() {
        System.out.println("子类的构造方法执行了");
        //2
    }
}

多态:静态方法(编译看左,运行看左)

package org.westos.demo2;

/**
 * @author lwj
 * @date 2020/4/17 17:16
 */
public class MyDemo {
    public static void main(String[] args) {
        SuperClass superClass = new SubClass();
        System.out.println(superClass.num);
        //20
        //采用多态形式访问成员变量,访问的是父类的成员变量
        //访问成员变量:编译看左边,运行看左边

        //多态形式访问构造方法

        //多态形式访问静态方法:编译看左,运行看左
        SuperClass.staticMethod();
        //父类的静态方法
        //静态方法和静态成员好像不能用对象访问了,只能用类访问
    }
}

class SuperClass {
    int num = 20;

    public SuperClass() {
        System.out.println("父类的构造方法执行了");
        //1
    }

    public static void staticMethod() {
        System.out.println("父类的静态方法");
    }
}

class SubClass extends SuperClass {
    int num = 200;

    public SubClass() {
        System.out.println("子类的构造方法执行了");
        //2
    }

    public static void staticMethod() {
        System.out.println("子类的静态方法");
    }
}

2、多态的好处

package org.westos.demo3;


/**
 * 多态的好处:提高扩展性
 * @author lwj
 * @date 2020/4/17 17:33
 */
public class MyTest {
    public static void main(String[] args) {
        Animal animal1 = new Cat();
        Utils.test(animal1);
        //猫吃鱼
        Animal animal2 = new Dog();
        Utils.test(animal2);
        //狗吃骨头
        Animal animal3 = new Tiger();
        Utils.test(animal3);
        //老虎吃肉
    }
}

class Utils {
    private Utils() {}
	
    //面向父类编程
    public static void test(Animal animal) {
        animal.eat();
    }
}

class Animal {
    public void eat() {
        System.out.println("吃饭");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
}

class Tiger extends Animal {
    @Override
    public void eat() {
        System.out.println("老虎吃肉");
    }
}

3、向下转型

多态的弊端:不能直接调用子类特有的方法。(因为编译看左,父类如果没有子类特有的方法,那么将编译报错)

package org.westos.demo4;

/**
 * @author lwj
 * @date 2020/4/17 19:05
 */
public class MyDemo {
    public static void main(String[] args) {
        SuperClass superClass = new SubClass();
        //向上转型
        SubClass subClass = (SubClass) superClass;
        //向下转型
        subClass.test();
        //调用子类特有的方法
    }
}

class SuperClass {
    public void show() {
        System.out.println("父类方法");
    }
}

class SubClass extends SuperClass {
    @Override
    public void show() {
        System.out.println("子类方法");
    }

    public void test() {
        System.out.println("子类独有的方法");
    }
}

向下转型失败

package org.westos.demo4;

/**
 * @author lwj
 * @date 2020/4/17 19:08
 */
public class MyDemo2 {
    public static void main(String[] args) {
        Animal animal = new Cat();
        //Dog dog = (Dog) animal;
        //类型转换异常
        //Exception in thread "main" java.lang.ClassCastException: org.westos.demo4.Cat cannot be cast to org.westos.demo4.Dog
    }
}

class Animal {
    public void eat() {
        System.out.println("吃饭");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    public void catchMouse() {
        System.out.println("猫抓老鼠");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }

    public void actAsDoorKeeper() {
        System.out.println("狗看门");
    }
}

向下转型的内存图

package org.westos.demo4;

/**
 * @author lwj
 * @date 2020/4/17 19:44
 */
public class MyDemo3 {
    public static void main(String[] args) {
        Father father = new Son();
        System.out.println(father.num);
        //30
        father.show();
        //子类重写父类的show方法
        Son son = (Son) father;
        //向下转型
        System.out.println(son.num);
        //300
        son.method();
        //子类特有的方法
    }
}

class Father {
    int num = 30;

    public void show() {
        System.out.println("父类的show方法");
    }
}

class Son extends Father {
    int num = 300;

    @Override
    public void show() {
        System.out.println("子类重写父类的show方法");
    }

    public void method() {
        System.out.println("子类特有的方法");
    }
}

父类引用指向子类对象中的父类实例,当进行了向下转型后,引用指向子类对象。

4、多态题目

package org.westos.demo5;

/**
 * @author lwj
 * @date 2020/4/17 22:04
 */
public class MyTest {
    public static void main(String[] args) {
        GrandFather a = new Father();
        a.show();
        /*
        * 编译看左,GrandFather类中有show()方法,子类Father没有重写show()方法,所以执行GrandFather的show()方法,
        * show2()方法,由于Father类重写了GrandFather类的show2()方法,所以执行的是Father的show2()方法,输出"爱"
        * */

        Father b = new Son();
        b.show();
        /*
        * 编译看左,Father类中继承了GrandFather类的show()方法,所以编译不报错,Son类重写了show()方法,执行super.show()方法,
        * 执行show2()方法,Son类重写了show2()方法,执行Son类的show2()方法,输出"你"
        * */
    }
}

class GrandFather {
    public void show() {
        show2();
    }
    public void show2() {
        System.out.println("我");
    }
}

class Father extends GrandFather {
    @Override
    public void show2() {
        System.out.println("爱");
    }
}

class Son extends Father {
    @Override
    public void show() {
        super.show();
    }
    @Override
    public void show2() {
        System.out.println("你");
    }
}

抽象类

1、概述

在继承阶段的Animal类,动物的eat()方法不应该有具体的实现,应该设定为抽象方法,由子类实现该方法。

在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类。

抽象类的特点:

  • 抽象类和抽象方法必须用abstract关键字修饰
    • 抽象类格式:abstract class 类名 {}
    • 抽象方法格式:abstract void eat();
  • 抽象类不一定有抽象方法,但是有抽象方法的类一定是抽象类
  • 抽象类中可以有构造方法,但是抽象类不能进行实例化,构造方法的作用是用于子类创建对象时初始化父类的属性;
  • 抽象类不能直接实例化,可以由具体的子类实例化;(抽象类多态,多态的一种)
  • 抽象类的子类
    • 要么是抽象类
    • 要么重写抽象类中所有的抽象方法
package org.westos.demo;

/**
 * @author lwj
 * @date 2020/4/17 20:20
 */
public class MyDemo {
    public static void main(String[] args) {
        Animal animal = new Animal() {
            @Override
            public void eat() {
                System.out.println("吃东西");
            }
        };
        animal.eat();
        //吃东西
        //animal是抽象类Animal的子类(实现类)的一个实例。
        //只不过定义该子类的同时直接实例化了一个该子类的对象,并且指向了父类的引用。
    }
}

abstract class Animal {
    public Animal() {

    }

    /** 抽象方法 */
    public abstract void eat();
}

猜你喜欢

转载自www.cnblogs.com/shawnyue-08/p/12722882.html