Да, 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())