为什么用Java和C#而不是构造函数使用静态main方法?


54

我正在寻找来自主要来源或次要来源的明确答案,为什么Java和C#决定将静态方法作为其入口点,而不是通过Application类的实例表示应用程序实例(具有入口点)作为合适的构造函数)。


我先前研究的背景和细节

这已经被问过了。不幸的是,现有的答案只是在乞求这个问题。特别是,以下答案令我不满意,因为我认为它们是不正确的:

  • 如果构造函数被重载,将存在歧义。–实际上,C#(以及C和C ++)允许使用不同的签名,Main因此存在并解决相同的潜在歧义。
  • 一种static方法意味着在初始化之前,没有对象可以被实例化。–这实际上是错误的,某些对象之前实例化(例如,在静态构造函数中)。
  • 因此,它们可以由运行时调用,而无需实例化父对象。–这根本没有答案。

为了进一步说明为什么我认为这是一个有效且有趣的问题:

  • 许多框架确实使用类来表示应用程序,并使用构造函数作为入口点。例如,VB.NET应用程序框架使用专用的主对话框(及其构造函数)作为入口点1

  • 从技术上讲,Java和C#都不需要主要方法。好吧,C#需要一个才能进行编译,但是Java甚至不需要。而且在任何情况下都不需要执行它。因此,这似乎不是技术限制。而且,正如我在第一段中提到的那样,仅出于约定,它似乎不符合Java和C#的一般设计原理。

需要明确的是,使用静态方法并没有特定的缺点main,这很奇怪,这使我想知道其背后是否有某种技术原理。

我对主要来源或次要来源的明确答案感兴趣,而不仅仅是猜测。


1尽管有一个回调(Startup)可能会拦截它。


4
@mjfgates另外,我希望清楚表明这不仅是“人们为什么不按照我想要的方式来做”,而且我对原因真的很感兴趣。
康拉德·鲁道夫2012年

2
对于Java,我认为推理很简单:开发Java时,他们知道大多数学习该语言的人会事先知道C / C ++。因此,Java不仅看起来很像C / C ++,而不是说小话,而且还接管了C / C ++的特质(想一想八进制整数文字)。由于c / c ++都使用main方法,因此从该角度来看,对java进行相同的处理是有意义的。
Voo

5
@Jarrod你不公平。我以为我很清楚地做到了这一点。“不是建设性的”吗?怎么会这样?我明确要求提供参考,而不仅仅是疯狂的讨论。您当然可以不同意这是一个有趣的问题。但是,如果这类问题在这里很重要,我真的看不到Programmers.SE的作用是什么。
康拉德·鲁道夫

2
相关元讨论
扬尼斯

3
问题:如果它是一个应用程序对象,则不需要两件事。1)一个构造函数。2)在对象上运行您的应用程序的方法。构造函数必须完成,才能使对象有效并可以运行。
马丁·约克

Answers:


38

TL; DR

在Java中,原因public static void main(String[] args)

  1. 高斯林通缉
  2. 由具有C语言(不是Java)经验的人编写的代码
  3. 由曾经在NeWS上运行PostScript的人执行

http://i.stack.imgur.com/qcmzP.png

 
对于C#来说,推理在传递上相似的。语言设计者使Java程序员熟悉程序入口点语法。正如C#架构师Anders Hejlsberg所说

...我们使用C#的方法只是为Java程序员提供了替代方案...

 

长版

在上面扩展并以无聊的引用作为备份。

 

java终结者Hasta la vista宝贝!

VM Spec 2.17.1虚拟机启动

...向Java虚拟机指定初始类的方式超出了本规范的范围,但是在使用命令行的主机环境中,通常将类的完全限定名称指定为:命令行参数,以及随后的命令行参数将用作字符串,以作为方法main的参数提供。例如,使用Sun的Java 2 SDK for Solaris,命令行

java Terminator Hasta la vista Baby!

通过调用类的main方法Terminator(未命名包中的类)并向其传递包含四个字符串“ Hasta”,“ la”,“ vista”和“ Baby!”的数组,将启动Java虚拟机。

...另请参阅:附录:我需要您的衣服,靴子和摩托车

  • 我的解释:
    执行的目标是像命令行界面中的典型脚本一样使用。

 

重要的一步

...这有助于避免在我们的调查中出现一些错误的痕迹。

VM Spec,1.2 Java虚拟机

Java虚拟机对Java编程语言一无所知...

在学习上一章-1.1历史时,我注意到上面的内容,我认为这可能是有帮助的(但后来证明是无用的)。

  • 我的解释是:
    执行仅受VM规范支配,该规范
    明确声明它与Java语言无关
    =>可以忽略JLS和所有与Java语言相关的所有内容

 

高斯林:C语言与脚本语言之间的折衷...

基于以上内容,我开始在Web上搜索JVM历史记录。没有帮助,结果中有太多垃圾。

然后,我回想起有关Gosling的传说,并将搜索范围缩小到Gosling JVM历史上

尤里卡!JVM规范如何成为

在2008年JVM语言峰会的主题演讲中,James Gosling讨论了... Java的创建,... C和脚本语言之间的折衷...

  • 我的解释:
    明确声明在创建之时,
    C和脚本已被视为最重要的影响。
     
    已经看到VM规格2.17.1点头脚本,
    命令行参数足以解释String[] args
    ,但staticmain现在还没有,需要进一步挖掘...

请注意,在键入此内容时-将C,脚本和VM Spec 1.2与Java无关连接-我感觉有些熟悉,有些…… 面向对象的特性正在慢慢消失。握住我的手,保持前进'别放慢速度,我们现在快到了

主题幻灯片可在线获得:20_Gosling_keynote.pdf,非常方便复制关键点。

    第3页

        Java的史前史
        *是什么影响了我的思维

    第9页

        新闻
        *网络可扩展窗口系统
        *基于脚本的窗口系统。
          PostScript(!!)

    第16页

        一个大目标(但安静):
          我能走多近
          “脚本”的感觉...

    第19页

        原始概念
        *全部关于建筑
          物联网
          由脚本编排
          语言
        *(Unix外壳,AppleScript等)

    第20页

        披着羊皮的狼
        * C语法使开发人员
          自在

哈!让我们仔细看看C语法

“你好,世界”的例子...

main()
{
    printf("hello, world\n");
}

...正在定义一个名为main的函数。的主要功能用于在C程序中一个特殊的目的; 运行时环境调用main函数开始执行程序。

...主要功能实际上有两个参数,int argcchar *argv[]分别可用于处理命令行参数...

我们越来越近了吗?你打赌。上面引用中的“主要”链接也值得关注:

主要功能是程序开始执行的地方。它负责程序功能的高级组织,通常可以在执行程序时访问赋予程序的命令参数。

  • 我的解释:
    为了让C开发人员满意,程序入口必须为main
    另外,由于Java要求任何方法都必须在类中,Class.main因此
    与它的实现非常接近:静态调用,仅类名和点,
    无构造函数-C对此一无所知。考虑到 易于从Java迁移到C#的想法,
     
    这也可传递地应用于C#

欢迎那些认为熟悉程序入口点无关紧要的读者搜索和检查Stack Overflow问题,这些问题来自Java SE的人们正在尝试为Java ME MIDP 编写Hello World。注意MIDP入口点没有main,也没有static

 

结论

基于以上我要说的是staticmainString[] args是在Java和C#创建最合理的选择时刻定义程序入口点

 

附录:我需要你的衣服,靴子和摩托车

必须承认,阅读VM Spec 2.17.1非常有趣。

...命令行

java Terminator Hasta la vista Baby!

通过调用类的main方法Terminator(未命名包中的类)并向其传递包含四个字符串“ Hasta”,“ la”,“ vista”和“ Baby!”的数组,将启动Java虚拟机。

现在,我们概述了虚拟机可能要执行的步骤Terminator,以作为加载,链接和初始化过程的示例,这将在后面的部分中进一步描述。

最初的尝试...发现该类Terminator未加载...

之后Terminator被加载时,它必须被初始化主可以被调用之前,它被初始化之前一个类型(类或接口)必须被链接。链接(§2.17.3)涉及验证,准备和(可选)解决方案...

验证(§2.17.3)检查所加载的表示形式Terminator是否正确。

解析(§2.17.3)是检查类中的符号引用的过程Terminator

 
从符号引用Terminator哦耶。


2
由于某种原因,我很难相信“现代性”是一个实际的词。
someguy 2012年

@Songo的故事故事也像一部电影。它首先发布在meta上,在有关关闭问题的讨论中:“如果问题将被重新打开,我可能会写一个如下所示的答案...”然后它被用于支持重新打开的呼吁,最后移到这里
蚊蚋

16

这对我来说隐约有辱骂。构造函数用于对象的初始化:它建立一个对象,然后由创建该对象的代码使用。

如果将基本用法功能放在构造函数中,而从不实际使用构造函数在外部代码中创建的对象,那么您就违反了OOP的原理。基本上,做某件事确实很奇怪,没有明显的原因。

您为什么仍要这样做?


5
但是“应用程序实例”在逻辑上不是对象吗?为什么那样侮辱?至于使用对象,它有一个目的:代表正在运行的应用程序。听起来非常的SoC -y我。“您为什么要这样做?” –我只是对决策的理由感兴趣,因为我发现它与其他心态背道而驰。
康拉德·鲁道夫

7
@KonradRudolph:像属性获取器一样,通常希望构造函数在一定时间内完成,而不必等待某些异步事件(例如用户输入)发生。可能会有一个构造函数启动了一个主应用程序线程,但是却增加了并非所有应用程序都需要的复杂程度。要求仅将“ Hello world”打印到标准输出的控制台应用程序应该产生一个额外的线程是愚蠢的。使用Main方法在简单情况下效果很好,在较困难的情况下并不是真正的问题,为什么不呢?
超级猫

9

对于Java,我认为推理很简单:在开发Java时,开发人员知道,大多数学习该语言的人会事先知道C / C ++。

因此,Java不仅看起来很像C / C ++,而不是说小话,而且还接管了C / C ++的特质(想一想八进制整数文字)。由于c / c ++都使用main方法,因此从该角度来看,对java进行相同的处理是有意义的。

我敢肯定,我记得bloch或有人说过这样的话,为什么要添加八进制整数文字,我看看是否可以找到一些来源:)


2
如果看起来与C ++相同对Java非常重要,那么为什么将它们例如更改:extends?而public static void main(String [ ] args)一个类里面是相当大的差异int main(int argc, char **argv)的一类外。
svick

2
@svick一种可能性:Java引入了接口,显然他们想将两个概念分开(继承接口/类)-仅使用一个无效的“关键字”。和“相当不同”吗?它是最接近它的映射,到目前为止,我从未见过c ++程序员在理解静态main方法是切入点时遇到问题。与拥有一个称为Application的类或使用其构造函数的类相反,对于大多数c ++程序员而言,它们看起来很奇怪。
Voo 2012年

c中的@svick int要在Java中无效,必须使用如何生成应用程序的返回代码-在Java中,除非调用System.exit(int),否则它的值为0。参数的更改与每种语言如何传递字符串数组有关。java中的所有内容都在一个类中-别无选择。更改:extends只是语法问题,基本上是相同的。其他所有内容均由语言决定。

@MichaelT但是所有这些都是使Java与C ++不同的设计决策。因此,在的情况下main(),为什么要使Java与C ++保持相同是很重要的,而在其他情况下显然还不够重要。
svick

@svick除了不要完全从C中的main返回任何东西是完全可以的,而且这样的琐事几乎不会使任何人感到困惑。关键不是要重新创建c ++及其所有错误,而只是使程序员更加熟悉。您认为C ++程序员会更轻松地阅读什么:Java或Objective-c代码?您认为对于C ++程序员来说,以main方法或某个类的构造函数作为入口点会更明显吗?
Voo 2012年

6

好吧,那里有很多主要功能只是在运行无限循环。以这种方式工作的构造函数(使用永不构造的对象)对我来说似乎很奇怪。

关于这个概念有很多有趣的事情。您的逻辑运行在一个未出生的对象之上,这些对象已经死了(因为它们在构造函数中完成了所有工作),...

所有这些副作用是否会比简单的公共操作破坏更多的OO旅行车(因为它需要由未知人员访问)静态(因为不需要任何实例来开始使用)静态main(因为这是切入点) )?

对于Java中存在的简单,简单的函数入口点,将自动需要public和static。尽管它是一个静态方法,但可以归结为一个简单的入口点,它可以使我们更接近普通函数来完成所需的工作。

如果您不打算采用简单,简单的函数入口点作为入口点。下一步,作为一个不打算构造的构造函数,这看起来并不奇怪吗?


1
我会说问题不在于一流的功能。将main()粘贴在对象内(在调用main之前未实例化)是有点反模式的。也许他们应该有一个可以构造并运行其非静态main()方法的“应用程序”对象,然后可以将启动初始化放入构造函数中,尽管使用一个简单的top- = level main()fn也会很好。静态主体总的来说有点麻烦。
gbjbaanb 2012年

3

您可以在开发期间通过将a main()插入要测试的类中,从而在类上快速运行一些独立测试。


1
这对我来说可能是最有说服力的理由,因为它的发展过程中也允许多个入口点用于测试不同的配置,等等
cgull

0

您必须从某个地方开始。静态主体是您可以拥有的最简单的执行环境-无需创建任何实例(在JVM和简单的字符串参数之外)的实例-因此它可以以最小的麻烦(且可能性很小)“出现”编码错误导致无法启动等),并且无需执行许多其他设置即可完成简单的工作。

基本上是KISS的应用程序。

[并且,当然,主要原因是:为什么不呢?]


3
如我所说,我一点也不相信。对象确实在之前被实例化,并且代码在之前被执行。它需要原始开发人员之一的报价才能说服我,这就是原因。
康拉德·鲁道夫2012年

2
从C代码实例化一个类所需的工作量与调用静态方法几乎相同。您甚至必须进行相同的检查(该类是否存在?可以,它是否具有带有正确签名的公共构造函数?好,然后继续)。
Voo

无需创建用户对象。对象构造函数未执行。该API非常简单。这是最容易理解的。
Daniel R Hicks

0

据我了解,主要原因很简单。Sun是一家出售Unix机器的Unix公司,而Unix是为调用二进制文件而设计的C“ main(args)” 约定

另外,Java的显式设计使其易于为C和C ++程序员所接受,因此没有充分的理由不仅仅接受C约定。

每个类都可以具有调用方法的所选方法非常灵活,特别是与Main-Class可运行jar中MANIFEST.MF文件中的行结合使用时。


当然,jar文件直到很晚才被发明。
Daniel R Hicks

-1

从OS进程的角度来看,程序将成为对象与OOP的哲学观点不一致,因为按照定义,没有办法拥有多个对象。

最重要的是,无论如何构造函数都不是入口点。

在我看来,将main作为静态函数似乎是最合理的选择,实际上这是一天结束的时候。考虑到诸如JVM和CLR之类的VM的体系结构,任何其他选择都将不必要地推动它。


1
我认为你错了。它可以具有一个以上的过程中,因此一个以上的对象。顺便说一句,这完全等同于实例化Runnable对象以具有多个线程。
康拉德·鲁道夫2012年

进程是一个正在运行的程序,您只能通过一个入口点启动一个进程。线程具有自己的入口点,但仍在同一进程中。
Yam Marcovic

1
就像我在someguy的答案下面说的那样,这无关紧要。重要的是逻辑一致性。从逻辑上讲,进程由启动器(OS,JVM等)表示为对象并进行初始化。
康拉德·鲁道夫2012年

@KonradRudolph是的,但是程序的初始化只是进程初始化的一部分,并且不会使程序构造函数合法化。
Yam Marcovic
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.