【问题标题】:java: relationship of the Runnable and Thread interfacesjava: Runnable 和 Thread 接口的关系
【发布时间】:2010-03-09 00:59:32
【问题描述】:

我意识到必须声明方法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(){

    }
}

【问题讨论】:

    标签: java interface


    【解决方案1】:

    在这种情况下,我喜欢将接口视为合约。通过说你的类实现了 Runnable,你明确声明你的类遵守 Runnable 契约。这意味着其他代码可以创建您的类的实例并分配给 Runnable 类型:

    Runnable r = new PrimeFinder();
    

    此外,通过遵守此合同,您可以保证调用您的类的其他代码可以期望找到 Runnable 实现的方法(在本例中为 run() )。

    【讨论】:

    • 但是,如果他们实际上什么都不做,只是强迫你在你的类中重新输入方法,但现在使用实际代码,那么它们的意义何在?
    • 他们做得很多。考虑在您的应用程序中解析 XML 的示例。您将在 Java JDK(在 javax.xml 下)中找到 XML 处理的接口。您可能想知道,这个界面的意义何在?好吧,任何第三方供应商现在都可以出现并实现该接口(就像您在上面的代码中所做的那样)并将其作为产品提供给其他开发人员使用。关键在于,第三方用于其解决方案的实际类并不重要,只要它实现了 javax.xml 中定义的 XML 接口。
    • darren 提出了一个非常好的观点,它不应该被忽视 - 如果你刚刚开始,理解这个概念真的可以帮助你。查看javaworld.com/javaworld/jw-08-1999/jw-08-interfaces.html 还可以为您提供一些关于为什么接口是好东西的信息。
    • 好的,所以如果所有这些类都实现了相同的接口,那么就像我的示例一样,在您自己的类中实现该接口。当我去使用线程时,线程已经知道它需要一个“可运行”对象,即使该对象可以是任何东西?我想我开始明白了。谢谢你的帮助。
    • 完全正确。你的“真实”类可以是你喜欢的任何东西,但如果你声明它实现了某个接口,那么它也可以是那种类型。开车回家。你可能有一个名为 Boy 的类和一个名为 Girl 的类,但如果这两个类都实现了接口 Loser,那么你可以执行 Loser l = new Boy() 或 Loser l = new Girl(),并将它们都视为失败者。
    【解决方案2】:

    不。线程在 lava.lang 包中,所以是隐式导入的。

    并且:线程知道 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();
    }
    

    【讨论】:

      【解决方案3】:

      是的,Thread 实现了Runnable

      正如 API 引用所述,可运行接口旨在为希望在活动时执行代码的对象提供通用协议

      您会感到困惑,因为在 Java 中有两种方法可以实现这种并发:

      • 你可以扩展一个Thread类覆盖默认的run方法,然后以类似于new MyThread().start()的方式调用线程
      • 你可以编写一个实现Runnable接口的类并以类似的方式启动它:new Thread(new MyRunnable()).start()

      这些方法相同。事实上,Thread 类的run 方法通常调用附加到线程的Runnable 对象的run 方法,如果有,则返回。

      拥有Runnable 接口有什么需要?它很有用,因为它声明了一个让类被视为具有特定特征的协议。

      这与Comparable 接口或Serializable 接口相同,但在这里你实际上有一个方法可以覆盖(public void run()),而例如Serializable 只是你赋予你的类的一个特征。

      最后一个例子是TimerTask 实现了RunnableTimerTaskTimer 类一起用于执行延迟或周期性任务,因此 timertask 也可以运行是有意义的,因此 Timer 可以使用该方法启动任务。

      编辑:因为您似乎对接口的有用性感到困惑,所以您必须认为:Java 是一种静态类型语言。这是什么意思?这意味着它需要在编译期间了解有关类型的所有信息,才能保证不会抛出运行时类型错误。

      好的,现在假设 Java API 支持一个 hipotetically 类来绘制形状。因此,您可以为形状编写自己的类,然后将它们提供给这个类(我们称之为ShapeDrawer)。

      ShapeDrawer 需要知道如何绘制您传递给它的形状,唯一的方法是确定每个Shape 对象必须有一个名为@987654344 的方法@,以便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 的类,您的类确切地知道如何绘制这些形状,并且开发人员很高兴,因为您将对象的 通用协议 与他们的具体实现。

      这是一种契约,如果你想要一个能够被ShapeManager 绘制的Triangle 类,你必须声明该方法,以便你可以调用例如

      shapeDrawerInstance.addShape(new Triangle())

      【讨论】:

        【解决方案4】:

        这与接口无关。相反,Thread 位于 java.lang 包中,并且由于 java.lang 是默认包,因此您无需导入它。即默认导入java.lang.*,不需要自己显式导入。

        【讨论】:

        • 我不懂接口。这只是我不明白的一个例子。如果接口只声明没有主体的方法,我根本看不出它有什么用。
        • 你原来的问题实际上有两个问题:(1)为什么我不需要import语句来使用线程和(2)接口有什么用。第一个很简单,我回答了。第二个本身就是一本书。我建议在 SO 上搜索答案或在其他地方寻找文章。
        【解决方案5】:

        类 Thread 实现 Runnable。 Runnable 对 Thread 一无所知,但 Thread 对 Runnable 无所不知。

        其他使用 Thread 的类都知道 Thread 实现了 Runnable 中指定的接口。

        【讨论】:

          【解决方案6】:

          您编写的代码无法编译。你声明了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

          • 第 18 条:优先选择接口而不是抽象类
          • 第 19 条:使用接口定义类型

          【讨论】:

          • 它不是用来编译的。我的问题是因为我根本不懂接口,我以这个 sn-p 为例。但我在很短的时间内得到了一些很好的反馈。我想我真正的问题是,如果接口只声明没有任何主体或代码的方法,为什么还要使用接口?有什么意义?
          • 我刚刚添加了一些东西来尝试解决这个问题。
          • 我只是看了列表。所以close(),在实现的时候,你要定义对象是如何关闭的。例如,StringReader 类实现了 Closable,然后它定义了如何在其自己的类中关闭 StringReader 对象。如果无论如何都必须定义代码,为什么在这里使用任何接口?
          • 有了接口,你不会继承实现;你继承了一个TYPE。如果有 另一个 代码适用于 Closeable 类型(例如,它所做的只是在它们上调用 close() 并进行异常检查),那么您可以将您的 StringReader 传递给该类,或者implements Closeable 的其他任何内容。事实上,这种“请帮我关闭这个东西,好的,谢谢”的实用方法已经存在,而且它非常灵活且可重用,因为它与界面一起使用。
          • 好的,所以如果所有这些类都实现了相同的接口,那么就像我的示例一样,在您自己的类中实现该接口。当我去使用线程时,线程已经知道它需要一个“可运行”对象,即使该对象可以是任何东西?我想我开始明白了。谢谢你的帮助。
          猜你喜欢
          • 1970-01-01
          • 2023-02-02
          • 1970-01-01
          • 2019-11-04
          • 2013-07-02
          • 2012-02-15
          • 1970-01-01
          相关资源
          最近更新 更多