Loading

Java多态及类初始化顺序

多态

多态是Java面向对象的三大特性之一,多态建立于封装和继承之上,指对于父类中定义的属性和方法被子类继承后,可以具有不同的数据类型或表现出不同的行为。
可分为编译时多态运行时多态, 编译时多态是静态的,通过方法的重载体现,通过编译之后会产生不同的方法;运行时多态通过动态绑定实现。
Java实现多态有3个必要条件:继承重写向上转型

  • 继承:多态中必须存在子类继承父类的关系;
  • 重写:子类对父类方法进行重写,运行时就会动态调用子类重写的方法;
  • 向上转型:将子类的引用赋给父类对象,父类类型 引用名 = new 子类类型();

多态的实现

一个对象的编译类型和运行类型可以不一致,编译类型在定义对象时就确定了,不能改变,运行类型可以随运行时具体传入的对象引用而改变。
编译类型由=左边的类型确定,运行类型由=右边的类型确定。
instanceof操作符用于判断对象的运行类型是否为XX类型或其子类型。

看下面的的例子:

class A {
    int val1 = 1;
    void test() {
        System.out.println("A test");
    }

    void test2() {
        System.out.println("A test2");
    }
}
public class B extends A {
    int val1 = 11;
    int val2 = 2;
    @Override
    public void test() {
        System.out.println("B test");
    }
    public void test3() {
        System.out.println("B test3");
    }

    public static void main(String[] args) {
        A a = new B(); //向上转型
        a.test(); // 调用子类重写的方法
        a.test2(); //调用父类方法
        ((B) a).test3(); //无法直接调用子类特有方法
        B b = (B)a; //向下转型
        b.test(); //调用子类重写的方法
        b.test2(); //调用父类方法
        b.test3(); //调用子类特有方法
        System.out.println(a.val1); //访问父类成员变量
        System.out.println(((B) a).val2); //无法直接访问子类特有成员变量
        System.out.println(b.val1);
        System.out.println(b.val2);
    }
}

输出:

B test
A test2
B test3
B test
A test2
B test3
1
2
11
2

动态绑定

动态绑定又称后期绑定,即在运行时根据具体对象的类型进行绑定。

  1. 当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定;
  2. 当调用对象属性时,无动态绑定机制,哪里声明,哪里使用。

静态绑定又称前期绑定,程序执行前方法就已经被绑定,Java中的方法只有final、static、private和构造方法是前期绑定。

向上转型与向下转型

多态的向上转型:
父类的引用指向了子类的对象,父类类型 引用名 = new 子类类型();,子类对象可以调用父类的所有成员,但不能调用子类特有的成员(在编译阶段,能调用哪些成员是由编译类型决定),最终运行效果看子类[运行类型]的具体实现(调用方法时,先从子类[运行类型]开始查找)。

多态的向下转型:
子类类型 引用名 = (子类类型) 父类引用,只能强转父类的引用,不能强转父类的对象;要求父类的引用必须是指向当前目标类型的对象;向下转型后可以调用子类类型的所有成员。

多态的应用

多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。

public static void main(String[] args) {
    //Person Student Teacher都有say()方法
    Person[] persons = new Person[5];
    persons[0] = new Person("adam", 18);
    persons[1] = new Student("adam", 18, 99);
    persons[2] = new Student("bob", 19, 70.1);
    persons[3] = new Teacher("chuck", 30, 3000);
    persons[4] = new Teacher("dick", 29, 3100);

    for (int i = 0; i < persons.length; i++) {
        //persons[i]编译类型为Person,运行类型根据实际情况由JVM判断
        System.out.println(persons[i].say()); //动态绑定机制
        //teach()是Teacher类特有方法,study()是Student类特有方法
        if (persons[i] instanceof Student) {
            ((Student) persons[i]).stduy(); //向下转型
        } else if (persons[i] instanceof Teacher) {
            ((Teacher) persons[i]).teach(); //向下转型
        } else {
            System.out.println("类型有误");
        }
    }
}

多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型。

public class PloyTest {
    public static void main(String[] args) {
        //父类Employee,子类Worker和Manager继承Employee
        Worker frank = new Worker("Frank", 1000);
        Manager gordon = new Manager("Gordon", 20000, 2000);
        PloyTest ployTest = new PloyTest();
        ployTest.showEmpAnnual(frank);
        ployTest.showEmpAnnual(gordon);

        ployTest.testWork(frank);
        ployTest.testWork(gordon);
    }

    public void showEmpAnnual(Employee e) {
        System.out.println(e.getAnnual());
    }

    public void testWork(Employee e) {
        if (e instanceof Worker) {
            //work()为Worker类专有方法
            ((Worker) e).work(); //向下转型
        } else if (e instanceof Manager) {
            //manage()为Manager类专有方法
            ((Manager) e).manage(); //向下转型
        } else {
            System.out.println("无操作");
        }
    }
}

类初始化顺序

静态代码块:static{}声明,在JVM加载类时执行,且仅执行一次;
构造代码块:在类中用{}声明,每次实例化对象时执行。
执行顺序是:静态代码块->main方法->构造代码块->构造方法。

对于单个类的情况

public class TestA {
    public static int val1;
    public int val2;

    static {
        val1 = 1;
        System.out.println("A静态代码块" + val1);
        val3 = 3;
        // System.out.println(val3);
        // 编译异常Illegal forward reference
    }
    static int val3;
    {
        val2 = 2;
        System.out.println("A构造代码块" + val2);
    }

    public TestA() {
        System.out.println("A构造方法");
    }

    public static void main(String[] args) {
        TestA testA1 = new TestA();
        TestA testA2 = new TestA();
    }
}

输出:

A静态代码块1
A构造代码块2
A构造方法
A构造代码块2
A构造方法

对于单个类来说,静态变量、静态代码化块、变量、构造代码块、构造方法,执行顺序是:
(静态变量、静态代码块)早于(变量、构造代码块)早于 构造方法。
另外,类初始化时规定了,静态代码块只能访问到声明于它之前的静态变量,对于声明在静态代码块之后的变量,只能赋值不能访问,否则出现编译异常非法前向引用

对于继承的情况

class A {
    A() {
        System.out.println("A constructor");
    }

    static {
        System.out.println("A static");
    }
    {
        System.out.println("A code block");
    }
}
public class B extends A {
    B() {
        System.out.println("B constructor");
    }

    static {
        System.out.println("B static");
    }
    {
        System.out.println("B code block");
    }

    public static void main(String[] args) {
        System.out.println("B main");
        A a = new A();
        System.out.println("---");
        B b = new B();
        System.out.println("---");
    }
}

输出:

A static
B static
B main
A code block
A constructor
---
A code block
A constructor
B code block
B constructor
---

对于继承的情况,类初始化顺序是:父类静态成员及静态代码块 -> 子类静态成员及静态代码块 -> 子类main方法 -> 父类普通成员初始化 -> 父类构造方法 -> 子类普通成员初始化 -> 子类构造方法。

多态的情况:

package test;
class A {
    A() {
        System.out.println("A constructor");
    }

    static {
        System.out.println("A static");
    }
    {
        System.out.println("A code block");
    }
}
public class B extends A {
    B() {
        System.out.println("B constructor");
    }

    static {
        System.out.println("B static");
    }
    {
        System.out.println("B code block");
    }

    public static void main(String[] args) {
        System.out.println("main");
        A a = new B();
    }
}

输出:

A static
B static
main
A code block
A constructor
B code block
B constructor

最后运行时绑定的对象是子类对象。

posted @ 2023-10-13 14:14  KRDecad3  阅读(90)  评论(0编辑  收藏  举报