一、定义
1、父类引用指向子类对象,使得方法中的代码在编译时和实际运行时产生的多种状态就是多态
Animal dog=new Dog();
Animal cat=new Cat();
public void feedAnimal(Animal animal){
animal.eat();
}
person.feedAnimal(dog);
person.feedAnimal(cat);
2、把子类类型默认转为父类类型,使得父类在方法中的不同时间段,表示不同的状态(不同的子类),这一现象就是多态
3、同一个父类,可以表示不同的子类对象,使得父类在程序编译时和运行时呈现出多个不同的状态,这些多个不同的状态就是多态
二、特点
1、同一种事物,在不同时刻表现不同状态
2、只有当两个类具有直接或者间接的继承关系时,才能使用父类引用指向子类对象,进而使用多态
三、多态的好处-提高了代码的扩展性
定义三个类
package com.ffyc.bcms.demo;
public abstract class Animal {
public abstract void eat();
}
package com.ffyc.bcms.demo;
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
package com.ffyc.bcms.demo;
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
1、不用多态,没有遵循面向对象设计原则-开闭原则(扩展开放,修改关闭)。由于参数类型时Cat,只能接受Cat类型,为每一种动物都需要定义一个方法,不利于程序的扩展(每添加一种动物类型,就需要添加一个方法,修改一次类中的代码)。
package com.ffyc.bcms.demo;
public class Person {
/*人喂动物*/
/*人喂猫*/
public void feedCat(Cat cat){
cat.eat();
}
/*人喂狗*/
public void feedDog(Dog dog){
dog.eat();
}
}
package com.ffyc.bcms.demo;
public class Test {
public static void main(String[] args) {
Dog dog=new Dog();
Cat cat=new Cat();
Person person=new Person();
person.feedDog(dog);
person.feedCat(cat);
}
}
2、使用多态,只需要定义一个方法,就可以定义多个不同的子类,遵循了面向对象设计原则-开闭原则(扩展开放,修改关闭),提高了代码的扩展性
package com.ffyc.bcms.demo;
public class Person {
/*人喂动物,父类Animal,可以表示任意的动物子类*/
public void feedAnimal(Animal animal){
animal.eat();
}
}
package com.ffyc.bcms.demo;
public class Test {
public static void main(String[] args) {
Animal dog=new Dog();
Animal cat=new Cat();
Person person=new Person();
person.feedAnimal(dog);
person.feedAnimal(cat);
}
}
四、多态中的注意事项-所调用的成员到底是什么取决于程序是在编译期间还是在运行期间
编译期间(写代码时),由于子类类型上升为了父类类型,所以调用的都是父类中定义的成员(包括非静态成员方法、静态成员方法、非静态成员变量、静态成员变量)
运行期间(运行代码时),对于非静态成员方法,调用的是子类对象中的,而对于静态成员方法、非静态成员变量、静态成员变量,调用的都是父类中的
1、对于非静态成员方法,编译看左边,运行看右边。(即非静态成员方法在被调用时,在程序编译期间是父类中的非静态成员方法,在程序运行期间是子类对象中的非静态成员方法)
举例一:子类重写父类中的非静态成员方法
package com.ffyc.bcms.demo;
public abstract class Animal {
public abstract void eat();
}
package com.ffyc.bcms.demo;
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
package com.ffyc.bcms.demo;
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
package com.ffyc.bcms.demo;
public class Test {
public static void main(String[] args) {
Animal dog=new Dog();
Animal cat=new Cat();
dog.eat();
cat.eat();
}
}
举例二:子类未重写父类中的非静态成员方法(程序在编译时调用是子类对象中继承自父类的非静态的成员方法)
package com.ffyc.bcms.demo;
public abstract class Animal {
public abstract void eat();
public void sleep(){
System.out.println("动物睡觉");
}
}
package com.ffyc.bcms.demo;
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
package com.ffyc.bcms.demo;
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
package com.ffyc.bcms.demo;
public class Test {
public static void main(String[] args) {
Animal dog=new Dog();
Animal cat=new Cat();
dog.sleep();
cat.sleep();
}
}
2、对于静态成员方法,编译看左边,运行也看左边。(即静态成员方法在被调用时,在程序编译期间是父类中的静态成员方法,在程序运行期间也是父类中的静态成员方法)
举例一:调用静态方法,满足编译看左边,运行也看左边
package com.ffyc.bcms.demo;
public abstract class Animal {
public abstract void eat();
public void sleep(){
System.out.println("动物睡觉");
}
public static void show(){
System.out.println("动物中的show()");
}
}
package com.ffyc.bcms.demo;
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public static void show(){
System.out.println("猫中的show()");
}
}
package com.ffyc.bcms.demo;
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public static void show(){
System.out.println("狗中的show()");
}
}
package com.ffyc.bcms.demo;
public class Test {
public static void main(String[] args) {
Animal dog=new Dog();
Animal cat=new Cat();
dog.show();
cat.show();
}
}
举例二:调用静态成员变量和非静态成员变量,满足编译看左边,运行也看左边
package com.ffyc.bcms.demo;
public abstract class Animal {
int num=12;
static int age=122;
public abstract void eat();
public void sleep(){
System.out.println("动物睡觉");
}
public static void show(){
System.out.println("动物中的show()");
}
}
package com.ffyc.bcms.demo;
public class Cat extends Animal{
int num=13;
static int age=133;
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public static void show(){
System.out.println("猫中的show()");
}
}
package com.ffyc.bcms.demo;
public class Dog extends Animal{
int num=14;
static int age=144;
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public static void show(){
System.out.println("狗中的show()");
}
}
package com.ffyc.bcms.demo;
public class Test {
public static void main(String[] args) {
Animal dog=new Dog();
Animal cat=new Cat();
/*调用非静态成员变量*/
System.out.println(dog.num);
System.out.println(cat.num);
/*调用静态成员变量*/
System.out.println(dog.age);
System.out.println(cat.age);
}
}
五、多态的不足之处以及对应的解决方案
1、不足之处:为了实现多态,我们将子类类型向上转为了父类类型(引用类型之间的转换,分为两种,子类型向父类型转换的隐式类型转换和父类型向子类型转换的强制类型转换,但是要注意,引用类型之间的转换只存在于父类型和子类型之间),但是子类类型一旦上升为父类类型,那么就调用不到子类类型中特有的方法了
2、解决方案:向下转型,将父类类型向下转为子类类型
情况一:父类变量的实际类型就是要强制转换的子类类型
package com.ffyc.bcms.demo;
public class Test {
public static void main(String[] args) {
Animal dog=new Dog();
Dog dog1=(Dog) dog;// 向下转型
dog1.lookHome();
}
}
情况一:父类变量的实际类型不一定是要强制转换的子类类型,于是可以使用intanceof关键字(instanceof关键字前面必须是父类的引用,后面要么是子类的类型,要么是任意的接口名)去判断父类变量的实际类型是否是强制转换的子类类型,避免出现类转换异常
错误示例:
package com.ffyc.bcms.demo;
public abstract class Animal {
int num=12;
static int age=122;
public abstract void eat();
public void sleep(){
System.out.println("动物睡觉");
}
public static void show(){
System.out.println("动物中的show()");
}
}
package com.ffyc.bcms.demo;
public class Cat extends Animal{
int num=13;
static int age=133;
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public static void show(){
System.out.println("猫中的show()");
}
}
package com.ffyc.bcms.demo;
public class Dog extends Animal{
int num=14;
static int age=144;
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public static void show(){
System.out.println("狗中的show()");
}
public void lookHome(){
System.out.println("狗看家");
}
}
package com.ffyc.bcms.demo;
public class Person {
/*人喂动物*/
public void feedAnimal(Animal animal){
animal.eat();
Dog dog=(Dog) animal;
dog.lookHome();
}
}
package com.ffyc.bcms.demo;
public class Test {
public static void main(String[] args) {
Animal dog=new Dog();
Animal cat=new Cat();
Person person=new Person();
person.feedAnimal(dog);
person.feedAnimal(cat);
}
}
正确示例:
package com.ffyc.bcms.demo;
public class Person {
/*人喂动物*/
public void feedAnimal(Animal animal){
animal.eat();
if(animal instanceof Dog){// 判断父类变量animal在实际运行时是否是Dog类型,是的话为true,不是的话为false
Dog dog=(Dog) animal;
dog.lookHome();
}
}
}
package com.ffyc.bcms.demo;
public class Test {
public static void main(String[] args) {
Animal dog=new Dog();
Animal cat=new Cat();
Person person=new Person();
person.feedAnimal(dog);
person.feedAnimal(cat);
}
}
3、向下转型
(1)原因
父类引用仅能访问父类所声明的属性和方法,不能访问子类独有的属性和方法
(2)作用
为了在使用了多态的情况下依然可以使用子类中特有的属性和方法
(3)结果
向下转型得到的子类引用可以使用子类和父类的所有属性和方法(在访问权限以内)
六、final关键字
1、final修饰类
(1)特点:final修饰的类不能被继承
(2)注意:final不能修饰抽象类和接口,因为程序在被编译时会报错
2、final修饰成员方法
(1)final可修饰静态成员方法(静态成员方法本来就无法被子类重写)
(2)final可修饰非静态成员方法,则会导致该方法在子类中无法被重写
3、final修饰方法中的参数-参数的值在方法中不可以被修改
(1)final修饰构造方法中的参数
(2)final修饰静态成员方法中的参数
(3)final修饰非静态成员方法中的参数
4、final修饰成员变量-这种成员变量在定义时就必须对其直接赋值或在构造方法中对其进行赋值并且赋值之后其值都不能再被重新修改
(1)情况一:对于类Animal中的成员变量num,在编写类中的程序代码时num的值就已经确定了并且不允许num的值发生改变,即将num作为一个类中的常量来进行使用,此时可以使用static和final来修饰num,使得程序在运行时num在内存中只有一份且不允许被修改
(2)情况二:对于类Animal的成员变量count,使其成为每一个对象中的自己的常量(即每个对象中count的值都由自己决定),此时仅需使用final对其进行修饰并且必须在构造方法中为该num常量进行初始化,使得程序在运行时创建对象后该对象中的num将不允许被修改