一、多态
1.1泛化
泛化就是抽象化,把具体的事物当作抽象的东西看待(把猫看成动物)。
泛化也叫向上转型,是通往多态的路口。
Cat c = New Cat()
Animail n = New Cat();
//这种不叫泛化 叫自动转换
double x = 1;
这样做有用吗?
有用,我们可以用同样的类型处理不同的东西。
示例代码
public class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
public void eat(){
System.out.println("吃");
}
public void sleep(){
System.out.println("睡");
}
}
public class Cat extends Animal {
private String eyeColor;
public String getEyeColor() {
return eyeColor;
}
public void setEyeColor(String eyeColor) {
this.eyeColor = eyeColor;
}
public void climbTree(){
System.out.println("爬树###");
}
}
public class Demo01{
/*泛化——向上转型
*子类中特有的内容将不能被访问
* */
public static void main(String[] args) {
/*
Cat cat = new Cat();
cat.climbTree();
cat.eat();
cat.sleep();
Animal a = cat; // 把猫看成动物——泛化——向上转型
a.eat();
a.sleep();
//a.climbTree(); //子类中特有的内容不能访问
*/
/*
Animal a = new Animal();
Cat cat= (Cat)a; // 很不安全
cat.climbTree();
*/
/*
Animal a = new Cat();
Cat c = (Cat)a; //强转 这样做还勉强可以
*/
}
}
这样做的缺点就是Cat中特有的方法就不能调用了
注意:只有向上转型,没有向下转型。向下转型叫做强转,这样做很不安全!
Cat c = (Cat)o; 编译可以通过,但是运行时会出现错误。
泛化指的是引用的泛化,不是对象的泛化,内存中的对象还是原来的对象,只是把引用的类型给改变了。
1.2多态的概念
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才能确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
简单来说,多态就是一个事物的多种状态(形态)。
现实生活中,关于多态的例子不胜枚举。比方说按下 F1 键这个动作,如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是Word 帮助;在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。
1.3多态的分类
Java中的多态,可以分为静态多态和动态多态。
静态多态就是指方法的重载。
动态多态是指“动态绑定”。而我们这里所说的多态,大多数是指动态多态。
静态多态是方法重载。
多态应该具备的条件
-
继承
-
子类重写父类的方法
-
父类引用指向子类对象(泛化)
二、动态绑定和静态绑定
1.1动态绑定
所谓动态绑定,是指在编译期不能确定引用指向什么类型,而是在运行期选择具体的类型。
示例代码
Animal a = new Cat() 编译期不能确定类型。
运行时确定是Cat类型。
为什么说编译器不能确定是Cat类型呢?
public class Animal {
String name="动物";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void eat(){
System.out.println("吃");
}
public void sleep(){
System.out.println("睡");
}
public static void sayHello(){
System.out.println("你好,我是动物");
}
}
public class Cat extends Animal {
String name="猫";
//重写
public void eat(){
System.out.println("吃鱼@@@");
}
//重写
public void sleep(){
System.out.println("白天睡觉,晚上行动@@@");
}
public void climbTree(){
System.out.println("爬树");
}
public static void sayHello(){
System.out.println("你好,我是猫");
}
}
public class Dog extends Animal {
//重写
public void eat(){
System.out.println("吃骨头...");
}
//重写
public void sleep(){
System.out.println("在狗窝中睡觉...");
}
}
public class Demo01_动态绑定_多态 {
double ran = Math.random();
Animal a;
if(ran>0.5){
a = new Cat();
}else{
a = new Dog();
}
a.eat();
}
}
编译器能确定a的类型吗???不能,因为编译期,随机数的值还没有生成。
注意
子类重写父类的方法。
当使用父类的引用指向子类的对象时,调用这个重写的方法,调用到的是子类重写的方法。
示例代码
Animal a = new Cat();
//a.climbTree();
a.eat(); //动态绑定——绑定的是Cat类中重写的eat方法
总结
多态中成员方法的访问特点:编译看左边,运行看右边
1.2静态绑定
静态绑定是指编译期就确定调用的是父类中的成员。
动态绑定只是针对类中的普通方法,而对于成员变量,静态变量,静态方法,采用的是静态绑定。也就是说,对于成员变量,静态变量,和静态方法,绑定的是声明时类型中的成员。
示例代码
Animal a = new Cat();
a.eat(); //动态绑定
System.out.println(a.name); //静态绑定
a.sayHello(); // 静态绑定
总结:成员变量、静态方法、静态变量、私有变量等采用静态绑定
编译看左边,运行看右边
1.3 引用类型转换
多态的转型分为向上转型与强转两种:
向上转型
-
向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。
当父类引用指向一个子类对象时,便是向上转型。
使用格式:
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
强转
-
强转:父类类型向子类类型向下转换的过程,这个过程是强制的。
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
使用格式:
子类类型 变量名 = (子类类型) 父类变量名;
如:Cat c =(Cat) a;
为什么要转型
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。
转型演示,代码如下:
定义类:
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void watchHouse() {
System.out.println("看家");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
}
}
转型的异常
转型的过程中,一不小心就会遇到这样的问题,请看如下代码:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
这段代码可以通过编译,但是运行时,却报出了 ClassCastException
,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,格式如下:
变量名 instanceof 数据类型
如果变量属于该数据类型,返回true。
如果变量不属于该数据类型,返回false。
所以,转换前,我们最好先做一个判断,代码如下:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}
三、多态的好处
1.1多态的好处
站在更高的层次管理对象的行为
例如:气象专家告诉农民收割庄稼。
1.2多态的应用
案例1: 多态做为形参(接受范围更广的对象,避免方法重载过度使用) 需求:完成喂动物操作。定义一个动物园类,类中有一个喂动物的方法。 动物包括:老虎,猴子,狗,猫。每个动物类中都有一个eat的方法。(喂动物即是调用动物的eat方法)
示例代码
public class Animal { public void eat(){ System.out.println("吃"); } } public class Tiger extends Animal{ public void eat(){ System.out.println("吃肉###"); } } public class Monkey extends Animal{ public void eat(){ System.out.println("吃桃..."); } } public class Cat extends Animal{ public void eat(){ System.out.println("吃鱼"); } } public class Dog extends Animal{ public void eat(){ System.out.println("啃骨头"); } } public class Zoo { public void feed(Animal a){ a.eat(); } /* public void feed(Tiger tiger){ tiger.eat(); } public void feed(Monkey monkey){ monkey.eat(); } public void feed(Cat cat){ cat.eat(); } public void feed(Dog dog){ dog.eat(); } */ } public static void main(String[] args) { Zoo zoo = new Zoo(); Tiger tiger = new Tiger(); zoo.feed(tiger); Monkey monkey = new Monkey(); zoo.feed(monkey); Cat cat = new Cat(); zoo.feed(cat); Dog dog = new Dog(); zoo.feed(dog); }
案例2: 多态做为返回值类型(简单工厂模式) 需求:有一个生产汽车的工厂,有一个生产汽车的方法,根据客户不同的需求,可以生产出不同的汽车。汽车包括:奔驰(benz)、宝马(BMW)、法拉利(ferrari)
示例代码
public class Car { public void run(){ System.out.println("run"); } } public class CarFactory { public Car makeCar(String name){ if("bmw".equals(name)){ return new BMW(); } if("benz".equals(name)){ return new Benz(); } if("ferrari".equals(name)){ return new Ferrari(); } return null; } /* public BMW makeBMW(){ return new BMW(); } public Benz makeBenz(){ return new Benz(); } public Ferrari makeFerrari(){ return new Ferrari(); } */ } public class BMW extends Car{ public void run(){ System.out.println("宝马在路上飞驰..."); } } public class Benz extends Car{ public void run(){ System.out.println("奔驰在路上奔驰###"); } } public class Ferrari extends Car{ public void run(){ System.out.println("法拉利跑的很快"); } }