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

13
我听说有些人说:"如果main函数不是静态的,那么JVM可以创建一个包含main函数的类的对象,并通过对象调用该main函数。" 但问题在于,对于重载的构造函数,或者仅有一个带参数的构造函数,JVM如何知道要调用哪个构造函数以及传递什么参数。

这是否是正确的原因?因为如果没有进入main函数,怎么能创建类的对象呢?请发表您的观点。如果这不是正确的原因,那么正确的原因是什么呢?

9个回答

18

这只是一种惯例。Java语言设计者本可以轻易地决定你必须指定一个类来实例化,并使其构造函数成为主方法。但调用静态方法同样有效,而且不需要首先实例化一个类。

另外,如果该类有一个超类,你可能会通过更改超类(由于必须先调用超类构造函数再调用子类),无意中改变程序启动的行为。静态方法就没有这个问题。

主方法是静态的,因为它让事情变得更简单,但如果他们想让它变得更复杂,他们也可以这么做。


2
Java是基于C模型的,而Java中的“main”概念非常接近于C中的“main”。 - Thorbjørn Ravn Andersen

5

"这个理由正确吗?"

在某种程度上是正确的,尽管从未明确解释过。

我们可以约定使用String...args来调用构造函数,这将意味着您至少需要一个对象才能运行,但(可能)设计者认为这不是必需的。

例如,没有技术障碍可以像这样编写代码:

 class Hello {
       public void main(String...args){
           System.out.println("Hello, world");

       }
 }

事实上,如果您没有指定构造函数,Java会为您创建一个无参构造函数。如果该类包含一个main方法,那么很容易包含一个var args构造函数,并在幕后创建以下代码:

 class Hello {
      // created by the compiler
      public Hello(){}
      public Hello( String ... args ) {
          this();
          main( args );
      }
      // end of code created by the compiler
      public void main( String ... args ) {
           System.out.println("Hello, world!");
      }
  }

但这样会产生不必要的代码和额外分配的对象,在这种情况下不会有任何作用;两个构造函数(而不是一个)等。最终看起来就像太多的魔法。

这取决于语言设计者。在这种情况下,他们可能认为不这样做更简单,只需验证被调用的类是否具有名为public static void main(String[] args)的方法的特殊签名。

顺便说一下,你可以在Java中有一个没有main方法的Hello! world程序,但它会抛出java.lang.NoSuchMethodError: main

public class WithoutMain {
    static {
        System.out.println("Look ma, no main!!");
        System.exit(0);
    }
}

$ java WithoutMain
Look ma, no main!!

我知道这不是一个替代方案,但是,这还是很有趣的,不是吗?


2
我听到有人说:“如果 main 方法不是静态的,那么 JVM 可以创建一个包含 main 方法的类的对象,并通过该对象调用 main 方法。”但这是不正确的。至少,在JLS中没有这样的规定。问题在于,当构造函数存在重载或者只有一个带参数的构造函数时,JVM 如何知道要调用哪个构造函数,或者传递什么参数。如果这是真的,我只会期望它调用(隐式)默认无参构造函数。请参见JLS-调用主方法

2
我希望它使用 this(String... args) 构造函数。 - Daniel
好的,我想问一下,在任何情况下JVM是否可以创建一个包含main方法的类的对象? 如果可以创建对象,那么通过该对象调用main方法是否可能? 请给出一个例子。 - Happy Mittal
2
@Pete:我的回答是基于 JLS 的。因为从技术上讲这不是不可能的,所以责怪我真的没有意义 :) - BalusC
“Technically not impossible” 意味着在技术上是可能的,就像我所提供的示例代码一样。 - Pete Kirkham
@Pete:请指出我说过这是技术上不可能的地方。它确实在技术上是可能的 :) 否则像Clojure/Scala/Etc这样的其他JVM上层语言就不可能存在。你的回答恰好证明了这一点,通过绕过JLS并操作JVM。你对我的回答有些狭隘了。太遗憾了,否则我会很乐意为你的回答点赞,因为那只是一个不错的演示,尽管benzado的回答比我们的回答更好地解决了问题。 - BalusC
显示剩余4条评论

2

static是一个关键字,当它被应用在main方法之前时,JVM会认为这是程序执行的起点。为什么JVM会这样想呢?这是因为Java开发人员将责任交给JVM通过main()方法进入指定的类。 例如:假设有两个类A和B,其中B扩展了A,在Java中,对于每个类都应该创建一个对象以访问该类中的变量和方法。在类B中编写了静态的main()方法,静态关键字表示无论程序执行何时,它都将分配内存给该关键字。


1

是的,其他运行在JVM上的语言会创建对象或模块(也是对象),并运行它们。例如,Fortress语言的“Hello world”看起来像:

Component HelloWorld
Export Executable
run(args) = print "Hello, world!"
end

或者没有参数:

Component HelloWorld
Export Executable
run() = print "Hello, world!"
end

Java比纯面向对象的语言更加实用,具有静态方法和字段以及原始类型。它的静态主方法更接近于C的主函数。你可以问Gosling为什么选择这种约定。

启动JVM的代码非常简单 - 这个例子创建了一个JVM,创建了一个对象并调用它的run方法来处理命令行参数 - 使启动函数成为(new main.HelloWorld()).run(args)而不是main.HelloWorld.main(args)

#include <stdio.h>
#include <jni.h>

JNIEnv* create_vm() {
    JavaVM* jvm;
    JNIEnv* env;
    JavaVMInitArgs args;
    JavaVMOption options[1];

    args.version = JNI_VERSION_1_2;
    args.nOptions = 1;

    options[0].optionString = "-Djava.class.path=C:\\java_main\\classes";
    args.options = options;
    args.ignoreUnrecognized = JNI_TRUE;

    JNI_CreateJavaVM(&jvm, (void **)&env, &args);

    return env;
}

int invoke_class(JNIEnv* env, int argc, char **argv) {
    jclass helloWorldClass;

    helloWorldClass = env->FindClass("main/HelloWorld");

    if (helloWorldClass == 0)
        return 1;

    jmethodID constructorMethod = env->GetMethodID(helloWorldClass, "<init>", "()V");

    jobject object = env->NewObject(helloWorldClass, constructorMethod);

    if (object == 0)
        return 1;

    jobjectArray applicationArgs = env->NewObjectArray(argc, env->FindClass("java/lang/String"), NULL);

    for (int index = 0; index < argc; ++index) {
        jstring arg = env->NewStringUTF(argv[index]);
        env->SetObjectArrayElement(applicationArgs, index, arg);
    }

    jmethodID runMethod = env->GetMethodID(helloWorldClass, "run", "([Ljava/lang/String;)V");

    env->CallVoidMethod(object, runMethod, applicationArgs);

    return 0;
}

int main(int argc, char **argv) {
    JNIEnv* env = create_vm();

    return invoke_class( env, argc, argv );
}

1
不需要创建对象来调用静态方法。因此,JVM无需分配额外的内存来创建main对象,然后调用它。

1

因为可以有主方法。而且主对象不需要是一个对象。如果是这样的话,你需要实例化一个。

如果你为自己使用jvm.dll,并创建一个对象并调用它,那么你就不需要一个主函数。

然而,以这种方式进行非面向对象编程是可能的,只是为了那些出于某种原因需要这样做的人。 :)


1

main函数是静态的,因此您的代码可以在不必先实例化类的情况下执行。也许您甚至不想创建一个类,或者创建类很慢,您想先打印出“正在加载…”文本,或者您有多个构造函数等等…有很多原因不要强制用户在命令执行之前创建一个类。

如果您以静态方式创建对象,则可以在main()执行之前创建它们。


0
但问题在于JVM如何知道在存在重载构造函数的情况下调用哪个构造函数,或者即使只有一个参数化构造函数,也要传递什么。

我也这样认为。我不使用Java,我使用C ++。如果您没有自己编写构造函数,则会隐式提供默认无参数构造函数和复制构造函数。但是当您自己编写构造函数时,编译器不会提供任何构造函数。我认为Java也遵循这个理论。

因此,在类中不能保证不会有构造函数。同时,限制类不具有用户定义的构造函数是一个坏主意。但是,如果系统允许您编写自己的构造函数,则甚至没有保证它将是无参数构造函数。因此,如果它是参数化构造函数,则不知道要发送什么参数。

因此,我认为这就是静态Main函数背后的实际原因。


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