多态是面向对象软件的基本原理之一. 该术语通常表示可以具有多种形式的事物. 在面向对象的方法中, 多态使编写具有后期绑定引用的程序成为可能. 尽管在 Java 中创建多态引用很容易, 但其背后的概念对整体编程产生了更深远的影响. 本文结合在优锐课学习到的知识点, 探讨了有关多态性及其对面向对象编程的影响的一些复杂细节.
多态参考: 概述
多态引用是一个变量, 可以在不同的时间点引用不同类型的对象. 它通常与它所引用的类兼容. 例如, 在以下情况下:
Employee employee;
'employee'是一个引用变量, 可以引用 Employee 类的实例. 参考变量对参考对象的限定取决于其兼容性. 这似乎是唯一可靠的条件, 但事实并非如此, 特别是在实现多态时. 规则太严格了, 但是通过结合 "具有多种形式" 的思想, 多态性使得灵活性更高. 这意味着多态引用保证了它可以在不同的时间点引用不同类型的对象, 而不是为兼容性而完全依赖. 因此, 如果可以在一个时间点使用引用来调用方法, 则可以将其动态更改为指向另一个对象, 并在下一次调用其他方法. 通过提供引用变量的另一个使用维度, 这可以利用灵活性.
当引用变量绑定到无法在运行时更改的对象时, 或者换句话说, 方法调用与方法定义的绑定是在编译时完成的, 称为静态绑定. 如果绑定在运行时是可更改的, 例如在多态引用的情况下, 绑定的决策仅在执行期间做出, 则称为动态绑定或后期绑定. 两者在面向对象程序设计中都有其用途, 并非一个都胜过另一个. 但是, 在多态引用的情况下, 推迟的绑定承诺在灵活性方面使其比编译时绑定更具优势, 但另一方面, 这会降低性能开销. 但是, 这在很大程度上是可以接受的, 并且在提高效率方面, 开销通常具有很小的吸引力..
创建多态
在 Java 中, 可以通过两种方式创建多态引用: 使用继承或使用接口.
继承多态
引用变量引用类的实例. 对于继承层次结构, 如果引用变量在层次结构树中声明为父类类型, 则引用对象可以指向层次结构中任何类的实例. 这意味着, 在 Java 中, 因为 Objectclass 是所有类的父类或超类, 或者换句话说, Java 中的所有类实际上是 Object 类的子类隐式或显式地是对象类的引用变量. 对象类型可以引用 Java 中的任何类实例. 这就是我们的意思.
- Employee employee;
- Object object;
- employee = new Employee();
- object = employee; // This is a valid assignment
如果情况相反, 则如下所示:
- Employee employee;
- Object object = new Object();
- employee = (Employee)object // Valid, but needs explicit cast
观察到它需要显式强制转换; 只有这样, 它才能成为有效的声明. 可以看出, 这种反向分配对于在许多情况下出现问题的边缘来说没有多大用处. 这是因为 Object 实例的功能与 Employee 引用变量预期的功能几乎没有关系. 关系 is-a 可以从 employee-is-an-object 派生派生; 在这种情况下, 相反的关系 (例如, 对象是雇员) 太牵强.
继承多态性: 一个例子
让我们尝试借助示例来理解它.
图 1: 从驱动程序类, 公司派生的类
称为 Company 的驱动程序类创建一个雇员列表, 并调用 paySalary()方法. 薪资类维护公司中不同类型员工的列表. 请注意, 该数组被声明为派生自 Employee 类 (所有雇员子类的父级或超类) 的引用变量的数组. 结果, 可以用从 Employee 类的任何子类 (例如 CommissionEmployee, HourlyEmployee, SalariedEmployee) 创建的对象引用填充数组. 在 paySalary()定义中, 根据数组中的对象引用调用适当的 salary()方法. 因此, 对 salary()方法的调用是多态的, 很明显, 每个类都有其自己版本的 salary()方法.
Payroll 类中的 employee 数组不代表特定类型的 Employee. 它用作可以指向任何类型的 Employee 子类引用的句柄. 尽管继承的类共享作为后代继承的一些公共数据, 但是它们具有各自的属性集.
通过继承实现多态: 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 List<Employee> employees =
- 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 可以引用实现 Player 接口的任何类的任何对象. 为了证明这一点, 我们在这里使用了同一播放器变量来引用 VideoPlayer 对象和 AudioPlayer 对象. 运行时调用的方法是特定于其引用的类对象的方法. 实现接口的类与接口本身之间的关系是父子关系, 正如我们在带有继承的多态示例中所看到的. 它也是一个 is-arelationship, 并构成了多态性的基础.
总结
通过类继承或通过接口实现多态性之间的差异是一个选择问题. 实际上, 区别在于理解类和接口的属性和特性. 除了了解其性质外, 没有严格的规则来定义何时使用. 这超出了本文的范围. 但是, 在多态中, 这个想法既适合并且也有能力完成我们想对它们进行的操作. 就这样. 抽丝剥茧, 细说架构那些事 --[优锐课]
来源: https://www.cnblogs.com/youruike-/p/12047884.html