为什么Thread不是抽象类,而start()不是final?


79

为什么将Thread类实现为常规类而不是将抽象run()方法实现为抽象类。

会不会带来任何问题?或以这种方式有什么用吗?

另外,该Thread.start()方法应该是一种非常特定的方法,其功能不能由任何其他类实现(如果我没记错的话)。因此,我想该final关键字比其他任何方法都更适合此操作。

但是我可以重写此方法并根据需要使用它,

public class Test extends Thread {
    public static void main (String... args) {
        Thread test = new Test();
        test.start();
    }

    @Override
    public void run() {
        System.out.println("New thread started...");
    }

    @Override
    public void start() {
        System.out.println("Did anyone tell you I will spawn a new thread??");
    }
}

它显然只印了,

有人告诉你我将产生一个新线程吗?

除了使工程师替换您感到困惑之外,还有什么其他用途?

如果不是,为什么在Thread类中未将方法声明为final?


在这里,您将方法start作为类的成员进行调用……它不是从调度程序中调用的……
CoderNeji 2015年

2
为了清楚起见-您在问为什么Thread.run()不是抽象的,因为用户需要重写它,以及为什么Thread.start()不是最终的,因为根本没有意义重写它?好问题!我注意到有人投票了-我想知道为什么吗?
NickJ

3
我宁愿Thread班级是决赛。构造函数已经可以运行,因此不需要继承来创建线程。
2015年

Answers:


50

为什么将Thread类实现为常规类而不是将run()方法抽象为抽象类。

这个问题实际上可以归结为一个事实,即您应该始终偏向于继承而不是继承。

如果Thread该类被声明为abstract,该语言将必须提供另一个从它扩展的类,程序员可以使用它创建一个Thread。然后,您的问题将是关于为什么extends源于此的类Thread不是这样abstract。如果该语言没有提供extendsfrom的其他类Thread,那么程序员将必须创建自己的class,该类extend来自Threadand重写该run()方法。

如果不是,为什么该方法未在Thread类中声明为final?

我能给出的唯一可能的解释是,start当将类引入JDK时,该语言的开发人员看到了一些重写的​​用例。我使用的Java的第一个版本是1.5,而我个人没有遇到用例,在该用例中我发现需要重写start。正如JB Nizet在回答中所说

如果今天从头开始重新设计Java,那么很有可能设计会有所不同


1
尽管您不必扩展用于命名线程的Thread类(只需调用Thread.setName()即可),但解释为什么类不能抽象的部分非常清楚而精确……
Codebender 2015年

@Codebender同意。我已将其从答案中删除,因为它不是一个很好的例子,也因为您的问题没有询问何时扩展线程。
2005年

这是一个干净的设计;为什么今天会有所不同?
沃伦·露

76

您当然可以选择用脚射击,但这并不意味着您必须这么做。

为什么将Thread类实现为常规类而不是将run()方法抽象为抽象类。

因为建议的创建启动线程的方法不是子类Thread。建议的方法是定义一个Runnable,并将其作为参数传递给Thread构造函数:

Runnable r = new Runnable() {
    @Override
    public void run() {
        ...
    }
};
Thread t = new Thread(r);
t.start();

因此,我认为final关键字比其他任何方法都更适合此操作。

是的,没有。您不能用自己的实现替换start()的实现,但是如果需要,可以在start()中做其他事情:

@Override
public void start() {
    System.out.println("Did anyone tell you I will spawn a new thread??");
    super.start();
}

也就是说,如果今天从头开始重新设计Java,那么很有可能设计会有所不同。请记住,此类始于Java 1.0,并且仍然向后兼容。


我已经回答了你。希望您
一切都好

11
没问题。它是开源的。
JB Nizet

2
@NayukiMinase号 接口一直是该语言的语言。从JDK 1.0开始存在Runnable。
JB Nizet

糟糕,我对1.1中引入了基于接口回调(而不是方法重写)的新AWT事件处理系统感到困惑。
Nayuki 2015年

1
线程设计简洁;为什么今天会有什么不同?
沃伦·露

40

为什么Thread.start()final呢?

确定您永远都不想覆盖它吗?

Class MyThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        return new Thread(r) {
            @Override
            public void start() {
                LOGGER.info("Starting thread " + this);
                super.start();
            }
        };
    }
}

0

我觉得应该发布一些校准结果:

“您确定您永远不想覆盖它吗?”

不行 我不会。就像说:“您确定要将此变量声明为私有吗?” 因为如果我可以将任何变量声明为公共变量,而不必担心其他开发人员会弄乱我的设计逻辑,那么编码就很容易了。范围,抽象,多态,错误处理等OOPS概念的最重要目的之一是与其他开发人员交流代码背后的设计和意图。正如问题中指出的那样,当您覆盖启动方法时,没有人强迫您使用super.start()。@Codebender在不使用super.start()的情况下为线程编写了一个启动方法,并且编译器从不抱怨。因此,他可以随意打破Threading的整个机制,而编译器应该只是让它通过?线程的start方法可确保run方法在正确的时间被调用并执行!这对于线程的概念至关重要。如果我在这里错过了一些事情,我会很乐意得到纠正。2。

因为建议的创建启动线程的方法不是子类Thread。

好的,如果设计允许代码转换器,则对Thread进行细分并弄乱start方法,按照这种逻辑,这就是设计在自己的脚上射击。通过另一句话(我完全同意),推荐使用Runnable。那么,为什么我们完全允许Thread类无限制地实例化线程?其次是:

您不能用自己的实现替换start()的实现,

这实际上支持Codebender的说法,即您说的话start方法应该是最终方法。

这一点是正确的,已经在旁注中提及,但这是对该问题的实际答案

“向后兼容”。

实际上,直到JDK 5.0才进行了改进,当时它们对Java并发模型进行了许多主要的补充和说明。我们希望一直支持向后兼容性,这就是为什么Thread类仍然与以前完全一样的原因,即使当今推荐使用Runnable接口。

同样,如果我错过了一些事情,我会很乐意得到纠正。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.