¿Por qué un método principal estático en Java y C #, en lugar de un constructor?

54

Estoy buscando una respuesta definitiva de una fuente primaria o secundaria de por qué (especialmente) Java y C # decidieron tener un método estático como punto de entrada, en lugar de representar una instancia de aplicación por una instancia de una Applicationclase (con el punto de entrada ser un constructor apropiado).


Antecedentes y detalles de mi investigación previa.

Esto se ha preguntado antes. Desafortunadamente, las respuestas existentes simplemente están planteando la pregunta . En particular, las siguientes respuestas no me satisfacen, ya que las considero incorrectas:

  • Habría ambigüedad si el constructor estuviera sobrecargado. - De hecho, C # (así como C y C ++) permite diferentes firmas para Mainque exista la misma ambigüedad potencial y se aborde.
  • Un staticmétodo significa que no se pueden instanciar objetos antes, por lo que el orden de inicialización es claro. - Esto es realmente incorrecto, algunos objetos se instancian antes (por ejemplo, en un constructor estático).
  • Por lo tanto, el tiempo de ejecución puede invocarlos sin tener que crear una instancia de un objeto primario. - Esta no es una respuesta en absoluto.

Solo para justificar aún más por qué creo que esta es una pregunta válida e interesante:

  • Muchos marcos hacen uso de clases para representar las aplicaciones y los constructores como puntos de entrada. Por ejemplo, el marco de la aplicación VB.NET utiliza un diálogo principal dedicado (y su constructor) como punto de entrada 1 .

  • Ni Java ni C # técnicamente necesitan un método principal. Bueno, C # necesita uno para compilar, pero Java ni siquiera eso. Y en ninguno de los casos es necesario para la ejecución. Por lo tanto, esto no parece ser una restricción técnica. Y, como mencioné en el primer párrafo, para una simple convención, parece extrañamente inadecuado para el principio general de diseño de Java y C #.

Para ser claros, no hay una desventaja específica de tener un mainmétodo estático , es claramente extraño , lo que me hizo preguntarme si había alguna razón técnica detrás de esto.

Estoy interesado en una respuesta definitiva de una fuente primaria o secundaria, no meras especulaciones.


1 Aunque hay una devolución de llamada ( Startup) que puede interceptar esto.

Konrad Rudolph
fuente
44
@mjfgates Además, esperaba haber dejado en claro que esto no es simplemente "por qué la gente no lo hizo como yo quería", y que estoy realmente interesado en las razones.
Konrad Rudolph
2
Para Java, creo que el razonamiento es simple: al desarrollar Java, sabían que la mayoría de las personas que aprenden el lenguaje sabrían C / C ++ de antemano. Por lo tanto, Java no solo se parece mucho a C / C ++ en lugar de decir smalltalk, sino que también se hizo cargo de las idiosincrasias de C / C ++ (solo piense en literales enteros octales). Dado que c / c ++ utilizan un método principal, hacer lo mismo para Java tiene sentido desde ese punto de vista.
Voo
55
@Jarrod Eres injusto. Pensé que lo había hecho claramente, no en una queja. "No constructivo"? ¿Cómo es eso? Estoy pidiendo referencias explícitamente, no solo discusiones alocadas. Por supuesto, usted es libre de estar en desacuerdo de que esta es una pregunta interesante . Pero si este tipo de preguntas es OT aquí, realmente no puedo ver para qué sirve Programmers.SE.
Konrad Rudolph
2
Meta discusión relevante .
yannis
3
Pregunta: Si se trata de un objeto de aplicación, no necesita dos cosas. 1) Un constructor. 2) Un método en el objeto para ejecutar su aplicación. El constructor debe completar para que el objeto sea válido y, por lo tanto, ejecutable.
Martin York

Respuestas:

38

TL; DR

En Java, la razón public static void main(String[] args)es que

  1. Gosling quería
  2. El código escrito por alguien con experiencia en C (no en Java)
  3. para ser ejecutado por alguien acostumbrado a ejecutar PostScript en NeWS

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

 
Para C #, el razonamiento es transitivamente similar, por así decirlo. Los diseñadores de lenguaje mantuvieron la sintaxis del punto de entrada del programa familiar para los programadores provenientes de Java. Como dice el arquitecto C # Anders Hejlsberg ,

... nuestro enfoque con C # ha sido simplemente ofrecer una alternativa ... a los programadores de Java ...

 

Versión larga

expandiéndose arriba y respaldado con aburridas referencias.

 

java Terminator Hasta la vista Baby!

VM Spec, 2.17.1 Inicio de máquina virtual

... La manera en que se especifica la clase inicial en la máquina virtual Java está más allá del alcance de esta especificación, pero es típico, en entornos host que usan líneas de comando, que el nombre completo de la clase se especifique como un argumento de línea de comandos y para los argumentos de línea de comandos posteriores que se utilizarán como cadenas que se proporcionarán como argumento al método main. Por ejemplo, usando Sun 2 Java 2 SDK para Solaris, la línea de comando

java Terminator Hasta la vista Baby!

iniciará una máquina virtual Java invocando el método main of class Terminator(una clase en un paquete sin nombre) y pasándole una matriz que contiene las cuatro cadenas "Hasta", "la", "vista" y "Baby!" ...

... vea también: Apéndice: Necesito su ropa, sus botas y su motocicleta.

  • Mi interpretación:
    ejecución dirigida para su uso como scripts típicos en la interfaz de línea de comandos.

 

paso lateral importante

... eso ayuda a evitar un par de rastros falsos en nuestra investigación.

VM Spec, 1.2 La máquina virtual Java

La máquina virtual Java no sabe nada del lenguaje de programación Java ...

Noté lo anterior al estudiar el capítulo anterior - 1.1 Historia que pensé que podría ser útil (pero resultó inútil).

  • Mi interpretación: la
    ejecución se rige solo por las especificaciones de VM, que
    declara explícitamente que no tiene nada que ver con el lenguaje Java
    => OK para ignorar JLS y cualquier cosa relacionada con el lenguaje Java

 

Gosling: un compromiso entre C y el lenguaje de script ...

Basado en lo anterior, comencé a buscar en la web el historial de JVM . No ayudó, demasiada basura en los resultados.

Luego, recordé las leyendas sobre Gosling y reduje mi búsqueda a la historia de Gosling JVM .

Eureka! Cómo surgió la especificación JVM

En esta nota clave de la Cumbre de idiomas JVM 2008, James Gosling analiza ... la creación de Java, ... un compromiso entre C y el lenguaje de secuencias de comandos ...

  • Mi interpretación:
    declaración explícita de que en el momento de la creación,
    C y las secuencias de comandos se han considerado las influencias más importantes.
     
    Ya visto un guiño a las secuencias de comandos en VM Spec 2.17.1, los
    argumentos de la línea de comandos explican lo suficiente String[] args
    pero, staticy mainaún no están allí, necesitan profundizar más ...

Tenga en cuenta que mientras escribo esto, conectando C, scripting y VM Spec 1.2 con su nada de Java, siento que algo familiar, algo ... orientado a objetos está desapareciendo lentamente. Toma mi mano y sigue moviéndote No disminuyas la velocidad, ya casi estamos allí

Las diapositivas de Keynote están disponibles en línea: 20_Gosling_keynote.pdf , bastante conveniente para copiar puntos clave.

    página 3

        La prehistoria de Java
        * ¿Qué formó mi pensamiento?

    página 9

        Noticias
        * Sistema de ventana extensible en red
        * Un sistema de ventanas basado en secuencias de comandos ...
          PostScript (!!)

    página 16

        Un gran (pero tranquilo) objetivo:
          ¿Qué tan cerca podría llegar a un
          sensación de "guión" ...

    página 19

        El concepto original
        * Se trataba de construir
          redes de cosas,
          orquestado por una secuencia de comandos
          idioma
        * (Shells de Unix, AppleScript, ...)

    página 20

        Un lobo con ropa de oveja
        * Sintaxis C para hacer desarrolladores
          cómodo

A-ha! Veamos más de cerca a la sintaxis C .

El ejemplo de "hola, mundo" ...

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

... se está definiendo una función llamada main. La función principal tiene un propósito especial en los programas en C; El entorno de tiempo de ejecución llama a la función principal para comenzar la ejecución del programa.

... La función principal en realidad tiene dos argumentos int argcy char *argv[], respectivamente, que se pueden usar para manejar argumentos de línea de comando ...

¿Nos estamos acercando? usted apuesta También vale la pena seguir el enlace "principal" de la cita anterior:

La función principal es donde un programa inicia la ejecución. Es responsable de la organización de alto nivel de la funcionalidad del programa y, por lo general, tiene acceso a los argumentos de comando dados al programa cuando se ejecutó.

  • Mi interpretación:
    para estar cómodo con el desarrollador de C, el punto de entrada del programa debe serlo main.
    Además, dado que Java requiere que cualquier método esté en clase, Class.maines
    lo
    más parecido posible : invocación estática, solo nombre de clase y punto, sin constructores, por favor. C no sabe nada de eso.
     
    Esto también se aplica de forma transitiva a C #, teniendo en cuenta
    la idea de una fácil migración desde Java.

Los lectores que piensan que el punto de entrada del programa familiar no importa están amablemente invitados a buscar y verificar las preguntas de desbordamiento de pila en las que los hombres que vienen de Java SE intentan escribir Hello World para Java ME MIDP. Nota El punto de entrada MIDP no tiene mainni static.

 

Conclusión

Basado en lo anterior, diría eso static, mainy String[] argsen los momentos de creación de Java y C #, las opciones más razonables para definir el punto de entrada del programa .

 

Apéndice: necesito tu ropa, tus botas y tu motocicleta

Debo admitir que leer VM Spec 2.17.1 fue muy divertido.

... la línea de comando

java Terminator Hasta la vista Baby!

iniciará una máquina virtual Java invocando el método main of class Terminator(una clase en un paquete sin nombre) y pasándole una matriz que contiene las cuatro cadenas "Hasta", "la", "vista" y "Baby!".

Ahora describimos los pasos que la máquina virtual puede seguir para ejecutar Terminator, como un ejemplo de los procesos de carga, vinculación e inicialización que se describen más adelante en secciones posteriores.

El intento inicial ... descubre que la clase Terminatorno está cargada ...

Después de Terminatorcargarse, debe inicializarse antes de que se pueda invocar main, y un tipo (clase o interfaz) siempre debe vincularse antes de inicializarse. La vinculación (§2.17.3) implica verificación, preparación y (opcionalmente) resolución ...

La verificación (§2.17.3) verifica que la representación cargada de Terminatoresté bien formada ...

La resolución (§2.17.3) es el proceso de verificar referencias simbólicas de la clase Terminator...

 
Referencias simbólicas de Terminatoroh sí.

mosquito
fuente
2
Por alguna razón, me fue difícil creer que "modernidad" era una palabra real.
someguy
@Songo la historia de la respuesta también es como una película. Se publicó por primera vez en meta , en una discusión sobre el cierre de la pregunta: "Si la pregunta se reabre, probablemente escribiría una respuesta como a continuación ..." Luego se usó para respaldar la apelación para reabrir y finalmente se mudó aquí
mosquito
16

Eso se siente vagamente abusivo para mí. Un constructor se utiliza para la inicialización de un objeto: configura un objeto, que luego es utilizado por el código que lo creó.

Si coloca la funcionalidad de uso básica dentro del constructor, y luego nunca usa el objeto que el constructor crea en código externo, entonces está violando los principios de OOP. Básicamente, hacer algo realmente extraño sin razón aparente.

¿Por qué querrías hacer eso de todos modos?

Mason Wheeler
fuente
55
¿Pero no es la "instancia de aplicación" lógicamente un objeto? ¿Por qué sería abusivo? En cuanto al uso del objeto, tiene un propósito: representar la aplicación en ejecución. Sonidos muy SoC -y para mí. "¿Por qué querrías hacer eso?" - Simplemente estoy interesado en la justificación de la decisión, ya que la encuentro en desacuerdo con el resto de la mentalidad.
Konrad Rudolph
77
@KonradRudolph: generalmente se espera que un constructor, como un captador de propiedades, se complete en un tiempo limitado sin esperar a que ocurra algún evento asincrónico (como la entrada del usuario). Sería posible tener un constructor que lanzó un hilo principal de la aplicación, pero eso agrega un nivel de complejidad que puede no ser necesario para todas las aplicaciones. Exigir que una aplicación de consola que simplemente imprime "Hola mundo" a la salida estándar genere un hilo adicional sería una tontería. El uso de un Mainmétodo funciona bien para el caso simple, y no es realmente un problema en casos más difíciles, entonces, ¿por qué no?
supercat
9

Para Java, creo que el razonamiento es simple: al desarrollar Java, los desarrolladores sabían que la mayoría de las personas que aprenden el lenguaje sabrían C / C ++ de antemano.

Por lo tanto, Java no solo se parece mucho a C / C ++ en lugar de decir smalltalk, sino que también se hizo cargo de las idiosincrasias de C / C ++ (solo piense en literales enteros octales). Dado que c / c ++ utilizan un método principal, hacer lo mismo para Java tiene sentido desde ese punto de vista.

Estoy bastante seguro de que recuerdo que Bloch o alguien dijo algo en este sentido sobre por qué agregaron literales enteros octales, veré si puedo encontrar algunas fuentes :)

Voo
fuente
2
Si busca lo mismo que C ++ fue tan importante para Java, ¿por qué, por ejemplo, el cambio :a extends? Y public static void main(String [ ] args)dentro de una clase es bastante diferente que int main(int argc, char **argv)fuera de una clase.
svick
2
@svick Una posibilidad: Java introdujo interfaces y claramente querían separar los dos conceptos (heredar interfaces / clases), con solo una "palabra clave" que no funcionaría. ¿Y "bastante diferente"? Es el mapeo más cercano posible y hasta ahora nunca he visto que un programador de c ++ tenga problemas para entender que el método principal estático es el punto de entrada. Contrariamente a que tener una clase llamada Aplicación o algo cuyo constructor se use, es algo que parecería extraño para la mayoría de los programadores de c ++.
Voo
@svick int en c para anular en java tuvo que ver con cómo se generó un código de retorno de una aplicación: en java, es 0 a menos que se invoque System.exit (int). El cambio de parámetros tiene que ver con cómo se pasan las matrices de cadenas en cada idioma. Todo en Java está en una clase, no hay opción de tenerlo en otro lugar. Cambiar :a extendses una cuestión de sintaxis y son esencialmente lo mismo. Todo lo demás está dictado por el idioma.
@MichaelT Pero todas esas son decisiones de diseño que hacen que Java sea diferente de C ++. Entonces, ¿por qué sería importante mantener Java igual que C ++ en el caso de main(), cuando aparentemente no fue lo suficientemente importante en otros casos?
svick
@svick Excepto que está perfectamente bien no devolver nada de main en C también y tales trivialidades difícilmente confundirían a alguien de todos modos. El punto no era recrear c ++ y todos sus errores, sino solo hacer que el programador estuviera más en casa. ¿Qué crees que un programador de C ++ tendrá más facilidad para leer: Java o código Objective-C? ¿Qué crees que parecerá más obvio para un programador de C ++, un método principal o un constructor de alguna clase como punto de entrada?
Voo
6

Bueno, hay muchas funciones principales que solo ejecutan un bucle infinito. Un constructor que trabaja de esta manera (con un objeto que nunca se construye) es lo que me parece extraño.

Hay muchas cosas divertidas sobre este concepto. Su lógica se ejecuta sobre un objeto no nacido, objetos que nacen para morir (ya que hacen todo su trabajo en el constructor), ...

¿No todos estos efectos secundarios corromperían mucho más el vagón OO que un simple público (porque necesita ser accedido por un desconocido) estático (porque no se necesita ninguna instancia para comenzar) void main (porque es el punto de entrada )?

Para que exista un punto de entrada de función simple y simple en Java, se requerirá automáticamente public y static. Aunque es un método estático , se reduce a lo que podemos acercarnos más a una función simple para lograr lo que se desea: un simple punto de entrada.

Si no va a adoptar un punto de entrada de función simple y llano como punto de entrada. ¿Qué sigue después que no parece extraño como un constructor que no está destinado a construir?

pepper_chico
fuente
1
Yo diría que el problema no era tener funciones de primera clase. Pegar el main () dentro de un objeto (que no se instancia antes de que se llame al main) es un poco anti-patrón. Tal vez deberían tener un objeto de "aplicación" que se construya y ejecute su método main () no estático, luego puede poner la inicialización de inicio en el constructor, y se sentiría mucho mejor que tener métodos estáticos, aunque un simple = nivel main () fn también sería bueno. La estática principal es un poco difícil de entender en general.
gbjbaanb
3

Puede ejecutar rápidamente algunas pruebas independientes en una clase, durante el desarrollo, al pegar una main()en la clase que está tratando de probar.

Graham Borland
fuente
1
Esto para mí es probablemente la razón más convincente, ya que también permite múltiples puntos de entrada durante el desarrollo para probar diferentes configuraciones, etc.
cgull
0

Tienes que empezar por alguna parte. Un entorno estático principal es el entorno de ejecución más simple que puede tener, no es necesario crear ninguna instancia (fuera de la JVM y los parámetros de cadena simples), por lo que puede "generar" un mínimo de alboroto (y baja probabilidad de un error de codificación que impide el inicio, etc.) y puede hacer cosas simples sin mucha otra configuración.

Básicamente una aplicación de KISS.

[Y, por supuesto, la razón principal es: ¿por qué no?]

Daniel R Hicks
fuente
3
Como dije, eso no me convence en absoluto. Objetos no consiguen crear instancias antes, y código se ejecutan antes. Necesitaría una cita de uno de los desarrolladores originales para convencerme de que esta era la razón.
Konrad Rudolph
2
La cantidad de trabajo necesaria para crear una instancia de una clase del código C es prácticamente idéntica a la de llamar a un método estático. Incluso tiene que hacer las mismas verificaciones (¿existe la clase? Bien, ¿tiene un constructor público con la firma correcta? bien, entonces adelante).
Voo
No se necesita crear ningún objeto de usuario . El constructor de objetos no se ejecuta. La API es increíblemente simple. Y es lo más fácil de entender.
Daniel R Hicks
0

A mi entender, la razón principal es simple. Sun era una compañía de Unix que vendía máquinas Unix y para eso se diseñó la convención C "main (args)" para invocar un binario .

Además, Java se diseñó explícitamente para que fuera fácil de aprender para los programadores de C y C ++, por lo que no había una buena razón para no simplemente elegir la convención de C.

El enfoque elegido donde cada clase puede tener un método de invocación es bastante flexible, especialmente en combinación con la Main-Classlínea en el archivo MANIFEST.MF en un jar ejecutable.

mosquito
fuente
Por supuesto, el archivo jar no fue inventado hasta mucho más tarde.
Daniel R Hicks
-1

No es coherente con la filosofía OOP que un programa sea un objeto desde el punto de vista del proceso del sistema operativo, ya que no hay forma de tener más de uno por definición.

Además de eso, un constructor no es un punto de entrada de ninguna manera.

Me parece la opción más razonable para tener main como una función estática, que en realidad es al final del día. Dada la arquitectura de las máquinas virtuales como JVM y CLR, cualquier otra opción sería empujarla innecesariamente.

Ñame Marcovic
fuente
1
Creo que te equivocas allí. Se es posible tener más de un proceso, por lo tanto, más de un objeto. Por cierto, esto es completamente equivalente a crear instancias de Runnableobjetos para que tengan múltiples hilos.
Konrad Rudolph
Un proceso es un programa en ejecución, y solo puede iniciar un proceso una vez a través de un punto de entrada. Los hilos tienen sus propios puntos de entrada pero todavía están dentro del mismo proceso.
Yam Marcovic
1
Como dije debajo de la respuesta de alguien, esto no es relevante. Lo relevante es la coherencia lógica . Lógicamente, los procesos son representados como objetos por el lanzador (OS, JVM, lo que sea) y se inicializan.
Konrad Rudolph
@KonradRudolph Verdadero, pero la inicialización de un programa es solo una parte de la inicialización de un proceso y no legitima un constructor de programas.
Yam Marcovic