位于上海,服务全国!

位于上海,服务全国!

深入研究多态性及其在Java中的优势

作者:admin 分类: 时间:2019-04-04 15:57:16 点击量:1356



多态性是面向对象编程的基本原理之一。这个术语通常意味着某物可以有多种形式。在面向对象的方法中,多态性允许编写可以后期绑定引用的程序。虽然在Java中创建多态引用很容易,但它背后的概念对整个编程有更深的影响。本文探讨了多态性的一些复杂细节及其在面向对象编程中的含义。
多态引用:概述
多态引用是一个变量,可以在不同的时间点引用不同类型的对象。它通常与所引用的类兼容。例如,在以下情况下:


Employee
“Employee”是一个引用变量,可以引用Employee类的实例。对应于引用对象的引用变量的限定性由其兼容性决定。这似乎是唯一可靠的条件,但事实并非如此,尤其是在实现多态性时。由于规则太严格,而多态性通过结合“具有多种形式”的思想使其更加灵活。这意味着多态引用可以保证它可以在不同的时间点引用不同类型的对象,而不必固守完全匹配兼容性的思想。因此,如果引用可用于在某个时间点调用某个方法,则可以动态更改它以指向另一个对象,并在下一次调用其他方法。通过给出另一个维度来利用灵活性,该维度由引用变量使用。
当引用变量被绑定到某个对象,且其运行时无法被更改,或者换句话说,方法定义至绑定方法调用在编译时完成,称为静态绑定。如果绑定在运行时可更改,就像多态引用的情况一样,其中绑定的决定只在执行期间作出,则称为动态绑定或延迟绑定。两者在面向对象编程中都有各自的用途,但并不是一个比另一个重要。然而,在多态引用情况下,延迟的承诺绑定在灵活性方面优于编译时绑定,但在另一方面,会降低性能开销。
但是,这在很大程度上是可以接受的,对于提高效率的普通开销异议很小。


创建多态性
在Java中,多态引用可以通过两种方式来创建:使用继承或接口。
继承多态性
引用变量引用的类实例。在继承层次结构的情况下,如果引用变量在层次结构树中声明为父类类型,则引用对象可以指向层次结构中任何类的实例。


这意味着,在Java中,因为对象类是所有类的父类或超级类,换句话说,Java中的所有类实际上是对象类的隐式或显式的子类,对象类型的引用变量可以引用Java中的任何类实例。这就是我们要表达的内容。


Employee employee;
Object object;
employee = new Employee();
object = employee;   // This is a valid assignment
If the situation is reversed, such as follows:


Employee employee;
Object object = new Object();
employee = (Employee)object   // Valid, but needs explicit cast


请注意,它需要显式转换;只有这样它才能成为有效的语句。可以看出在许多情况下,此逆向分配对于边缘问题不太有用。这是因为对象实例的功能与Employee引用变量所期望的功能几乎没有关系。关系is-a可以由employee-is-an-object派生对象派生;在这种情况下的逆向关系,例如,an-object-is-an-employee只是太牵强。
多态性的继承:一个例子
让我们用一个示例来进行理解。

图1:由公司驾驶员类派生的类
驾驶员类,(被称为Company),创建员工列表并调用paySalary()方法。工资单类维护公司中不同类型员工的列表。请注意,该数组声明为从Employee类派生的引用变量数组,Employee类是所有Employee子类的父类或超级类。

因此,可以用从Employee类的任何子类(如CommissionEmployee、HourlyEmployee或SalariedEmployee)创建的对象引用填充数组。在paySalary()的定义中,根据数组中的对象引用调用相应的salary()方法。因此,salary()方法的调用是多态的,很明显,每个类都有自己版本的salary()方法。
Payroll类中的Employees数组不代表特定类型的员工。它充当一个句柄,可以指向任何类型的雇员子类引用。虽然继承的类共享一些作为后代继承的公共数据,但它们具有自己的一组属性。
Polymorphism by Inheritance: Java Implementation
Here is the quick implementation of the example in Java.
继承多态性:Java实现
下面是Java中快速实现的示例。

package org.mano.example;
public class Company
{
   public static void main( String[] args )
   {
      Payroll payroll = new Payroll();
      payroll.paySalary();
   }
}
package org.mano.example;
import java.util.ArrayList;
import java.util.List;
public class Payroll {
   private Listemployees =
      new ArrayList<>();
   public Payroll() {
      employees.add(new
            SalariedEmployee("Harry Potter",
         "123-234-345",7800));
      employees.add(new
            CommissionEmployee("Peter Parker",
         "234-345-456",2345.67,0.15));
      employees.add(new
            HourlyEmployee("Joker Poker",
         "456-567-678",562.36,239.88));
   }
   public void paySalary() {
      for (Employee e: employees) {
         System.out.println
            ("----------------------------------------------------");
         System.out.println(e.toString());
         System.out.printf
            ("Gross payment: $%,.2f\n",e.salary());
         System.out.println
            ("----------------------------------------------------");
      }
   }
}


package org.mano.example;


public abstract class Employee {
   protected String name;
   protected String ssn;
   public Employee(String name, String ssn) {
      this.name = name;
      this.ssn = ssn;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getSsn() {
      return ssn;
   }
   public void setSsn(String ssn) {
      this.ssn = ssn;
   }
   @Override
   public String toString() {
      return String.format("%s\nSSN: %s",
         getName(),getSsn());
   }
   public abstract double salary();
}


package org.mano.example;
public class SalariedEmployee extends Employee {
   protected double basicSalary;
   public SalariedEmployee(String name, String ssn,
         double basicSalary) {
      super(name, ssn);
      setBasicSalary(basicSalary);
   }
   public double getBasicSalary() {
      return basicSalary;
   }
   public void setBasicSalary(double basicSalary) {
      if(basicSalary>= 0.0)
         this.basicSalary = basicSalary;
      else
         throw new IllegalArgumentException("basic " +
            "salary must be greater than 0.0");
   }
   @Override
   public double salary() {
      eturn getBasicSalary();
   }
   @Override
   public String toString() {
      return String.format("%s\nBasic Salary: $%,.2f",
         super.toString(),getBasicSalary());
   }
}


package org.mano.example;
public class HourlyEmployee extends Employee {
   protected double wage;
   protected double hours;
   public HourlyEmployee(String name, String ssn,
   double wage, double hours) {
      super (name, ssn);
      setWage(wage);
      setHours(hours);
   }
   public double getWage() {
      return wage;
   }
   public void setWage(double wage) {
      if(wage >= 0.0)
         this.wage = wage;
      else
         throw new IllegalArgumentException("wage " +
            "must be > 0.0");
   }
   public double getHours() {
      return hours;
   }
   public void setHours(double hours) {
      if(hours >= 0.0)
         this.hours = hours;
      else
         throw new IllegalArgumentException("hours "  +
            "must be > 0.0");
   }
   @Override
   public double salary() {
      return getHours() * getWage();
   }
   @Override
   public String toString() {
      return String.format("%s\nWage: $%,
         .2f\nHours worked: %,.2f",
         super.toString(),getWage(),getHours());
   }
}


package org.mano.example;
public class CommissionEmployee extends Employee {
   protected double sales;
   protected double commission;
   public CommissionEmployee(String name, String ssn,
         double sales, double commission) {
      super(name, ssn);
      setSales(sales);
      setCommission(commission);
   }
   public double getSales() {
      return sales;
   }
   public void setSales(double sales) {
      if(sales >=0.0)
         this.sales = sales;
      else
         throw new IllegalArgumentException("Sales " +
            "must be >= 0.0");
   }
   public double getCommission() {
      return commission;
   }
   public void setCommission(double commission) {
      if(commission > 0.0 && commission < 1.0)
         this.commission = commission;
      else
         throw new IllegalArgumentException("Commission " +
            "must be between 0.0 and 1.0");
   }
   @Override
   public double salary() {
      return getCommission() * getSales();
   }
   @Override
   public String toString() {
      return String.format("%s\nSales: %,
         .2f\nCommission: %,.2f",
         super.toString(),getSales(),getCommission());
   }
}
界面多态性
接口的多态性与前面的示例非常相似,除了多态性规则是根据Java接口指定的规范之外。接口名可以用作引用变量,正如我们对上面的类名所做的那样。它引用实现接口的任何类的任何对象。下面是一个例子。


package org.mano.example;
public interface Player {
   public enum STATUS{PLAY,PAUSE,STOP};
   public void play();
   public void stop();
   public void pause();
}


package org.mano.example;
public class VideoPlayer implements Player {
   private STATUS currentStatus = STATUS.STOP;
   @Override
   public void play() {
      if(currentStatus == STATUS.STOP ||
            currentStatus == STATUS.PAUSE) {
         currentStatus = STATUS.PLAY;
         System.out.println("Playing Video...");
      }
      else
         System.out.println("I am ON playing man!");
   }
   @Override
   public voidstop() {
      if(currentStatus == STATUS.PLAY ||
            currentStatus == STATUS.PAUSE) {
         currentStatus = STATUS.STOP;
         System.out.println("Video play stopped.");
      }
      else
         System.out.println("Do you want me to go fishing?");
   }
   @Override
   public void pause() {
      if(currentStatus == STATUS.PLAY) {
         currentStatus = STATUS.PAUSE;
         System.out.println("Video play paused.");
      }
      else
         System.out.println("I'm a statue. You froze me
            already!");
      }
   }


package org.mano.example;
public class AudioPlayer implements Player {
   private STATUS currentStatus = STATUS.STOP;
   @Override
   public void play() {
      if(currentStatus == STATUS.STOP ||
            currentStatus == STATUS.PAUSE) {
         currentStatus = STATUS.PLAY;
         System.out.println("Playing Audio...");
      }
      else
         System.out.println("I am ON playing man!");
   }
   @Override
   public void stop() {
      if(currentStatus == STATUS.PLAY ||
            currentStatus == STATUS.PAUSE) {
         currentStatus = STATUS.STOP;
         System.out.println("Audio play stopped.");
      }
      else
         System.out.println("Do you want me to go fishing?");
   }
   @Override
   public void pause() {
      if(currentStatus == STATUS.PLAY) {
         currentStatus = STATUS.PAUSE;
         System.out.println("Audio play paused.");
      }
      else
         System.out.println("I'm a statue. You froze me
            already!");
      }
   }


package org.mano.example;
public class PlayerApp {
   public static void main(String[] args) {
      Player player= new VideoPlayer();
      player.play();
      player.pause();
      player.stop();
      player= new AudioPlayer();
      player.play();
      player.pause();
      player.stop();
   }
}
注意,在PlayerApp中,我们使用了接口播放器来声明一个对象引用变量。引用变量player可以引用实现Player接口的任何类的任何对象。为了证明这一点,这里我们使用了相同的播放器变量来引用视频播放器对象和音频播放器对象。在运行时调用的方法是那些特定于它所引用的类对象的方法。实现接口与接口本身之间类的关系是父类和子类,正如我们在带有继承的多态性示例中看到的那样。它也是一种IS-A关系,形成多态性的基础。
结论
通过类继承或接口实现多态性的区别是一个选择问题。实际上,区别在于理解类和接口的属性和特性。没有严格的规则来定义什么时候使用什么,除非理解它们的性质。这超出了本文的范围。但是,在多态性中,这两个方法都适合并且非常有能力做我们想用它们做的事情。