多态(基本介绍、快速入门、注意事项以及细节、动态绑定机制)

目录

1.一个问题引出多态

2.多态的基本介绍

3.对象的多态

4.多态快速入门案例

5.多态注意事项和细节问题(向上转型和向下转型)

6.属性重写问题

7.instanceOf比较操作符

8.练习题

9.java的动态绑定机制(非常非常重要)

10.多态数组

11.多态参数


1.一个问题引出多态

public class Food {
	private String name;

	public Food(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}
public class Fish extends Food{
	
	public Fish(String name) {
		super(name);
	}
}
public class Bone extends Food{

	public Bone(String name) {
		super(name);
	}
}
public class Animal {
	
	private String name;

	public Animal(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}
public class Cat extends Animal {
	
	public Cat(String name) {
		super(name);
	}
}
public class Dog extends Animal{
	public Dog(String name) {
		super(name);
	}
}

这些类写好了之后,再单写一个Master类  

public class Master{
	
	private String name; //主人名称

	public Master(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public void feed(Dog dog,Bone bone){
		System.out.println("主人 "+name+" 给 "+ dog.getName()+" 吃"+bone.getName());
	}
}

然后进行测试

public class Poly01 {

   public static void main(String[] args) {
      Master tom = new Master("汤姆");
      Dog dog = new Dog("大黄");
      Bone bone = new Bone("大棒骨");
      tom.feed(dog,bone);
   }
   
}

                               


那么我们现在又要让主人给小猫喂鱼,又要在Master类里写一个feed的方法,只是参数不一样,也就是重载。  

//主人给小狗 喂食 黄花鱼
public void feed(Cat cat,Fish fish){
	System.out.println("主人 "+name+" 给 "+ cat.getName()+" 吃"+fish.getName());
}

测试

public class Poly01 {

	public static void main(String[] args) {
		Master tom = new Master("汤姆");
		Dog dog = new Dog("大黄");
		Bone bone = new Bone("大棒骨");
		tom.feed(dog,bone);

		Cat cat = new Cat("小花猫");
		Fish fish = new Fish("黄花鱼");
		System.out.println("==========");
		tom.feed(cat,fish);
	}
}

注:

从功能实现上来说,效果确实是出来了,但是我们这样写下去会出现一种后果

如果随着代码的扩展,将来主人可能还会养猪、兔子、鸟、等各种动物,这样的话不同的动物吃的东西也不一样,猪喜欢吃米饭,鱼喜欢吃虫子,

这样动物很多,食物也很多,就会造成feed方法扩展越来越多,不利于管理和维护。

这里就引出多态


2.多态的基本介绍

方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。

public class PloyMethod {

   public static void main(String[] args) {

   }
}

class B { //父类
   public void say() {
      System.out.println("B say() 方法被调用...");
   }
}

class A extends B { //子类

   public int sum(int n1, int n2) { //和下面的sum 构成重载
      return n1 + n2;
   }

   public int sum(int n1, int n2, int n3) {
      return n1 + n2 + n3;
   }
   
   public void say(){
      System.out.println("A say()方法被调用...");
   }
}

这里有父类和子类,子类里有2个sum构成重载,那么这是如何构成多态的呢?

此时我们在main方法里这样写:

public static void main(String[] args) {
	// 方法重载体现多态
	A a = new A();
	// 这里我们传入不同的参数,就会调用不同的sum方法,就体现出多态
	System.out.println(a.sum(10,20));
	System.out.println(a.sum(10,20,30));
}

我们通过不同的参数个数去调用sum方法,就会去调用不同的方法,因此对sum方法来说,就是多种状态的体现。  


重载体现出了多态,那么方法的重写又是如何体现出多态?

同样是刚刚的例子,我们可以看到父类由一个say方法,子类也有一个say方法

这里我们在main方法中new一个B对象,调用a的say方法和b的say方法。

//方法重写体现多态
B b = new B();
a.say();
b.say();

                                                  

这里也会体现出多态。

注:

重载和重写体现的多态是比较好理解的,真正难理解的是对象的多态,因为对象多态里面很复杂,对象里面会有编译类型和运行类型的概念。  


3.对象的多态

(1)一个对象的编译类型和运行类型可以不一致

这里Dog类是Animal的子类,这里可以把一个子类对象赋给一个父类的对象引用,也就是说我能用一个父类的引用指向子类对象,现在这个animal准确的讲是对象的引用,并不是对象,而是对象的引用,而后面new的才是真正的对象,此时animal的编译类型是Animal,运行类型是Dog

(2)编译类型在定义对象时,就确定了,不能改变  

Animal animal = new Dog();

我们可以看到以上代码,它一但定义好了,也就是animal,它的编译类型就是Animal,不可以改变,已经确定下来了

(3)运行类型是可以改变的

animal = new Cat();

这个时候animal的运行类型变成了Cat,编译类型仍然是Animal

(4)编译类型看定义时 = 号的左边,运行类型看 = 号的右边


代码演示

public class Animal {
	
	public void cry(){
		System.out.println("Animal 动物在叫...");
	}
	
}
public class Cat extends Animal{
	
	public void cry() {
		System.out.println("Cat cry() 小猫喵喵叫...");
	}
}
public class Dog extends Animal{
	
	public void cry() {
		System.out.println("Dog cry() 小狗汪汪叫...");
	}
}
public class PolyObject {
	public static void main(String[] args) {
		//体验对象多态的特点
		
		//animal 编译类型已经确定 --> Animal,运行类型是 Dog
		Animal animal = new Dog();
		//因为运行时,也就是执行到animal.cry()这一行代码的时候,animal的运行类型是Dog,因此这个cry就会找运行类型的cry,也就是dog里的cry
		animal.cry(); //小狗汪汪叫
	}
}

运行时,也就是执行到animal.cry()这一行代码的时候,animal的运行类型是Dog,因此这个cry就会找运行类型的cry,也就是dog里的cry


此时我们再把运行类型改成Cat

animal = new Cat();
animal.cry(); //小猫喵喵叫

编译类型在定义的时候就已经确定了,这是无法改变的,一直是animal,但是运行类型是Cat。

一但我们将右边new为Cat的时候,animal的指向就发生变化了,这时候是指向Cat,也就是说,在运行的时候主要看的是堆里面的,既然运行类型变成猫了,那么在执行执行的时候又以猫为主,运行的时候全部看的是堆里面的真正对象。

4.多态快速入门案例

回顾我们一开始多态问题的引出,此时我们来分析一下,这确实要写很多个feed方法

//主人给小狗 喂食 骨头
public void feed(Dog dog,Bone bone){
	System.out.println("主人 "+name+" 给 "+ dog.getName()+" 吃"+bone.getName());
}
//主人给小狗 喂食 黄花鱼
public void feed(Cat cat,Fish fish){
	System.out.println("主人 "+name+" 给 "+ cat.getName()+" 吃"+fish.getName());
}

我们可以对代码进行简化

//使用多态机制,可以统一的管理主人喂食的问题
public void feed(Animal animal,Food food){
	System.out.println("主人 "+name+" 给 "+ animal.getName()+" 吃"+food.getName());
}

里面的参数类型可以用父类来接收,父类的引用可以指向它的子类,所以里面直接写个Animal就好了,同样食物也是。

由于animal的编译类型是Animal类型,它可以指向(接收) Animal子类的对象。

同样的道理,Food的编译类型是food,它可以指向(接收)Food的子类对象

这时候去测试:

public class Poly01 {

	public static void main(String[] args) {
		Master tom = new Master("汤姆");
		Dog dog = new Dog("大黄");
		Bone bone = new Bone("大棒骨");
		tom.feed(dog,bone);

		Cat cat = new Cat("小花猫");
		Fish fish = new Fish("黄花鱼");
		System.out.println("==========");
		tom.feed(cat,fish);
	}
}

                            

多态带来的好处不止这个。

假如我们此时让主人喂食给一只猪,我们来编写一下代码

public class Pig extends Animal{
	public Pig(String name) {
		super(name);
	}
}
public class Rice extends Food{
	public Rice(String name) {
		super(name);
	}
}

此时主人要给小猪喂食物,我们会发现,这个feed方法不用动,因为它已经可以接收所有动物的子类对象,也可以接收所有食物的子类对象  

//使用多态机制,可以统一的管理主人喂食的问题
public void feed(Animal animal,Food food){
	System.out.println("主人 "+name+" 给 "+ animal.getName()+" 吃"+food.getName());
}

直接测试:

public class Poly01 {

	public static void main(String[] args) {
		Master tom = new Master("汤姆");
		
		//添加给小猪喂米饭
		Pig pig = new Pig("小花猪");
		Rice rice = new Rice("米饭");
		tom.feed(pig,rice);

	}
}

                                


5.多态注意事项和细节问题(向上转型和向下转型)

多态的前提是:两个对象(类)存在继承关系

多态的向上转型:

那么为了举例子,我们重新建一个Animal类和Cat类

public class Animal {
	String name = "动物";
	int age=10;
	
	public void sleep(){
		System.out.println("睡");
	}
	public void run(){
		System.out.println("跑");
	}
	public void eat(){
		System.out.println("吃");
	}
	public void show(){
		System.out.println("hello,你好");
	}
}
public class Cat extends Animal{

	public void eat(){ //方法重写
		System.out.println("猫吃鱼");
	}

	public void catchMouse(){ //Cat特有的方法
		System.out.println("猫抓老鼠");
	}
}
public class PolyDetail {
	public static void main(String[] args) {
		
		//(向上转型):父类的引用指向子类的对象
		//语法:父类类型引用名 = new 子类类型();
		Animal animal = new Cat();
		Object object = new Cat();
		System.out.println("OK");
		
	}
}

将Animal和Object作为引用指向子类的对象这样都是可以的,虽然Object不是Cat的直接父类,但是是Animal的父类。

                            


可以调用父类中的所有成员(需遵守访问权限)

但是就是不能调用子类中的catchMouse()方法,如果需要调用的话,则需要强转

也就是说不能调用子类的特有成员,这个例子里catchMouse()是Cat类独有的方法,在编译阶段,能调用哪些成员,是由编译类型来决定的。

此时我们调用4个方法:

public class PolyDetail {
	public static void main(String[] args) {
		
		//(向上转型):父类的引用指向子类的对象
		//语法:父类类型引用名 = new 子类类型();
		Animal animal = new Cat();
		
		animal.eat();
		animal.run();
		animal.show();
		animal.sleep();
	}
}

那么这样的输出结果会是什么呢?? 

这里我们要清楚,在编译阶段,它指定编译类型,也就是javac,但是一但运行起来以后,其实运行的时候就是交给java虚拟机来做了,它在运行程序的时候,不管你编译类型,只看你运行类型,而当我们执行animal.eat()这一行的时候,会先去Cat类里面找eat方法,如果有的话直接找Cat里的eat方法,那么这里直接输出"猫吃鱼"。

注:

调用方法的时候,按照从子类(运行类型)开始查找方法,然后调用,规则和前面的方法调用规则一致


向下转型:

public class PolyDetail {
	public static void main(String[] args) {
		
		//(向上转型):父类的引用指向子类的对象
		//语法:父类类型引用名 = new 子类类型();
		Animal animal = new Cat();
		
		animal.eat();
		animal.run();
		animal.show();
		animal.sleep();
	}
}

向上转型的调用方法规则中有:可以调用父类中的所有成员,但是不能调用子类的特有成员。  


那么我们就是想调用子类的特有成员该怎么做呢?

这里就出现了多态的向下转型

//多态的向下转型
//语法:子类类型 引用名 = (子类类型) 父类引用.
Cat cat = (Cat)animal;
cat.catchMouse();

这个时候catchMouse(),就可以调用了,此时cat的编译类型是Cat,运行类型还是Cat,因为强转为Cat了


那么此时我们再来看一段代码,我们写了一个Dog类,让Dog类也去继承这个Animal

Dog dog = (Dog)animal;

一开始的Cat cat = (Cat)animal这段代码中的animal是因为指向的是Cat对象,那么强转为Cat,这时又有一个cat也是指向到Cat对象。

但是我们想强转为Dog,我们这么想一想,animal指向的是Cat对象,总不可能让一个狗指向猫吧?

                  

6.属性重写问题

属性没有重写之说,属性的值看编译类型。

public class PolyDetail2 {

	public static void main(String[] args) {
		Base base  = new Sub();
		System.out.println(base.count); //10
		Sub sub = new Sub();
		System.out.println(sub.count); //20
	}
	
}


class Base{ //父类
	int count=10; //属性
}

class Sub extends Base{ //子类
	int count=20; //属性
}

7.instanceOf比较操作符

instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型

public class PolyDetail3 {
	public static void main(String[] args) {
		BB bb = new BB(); //true
		System.out.println(bb instanceof BB); //true
	}
}

class AA{} //父类
class BB extends AA{} //子类

那么instanceOf判断的是编译类型还是判断的运行类型?

//aa 编译类型是 AA,运行类型是BB
AA aa = new BB();
System.out.println(aa instanceof AA); //true
System.out.println(aa instanceof BB); //true

那么得出结论:判断对象的运行类型是否为 XX 类型或 XX 类型的子类型


8.练习题


9.java的动态绑定机制(非常非常重要)

Java 重要特性: 动态绑定机制

我们先来看个问题:

public class DynamicBinding {

	public static void main(String[] args) {
		A a = new B();
		System.out.println(a.sum());  //40
		System.out.println(a.sum1()); //30
	}
}

class A {
	public int i = 10;

	public int sum() {
		return getI() + 10;
	}

	public int sum1() {
		return i + 10;
	}

	public int getI() {
		return i;
	}
}

class B extends A {
	public int i = 20;

	public int sum() {
		return i + 20;
	}

	public int getI() {
		return i;
	}

	public int sum1() {
		return i + 10;
	}
}

这里毫无疑问,输出40和30


但是我们此时,将子类的sum方法注释掉

此时由于子类没有sum()方法,则去找父类的,但是父类里面又有一个getI()方法,此时子类和父类都有这个getI()方法,那么调用的到底是父类的getI还是子类的?这里就会出现一个动态绑定机制在里面了。  

以上面的例子为例,a.sum()这一步,执行到父类的sum()方法时,我们会发现:

由于a.sum()中的a是运行类型是B,此时去调用子类的getI()

而到了子类的getI()的时候,里面的return i;i是个属性,那么此时这个i就是子类里的i,也就是20;所以在父类里的sum方法,return的getI()就是20+10

而最后的运行结果则是从40变为30。


那么现在我们继续把子类的sum1()注释掉 

这里调用方法,同样跟该对象的内存地址绑定,但是B类的sum1被我们注释掉,这里就是没有,但是这里继承机制发生了,去父类去找,父类由sum1方法,此时return i+10;属性是没有动态绑定机制的,也就是哪里声明哪里使用,这个i就是当前类,也就是10,那么最后输出20。


10.多态数组

public class Person {
	
	private String name;
	private int age;

	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	public String say(){
		return name + "\t" + age; 
	}
}
public class Student extends Person{
	
	private double score;

	public Student(String name, int age, double score) {
		super(name, age);
		this.score = score;
	}

	public double getScore() {
		return score;
	}

	public void setScore(double score) {
		this.score = score;
	}
	
	//重写父类say


	@Override
	public String say() {
		return super.say() + " score="+score;
	}
}
public class Teacher extends Person{
	private double salary;

	public Teacher(String name, int age, double salary) {
		super(name, age);
		this.salary = salary;
	}

	public double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}

	//重写父类say方法
	@Override
	public String say() {
		return super.say() + " salary="+salary;
	}
}
public class PloyArray {
	public static void main(String[] args) {

		/**
		 * 应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象
		 * 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
		 */

		Person[] persons = new Person[5];
		persons[0] = new Person("jack",20);
		persons[1] = new Student("jack",18,100);
		persons[2] = new Student("smith",19,30.1);
		persons[3] = new Teacher("scott",30,20000);
		persons[4] = new Teacher("king",50,25000);
		
		//循环遍历多态数组,调用say方法
		for (int i = 0; i < persons.length; i++) {
			//person[i]的编译类型是Person,运行类型是根据实际情况由JVM来判断
			System.out.println(persons[i].say());//动态绑定机制
			
		}
	}
}

既然要统一放在一个数组里,肯定得用父类类型来接收,因为父类的引用可以指向子类的对象,所以可以这样创建对象数组:Person[] persons = new Person[5];那这里面可以放Person和Person的子类,最后循环遍历,就会跟着运行类型进行输入say方法


此时我们对这个问题进行强化一下:

那么我们添加一下方法

//学生特有方法
public void sutdy(){
	System.out.println("学生 "+getName()+" 正在学java课程");
}
//老师特有方法
public void teach(){
	System.out.println("老师 "+getName()+" 正在讲java课程");
}

但是我们在循环里面调用,是没办法去调用的

因为person[i]的编译类型是Person类,而Person类是没有这个方法的,那么我们可以在循环里面进行判断,判断运行类型是不是学生,instanceof判断运行类型是不是Student,或者是Student的子类。

public class PloyArray {
	public static void main(String[] args) {

		/**
		 * 应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象
		 * 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
		 */

		Person[] persons = new Person[5];
		persons[0] = new Person("jack", 20);
		persons[1] = new Student("mary", 18, 100);
		persons[2] = new Student("smith", 19, 30.1);
		persons[3] = new Teacher("scott", 30, 20000);
		persons[4] = new Teacher("king", 50, 25000);

		//循环遍历多态数组,调用say方法
		for (int i = 0; i < persons.length; i++) {
			//person[i]的编译类型是Person,运行类型是根据实际情况由JVM来判断
			System.out.println(persons[i].say());//动态绑定机制

			if (persons[i] instanceof Student) { //判断person[i]的运行类型是不是Student
				Student student = (Student) persons[i]; //向下转型
				student.study();
				//这里可以使用一条语句写:((Student)person[i]).study();
			} else if (persons[i] instanceof Teacher) {
				((Teacher) persons[i]).teach();
			} else if (persons[i] instanceof Person){
				
			}else{
				System.out.println("你的类型有误,请自行判断...");
			}
		}
	}
}

这里要巧妙的运用instanceof判断运行类型,然后对其进行向下转型的处理,如果运行类型是Person则不进行处理


11.多态参数

其实在这里就已经体现出了参数的多态,cat和pig是Animal的子类,fish和rice是Food的子类。  


 

public class Employee {
	
	private String name;
	private double salary;

	public Employee(String name, double salary) {
		this.name = name;
		this.salary = salary;
	}
	
	//得到年工资的方法
	public double getAnnual(){
		return 12 * salary;
	}
	

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}
}
public class Manager extends Employee {

	private double bonus;

	public Manager(String name, double salary, double bonus) {
		super(name, salary);
		this.bonus = bonus;
	}

	public void manage() {
		System.out.println("经理 " + getName() + "is managing");
	}

	//重写获取年薪方法
	@Override
	public double getAnnual() {
		return super.getAnnual() + bonus;
	}

	public double getBonus() {
		return bonus;
	}

	public void setBonus(double bonus) {
		this.bonus = bonus;
	}
}
public class Worker extends Employee {

	public Worker(String name, double salary) {
		super(name, salary);
	}

	public void work() {
		System.out.println("普通员工 " + getName() + " is working");
	}

	@Override
	public double getAnnual() { //因为普通员工没有其他收入,则直接调用父类的方法。
		return super.getAnnual();
	}
}

那么我们就开始写测试类

public class PloyParameter {
	public static void main(String[] args) {
		Worker tom = new Worker("tom", 2500);
		Manager milan = new Manager("milan", 5000, 200000);
		PloyParameter ployParameter = new PloyParameter();
		ployParameter.showEmpAnnual(tom);
		ployParameter.showEmpAnnual(milan);
		
		ployParameter.testWork(tom);
		ployParameter.testWork(milan);
	}
	/**
	 * showEmpAnnual(Employee employee)
	 * 实现获取任何员工对象的年工资,并在 main 方法中调用该方法 [employee.getAnnual()]
	 */
	public void showEmpAnnual(Employee employee) {
		System.out.println(employee.getAnnual());
	}
}

此时:ployParameter.showEmpAnnual(tom);这里我们传进去的tom,它的编译类型是Worker,运行类型也是Worker,那么传进去肯定会去找worker。

那么我们传milan进去,就会给Manager绑定,因为有动态绑定机制。


那么我们再写第二个方法:

此时们将形参再写Employee,然后用instanceof判断运行类型,如果是Worker,直接调用e.work(),这里编译器会自动转换--->向下转型。

那么继续用istanceof判断是不是Manager。

public class PloyParameter {

	public static void main(String[] args) {
		Worker tom = new Worker("tom", 2500);
		Manager milan = new Manager("milan", 5000, 200000);
		PloyParameter ployParameter = new PloyParameter();
		ployParameter.showEmpAnnual(tom);
		ployParameter.showEmpAnnual(milan);
		
		ployParameter.testWork(tom);
		ployParameter.testWork(milan);
	}

	/**
	 * showEmpAnnual(Employee employee)
	 * 实现获取任何员工对象的年工资,并在 main 方法中调用该方法 [employee.getAnnual()]
	 */
	public void showEmpAnnual(Employee employee) {
		System.out.println(employee.getAnnual());
	}
	
	//添加一个方法,testWork,如果是普通员工,则调用 work 方法,如果是经理,则调用 manage 方法
	public void testWork(Employee e){
		if (e instanceof Worker){
			((Worker) e).work(); //有一个向下转型的操作
		}else if (e instanceof Manager){
			((Manager) e).manage();
		}else{
			
		}
	}
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GodAiro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值