吃透Java基础十四:继承引发的“血案”

在Java中,只允许单继承,也就是说一个类只能继承一个父类,但是可以实现多个接口。本文我们讨论一下子类和父类的构造方法调用顺序、代码块执行顺序、方法的重写与重载。

一、构造方法调用顺序

1、何为构造方法

构造方法是类的一种特殊方法,用来初始化类的一个新的对象,每个类至少有一个构造方法,如果类中没有显式定义构造方法,编译器在编译的时候会生成一个默认的构造方法,默认的构造方法不包含任何参数,并且方法体为空。如果类中显式地定义了一个或多个构造方法,则 Java 不再提供默认构造方法。

构造方法必须具有和类名相同的名称,而且没有返回类型。构造方法的默认返回类型就是对象类型本身,并且构造方法不能被 static、final、synchronized、abstract 和 native 修饰。

在一个类中,与类名相同的方法就是构造方法。每个类可以具有多个构造方法,但要求它们各自包含不同的方法参数。

构造方法可以用public、protected、默认、private修饰。

2、子类构造方法调用父类构造方法

子类构造方法要么调用父类无参构造方法(包括当父类没有构造方法时。系统默认给的无参构造方法),要么调用父类有参构造方法。当子类构造方法调用父类无参构造方法,一般都是默认不写的,要写的话就是super(),且要放在构造方法的第一句。当子类构造方法要调用父类有参数的构造方法,那么子类的构造方法中必须要用super(参数)调用父类构造方法,且要放在构造方法的第一句。

看完如下例子对子类父类构造函数调用顺序应该就能明白了:

public class Father {
    private String name;

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

    public Father(String name) {
        System.out.println("父类有参构造方法 name:" + name);
    }
}

public class Son extends Father {
    public Son() {
        System.out.println("子类无参构造函数");
    }

    public Son(String name) {
        super(name);
        System.out.println("子类有参构造函数");
    }

    public Son(String name, int age) {
        System.out.println("子类有参构造函数 name and age");
    }

    public static void main(String[] args) {
        new Son();
        System.out.println("------------参数-----------");
        new Son("bobo");
        System.out.println("------------参数 name and age-----------");
        new Son("bobo", 26);
    }
}

运行输出:

父类无参构造方法
子类无参构造函数
------------参数-----------
父类有参构造方法 name:bobo
子类有参构造函数
------------参数 name and age-----------
父类无参构造方法
子类有参构造函数 name and age

二、代码块执行顺序

Java中代码块分为普通代码块和静态代码块,普通代码块是属于实例对象的,其实在编译期编译器会把普通代码块的内容放在构造函数里面来执行的。静态代码块是属于类的,是随着类的初始化而调用的,那么类是什么时候初始化呢?请参考之前写的吃透Java基础三:触发类初始化的五种方式

1、普通代码块编译后自动添加到构造函数最上面执行

看下面例子:

public class Son {
    private String name;
    {
        System.out.println("普通代码块");
    }
    public Son() {
        System.out.println("子类无参构造函数");
    }
    public Son(String name) {
        this.name = name;
        System.out.println("子类有参构造函数");
    }
}

编译后的class文件反编译:

public class Son {
    private String name;

    public Son() {
        System.out.println("普通代码块");
        System.out.println("子类无参构造函数");
    }

    public Son(String name) {
        System.out.println("普通代码块");
        this.name = name;
        System.out.println("子类有参构造函数");
    }
}

2、有继承关系的静态代码块和普通代码块调用顺序

只需要记住一点:静态代码块是在类初始化的时候调用的,普通代码块是在创建对象实例的时候在构造函数里面调用的,类初始化一定在创建对象实例之前,所以静态代码块一定在普通代码块之前调用。

看如下例子:

public class Father {
    static {
        System.out.println("父类静态代码块");
    }
    {
        System.out.println("父类普通代码块");
    }
    public Father() {
        System.out.println("父类无参构造方法");
    }
}

public class Son extends Father{
    {
        System.out.println("子类普通代码块");
    }
    static {
        System.out.println("子类静态代码块");
    }
    public Son() {
        System.out.println("子类构造函数");
    }

    public static void main(String[] args) {
        new Son();
    }
}

运行输出:

父类静态代码块
子类静态代码块
父类普通代码块
父类无参构造方法
子类普通代码块
子类构造函数

三、方法的重载与重写

方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

1、重载

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。最常用的地方就是构造器的重载。
规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样)。
  • 被重载的方法可以改变返回类型。
  • 被重载的方法可以改变访问修饰符。
  • 被重载的方法可以声明新的或更广的检查异常。
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

综上:参数列表区分重载

public class MyTest {

    public int test(){
        System.out.println("test1");
        return 1;
    }

    public void test(int a){
        System.out.println("test2");
    }

    //以下两个参数类型顺序不同
    public String test(int a,String s){
        System.out.println("test3");
        return "test3";
    }

    public String test(String s,int a){
        System.out.println("test4");
        return "test4";
    }
}

2、重写

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 方法名和形参都不能改变。子类重写父类方法的规则:

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

  • 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符,特殊情况:子类不能重写父类被声明为private权限的方法。

  • 返回值类型:
    1、父类是void,子类只能是void。
    2、父类是A类型,子类是A类或A类的子类。
    3、父类是基本数据类型(如:int),子类返回值类型必须相同。

  • 子类重写的方法抛出的异常的类型不大于父类被重写的方法抛出的异常类型。

如下例子:

public class Father {
    
    public void overrideMethod1(){
        
    }
    public int overrideMethod2() throws IOException {
        return 0;
    }
    
    protected List<String> overrideMethod3(){
        return null;
    }
}

public class Son extends Father{
    /**
     * 父类方法返回值是void,子类重写此方法返回值只能是void
     */
    @Override
    public void overrideMethod1() {

    }

    /**
     * 1、父类方法访问权限是protected,子类重写时访问权限可以是public。
     * 2、父类方法抛出的异常是IOException,子类重写方法时抛出的异常可以是其子类FileNotFoundException。
     * 3、父类方法返回值是int基本数据类型,子类重写时返回值类型只能是int。
     * @return
     * @throws FileNotFoundException
     */
    @Override
    public int overrideMethod2() throws FileNotFoundException {
        return 0;
    }

    /**
     * 父类方法返回值类型是List,子类重写方法返回值类型可以是其子类ArrayList。
     * @return
     */
    @Override
    public ArrayList<String> overrideMethod3() {
        return null;
    }
}
发布了66 篇原创文章 · 获赞 138 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/u013277209/article/details/102996183