Java: отношения интерфейсов Runnable и Thread - PullRequest
4 голосов
/ 09 марта 2010

Я понимаю, что метод 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(){

    }
}

Ответы [ 6 ]

4 голосов
/ 09 марта 2010

В этой ситуации мне нравится думать об интерфейсах как о контрактах. Говоря, что ваш класс реализует Runnable, вы явно заявляете, что ваш класс придерживается контракта Runnable. Это означает, что другой код может создать экземпляр вашего класса и назначить тип Runnable:

Runnable r = new PrimeFinder();

Далее, придерживаясь этого контракта, вы гарантируете, что другой код, вызывающий ваш класс, может ожидать, что найдут реализованные методы Runnable (в данном случае run ()).

3 голосов
/ 09 марта 2010

Неа. Поток находится в пакете lava.lang, поэтому импортируется безупречность.

И: Поток знает Runnable.

Именно поэтому Thread получает Runnable (this реализует Runnable) и вызывает свой метод run () внутри своего собственного потока выполнения.

Поток содержит ссылку на реализуемый вами Runnable:

public Thread(Runnable runnable) {
   this.myRunnable = runnable;
}

private Runnable myRunnable;

Метод start класса Thread может выглядеть следующим образом:

public void start() {
  // do weird stuff to create my own execution and...
  myRunnable.run();
}
2 голосов
/ 09 марта 2010

Да, Thread реализует Runnable.

Как указывается в ссылках API интерфейс, работающий на основе, предназначен для предоставления общего протокола для объектов, которые хотят выполнять код, пока они активны .

Вы сбиты с толку, потому что есть два способа сделать этот тип параллелизма в Java:

  • вы можете расширить класс Thread, переопределяя метод run по умолчанию, а затем вызвать поток аналогично new MyThread().start()
  • вы можете написать класс, который реализует интерфейс Runnable и запустить его аналогичным образом: new Thread(new MyRunnable()).start()

Эти подходы ИДЕНТИЧНЫЕ . Фактически метод run класса Thread обычно вызывает метод run объекта Runnable, присоединенного к потоку, если таковой имеется, в противном случае он возвращает.

Зачем нужен интерфейс Runnable? Это полезно, поскольку объявляет протокол, позволяющий рассматривать классы с определенными характеристиками.

Это то же самое, что и интерфейс Comparable или Serializable, но здесь у вас фактически есть метод переопределения (public void run()), в то время как, например, Serializable - это просто черта, которую вы даете своему классу.

Последний пример - факт, что TimerTask реализует Runnable. TimerTask используется вместе с классом Timer для выполнения отложенных или периодических задач, поэтому имеет смысл, что таймерная задача также может быть запущена, так что Timer может запускать задачи, используя именно этот метод.

РЕДАКТИРОВАТЬ: , поскольку вы, кажется, смущены полезностью интерфейса , вы должны думать, что: Java - это язык статической типизации . Что это значит? Это означает, что ему нужно знать все о типе во время компиляции, чтобы иметь возможность гарантировать, что ошибка типа во время выполнения не будет выдана.

Хорошо, теперь предположим, что Java API поддерживает гипотетический класс для рисования фигур. Таким образом, вы можете написать свои собственные классы для фигур и затем передать их этому классу (назовем его 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, вы должны объявить этот метод, чтобы вы могли вызвать, например,

shapeDrawerInstance.addShape(new Triangle())

1 голос
/ 09 марта 2010

Это не имеет ничего общего с интерфейсами. Скорее, Thread находится в пакете java.lang, и, поскольку java.lang является пакетом по умолчанию, вам не нужно его импортировать. То есть java.lang. * Импортируется по умолчанию, поэтому вам не нужно явно импортировать его самостоятельно.

0 голосов
/ 09 марта 2010

Класс Thread реализует Runnable. Runnable ничего не знает о Thread, но Thread знает все о Runnable.

Другие классы, использующие Thread, знают, что Thread реализует интерфейс, указанный в Runnable.

0 голосов
/ 09 марта 2010

Ваш код так, как он написан, не компилируется. Вы объявили PrimeFinder implements Runnable, но на самом деле это не @Override public void run().

Что касается полезности интерфейсов, то это потому, что они определяют типы, с которыми вы можете работать независимо от реализаций. Например, если вы работаете с Closeable, вы можете close() это сделать. Фактический класс может быть любым из многих, которые его реализуют ( см. Полный список ), но работа с интерфейсом позволяет вам отступить и просто сказать, что все они Closeable.

С интерфейсами вы не наследуете реализацию; Вы наследуете ТИП. Если есть другой код, который работает с типами Closeable (например, все, что он делает, это вызывает для них close() с проверкой исключений), то вы можете передать ему все, что implements Closeable. На самом деле, этот служебный метод «пожалуйста, закройте это для меня, хорошо, спасибо» уже существует, и он очень гибкий и многократно используемый, так как работает с интерфейсом.

Используя интерфейсы вместо конкретных классов реализации, вы гораздо более четко инкапсулируете свою логику. Например, алгоритм, работающий с интерфейсом Set, не заботится о том, является ли он TreeSet или HashSet. Это отсутствие зависимости - хорошая вещь, потому что оно позволяет алгоритму быть очень гибким - он также может работать с SuperMagicalSet implements Set в будущем, например.

Работа с интерфейсами также обеспечивает правильную инкапсуляцию, поскольку, не зная, каким будет действительный класс реализации, вы должны работать только с тем, что обеспечивает интерфейс.

Рекомендую прочитать Джош Блох Effective Java 2nd Edition :

  • Пункт 18: Предпочитать интерфейсы абстрактным классам
  • Элемент 19: Использование интерфейсов для определения типов
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...