Banson's Blog
深究onClickListener
是常见的代码,也是常困惑的写法,终于搞懂啦!

困惑已久的问题

在 Android 开发过程中,下面这种模板代码是很常见的:

btn.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //Do something
        }
    });

一个很自然的想法是,每次需要设置点击监听就粘贴以上模板代码再做修改就好了。然而,有一个不容忽视的问题:调用 setOnClickListener 方法时,究竟传入了一个什么类型的参数?

接口与实现

接口(interface)是 OOP 中的一个概念。它描述了一类行为,但由于过于抽象的缘故,不能成为一个类(Class)。如果一个类实现(implements)了某接口,那么这个类就具有接口中定义的(具体的)方法。上述说法较难理解,下面举一个著名的“报警器与门”的例子。

假设有一个接口 Alarm,其中有方法 alarm();另有一个类 Door,并且 Door 类实现了接口 Alarm

public interface Alarm {
    public void alarm();
}

public Class Door implements Alarm {
    // Some properties and methods
    // belonging to Door
    // ...
    @Override
    public void alarm() {
        // Do something
    }
}

如果另一个类 Car 也具有报警器的功能,那么它也可以实现接口 Alarm

public Class Car implements Alarm {
    // Some properties and methods
    // belonging to Car
    // ...
    @Override
    public void alarm() {
        //Do something else
    }
}

两个类都分别提供了 Alarm.alarm 的具体实现,且它们不一定相同。

显然地,interface 本身不应被实例化,因为它就是一个“画中之饼”;类才可以被实例化,而在本例中,就是实现了接口 AlarmDoor 类和 Car 类可以被实例化。

匿名类和匿名对象

让我们回到最初的问题:setOnClickListener 方法究竟传入了什么参数?

首先,代码可以做初步的改写,以变得更加清晰:

View.OnClickListener listener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //Do something
    }
}

btn.setOnClickListener(listener);

首先,以上代码是没有问题的;然而,我们震惊地发现,View.OnClickListener 是一个接口(interface),它似乎通过 new 关键字被实例化了!事实果真如此?

并不是。接口并不能被实例化。上述操作相当于先创建了某个匿名类,这个匿名类实现了接口 View.OnClickListenernew 作用在这个匿名类上,实例化的实质上是该匿名类。到这里,我终于弄懂了被传入方法 setOnClickListener 中的参数 listener 的类型:它正是这个匿名类的一个实例化的对象。

有一个更加清晰的写法可以帮助我们理解以上说法:

public Class MyListener implements View.OnClickListener {
    @Override
    public void onClick(View v) {
        //Do something
    }
}

MyListener listener = new MyListener();

btn.setOnClickListener(listener);

在最后一种写法中,我们没有采用匿名类,而是创建了一个实现了接口的类。


Last modified on 2020-06-29