为什么实现Runnable接口比继承Thread类更好?
重要的一点是,在任何有疑问的情况下,都应该优先使用implements而非extends。
extends会将两个类文件紧密地绑定在一起,并可能导致一些非常难以处理的代码。
当我首次“理解”面向对象编程时,我会对所有东西进行扩展,但这会使我的整个设计变得混乱。现在,我只扩展那些明显通过“is-a”测试的少数事物,其他所有内容都是接口...
许多问题就此停止发生(混淆的多重继承情况,时间浪费于重构层次结构,倾向于具有“protected”变量,然后想知道为什么它们在当前类中没有更改而又发生了变化,构造函数的链接要求,搞清楚不同继承树如何相互作用...)
似乎每3年(过去20年)我都认为我真正“掌握”了编程,并回顾我在3年前做的愚蠢事情感到羞愧... 这是其中之一(但距此时更近的是7年前)
这样,您可以将计算(即 what)与执行(即 when 和/或 how)分离。
使用 Runnable
或 Callable
,例如,您可以提交许多工作/计算给一个 Executor
,它将负责安排这些任务。以下是来自 ExecutorService 的摘录:
pool = Executors.newFixedThreadPool(poolSize);
...
pool.execute(new Handler(serverSocket.accept()));
...
class Handler implements Runnable {
...
}
使用Runnable
/Callable
比直接使用线程更加灵活。
如果您的继承层次结构中没有扩展Thread
,那么这可能没有任何意义。只有在希望修改Thread
功能时才扩展Thread
。
使用Runnable
,任何继承层次结构中的类都可以公开一个任务,该任务可以被视为工作单元,以便由Thread
执行。
首先回答问题:
如果你扩展线程,你的类(扩展线程)的实例将始终调用超类线程的构造函数。因此, MyClass myclass = new MyClass() 将始终在MyClass的构造函数中调用“super()”,该构造函数实例化一个线程,并最终可以在类中实现一些开销(如果您不使用超类线程的任何方法)。因此,仅实现Runnable允许您的类在没有任何继承开销的情况下更快地运行。
此外,这里有一些错误的答案:
现在我只扩展那些明显通过“is-a”测试的少数内容,其他所有内容都是接口...
你从未考虑过创建一个没有任何接口的对象吗?因为为每个对象实现一个接口是非常错误的!
当您扩展Thread类时,每个线程都会创建唯一的对象并与之关联。当您实现Runnable时,它将相同的对象共享给多个线程。
错误的,每次调用类的构造函数时,您都会获得自己的对象,无论它是从哪里扩展还是实现了什么!
结论:在Java中,每个类实例都是一个对象,并且扩展了类Object(而原始类型不是...只有原始类型数组如int[]类扩展了Object,并且这些数组具有与任何对象相同的方法-但遗憾的是它们没有在Javadoc中提到!Sun的人可能不希望我们使用它们)。
顺便说一下,继承至少有两个优点:代码共享和清晰的Javadoc。因为如果你从一个类继承,你可以在Javadoc中看到所有子类。你也可以用接口做到这一点,但是你不能共享代码。更重要的是,那样你就需要创建“嵌套”对象并调用两个或多个对象的构造函数,而不仅仅是一个对象,这又意味着你要实现一些来自超类Object本身的开销!因为在Java中没有没有开销的对象,尽可能地创建较少的对象对于高性能应用程序非常重要。对象是最好的东西,但不必要的对象不是...