为什么Java的主方法是静态的?

539
Java的main方法的方法签名为:
public static void main(String[] args) {
    ...
}

这个方法必须是静态的有什么原因吗?


1
在这种情况下,我们不应该说“方法签名”,因为该术语仅指方法名称及其参数。 - Andrew Tobilko
1
Java被有意设计成与C程序员熟悉的外观非常接近。这非常接近C约定。 - Thorbjørn Ravn Andersen
37个回答

436
这只是惯例而已。实际上,甚至main()的名称和传入的参数都是纯粹的惯例。
Java 21引入了替代惯例作为预览功能;您可以省略String[]参数、public修饰符,甚至是static修饰符。如果省略static修饰符,在调用之前将创建该类的一个实例,这要求该类具有一个非私有的零参数构造函数。如果没有声明构造函数,编译器会创建一个默认构造函数,这是足够的。
当您运行java.exe(或Windows上的javaw.exe)时,实际上是进行了一些Java Native Interface(JNI)调用。这些调用加载的DLL实际上是JVM(没错 - java.exe不是JVM)。JNI是我们在虚拟机世界和C、C++等世界之间进行桥接时使用的工具。反过来也是如此 - 至少据我所知,实际上不可能启动一个JVM而不使用JNI。
基本上,java.exe是一个超级简单的C应用程序,它解析命令行,创建一个新的字符串数组在JVM中来保存这些参数,解析出你指定的包含main()方法的类名,使用JNI调用来找到main()方法本身,然后调用main()方法,将新创建的字符串数组作为参数传递进去。这非常类似于在Java中使用反射时所做的事情 - 它只是使用了令人困惑的命名的本地函数调用而已。
你完全可以编写自己的java.exe版本(源代码与JDK一起分发),并让它执行完全不同的操作是完全合法的。事实上,这正是我们所有基于Java的应用程序所做的。
我们的每个Java应用程序都有自己的启动器。我们主要这样做是为了获得自己的图标和进程名称,但在其他情况下也非常有用,比如我们想要做一些不同于常规main()调用的操作(例如,在某些情况下,我们正在进行COM互操作,并且实际上将一个COM句柄传递给main()而不是一个字符串数组)。
所以,长话短说:它之所以是静态的是因为这样方便。它被称为'main'的原因是因为它必须是某个东西,而在C的旧时代,main()就是他们所做的(在那个时代,函数的名称是重要的)。我想java.exe本来可以允许您只指定一个完全限定的main方法名称,而不仅仅是类(java com.mycompany.Foo.someSpecialMain)- 但这只会让IDE更难自动检测项目中的'可启动'类。

74
非常迷人(尤其是关于编写自定义 java.exe 的部分)。 - Adam Paynter
10
有趣,我不同意答案中的“这只是惯例”的部分。原帖的主要问题是声明中使用static的原因。我认为在main()的声明中使用static不仅仅是出于惯例而已。但它被用在'main()'而不是其他地方是可行的。 - Jared
2
@Jared - 他们本可以要求一个公共的无参构造函数,并使main方法非静态,仍然符合语言的限制。没有听到设计者的意见,我们只能同意不同意见。 :) - David Harkness
5
您可以调用LoadLibrary()函数加载jvm dll动态链接库。接着,您可以使用getprocaddress("JNI_CreateJavaVM")获取JNI_CreateJavaVM函数的地址,并调用该函数(参考文档:http://docs.oracle.com/javase/1.4.2/docs/guide/jni/spec/invocation.html)。一旦虚拟机被加载,您可以使用标准的JNI调用来找到正确的类,加载静态main方法并进行调用。在这个过程中没有太多的误解空间,JNI绝对是您加载虚拟机的方式。您可能已经习惯了只使用native关键字、javah -jni等方式编写客户端JNI程序,但这只是JNI的一半。 - Kevin Day
2
@BenVoigt 我认为目前这样做没有意义。你可以自由地相信你希望相信的事情。 - Kevin Day
显示剩余13条评论

353

这个方法是静态的,否则就会存在歧义:应该调用哪个构造函数?特别是如果你的类长这样:

public class JavaClass{
  protected JavaClass(int x){}
  public void main(String[] args){
  }
}

JVM是否应该调用 new JavaClass(int)?它应该为x传递什么?

如果不是,JVM是否应该实例化JavaClass而不运行任何构造函数方法?我认为不应该这样做,因为这将特殊处理整个类 - 有时您拥有一个未初始化的实例,并且您必须在可能被调用的每个方法中进行检查。

有太多的边缘情况和歧义,以至于JVM在调用入口点之前实例化类没有意义。这就是为什么main是静态的原因。

不过,我不知道为什么main总是标记为public


5
实现一个接口并不能解决实例化的问题。 - Jacob Krall
28
我个人喜欢public static void main作为入口点的标志,一个公共的无参构造函数并不像它那样大声喊出“这可能是一个入口点!” - Jacob Krall
6
将入口点类强制实例化会带来什么好处呢?调用静态方法对类的负担最小。如果实例化自身更符合设计需求,则可以自由地进行实例化。 - David Harkness
18
“应该调用哪个构造函数?”这怎么可能是问题?决定调用哪个 main 也存在同样的“问题”。令人惊奇的是(对于您来说),JVM 可以很好地处理这个问题。 - Konrad Rudolph
10
主函数一般都是public的,因为它需要被运行时引擎 JVM 访问。 - gthm
显示剩余11条评论

197

C++、C# 和 Java 中的 main 方法都是静态的。

这是因为它们可以被运行时引擎调用,而不需要实例化任何对象,然后在 main 代码块中的代码将执行其余操作。


1
好的,但是运行时不能实例化类的一个对象吗?然后再调用主方法?为什么? - Andrei Rînea
13
如果你的主类有重载构造函数,JVM如何知道要调用哪个构造函数?它会传递哪些参数? - Jacob Krall
1
@Noah,当你说父类时,你是指包含主方法的类吗?如果是这样,那么“父类”这个术语在此处相当令人困惑。否则,对我来说就没有任何意义了。另外,如果按照惯例我们使用public static void main...,为什么约定不能是应用程序入口点类应该有一个公共默认构造函数? - Edwin Dalorzo
2
@Jacob JVM怎么知道该调用哪个重载的static void main方法?这不是问题。 - Konrad Rudolph
5
@Namratha:是的,你漏掉了一些内容。“静态方法不能引用非静态方法”这种说法是不正确的。正确的说法应该是:“每个静态方法在使用任何非静态方法时都必须提供一个对象”。而且,像main这样的static方法经常使用new来创建这样的对象。 - Ben Voigt
显示剩余2条评论

42

假设我们不需要使用static作为应用程序的入口点。

那么一个应用程序类将如下所示:

class MyApplication {
    public MyApplication(){
        // Some init code here
    }
    public void main(String[] args){
        // real application code here
    }
}
构造函数和main方法之间的区别是必要的,因为在面向对象语言中,构造函数只应确保实例被正确地初始化。初始化后,实例可以用于预期的“服务”。将完整的应用程序代码放入构造函数中会破坏这一点。
因此,这种方法会对应用程序强制施加三个不同的约束条件:
  • 必须有一个默认构造函数。否则,JVM将不知道调用哪个构造函数以及应提供哪些参数。
  • 必须有一个main方法。这并不令人惊讶。
  • 类不能是抽象的。否则,JVM无法实例化它。
另一方面,static方法只需要一个约定:
  • 必须有一个main方法。
这里既不考虑abstract,也不考虑多个构造函数的情况。
由于Java被设计成一种简单的语言,“为用户”考虑到用户输入程序的入口点时,采用了一个简单的方式,使用一个约定而不是使用三个相互独立且脆弱的约定。
请注意:这个论点不是关于JVM或JRE内部的简单性。这个论点是关于对用户的简单性。

1
实际上,这些要求更为复杂:必须有一个 main 方法,它是 publicstatic 的,并且具有签名为 void main(String[])。我同意,如果该方法是实例方法,JRE 的工作会稍微增加,但工作的类型将是相同的,复杂性也不会显著提高(请参见先前答案的评论讨论)。我不认为这种差异解释了使入口点静态的决定,特别是因为解析实例方法所需的必需方法已经存在,并且可以轻松使用。 - Konrad Rudolph
3
我的意思不是关于JRE需要做什么工作。我的意思是强制每个使用该语言的用户按需遵循更多的契约。从这个意义上说,一个static public main(String[])方法是一个签名,因此是一个契约。否则,必须遵循三个独立的契约。 - A.H.
1
啊。我仍然不同意这会有任何区别。入口点类很可能实现“Runnable”。显然,Java期望开发人员始终遵循该合同,为什么应用程序入口点会过于困难?这没有意义。 - Konrad Rudolph
3
@KonradRudolph说的并没有矛盾:在一个情况下,系统会强制要求用户签订三份合同。这些合同是可疑的,无法通过编译器检查,并且从用户的角度来看是独立的。而在通常的“Thread”和“Runnable”情况下,用户没有隐藏的内容,可以清楚地看到正在发生的事情,并有机会仅实现适合自己的那些合同——他掌控着局面,而不是被系统控制。 - A.H.
2
这是这里最好的答案。很遗憾,许多用户只会阅读页面上排名前2或3的答案;而这个答案不太可能很快就到达那里。它提到了构造函数仅用于初始化的重要点 - 因此,在构造函数中运行整个应用程序的编码方式是没有意义的。 - Dawood ibn Kareem
显示剩余4条评论

41

为什么要用public static void main(String[] args)?

这是Java语言和Java虚拟机的设计方式。

Oracle Java语言规范

请查看第12章执行 - 第12.1.4节调用Test.main方法

最后,在完成Test类的初始化(在此期间可能已发生其他相关的加载、链接和初始化)后,将调用Test的main方法。

main方法必须声明为public、static和void。它必须接受一个字符串数组作为单个参数。该方法可以声明为

public static void main(String[] args)
或者
public static void main(String... args)

Oracle Java虚拟机规范

查看第2章Java编程语言概念-第2.17节执行

Java虚拟机通过调用某个指定类的方法main并传递单个参数(即字符串数组)来开始执行。这会导致指定类被加载(§2.17.2),与其使用的其他类型链接(§2.17.3),并进行初始化(§2.17.4)。该方法main必须声明为public、static和void。

Oracle OpenJDK源代码

下载并提取源码jar,查看JVM是如何编写的,请查阅../launcher/java.c,其中包含命令java [-options] class [args...]背后的本地C代码。

/*
 * Get the application's main class.
 * ... ...
 */
if (jarfile != 0) {
    mainClassName = GetMainClassName(env, jarfile);

... ...

    mainClass = LoadClass(env, classname);
    if(mainClass == NULL) { /* exception occured */

... ...

/* Get the application's main method */
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                   "([Ljava/lang/String;)V");

... ...

{    /* Make sure the main method is public */
    jint mods;
    jmethodID mid;
    jobject obj = (*env)->ToReflectedMethod(env, mainClass,
                                            mainID, JNI_TRUE);

... ...

/* Build argument array */
mainArgs = NewPlatformStringArray(env, argv, argc);
if (mainArgs == NULL) {
    ReportExceptionDescription(env);
    goto leave;
}

/* Invoke main method. */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

... ...

5
问题在于,原始形式下这实际上是一个非常好的回答,提供了很多参考文献(+1)。但是,我想了解为什么设计决策将静态方法作为入口点,而不是构造函数或实例方法的基本原理。 - Konrad Rudolph
1
@KonradRudolph,如果你有关于语言和JVM规范设计方面的问题,或许可以尝试联系Oracle的原始来源,看看能否得到任何积极的反馈。 - yorkw
3
通常情况下,如果一个方法的计算结果仅取决于其参数,而不取决于对象实例的内部状态,那么它可以是静态的。建议将其设置为静态以便于代码的可维护性和重复使用性。如果方法“main”不是静态的,这意味着必须知道类实例的状态,并且定义起来更加复杂,例如要先使用哪个构造函数。 - Yves Martin
有趣的是,Java 的前身 Oak 已经要求 main 方法具有类似的原型:public static void main(String arguments[]) - 参考资料:Oak 0.2 规范 - assylias
2
@Yves 可以这样设计,但如果另一种设计更合理,则不需要这样做。我在评论中听到了一些很好的论点,但我仍然认为进程实际上非常类似于线程(它是),而Java中的线程通常表示为 Runnable 的实例。以相同的方式表示整个进程(即将 Runnable.Run 作为入口点)在Java中绝对是有意义的。当然,Runnable 本身可以说是一个设计缺陷,这是由于Java没有匿名方法(尚未)。但既然已经存在... - Konrad Rudolph

14

如果不是这样,如果有多个构造函数,应该使用哪一个构造函数?

有关Java程序初始化和执行的更多信息,请参见Java语言规范


13
在调用主方法之前,没有实例化任何对象。使用static关键字意味着可以在先创建任何对象的情况下调用该方法。

错误的。或者至少非常不准确。 公共类Main { static Object object = new Object() { { System.out.println("对象已创建"); } }; public static void main(String[] args) { System.out.println("在主函数中"); } } - eljenso
公平的评论。严格来说,在调用Main方法之前,包含该方法的类未被实例化。 - BlackWasp

12

让我以更简单的方式解释这些事情:

public static void main(String args[])

除了小程序之外,所有Java应用程序都从main()开始执行。

public关键字是一种访问修饰符,允许从类的外部调用成员。

static被使用是因为它允许在不实例化该类的特定实例的情况下调用main()

void表示main()不返回任何值。


12
否则,它将需要执行对象的实例。但必须从头开始调用它,而不是先构造对象,因为通常由main()函数(引导程序)负责解析参数并构造对象,通常通过使用这些参数/程序参数来完成。

11

public static void main(String args[])的含义是什么?

  1. public is an access specifier meaning anyone can access/invoke it such as JVM(Java Virtual Machine.
  2. static allows main() to be called before an object of the class has been created. This is neccesary because main() is called by the JVM before any objects are made. Since it is static it can be directly invoked via the class.

    class demo {    
        private int length;
        private static int breadth;
        void output(){
            length=5;
            System.out.println(length);
        }
    
        static void staticOutput(){
            breadth=10; 
            System.out.println(breadth);
        }
    
        public static  void main(String args[]){
            demo d1=new demo();
            d1.output(); // Note here output() function is not static so here
            // we need to create object
            staticOutput(); // Note here staticOutput() function is  static so here
            // we needn't to create object Similar is the case with main
            /* Although:
            demo.staticOutput();  Works fine
            d1.staticOutput();  Works fine */
        }
    }
    

    Similarly, we use static sometime for user defined methods so that we need not to make objects.

  3. void indicates that the main() method being declared does not return a value.

  4. String[] args specifies the only parameter in the main() method.

    args - a parameter which contains an array of objects of class type String.


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接