多态
对于解决某些问题,比如 假设需要创建一个方法给宠物喂食,那么用传统方法来说,给猫喂鱼,狗喂骨头等等,需要每一个都写一个方法,这样导致代码的复用性不高,而且不利于代码的维护,因此需要多态。
多态指 方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
方法的多态
重写和重载就体现了多态。
// 方法重载体现了多态——在同类中方法有多种形态
public int sum(int num1,int num2){
return num1 + num2;
}
public int sum(int num1,int num2,int num3){
return num1+num2+num3;
}
// 方法重写体现了多态——在不同类中方法也有多种形态
public int sum(int num1,int num2)
{
return num1 + num2;
} //父类方法
public int sum(int num1,int num2)
{
return num1 + num2;
} //子类方法
对象的多态
1. 一个对象的编译类型和运行类型可以不一致。编译类型看定义时 =号 的左边,运行类型看 =号 的右边。
Animal animal = new Dog(); // animal 编译类型是Animal,运行类型为 Dog
2. 编译类型在定义对象时就确定了,不能改变。
3. 运行类型是可以变化的。
animal = new Cat(); // animal的运行类型变成了Cat,编译类型仍然是Animal
4. 对象多态的前提:两个对象(类)之间存在继承关系。
因为对象的多态,很多操作就简洁许多了。以上面的喂食例子,我们正常写的话应该是这样:
public void feed(Cat cat, Fish fish){
...; //输出喂食信息
}
public void feed(Dog dog, Fish fish){
...; //输出喂食信息
}
...... //所有动物类与食物类都要写一遍,写在Master类中
而如果用对象的多态方法来写,则是下面这样:
public void feed(Animal animal, Food food){
...;
} // 只需在Master类中写一个即可
当调用时,仍然是传入Animal类和Food类的子类,这时就用到了多态机制。
5. 多态参数:方法定义的形参类型为父类类型,实参类型允许为子类类型。
向上转型
1. 本质:父类的引用指向了子类的对象。
2. 语法:父类类型 引用名 = new 子类类型();
3. 特点:
1)编译类型看左边,运行类型看右边。
2)可以调用父类中的所有成员(当然前提是遵守访问权限)。
3)不能调用子类中特有成员(如果子类重写了父类的方法,那么由于动态绑定机制,调用父类的成员和方法会先从子类找,因此子类重写的方法是可以被调用的),因为在编译阶段,能调用哪些成员,是由编译类型来决定的。
Animal animal = new Cat();
animal.catchMouse(); //错误,因为catchMouse是子类的特有方法,向上转型不能调用
public class Car {
public int age;
public void say(){
System.out.println("这是一个car类");
}
}
//相同属性看编译类型,相同方法看运行类型
class BMW extends Car{
public int age = 10;
public void say(){
System.out.println("这是一个BMW类");
}
public static void main(String[] args) {
Car car = new BMW();
Car car1 = new Car();
BMW bmw = new BMW();
System.out.println(car.age); //0
System.out.println(car1.age); //0
System.out.println(bmw.age); //10
car.say(); //BMW类
car1.say(); //Car类
bmw.say(); //BMW类
}
}
4)最终运行效果看子类的具体实现,即调用方法时,从子类(运行类型)开始查找方法,规则与前面的方法调用规则一致。
向下转型
1. 语法:子类类型 引用名 = (子类类型) 父类引用。
Cat cat = (Cat)animal;
这里注意一点,向下转型后,cat的编译类型和运行类型都是Cat,cat和animal都指向Cat的对象(注意animal没有消失)。
2. 只能强转父类的引用,不能强转父类的对象。父类的对象是创建在堆区中的,这个是不能改变的(因为已经创建了),但是可以改变指向该对象的指针,向下转型就是让它指向一个子类的对象。
3. 要求父类的引用必须指向的是当前目标类型的对象。
Animal animal = new Cat();
Dog dog = (Dog)animal; //报错
Cat cat = (Cat)animal; //正确
就比如这个例子,父类的引用本来就指向Cat类的对象,因此向下转型只能使用Cat。按照错误语句的理解,让一只狗指向猫对象,那肯定是错误的。
4. 当向下转型后,可以调用子类类型中所有的成员(当然要符合访问范围)。
5. 下面是错误的向下转型写法,不能让没有引用的对象进行向下转型。
Cat cat = (Cat)(new Animal());
Cat cat = (Cat)new Animal();
// 这两种写法都是错误的,不能让没有引用的对象进行向下转型
// 编译器报告 cannot be cast to 错误
多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型,静态初始化可以直接写,动态初始化则需要new父类,然后对里面的元素单个向上转型。
Person[] a = new Student[3]; //不能这样写
// 第一种写法,静态初始化
Preson p = new Person();
Person t = new Teacher();
Person s = new Student();
Person[] persons = {p,t,s};
//第二种写法,动态初始化
Person[] a = new Person[3]; //new父类
a[0] = new Student();
这里需要介绍一个问题:如何调用子类特有的方法(很明显,光有向上转型是不能调用的,向上转型只能让子类加入到父类的数组中,因此需要用到向下转型)
public class Person {
private int age;
private String name;
}
public class Student extends Person{
public void Study(){
System.out.println("学生正在学习");
}
}
public class Test {
public static void main(String[] args) {
Person[] a = new Person[3];
a[0] = new Student(); //向上转型
a[0].Study();
//报错,因为Study是子类特有的方法,向上转型不能调用(调用方法由编译类型决定)
}
}
public class Test {
public static void main(String[] args) {
Person[] a = new Student[3];
a[0] = new Student();
Student stu = (Student)a[0]; //向下转型
stu.Study();// 语义相同:((Student) a[0]).Study();
}
}