摘要

教你认识多态

正文

一、多态的概述以及使用

啥叫多态?左父右子,就叫多态。此处联想css中的子绝父相。

多态1.png

代码当中提现多态性,其实就是一句话:父类引用指向子类对象

格式:

text
1
父类对象 对象名 = new 子类名称();

或者

text
1
接口名称 对象名 = new 实现类名称();

Fu.java

text
1
2
3
4
5
6
7
8
public class Fu {
	public void method() {
		System.out.println("父类方法");
	}
	public void methodFu() {
		System.out.println("这是父类特有的方法");
	}
}

Zi.java

text
1
2
3
4
5
6
public class Zi extends Fu {
	@Override
	public void method() {
		System.out.println("子类方法");
	}
}

Demo01Multi.java

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Demo01Multi {
	public static void main(String[] args) {
		
		//使用多态方法
		//左侧父类的引用,指向右侧子类的对象
		Fu obj=new Zi();
		
		obj.method();//子类方法。看右边new的是谁,就输出谁
		obj.methodFu();
	}
}

二、多态中成员变量使用特点

访问成员变量的两种方式:

  1. 直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找
  2. 间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找

Fu1.java

text
1
2
3
4
5
6
public class Fu1 {
	int num=10;
	public void method() {
		System.out.println("父类"+num);
	}
}

Zi1.java

text
1
2
3
4
5
6
7
public class Zi1 extends Fu1 {
	int num=20;
	@Override
	public void method() {
		System.out.println("子类"+num);
	}
}

Demo02MultiField.java

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Demo02MultiField {
	public static void main(String[] args) {
		//使用多态的方法,父类引用指向子类对象,左父右子
		Fu1 obj=new Zi1();
		System.out.println(obj.num);//10
		obj.method();//20,子类没有覆盖重写,就是父;子类如果覆盖重写,就是子
		
		Zi1 zi=new Zi1();
		System.out.println(zi.num);//20
		zi.method();//20
	}
}

三、多态中成员方法的使用特点

在多态的代码当中,成员方法的访问规则:

看new的是谁,就优先用谁,没有则向上找

口诀:编译看左边,运行看右边

对比:

成员变量:编译看左边,运行还看左边

成员方法:编译看左边,运行看右边

Fu2.java

text
1
2
3
4
5
6
7
8
9
public class Fu2 {
	public void method() {
		System.out.println("父类方法");
	}
	
	public void methodFu() {
		System.out.println("父类特有的方法");
	}
}

Zi2.java

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Zi2 extends Fu2 {
	@Override
	public void method() {
		System.out.println("子类方法");
	}
	
	public void methodZi() {
		System.out.println("子类特有方法");
	}
}

Demo03MultiMethod.java

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Demo03MultiMethod {
	public static void main(String[] args) {
		Fu2 obj=new Zi2();//多态
		obj.method();//父子都有,优先用子
		obj.methodFu();//子类没有,父类有,向上找
		
		//obj.methodZi();//报错了,因为编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错
		
		Zi2 zi=new Zi2();
		zi.methodZi();
	}
}

四、使用多态的好处

多态2.png

如果不用多态,只用子类,那么写法是:

text
1
2
3
4
Teacher one = new Teacher();
one.work();//讲课
Assistant two = new Assistant();
two.work();//辅导

我现在唯一要做的事情,就是调用work方法,其他的功能不关心。

如果使用多态的写法,对比一下:

text
1
2
3
4
Employee one = new Teacher();
one.work();//讲课
Employee two = new Assistant();
two.work();//辅导

好处:

无论右边new的时候换成哪个子类对象,等号左边调用方法不会发生变化

五、对象的向上转型与向下转型

多态3.png

向上转型

对象的向上转型,其实就是多态写法:

text
1
2
3
4
父类名称 对象名 = new 子类名称();

//Animal animal = new Cat();
//创建了一只猫,当做动物看待,没问题。

含义:右侧创建一个子类对象,把它当做父类来看待使用。

注意事项:**向上转型一定是安全的。**从小范围转向了大范围,从小范围的猫,转向了更大范围的动物。

类似于:

text
1
double num=100;//正确,int-->double,自动类型转换

向下转型

对象的向下转型,其实是一个【还原】的动作

格式:

text
1
2
3
4
子类名称 对象名 = (子类名称) 父类对象;//其实类似于 int a = (double) 10.0;

//Animal animal = new Cat();//本来是猫,向上转型为动物
//Cat cat = (Cat) animal;//本来是猫,已经被当做动物了,还原为本来的猫

含义:将父类对象,【还原】成为本来的子类对象。

注意事项:

  1. 必须保证对象本来创建的时候,就是猫,才能向下转型成为猫
  2. 如果对象创建的时候本来不是猫,现在非要向下转型为猫,就会报错。java.lang.ClassCastException

类似于:

text
1
2
int num = (int) 10.0;//可以
int num = (int) 10.5;//不可以,精度损失

Animal.java

text
1
2
3
4
public abstract class Animal {
	
	public abstract void eat();
}

Cat.java

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Cat extends Animal {

	@Override
	public void eat() {
		// TODO Auto-generated method stub
		System.out.println("猫吃鱼");
	}
	
	public void catchMouse() {
		System.out.println("猫抓老鼠");
	}

}

Demo04Main.java

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
向上转型一定是安全的,没有问题的,正确的,但是也有一个弊端:
对象一旦向上转型为父类,那么就无法调用子类原本特有的内容。

解决方案:用对象的向下转型【还原】
 */
public class Demo04Main {
	public static void main(String[] args) {
		// 对象的向上转型,就是:父类引用指向子类对象
		Animal animal = new Cat();//本来是一只猫,向上转型为动物;向上转型必定是成功的。
		animal.eat();
		// animal.catchMouse();//错误写法!要改成向下转型

		
		//向下转型,正确的转型,本来是只猫,还转换成猫,所以是正确的
		Cat cat = (Cat) animal;//类似于强制转换
		cat.catchMouse();//猫抓老鼠
		
		//下面是错误的转型
		//本来是只猫,现在当做狗
		//Dog dog=(Dog) animal;//编译时没错,运行会出现异常。java.lang.ClassCastException  
	}
}

六、instanceof判断类型

如何才能知道一个父类引用的对象,本来是什么子类?

格式:

text
1
对象 instanceof 类名称

这将会得到一个boolean值结果,也就是判断前面的对象能不能做后面类型的实例

向下转型,一定要进行instanceof判断,否则,可能会出现转换异常

Animal.java跟Cat.java跟上面那个是一个类,直接看Instanceof类

Demo02Instanceof.java

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo02Instanceof {
	public static void main(String[] args) {
		Animal animal = new Cat();
		animal.eat();// 猫吃鱼
		giveMeAPat(animal);

		
	}

	public static void giveMeAPat(Animal animal) {
		// 如果希望调用子类特有方法,需要向下转型
		if (animal instanceof Dog) {
			Dog dog = (Dog) animal;
			dog.watchHouse();
		}
		if (animal instanceof Cat) {
			Cat cat = (Cat) animal;
			cat.catchMouse();
		}
	}
}

七、接口多态综合案例

多态4.png

USB.java

text
1
2
3
4
5
6
7
public interface USB {

    public abstract void open(); // 打开设备

    public abstract void close(); // 关闭设备

}

Mouse.java

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//鼠标就是一个USB设备
public class Mouse implements USB {
	@Override
	public void open() {
		System.out.println("打开鼠标");
	}

	@Override
	public void close() {
		System.out.println("关闭鼠标");
	}

	public void click() {
		System.out.println("鼠标点击");
	}
}

Keyboard.java

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//键盘就是一个USB设备
public class Keyboard implements USB {
	@Override
	public void open() {
		System.out.println("打开键盘");
	}

	@Override
	public void close() {
		System.out.println("关闭键盘");
	}

	public void type() {
		System.out.println("键盘输入");
	}
}

Computer.java

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Computer {

    public void powerOn() {
        System.out.println("笔记本电脑开机");
    }

    public void powerOff() {
        System.out.println("笔记本电脑关机");
    }

    // 使用USB设备的方法,使用接口作为方法的参数
    public void useDevice(USB usb) {
        usb.open(); // 打开设备
        if (usb instanceof Mouse) { // 一定要先判断
            Mouse mouse = (Mouse) usb; // 向下转型
            mouse.click();
        } else if (usb instanceof Keyboard) { // 先判断
            Keyboard keyboard = (Keyboard) usb; // 向下转型
            keyboard.type();
        }
        usb.close(); // 关闭设备
    }

}

DemoMain.java

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class DemoMain {

    public static void main(String[] args) {
        // 首先创建一个笔记本电脑
        Computer computer = new Computer();
        computer.powerOn();

        // 准备一个鼠标,供电脑使用
//        Mouse mouse = new Mouse();
        // 首先进行向上转型
        USB usbMouse = new Mouse(); // 多态写法
        // 参数是USB类型,我正好传递进去的就是USB鼠标
        computer.useDevice(usbMouse);

        // 创建一个USB键盘
        Keyboard keyboard = new Keyboard(); // 没有使用多态写法
        // 方法参数是USB类型,传递进去的是实现类对象
        computer.useDevice(keyboard); // 正确写法!也发生了向上转型
        // 使用子类对象,匿名对象,也可以
//        computer.useDevice(new Keyboard()); // 也是正确写法

        computer.powerOff();
        System.out.println("==================");

        method(10.0); // 正确写法,double --> double
        method(20); // 正确写法,int --> double
        int a = 30;
        method(a); // 正确写法,int --> double
    }

    public static void method(double num) {
        System.out.println(num);
    }

}

八、多态的练习题

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package homework;

class A {
	public String f(D obj) {
		return ("A and D");
	}
	public String f(A obj) {
		return ("A and A");
	}
}
class B extends A {
	public String f(B obj) {
		return ("B and B");
	}
	public String f(A obj) {
		return ("B and A");
	}
}
class C extends B {
	
}
class D extends B {
	
}
public class Homework02 {
	/*
	 * 这个地方需要注意的点:
	 * 1. 如果public String f(A obj)方法传进来的对象是B对象,发现没有,就会自动向上转型!因为B extends A
	 * 2. 向上转型的写法中:A a2=new B();a2调用变量是A中的,调用方法是B中的(只能是A中已有的方法,被重写的可以被调用,独有的是不可以被调用的)
	 */
	public static void main(String[] args) {
		A a1=new A();
		A a2=new B();
		B b=new B();
		C c=new C();
		D d=new D();
		
		System.out.println(a1.f(a1));// A and A
		System.out.println(a1.f(b));// A and A
		System.out.println(a1.f(c));// A and A
		System.out.println(a1.f(d));// A and D
		
		System.out.println(a2.f(a1));//B and A
		System.out.println(a2.f(b)); //B and A
		System.out.println(a2.f(c));//B and A
		System.out.println(a2.f(d));//A and D
		
		System.out.println(b.f(a1));//B and A
		System.out.println(b.f(b));//B and B
		System.out.println(b.f(c));//B and B
		System.out.println(b.f(d));//A and D
	}
}