北川广海の梦

北川广海の梦

Java如何实现事件?

233
2020-04-15

C#的事件

相信很多学过C#的同学,都听过事件这个概念。
它是基于一种订阅者模式的编程模式。而C#的事件,都是基于委托的。
而委托其实就是对象化的函数。
在其他大部分语言中,也是支持的这个概念的,只是叫法不同而已。在C++中叫做函数指针,在JavaScript中函数本身就是一个对象。

事件的原理

事件也就是一个可能在未知的时候发生的事情,这件事情如果我们需要对它进行一个关注,比如我们关心明天会不会下雨,以便于我们在下雨的时候,将伞带在身上。 这个时候下雨就是我们关心的事件,将伞带在身上,就是我们对这个事件的处理。
这是站在订阅者的角度,去看这个事情,它真的非常简单。我们一旦订阅了这个事件,那么我们对事件的处理就会自动触发。

那么如果我们想实现这个事件机制,应该怎么做呢?

首先,我们的事件是需要在合适的时候被触发的。这个触发的时机,订阅者们根本不会关心。而事件的拥有者会处理这个事情,比如前面说的下雨,我们关注天气的人不会具体什么时候下雨,气象台的人才会去关心,并且是由他们来通知我们,下雨这个事情发生了

第二,我们事件本身,需要保存一份清单,我们需要知道哪些人订阅了这个事件。
就像送报纸,一个邮递员手上必然有一份清单,记录了那些人订阅了这份报纸,不然这报纸不就送丢了吗。

第三,我们的事件必须可以被订阅。就比如一个人想去订购一份报纸,它得去打电话告诉报刊社,但是这个报刊社根本就像不存在一样,找不到它的电话号码,那就根本订阅不了了。

Java实现

Java是没指针这个概念的,但是可以通过接口。
并且在Java8新增了lambda表达式和非常酷炫的函数接口(和委托非常像)。

前面说的条件,已经足够说明事情了,很显然,我们需要两个函数,一个函数用来处理订阅,这个函数会被订阅者调用。一个函数用来让这个事件发生,这个函数会被事件的拥有者在合适的时候调用。然后需要一个列表,用来存储所有对这个事件的订阅。

下面就上代码吧:

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;

//事件基类,定义了订阅,触发,取消订阅等方法。
public abstract class Event<T> {
    protected Event(Class c) {
        Annotation annotations = c.getAnnotation(FunctionalInterface.class);
        if (annotations == null) {
            throw new IllegalArgumentException("Type of Argument must be a FunctionalInterface");
        }
    }
    //用于保存所有事件处理的函数
    protected final List<T> funcList = new ArrayList<>();

    public void subscribe(T func) {
        this.funcList.add(func);
    }

    public void unSubscribe(T func) {
        this.funcList.remove(func);
    }

import java.util.function.Consumer;

//具体事件类,继承于Event,需要具体指明订阅者函数的类型(在C#中就是指明用什么委托来处理这个事件)。
public class MyEvent<T> extends Event<Consumer<T>> {


    public MyEvent() {
        super(Consumer.class);
    }


    public void invoke(T arg) {
        for (Consumer<T> trFunction : this.funcList) {
            trFunction.accept(arg);
        }
    }
}

下面来用一下

import demo.main.MyEvent;

import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Consumer;

public class Main {

    public static void main(String[] args) {
        Main main = new Main();
	//定义事件处理函数
        Consumer consumer = x ->
                System.out.println("MyEvent is Invoke,the param is " + x);
        main.myEvent.subscribe(consumer);
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                main.myEvent.invoke(100);
            }
        };
        timer.schedule(task, 10000);
    }
    MyEvent myEvent = new MyEvent();
}

在10000毫秒后,事件发生了。
输出结果:
Java_event1.png

Java的事件写起来,确实没有Js和C#那么方便,我觉得主要是
官方对事件,并没有像C#那样提供编译器层面的支持。
还有就是Java的泛型擦除,这样使用,会导致IDE警告,用起来有点不爽。