我意识到必须声明方法run()
,因为它在Runnable
接口中声明。但是,当这个类运行时,如果没有对特定包的导入调用,如何允许Thread
对象,我的问题就来了? runnable如何知道关于Thread或其方法的任何信息? Runnable接口是否扩展了Thread类?显然我不太了解接口。提前谢谢。
class PrimeFinder implements Runnable{
public long target;
public long prime;
public boolean finished = false;
public Thread runner;
PrimeFinder(long inTarget){
target = inTarget;
if(runner == null){
runner = new Thread(this);
runner.start()
}
}
public void run(){
}
}
答案 0 :(得分:4)
在这种情况下,我喜欢将接口视为合同。通过说您的类实现Runnable,您明确声明您的类遵守Runnable合约。这意味着其他代码可以创建类的实例并分配给Runnable类型:
Runnable r = new PrimeFinder();
此外,通过遵守此合同,您可以保证调用您的类的其他代码可以期望找到Runnable实现的方法(在本例中为run())。
答案 1 :(得分:3)
不。线程在lava.lang包中,所以它是隐含的导入。
而且:Thread知道Runnable。
这就是Thread收到Runnable(this
实现Runnable)并在其自己的执行线程中调用其方法run()的原因。
线程保留对您实现的Runnable的引用:
public Thread(Runnable runnable) {
this.myRunnable = runnable;
}
private Runnable myRunnable;
Thread类的start方法可能如下所示:
public void start() {
// do weird stuff to create my own execution and...
myRunnable.run();
}
答案 2 :(得分:2)
是的,Thread
实施Runnable
。
由于API引用状态,runnable接口旨在为希望在活动时执行代码的对象提供通用协议。
您感到困惑,因为在Java中有两种方法可以实现这种并发:
Thread
类覆盖默认的run
方法,然后以类似于new MyThread().start()
Runnable
接口的类,并以类似的方式启动它:new Thread(new MyRunnable()).start()
这些方法 IDENTICAL 。实际上,类run
的{{1}}方法通常会调用附加到线程的Thread
对象的run
方法,否则返回。
拥有Runnable
界面需要什么?它很有用,因为它声明了一个让类被认为具有特定特征的协议。
这与Runnable
接口或Comparable
接口相同,但在这里您实际上有一个覆盖(Serializable
)的方法,而例如public void run()
只是一个你给你班级的特质。
最后一个例子是Serializable
实现TimerTask
的事实。 Runnable
与TimerTask
类一起使用来执行延迟或定期任务,因此时间任务也是可运行的,这样Timer就可以使用该方法启动任务。
编辑,因为您似乎对界面的用处感到困惑,您必须认为: Java是一种静态类型语言。这是什么意思?这意味着它需要在编译期间知道关于类型的所有内容,以便能够保证不会抛出运行时类型错误。
好的,现在假设Java API支持hipotetically类来绘制形状。因此,您可以为形状编写自己的类,然后将它们提供给此类(我们称之为Timer
)。
ShapeDrawer
需要知道如何绘制传递给它的形状,并且唯一方式确定它是确定每个ShapeDrawer
对象必须具有名为Shape
的方法,以便public void drawMe()
可以在您附加到它的每个ShapeDrawer
上调用此方法,而不会知道更多内容。
所以你声明了一个接口
Shape
这些类可以被视为public interface Shape
{
public void drawMe();
}
。如果某个班级是Shape
,您可以将其传递给Shape
班级,而不会有任何问题:
ShapeDrawer
因此,编译器很高兴,因为在添加形状时添加了class ShapeDrawer
{
public void addShape(Shape shape) { ... }
public void drawShapes()
{
for (Shape s : shapes)
s.drawMe();
}
}
的类,您的类知道如何绘制这样的形状,开发人员很高兴,因为您将通用协议分开了来自他们特定实现的对象。
这是一种契约,如果你想要一个implements Shape
类能够由Triangle
绘制你必须声明那个方法,那么你可以调用例如
ShapeManager
答案 3 :(得分:1)
这与接口无关。相反,Thread位于java.lang包中,并且由于java.lang是默认包,因此您无需导入它。也就是说,默认情况下会导入java.lang。*,因此您无需自己显式导入它。
答案 4 :(得分:0)
您的代码编写方式无法编译。您声明了PrimeFinder implements Runnable
,但它实际上并非@Override public void run()
。
至于为什么接口很有用,这是因为它们定义了无论实现如何都可以使用的类型。例如,如果您使用Closeable
,则表示您可以close()
。实际的类可以是实现它的任何一个(see full list),但是使用界面可以让你退后一步,只说它们都是Closeable
。
使用接口,您不会继承实现;你继承了一个TYPE。如果有另一个代码适用于Closeable
类型(例如,它所做的就是通过异常检查对它们调用close()
),那么您可以将implements Closeable
传递给它。事实上,这个“请为我关闭这个东西,好的,谢谢”实用工具方法已经存在,并且它非常灵活,可以高度重复使用,因为它适用于界面。
通过使用接口而不是特定的实现类,您可以更清晰地封装逻辑。例如,使用Set
接口的算法并不关心它实际上是TreeSet
还是HashSet
。缺乏依赖性是一件好事,因为它允许算法非常灵活 - 例如,它可以在将来使用SuperMagicalSet implements Set
。
使用接口还可以确保正确的封装,因为不知道实际的实现类是什么,您必须只使用接口提供的内容。
我建议阅读Josh Bloch的 Effective Java 2nd Edition :
答案 5 :(得分:0)
Thread类实现了Runnable。 Runnable对Thread没有任何了解,但是Thread知道Runnable的一切。
使用Thread的其他类知道Thread实现了Runnable中指定的接口。