毕向东Java基础教程-继承【上】

概述

  • 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承单独的类即可。
  • 多个类可以称为子类,单独的类称为父类或者超类。
  • 子类可以访问父类中的非私有的属性和行为。
  • 通过extends关键字让类与类之间产生继承关系。
    class SubDemo extends Demo{}
  • 继承的好处:
    继承的出现提高了代码的复用性。
    继承的出现让类与类之间产生了关系,提供了多态的前提。

特点

  • Java中支持单继承,不支持多继承
    一个子类只能有一个直接父类,否则若多个父类中有相同成员,会产生调用的不确定性。

    class SubDemo extends Demo{} //ok
    class SubDemo extends Demo1,Demo2...{} //error
    
  • Java支持多层继承(继承体系)

    class A{}
    class B extends A{}
    class C extends B{} 
    

    当要使用一个继承体系时:
    1)查看该体系中的顶层类,了解该体系的基本功能
    2)创建体系中的最子类对象,完成功能的使用

  • 什么时候定义继承?
    当类与类之间存在着所属("is a")关系的时候,就定义继承。如xxx是yyy的一种,则xxx extends yyy。或者判断父类中的功能是否子类都应该具备,若有些不具备,则不存在继承。不要仅为了获取其他类中某个功能而去继承 。

super关键字

this和super的用法很相似。

  • this代表一个本类对象的引用;super代表一个父类空间(注意并不是代表父类对象,如下代码,从未定义过父类对象,子类之所以可以获取父类的内容,是因为其持有super引用)。

  • 当本类中的成员和局部变量同名用this区分;当子父类中的成员变量同名用super区分父类(如果不使用super,则默认调用子类自己的成员变量),子类要调用父类构造函数时,可以使用super关键字。

    Example1

    public class Father 
    {    
        int num = 4;
    }
    class Son extends Father{
        int num = 5;
        void show()
        {
            System.out.println(this.num+"..."+super.num);
        }
    }
    class Demo
    {
        public static void main(String[] args)
        {
            Son s = new Son();
            s.show();
        }
    }
    

    父类比子类先加载。

注意:
问题:父类中的私有内容,子类是否具备——实际上子类对于父类中私有的成员变量是继承了的,即定义子类对象时,在堆中存有父类的私有成员变量(上图堆中fu的方框内),只是不能直接访问,所以确切地说,应该是子类中不能直接地访问父类中的私有内容。

Example2

class Person
{
    private String name;
    private int age;

    Person(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public int getAge() {return age;}
    public void setAge(int age) {this.age = age;}
}

class Student extends Person
{
    Student(String name, int age)
    {
        super(name, age);//调用父类的构造函数
    }
}
class Worker extends Person
{
    Worker(String name, int age)
    {
        super(name, age);//调用父类的构造函数
    }
}

函数覆盖(Override)

  • 函数覆盖发生在父类与子类之间,其函数名、参数类型、返回值类型必须同父类中的相对应被覆盖的函数严格一致,覆盖函数和被覆盖函数只有函数体不同,当派生类对象调用子类中该同名函数时会自动调用子类中的覆盖版本,而不是父类中的被覆盖函数版本,这种机制就叫做函数覆盖。
  • 父类中的私有方法不可以被覆盖。
  • 在子类覆盖方法中,继续使用被覆盖的方法可以通过super.函数名获取。
  • 覆盖注意事项:
    覆盖时,子类方法权限一定要大于等于父类方法权限。
    静态只能覆盖静态,或被静态覆盖(非静态不能覆盖静态,静态不能覆盖非静态,否则会报编译错)。
  • 覆盖的应用:
    当子类需要父类的功能,而功能主体子类有自己特有内容时,可以复写父类中的方法,这样即沿袭了父类的功能,又定义了子类特有的内容。

子类的实例化过程

注意构造函数不会继承更不会覆盖(首先函数名都不一样...
子类的实例化过程:子类中所有的构造函数默认都会访问父类中空参数的构造函数(因为每一个构造函数的第一行都有一条默认的语句super();之所以会有这样一条默认语句,是因为子类继承了父类,获取到了父类中的内容(属性),所以在使用父类内容之前,要先明确父类是如何对这些数据初始化的)。

public class Father 
{
    public Father()
    {
        System.out.println("Father run");
    }
}
class Son extends Father
{
    public Son()
    {
        //super(); //默认会调用父类的空参构造函数
        System.out.println("Son run");
    }
}

当父类中没有空参数的构造函数时,子类的构造函数必须通过this或者super语句指定要访问的构造函数。
注意:super语句必须要定义在子类构造函数的第一行,因为父类的初始化动作要先完成。
如果子类构造函数中使用this调用了本类构造函数,那么就没有super了,因为super和this都只能定义在第一行。但是可以保证的是,子类中肯定会有其他的构造函数访问父类的构造函数,即子类构造函数中至少有一个访问父类构造函数。

public class Father 
{
    public Father(int num)
    {
        System.out.println("Father run");
    }
}
class Son extends Father
{
    public Son()
    {
        this(4);
    }

    public Son(int num)
    {
        super(num);
        System.out.println("Son run");
    }

}

注意,类默认的空参构造函数的修饰符与类保持一致。

class Demo
{
    /*
    Demo()
    {
        super(); //父类为Object
        return;
    }
    */
}

Example1:子类的实例化过程

public class Father {
    Father()
    {
        show();
    }
    public  void show()
    {
        System.out.println("father show");
    }
}
class Son extends Father
{
    int num = 8;
    Son()
    {
        super();
        //分水岭
        System.out.println("Son run..." + num);
    }

    public void show()
    {
        System.out.println("Son show..." + num);
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Son s = new Son();
        s.show();
    }
}

输出结果为:

Son show...0
Son run...8
Son show...8

内存图解:

默认初始化,成员变量num=0;显示初始化,成员变量num=8。
默认初始化后,执行super(),经过父类构造函数初始化,再进行自己的显示初始化,最后是自己的构造函数初始化。

注意,若子类当中有构造代码块,则其初始化的过程为:

一个对象的实例化过程:

Person p = new Person();
1、JVM读取指定路径下的Person.class文件,如果该类有直接父类,先加载Person的父类,再加载Person进内存;
2、在堆内存中开辟空间,分配地址;
3、在堆的对象空间中,对对象中的属性进行默认初始化;
4、调用对应的构造函数进行初始化;
5、在构造函数中,先调用父类的构造函数进行初始化;
6、父类初始化完毕后,再对子类的属性进行显示初始化;
7、构造代码块初始化;
8、进行子类构造函数的特定初始化;
9、初始化完毕后,将地址值赋值给引用变量(不一定是在栈中,如下)。

class Demo
{
    Person p = new Person(); //在堆中
}

final关键字

  • final可以修饰类,方法,变量。
  • final修饰的类不可以被继承。(继承弊端:打破了封装性。)
  • final修饰的方法不可以被覆盖。(可以用于在父类中,若有几个方法不希望被复写时)
  • final修饰的变量是一个常量,只能被赋值一次。(用于在类中值不会变化的,命名方式为所有字母大写,单词之间用下划线连接;为什么要用final修饰变量,其实在程序中若一个数据是固定的,那么直接使用这个数据就可以了,但是这样阅读性差,所以加上final)
  • 内部类只能访问被final修饰的局部变量。

Example1

class Person
{
    final int x;
}
class FinalDemo
{
    public static void main(String[] args){
        new Person();
    }
}

编译错误:可能尚未初始化变量x(指的显示初始化)。
注意:变量若是有final修饰符,则一般都为静态:static final int x = 7,每个对象的值都一样。

你可能感兴趣的:(毕向东Java基础教程-继承【上】)