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
动态绑定
动态绑定又称后期绑定,即在运行时根据具体对象的类型进行绑定。
- 当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定;
- 当调用对象属性时,无动态绑定机制,哪里声明,哪里使用。
静态绑定又称前期绑定,程序执行前方法就已经被绑定,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
最后运行时绑定的对象是子类对象。