Java的多态及用法

来自CloudWiki
跳转至: 导航搜索

问题引入

前面我们利用继承编写了很多子类,

那么我们如何管理这些子类呢 ?

写功能方法的时候需要每个子类都写一个独立的方法吗?

答案是:多态。

多态的概念

生活中的多态

多态是同一个行为具有多个不同表现形式或形态的能力。

多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:

Java5-2.png

多态性是对象多种表现形式的体现。

现实中,比如我们按下 F1 键这个动作:

       如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
       如果当前在 Word 下弹出的就是 Word 帮助;
       在 Windows 下弹出的就是 Windows 帮助和支持。

什么叫做多态 ?同一个动作发生在不同的对象上会产生不同的结果。


开发中的多态

在Java里面,多态代表“多种状态”。在面向对象思想中,对于同一个动作,父类有父类的实现方法,不同子类有不同子类的实现方法,那么子类和父类总起来就可以成为多态。

例如:在下面这个例子中,对于eat这件事情,父类Animal 和子类Cat、Dog 它的行为是截然不同的。

(以下例子供讲解)

abstract class Animal {  
    abstract void eat();  
}  
  
class Cat extends Animal {  //多态的第一个条件:继承,Cat继承自Animal
    public void eat() {  //多态的第二个条件:重写,Cat类对原先父类的方法进行重写
        System.out.println("吃鱼");  
    }  
    public void work() {  
        System.out.println("抓老鼠");  
    }  
}  
  
class Dog extends Animal {  //多态的第一个条件:继承,Dog继承自Animal
    public void eat() {  //多态的第二个条件:重写,Dog类对原先父类的方法进行重写
        System.out.println("吃骨头");  
    }  
    public void work() {  
        System.out.println("看家");  
    }  
}
public class Test {
    public static void main(String[] args) {
                      
      Animal a = new Cat();  // 向上转型,我们将创建的Cat对象赋予Animal类的对象
      Animal b = new Dog(); // 向上转型 ,我们将创建的Dog对象赋予Animal类的对象
      a.eat();               // 这里就体现了多态,虽然同为Animal的对象,但这里调用的是 Cat类 的 eat
      b.eat();               // 这里就体现了多态,虽然同为Animal的对象,但这里调用的是Dog类 的 eat
  }  

所以,总结一下,多态是同一个行为具有多个不同表现形式或形态的能力。

这种能力是基于我们前面学过的继承和方法重写实现的。

我们可以利用这种能力,为多态中的每个子类提供相同接口,从而简化功能代码的编写。

多态的作用

多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。

功能扩展和代码稳定的矛盾

1.对内部来讲:子类越写越多,对父类的功能进行扩展越来越多,

2.对外部来说:外界编写调用代码的时候,却希望无论子类的功能再怎么扩展,代码越稳定越好,这是一对矛盾。

我们还是来举栗子。

(以下例子供实操)

假设我们定义一种收入,需要给它报税,那么先定义一个Income类:


public class Income {
	 protected String name;
	 protected double income;
	 public Income(String n,double i) {
		 this.name =n;
		 this.income =i;
	 }
	 public double getTax() {
	        return income * 0.1; // 税率10%
	 }
	public static void main(String[] args) {
		// TODO Auto-generated method stub

	}

}

先写第一个子类:SalaryIncome

对于工资收入,可以减去一个基数,那么我们可以从Income派生出SalaryIncome,并覆写getTax():

class Salary extends Income {
	protected String name;
	protected double income;
	public Salary(String n,double i) {
		 super(n,i);
	 }
    @Override
    public double getTax() {
        if (income <= 5000) {
            return 0;
        }
        return (income - 5000) * 0.2;
    }
}

再考虑第二个子类:

如果你享受国务院特殊津贴,那么按照规定,可以全部免税:

class StateCouncilSpecialAllowance extends Income {
	
	protected String name;
	protected double income;
	public StateCouncilSpecialAllowance(String n,double i) {
		 super(n,i);
	}
	
    @Override
    public double getTax() {
        return 0;
    }
}

现在,我们要编写一个报税的财务软件,对于一个人的所有收入进行报税,可以这么写:

 
    public static void printTax(Income income) {
		    
		    System.out.println("纳税人姓名:" +income.name);
		    System.out.println("月平均收入:" +income.income);
		    System.out.println("纳税额="+income.getTax());
		    System.out.println();
		    
	}

来试一下:

// Polymorphic


public class Main {
	 public static void printTax(Income income) {
		    
		    System.out.println("纳税人姓名:" +income.name);
		    System.out.println("月平均收入:" +income.income);
		    System.out.println("纳税额="+income.getTax());
		    System.out.println();
		    
	}
	 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Income[] incomes = new Income[3];
		incomes[0] = new Income("王强",3000);
		incomes[1] = new Salary("张丽",7500);
		incomes[2] = new StateCouncilSpecialAllowance("陈博士",15000);
		
		
		for(int i=0;i<incomes.length;i++) {
			printTax(incomes[i]);
		}
		
	}

}


观察printTax()方法:

多态完美的帮我们为不同人群计算纳税额:

  • 形式上,不同对象调用形式相同,维持了功能代码的稳定
  • 内容上,不同对象都重写了父类的方法,体现了不同子类各自不同的内容。

利用多态,printTax()方法只需要和Income打交道,它完全不需要知道Salary和StateCouncilSpecialAllowance的存在,就可以正确计算出总的税。如果我们要新增一种稿费收入,只需要从Income派生,然后正确覆写getTax()方法就可以。把新的类型传入printTax(),不需要修改任何代码。

可见,多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码:

多态的必要条件

回过头来看,我们刚才是如何实现了多态的呢 ?

Java实现多态有三个必要条件:继承、重写、向上转型。

  • 继承:在多态中必须存在有继承关系的子类和父类。如上面的
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
  • 只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

【注 意】

  • 多态概念在很多书中分为运行时多态和静态多态。静态多态可简单理解为方法重载。实际上在程序编写时动态多态的用法更为广泛和有效。

多态的用法

多态的用法一般可以归结为2种:

  • 一种用法是使用父类声明的数组存储子类的对象;
  • 另一种用法是使用父类的声明作为方法的形参,子类对象作为实参传入。


用法一 基于父类的存储

测试类Main中使用父类数组盛放子类对象

		public static void main(String[] args) {
		// TODO Auto-generated method stub
		Income[] incomes = new Income[3];
		incomes[0] = new Income("王强",3000);
		incomes[1] = new Salary("张丽",7500);
		incomes[2] = new StateCouncilSpecialAllowance("陈博士",15000);
		
		for(int i=0;i<incomes.length;i++) {
			System.out.println(i+":"+incomes[i].name);
			System.out.println(i+":"+incomes[i].getTax());
		}
		
		
		
	}

用法二 基于父类的调用

实现多态,也可以由父类Income还可以作为形式参数,放到printTax的方法里,然后有主类调用:


public class Main {
	 public static void printTax(Income income) {
		    
		    System.out.println("纳税人姓名:" +income.name);
		    System.out.println("月平均收入:" +income.income);
		    System.out.println("纳税额="+income.getTax());
		    System.out.println();
		    
	}
	 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Income[] incomes = new Income[3];
		incomes[0] = new Income("王强",3000);
		incomes[1] = new Salary("张丽",7500);
		incomes[2] = new StateCouncilSpecialAllowance("陈博士",15000);
		
		
		for(int i=0;i<incomes.length;i++) {
			printTax(incomes[i]);
		}
		
	}

}


总结

无论对于上述哪一种,都实现了多态。

在内部利用子类实现了功能扩展的同时,外部调用的代码始终不变。

无论子类的功能再怎么扩展,都无需修改外界调用的代码。从而实现了外部代码的稳定。

拓展:多态和继承

多态和继承是面向对象的两大概念:

  1. 继承的好处使我们能够不必为每一个派生类编写功能调用,只需要在父类中统一编写即可,大大提高了程序的可复用性,即向前兼容。
  2. 多态的好处是指父类某个方法被其子类重写时,各个子类可以各自产生自己的功能行为,并且它们的这些功能可以被父类的对象所调用,大大丰富了类的形态和功能。这叫向后兼容

参考文档: [1] http://www.runoob.com/java/java-polymorphism.html [2] https://www.cnblogs.com/chenssy/p/3372798.html

[3]https://www.liaoxuefeng.com/wiki/1252599548343744/1260455778791232

返回 Java程序设计