20-面向对象编程-多态
面向对象编程有三大特征:封装、继承和多态
访问修饰符可以控制属性的访问范围
public:表示公共访问级别,可以被任何类访问。
protected:表示受保护访问级别,可以被类本身、子类和同一包中的类访问。
default(缺省):表示默认访问级别,即如果没有使用访问修饰符,默认是此级别,可以被同一包中的类访问。
private:表示私有访问级别,只能在类内部访问。
修饰符
|
类
|
包
|
子类
|
其他包
|
---|---|---|---|---|
public
|
√
|
√
|
√
|
√
|
protected
|
√
|
√
|
√
|
×
|
default
|
√
|
√
|
×
|
×
|
private
|
√
|
×
|
×
|
×
|
多态介绍
方法或对象具有多种形态,多态是建立在封装和继承基础之上的。
换句话说,多态可以理解为事物的多种形态,同一个行为具有不同的表现形式或形态的能力。
多态的本质
父类的引用指向了子类的对象
多态的语法
父类类型 引用名 = new 子类类型();
编译类型看左边,运行类型看右边
多态实现的前提条件
在Java中要实现多态,必须要满足以下几个条件,缺一不可:
1) 继承或实现:在多态中必须存在有继承或实现关系的子类和父类
2) 方法的重写:子类必须要对父类中的某些方法进行重写(使用@Override注解进行重写)
3) 通过父类的引用调用重写方法
例如:打印机分为黑白打印机和彩色打印机,在黑白打印机情况下打出来为黑白,在彩色打印机情况下打印出来为彩色
1. 方法的多态(方法的重载和重写)
public class PolyMethod { public static void main(String[] args) { A a = new A(); B b = new B(); //1.方法的重载体现多态 //这里我们传入不同的参数,就会调用不同的sum方法 System.out.println(b.sum(10,20)); //30 System.out.println(b.sum(10,20,30)); //60 //2.方法的重写体现多态 a.say(); //父类A的say方法被调用 b.say(); //子类B的say方法被调用 } } class A { public void say(){ System.out.println("父类A的say方法被调用"); } } class B extends A { //方法重载 public int sum(int n1, int n2){ return n1 + n2; } public int sum(int n1, int n2, int n3){ return n1 + n2 + n3; } //方法重写 public void say(){ System.out.println("子类B的say方法被调用"); } }
2. 对象的多态
1) 一个对象的编译类型和运行类型可以不一致
2) 编译类型在定义对象时,就确定了,不能改变
3) 运行类型是可以变化的
4) 编译类型是看定义时 “=” 等号的左边,运行类型是看 “=” 等号的右边
public class PolyMethod { public static void main(String[] args) { //对象的多态 //1.animal的编译类型是 Animal,animal的运行类型是 Dog Animal animal = new Dog(); //当代码执行到当前行时,animal的运行类型时Dog,所以cry方法是Dog类cry方法 animal.cry(); //Dog cry 小狗汪汪叫... //2.此时,animal的运行类型是 Cat,但编译类型仍然是 Animal animal = new Cat(); //当代码执行到当前行时,animal的运行类型时Cat,所以cry方法是Cat类cry方法 animal.cry(); //Cat cry 小猫喵喵叫... } } class Animal{ public void cry(){ System.out.println("Animal cry 一直在叫..."); } } class Cat extends Animal{ @Override public void cry(){ System.out.println("Cat cry 小猫喵喵叫..."); } } class Dog extends Animal{ @Override public void cry(){ System.out.println("Dog cry 小狗汪汪叫..."); } }
练习1. 请编写一个程序,Food类、Animal类和Master类,其中Master类中有feed方法,使用多态完成主人给动物喂食的信息
//请编写一个程序,Food类、Animal类和Master类,其中Master类中有feed方法,使用多态完成主人给动物喂食的信息 public class Test { public static void main(String[] args) { Master m = new Master("马铃薯"); Animal animal_1 = new Dog("小狗"); Food food_1 = new Bone("大棒骨"); m.feed(animal_1,food_1); //马铃薯正在给小狗喂食大棒骨... Animal animal_2 = new Cat("小猫"); Food food_2 = new Fish("黄花鱼"); m.feed(animal_2,food_2); //马铃薯正在给小猫喂食黄花鱼... } } //食物类 class Food{ public String name; //构造器 public Food(String name){ this.name = name; } //get() 和 set() 方法 public String getName() { return name; } public void setName(String name) { this.name = name; } } class Bone extends Food{ public Bone(String name){ super(name); } } class Fish extends Food{ public Fish(String name){ super(name); } } //动物类 class Animal{ public String name; //构造器 public Animal(String name){ this.name = name; } //get() 和 set()方法 public String getName(){ return name; } public void setName(String name){ this.name = name; } } class Dog extends Animal{ public Dog(String name){ super(name); } } class Cat extends Animal{ public Cat(String name){ super(name); } } //主人类 class Master{ private String name; //构造器 public Master(String name){ this.name = name; } //get() 和 set() 方法 public String getName() { return name; } public void setName(String name) { this.name = name; } //feed方法,使用多态完成主人给动物喂食的信息 //1.使用多态机制,可以统一的管理主人喂食的问题 //animal 编译类型是Animal,可以指向(接收)Animal子类的对象 //food 编译类型是Food,可以指向(接收)Food子类的对象 public void feed(Animal animal, Food food){ System.out.println(name + "正在给" + animal.getName() + "喂食" + food.getName() + "..."); } }
多态的向上转型和向下转型
1. 向上转型
向上转型:父类的引用指向了子类的对象。
父类类型 引用名 = new 子类类型()
a) 可以调用父类中的所有属性和方法(需遵守访问权限),但不能调用子类中特有属性和方法。这是因为在编译阶段,能调用哪些成员,是由编译类型决定的。
b) 可以调用子类中重写的父类方法,但最终运行效果看子类的具体实现。这是因为在执行代码阶段,具体实现哪些内容,是由运行类型决定的。
也就是说,左边的编译类型决定调用哪些属性和方法,右边的运行类型决定具体实现哪些内容。
public class PolyDetail { public static void main(String[] args) { //向上转型:父类的引用指向了子类的对象 //父类类型 引用名 = new 子类类型() Animal animal = new Dog(); animal.eat(); //小狗在吃大棒骨 //animal.cry(); 报错,无法调用子类的特有方法cry() } } class Animal{ String name = "动物"; int age = 10; public void run(){ System.out.println("跑"); } public void eat(){ System.out.println("吃"); } } class Dog extends Animal{ public void eat(){ System.out.println("小狗在吃大棒骨"); } public void cry(){ System.out.println("小狗在一直哭"); } }
2. 向下转型
向下转型:只有向上转型过的对象才能向下转型,向下转型为恢复子类所有功能。
子类类型 引用名 = (子类类型) 父类引用;
a) 只能强转父类的引用,不能强转父类的对象
b) 要求父类的引用,必须指向的是当前目标类型的对象
c) 当向下转型后,就可以调用子类类型中所有的属性和方法
public class PolyDetail2 { public static void main(String[] args) { //先向上转型 Animal animal2 = new Dog(); //再向下转型 //这时dog的编译类型是Dog类,运行类型也是Dog类,所以能调用子类类型中的所有属性和方法 Dog dog = (Dog)animal2; dog.eat(); //小狗在吃大棒骨 dog.cry(); //小狗在一直哭 } } class Animal{ String name = "动物"; int age = 10; public void run(){ System.out.println("跑"); } public void eat(){ System.out.println("吃"); } } class Dog extends Animal{ public void eat(){ System.out.println("小狗在吃大棒骨"); } public void cry(){ System.out.println("小狗在一直哭"); } }
Java中为了提高向下转型的安全性,引入了 instanceof关键字,判断左边的对象是否为右边类型或子类型的实例,返回 boolean 的数据类型。
如果该表达式为true,则表示左边的对象是右边类或子类所创建的实例;否则,返回false。
Dog dog = new Dog(); Animal animal = dog; if(animal instanceof Dog){ dog = (Dog)animal; dog.cry(); }
Java的动态绑定机制
1. 当调用对象方法时,该方法会和该对象的 运行类型/内存地址 绑定
2. 当调用对象属性时,没有动态绑定机制,哪里声明就在哪里使用
public class DynamicBinding { public static void main(String[] args) { A a = new B(); System.out.println(a.sum()); } } class A{ public int i = 10; public int sum(){ System.out.println("我是父类A中的sum()方法"); return getI(); } public int getI(){ System.out.println("我是父类A中的getI()方法"); return i; } } class B extends A{ public int i = 20; public int getI(){ System.out.println("我是子类B中的getI()方法"); return i; } }