`
jiagou
  • 浏览: 2595292 次
文章分类
社区版块
存档分类
最新评论

java单分派与多分派(多路分发和单路分发)

 
阅读更多

1.分派的概念

变量被声明时的类型叫做变量的静态类型(Static Type) 又叫明显类型(Apparent Type)。变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type)。

根据对象的类型而对方法进行的选择,就是分派(Dispatch)。根据分派发生的时期,可以将分派分为两种,即分派分静态分派和动态分派。

静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。方法重载(Overload)就是静态分派。(所谓的:编译时多态)

动态分派(Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法。面向对象的语言利用动态分派来实现方法置换产生的多态性。(所谓的:运行时多态)

方法重载(静态分派)

Java通过方法重载来支持静态分派。下面考察下墨子骑马的故事。

public class Mozi
{
    public void ride(Horse h)
    {
		System.out.println("Riding a horse");
    }

    public void ride(WhiteHorse wh)
    {
		System.out.println("Riding a white horse");
    }

    public void ride(BlackHorse bh)
    {
		System.out.println("Riding a black horse");
    }

    public static void main(String[] args)
    {
        Horse wh = new WhiteHorse();

        Horse bh = new BlackHorse();

        Mozi mozi = new Mozi();

        mozi.ride(wh);
        mozi.ride(bh);
    }

    /**
     * @directed 
     */
    private Horse lnkHorse;
}


打印了两次的“Riding a horse”。 墨子发现他骑的都是马。

两次对ride()方法的调用传入的是不同的参量,也就是wh和bh。它们虽然具有不同的真实类型,但是它们的静态类型都是一样的,均是Horse类型。

重载方法的分派是根据静态类型进行的。这个分派过程在编译时期就完成了。

动态分派

Java通过方法的置换(Overriding)支持动态分派。

String s1 = "ab";
Object o = s1 +"c";
String s = "abc";
boolean b = o.equals(s);

上述代码返回了true(基础多态就不多说了)。

2.分派的类型

一个方法所属的对象叫做方法的接收者,方法的接收者与方法的参量统称做方法的宗量。

根据分派可以基于多少种宗量,可以将面向对象的语言划分为单分派语言和多分派语言。单元分派语言根据一个宗量的类型(真实类型)进行对方法的选择,多分派语言根据多于一个的宗量的类型对方法进行选择。

C++和Java以及Smaltalk都是单分派语言;多分派语言的例子包括CLOS和Cecil。按照这样的区分,C++Java就是动态的单分派语言,因为这两种语言的动态分派仅仅会考虑到方法的接收者的类型,同时又是静态的多分派语言,因为这两种语言对重载方法的分派会考虑到方法的接收者的类型和方法所有参量的类型。

在一个支持动态单分派的语言里面,有两个条件决定了一个请求会调用哪一个操作:一是请求的名字,二是接收者的真实类型。单分派限制了方法的选择过程,使得只有一个宗量可以被考虑到,这个宗量通常就是方法的接收者。在JAVA语言里面,如果一个操作是作用于某个类型不明的对象上面的。那么这个对象的真实类型测试仅会发生一次。这个就是动态的单分派的特征。

一言以蔽之,JAVA语言支持静态的多分派和动态的单分派。

3.双重分派

一个方法根据两个宗量的类型来决定执行不同的代码,这就是“双分派”或者“多重分派”。Java不支持动态的多分派。但可以通过使用设计模式,在Java语言里面实现动态的双重分派(ps:就是“伪双重分派”是由两次的单分派组成)。

方案一:类型判断

在方法里使用instanceof判断真实类型,比如(java.awt.Component的源码):
protected void processEvent(AWTEvent e) {
        if (e instanceof FocusEvent) {
            processFocusEvent((FocusEvent)e);

        } else if (e instanceof MouseEvent) {
            switch(e.getID()) {
              case MouseEvent.MOUSE_PRESSED:
              case MouseEvent.MOUSE_RELEASED:
              case MouseEvent.MOUSE_CLICKED:
              case MouseEvent.MOUSE_ENTERED:
              case MouseEvent.MOUSE_EXITED:
                  processMouseEvent((MouseEvent)e);
                  break;
              case MouseEvent.MOUSE_MOVED:
              case MouseEvent.MOUSE_DRAGGED:
                  processMouseMotionEvent((MouseEvent)e);
                  break;
              case MouseEvent.MOUSE_WHEEL:
                  processMouseWheelEvent((MouseWheelEvent)e);
                  break;
            }

        } else if (e instanceof KeyEvent) {
            processKeyEvent((KeyEvent)e);

        } else if (e instanceof ComponentEvent) {
            processComponentEvent((ComponentEvent)e);
        } else if (e instanceof InputMethodEvent) {
            processInputMethodEvent((InputMethodEvent)e);
        } else if (e instanceof HierarchyEvent) {
            switch (e.getID()) {
              case HierarchyEvent.HIERARCHY_CHANGED:
                  processHierarchyEvent((HierarchyEvent)e);
                  break;
              case HierarchyEvent.ANCESTOR_MOVED:
              case HierarchyEvent.ANCESTOR_RESIZED:
                  processHierarchyBoundsEvent((HierarchyEvent)e);
                  break;
            }
        }
    }

这种方法实现的双重分派都格外的冗长、复杂和容易出错,也不符合“开闭原则”

方案二:反转球

通过两次的调用来实现,比如下面剪刀石头布的游戏:
public enum Outcome { WIN, LOSE, DRAW } ///:~


interface Item {
	Outcome compete(Item it);

	Outcome eval(Paper p);

	Outcome eval(Scissors s);

	Outcome eval(Rock r);
}

class Paper implements Item {
	public Outcome compete(Item it) {
		return it.eval(this);
	}

	public Outcome eval(Paper p) {
		return DRAW;
	}

	public Outcome eval(Scissors s) {
		return WIN;
	}

	public Outcome eval(Rock r) {
		return LOSE;
	}

	public String toString() {
		return "Paper";
	}
}

class Scissors implements Item {
	public Outcome compete(Item it) {
		return it.eval(this);
	}

	public Outcome eval(Paper p) {
		return LOSE;
	}

	public Outcome eval(Scissors s) {
		return DRAW;
	}

	public Outcome eval(Rock r) {
		return WIN;
	}

	public String toString() {
		return "Scissors";
	}
}

class Rock implements Item {
	public Outcome compete(Item it) {
		return it.eval(this);
	}
	
	public Outcome eval(Paper p) {
		return WIN;
	}

	public Outcome eval(Scissors s) {
		return LOSE;
	}

	public Outcome eval(Rock r) {
		return DRAW;
	}

	public String toString() {
		return "Rock";
	}
}

public class RoShamBo1 {
	static final int SIZE = 20;
	private static Random rand = new Random(47);

	public static Item newItem() {
		switch (rand.nextInt(3)) {
		default:
		case 0:
			return new Scissors();
		case 1:
			return new Paper();
		case 2:
			return new Rock();
		}
	}

	public static void match(Item a, Item b) {
		System.out.println(a + " vs. " + b + ": " + a.compete(b));
	}

	public static void main(String[] args) {
		for (int i = 0; i < SIZE; i++)
			match(newItem(), newItem());
	}
} 
RoshamBol.match()有2个item参数,通关过Item.compete()方法开始2路分发,要判定a的类型,分发机制会在a的实际类型的compete()内部起到分发作用。compete()方法通关过eval()来为另一个类型实现第二次分发, 将自身(this)作为参数调用eval(),能够调用重载过的eval()方法,这能够保留第一次分发的类型信息,第二次分发完成时,就能知道两个Item对象的具体类型了。
这种实现也就是“访问者模式”的精华。
这种的详细解释请看:
整理至《java与模式》


分享到:
评论

相关推荐

    java 任务分配样例3

    在Java开发中,Quartz 2是一个非常强大的作业调度库,它允许开发者安排任务并在特定时间执行。在本文中,我们将深入探讨如何使用`@PersistJobDataAfterExecution`和`@DisallowConcurrentExecution`注解来通过...

    【Java设计模式-源码】双重分发模式:增强多态行为

    双重分发模式用于根据方法调用中涉及的两个对象的类型实现动态多态性。它允许方法行为根据调用方法的对象和作为参数传递的对象的运行时类型的组合而有所不同。 ## 二、详细解释及实际示例 1. **实际示例**: - 在...

    把多个任务分派给多个线程去执行

    由三个类实现,写在了一个 Java 文件中:TaskDistributor 为任务分发器,Task 为待执行的任务,WorkThread 为自定的工作线程。代码中运用了命令模式,如若能配以监听器,用上观察者模式来控制 UI 显示就更绝妙不过了...

    指定模块指定分发例程的原始地址

    模块可以包含多个分发例程,每个例程都有其特定功能。 2. **分发例程**:也称为调度函数,是处理特定任务的回调函数。例如,在Windows系统中,驱动程序可能会有分发例程来处理I/O请求包(IRP)。当一个事件发生时,...

    基于Java NIO反应器模式设计与实现

    在反应器模式的Java实现中,一个Reactor(反应器)负责监听和分发事件,它持续监测一个或多个输入源,一旦有事件发生,它就将事件分发给相应的处理器进行处理。处理器(Handler)定义了事件处理的方法,每个处理器...

    用Java事件处理机制实现录制回放功能

    在本文中,我们将深入探讨如何利用Java事件处理机制实现录制和回放功能,这种功能常见于GUI测试工具中,用于捕获用户操作并在代码修改后自动回放这些操作。 1. Java 事件介绍 - **事件是什么**:在Java中,事件是...

    AOMultiproxier:一个简单的代理类,将协议方法多路复用并分派给多个对象

    一个简单的代理类,它多路复用和分派协议方法到多个对象。 安装 AOMultiproxier 可通过。 要安装它,只需将以下行添加到您的 Podfile 中: pod "AOMultiproxier" 例子 在您的标题中: @property (nonatomic, ...

    scalable-io-in-java-中文1

    - **主-从Reactor**:主Reactor负责监听和分发事件,从Reactor(通常为多个)执行实际的Handler逻辑,提高并行性。 - **多线程处理Reactor**:每个Handler在自己的线程中运行,允许完全并行处理,但增加了线程同步...

    php使用gearman进行任务分发操作实例详解

    Gearman是一个工作负载分发服务器和库,允许将工作分散到多个机器或者机器上的多个核心上进行异步处理。它在Web应用程序中处理耗时的后台任务时尤其有用,如文件上传、邮件发送、图片处理等。 2. PHP Gearman扩展...

    一种基于JAVA与UNI-APP的智慧农业派工单系统(毕设&课设论文参考).pdf

    《一种基于JAVA与UNI-APP的智慧农业派工单系统》这篇文章提出了一个结合JAVA与UNI-APP技术的应用方案,旨在解决传统农业机械和车载终端在安装与维护过程中存在的问题。 #### 技术背景 - **JAVA**: 是一种广泛使用...

    Java_Android笔试、面试知识整理.pdf

    本文档涵盖了 Java 和 Android 相关的笔试和面试知识点,包括计算机基础、计算机网络、数据结构与算法、操作系统、数据库系统、Java 基础、Android 开发等方面。 计算机基础知识点包括计算机网络的网络分层、底层...

    KDispatcher,用于kotlin的简单轻量级事件分派器.zip

    KDispatcher,作为一个专为Kotlin设计的轻量级事件分派器,旨在简化事件处理和通信机制,提高代码的可读性和可维护性。在现代软件开发中,组件间的通信是至关重要的,而KDispatcher为此提供了一个高效且易于使用的...

    分派

    总的来说,"分派"在C语言及其相关领域中扮演着多方面角色,从内存管理和函数调用到操作系统调度和网络通信,再到软件开发流程,都体现了分派的核心思想。深入理解这些概念有助于提升作为IT专业人员的技术素养和解决...

    java-aop-predicate-dispatch:java中使用AOP实现通用谓词分派机制

    在Java编程中,AOP(面向切面编程)是一种强大的设计模式,它允许开发者将关注点分离,将横切关注点如日志、事务管理、权限...这样的机制在处理复杂条件判断和行为分发时非常有用,有助于提高代码的复用性和可维护性。

    canppe2 文档说明

    - **到图通知单-主任分派**:主任将到图通知单分派给相关人员。 - **到图通知单-个人**:个人用户对到图通知单的操作。 - **根据到图通知单借阅图纸**:基于到图通知单进行图纸的借阅。 - **到图通知单查询-全部**...

    suro, Netflix数据管道的分发.zip

    suro, Netflix数据管道的分发 Suro...聚集和分发大量应用程序事件( 包括日志数据)的数据管道服务。 它具有以下功能:它是分布的,可以水平缩放。它支持流数据流。大量连接和高吞吐量。它允许将事件动态地分派到不同位置

    C ++的事件分派器和回调列表-C/C++开发

    eventpp-用于事件分派器和回调列表的C ++库eventpp是一个C ++事件库,它提供了一些工具,使您的应用程序组件可以通过分派事件并侦听事件相互进行通信。 eventpp-用于事件分派器和回调列表的C ++库eventpp是一个C ++...

Global site tag (gtag.js) - Google Analytics