¿Por qué Java tiene métodos `void`?

51

¿Tiene / por qué Java necesita tener voidmétodos? Referencia :

Cualquier método declarado vacío no devuelve un valor.

Hasta donde puedo pensar, cada uso de voidsería mejor al devolver un indicador de estado, el objeto que se invoca, o null.

Esto haría que cada llamada sea una declaración asignable y facilitaría los patrones de creación y el encadenamiento de métodos. Los métodos que solo se invocan por sus efectos generalmente devolverían un tipo booleano o genérico Successo arrojarían una excepción en caso de falla.

marisbest2
fuente
Para que no necesitemos una sintaxis especial para escribir subrutinas. Podemos usar las mismas funciones que usamos.
candied_orange
Relevante
jpmc26

Respuestas:

158

Debido a que existe una diferencia entre "Esta función puede tener éxito o fallar y es lo suficientemente consciente de sí misma como para poder notar la diferencia" y "No hay comentarios sobre el efecto de esta función". Sin esto void, verificaría infinitamente los códigos de éxito y creería que está escribiendo un software robusto, cuando en realidad no está haciendo nada por el estilo.

Kilian Foth
fuente
10
Pero si tuvo éxito o fracasó es algo para expresar lanzando o no lanzando una excepción, no devolviendo una bandera. Y regresar voidno dice nada sobre si el método o la función es lo suficientemente consciente de sí mismo como para lanzarlo según corresponda.
Volker Siegel
12
@VolkerSiegel: El contexto de la respuesta es la pregunta. El OP sugirió que en todos los casos en los que se usa void, la función debería devolver un indicador de estado que indica el éxito del fallo. En ese contexto, volver vacío no dice que la función literalmente no tiene nada que devolver. Obligar a dicha función a que siempre devuelva 0 para indicar el éxito dará a los usuarios de la función una falsa confianza de que están verificando errores cuando en realidad el valor de retorno siempre es 0, independientemente del éxito o el fracaso.
slebetman
19
@VolkerSiegel Hay una gran cantidad de situaciones en las que las excepciones no son la forma correcta de hacer las cosas. De hecho, diría que es más raro que una excepción sea correcta que incorrecta: una excepción significa que sucedió algo horrible que debe tenerse en cuenta. Un caso de falla esperado nunca debería usar una excepción.
Gabe Sechan
35
@GabeSechan No, se deben lanzar excepciones si el método no puede cumplir su contrato. Si el método requiere una referencia no nula pero la obtiene, es una falla esperada y debería arrojarse.
Andy
55
Hay muchas sintaxis alternativas para arreglar eso. La razón por la que eligieron esta solución (incómoda) es porque C la emplea.
Marco van de Voort
95
  1. Porque C tiene un voidtipo, y Java fue diseñado para seguir muchas de las convenciones de la familia de lenguaje C.
  2. Hay muchas funciones que no desea que devuelvan un valor. ¿Qué vas a hacer con "un Successtipo genérico " de todos modos? De hecho, los valores de retorno para indicar el éxito son incluso menos importantes en Java que en C, porque Java tiene excepciones para indicar fallas y C no.
Mason Wheeler
fuente
8
> "¿Qué vas a hacer con" un tipo de éxito genérico "de todos modos?" - al escribir código genérico, un tipo de valor único (se llama Unit type btw) es muy útil porque elimina el caso especial de funciones que "simplemente devuelven" pero no devuelven nada en particular. Pero cuando se creó Java por primera vez, tenía un sistema de tipos aún menos expresivo (sin parámetros de tipo), por lo que no había mucha diferencia práctica. Y ahora es demasiado tarde para cambiar. Los lenguajes más modernos en realidad evitan el "tipo" vacío (en realidad ni siquiera es un tipo, solo un marcador especial para los métodos) y usan el tipo de Unidad en su lugar.
Sarge Borsch
99
@SargeBorsch El Voidtipo de Java actúa como un tipo de Unidad (por ejemplo, para genéricos), aunque lamentablemente, es diferente de void(nota al margen: un gatito adorable muere cada vez que inventa un nuevo tipo para arreglar el tipo que estropeó en la versión anterior). Si alguien no está familiarizado con los tipos de unidades, esta es una introducción decente: en.wikipedia.org/wiki/Unit_type
Ogre Psalm33
1
@ OgrePsalm33 realmente no actúa como un tipo de Unidad (al menos en medios prácticos; uno todavía tiene que escribir "return null"; para regresar del método que tiene el tipo de retorno Void, y el compilador AFAIK asigna espacio en la pila para referencias Void , sin aprovechar el hecho de que no tiene sentido leer estos valores), es simplemente una convención usarlo donde el tipo de Unidad "apropiado" sería más apropiado.
Sarge Borsch
3
@immibis porque el tipo de Unidad por definición debe tener exactamente un valor, y Void no tiene valores. Sí, es posible usarlo null(de hecho, esta es la única opción), pero nulo es algo especial, cualquier valor de tipo de referencia en Java puede ser nulo (este es otro error en el diseño del lenguaje). Y, como dije antes, si está utilizando el Voidtipo de retorno en lugar de voidmark, el compilador de Java no es su amigo.
Sarge Borsch
55
@SargeBorsch Void tiene exactamente un valor, que es null. El hecho de que nullsea ​​un miembro de la mayoría de los tipos es irrelevante para saber si Voides un tipo de unidad.
user253751
65

La razón original por la cual el lenguaje tiene un voidtipo es porque, como en C, los creadores del lenguaje no querían complicar innecesariamente la sintaxis del lenguaje con procedimientos y funciones como lo hizo Pascal.

Esa fue la razón original.

Tus sugerencias:

devolver una bandera de estado

Eso es un no-no. No utilizamos banderas de estado. Si algo sale mal, lo informamos a través de excepciones.

el objeto que se invoca

El estilo fluido de las invocaciones es un desarrollo reciente. Muchos de los programadores que usan muy bien el fluido hoy y ponen los ojos en blanco si alguna vez tienen que usar una interfaz que no lo admite, ni siquiera nacieron cuando se creó Java.

volviendo nulo

Eso lo obligaría a declarar que un método devuelve algo, cuando de hecho no devuelve nada, por lo que sería muy confuso para alguien que está mirando una interfaz tratando de descubrir qué hace. La gente inevitablemente inventaría alguna clase inútil que significa "sin valor de retorno" para que todas las funciones que no tienen nada que devolver puedan devolver una referencia nula a dicha clase. Eso sería torpe. Afortunadamente, hay una solución para esto, se llama void. Y esa es la respuesta a tu pregunta.

Mike Nakis
fuente
3
Proporcione una fuente para "La razón original ...". Que yo sepa, la verdadera razón es porque C fue diseñado para ser ensamblador portátil.
Thorbjørn Ravn Andersen
66
@ ThorbjørnRavnAndersen, ¿realmente me está pidiendo que proporcione una fuente para la afirmación de que la sintaxis de Java se deriva de la sintaxis C?
Mike Nakis
3
@ ThorbjørnRavnAndersen oh, ya veo, no entendí bien. Entonces, quieres una fuente para mi reclamo sobre C, no sobre Java. Bien, lo siento, no tengo fuente para eso. Pero creo que es bastante obvio. (Solía ​​programar en Pascal en 1987, cuando me cambié a C. Dios mío, eso fue hace 30 años.)
Mike Nakis
66
No es obvio C fue desarrollado aproximadamente al mismo tiempo que pascal por personas que probablemente ni siquiera sabían que existía. Por favor no declare los supuestos como hechos.
Thorbjørn Ravn Andersen
12
La verdadera razón original era que, por defecto, las funciones de C regresaban int, por lo que se agregaba una palabra clave después de K&R para indicar 'tipo de no retorno'.
user207421
20

Algunos métodos, como System.out.printlnno devuelve nada útil, pero se llama únicamente por este efecto secundario. voides un indicador útil para el compilador y para el lector de código de que no se devuelve ningún valor útil.

Devolver en nulllugar de voidsignifica que solo obtendría NullPointerExceptionel momento en que use este valor para cualquier cosa. Por lo tanto, intercambia un error de tiempo de compilación por un error de tiempo de ejecución que es mucho peor. Además, tendría que definir el tipo de retorno como Object, lo que sería engañoso y confuso. (Y el encadenamiento aún no funcionaría).

Los códigos de estado generalmente se usan para indicar condiciones de error, pero Java tiene excepciones para este propósito.

thisNo es posible regresar de métodos estáticos.

Devolver un Successobjeto genérico no tendría ningún propósito útil.

JacquesB
fuente
1
Totalmente de acuerdo con usted, la respuesta más simple y concisa.
webo80
11

Una de las razones es que puede ser engañoso para volver jamás otra que, digamos nada, null. Ejemplo:

¿Qué debería Arrays.sort(a)volver?

Si argumenta que adebe devolverse para poder encadenar la llamada (que parece ser su respuesta a juzgar por su pregunta), entonces ya no está claro si el valor devuelto es una copia del objeto original o el objeto original en sí . Ambos son posibles. Y sí, podría incluirlo en la documentación, pero es lo suficientemente ambiguo como para no crear la ambigüedad en primer lugar.

Por otro lado, si usted argumenta que nulldebería devolverse, eso plantea la pregunta de qué información posiblemente el valor de retorno está proporcionando al llamante, y por qué el programador debería verse obligado a escribir return nullcuando no transmite información.

Y si devuelve algo totalmente absurdo (como la longitud de a), entonces hace que usar ese valor de retorno sea realmente confuso, ¡solo piense cuánto más confuso es decir en int len = Arrays.sort(a)lugar de int len = A.length!

Mehrdad
fuente
1
Para ser justos, no se requeriría necesariamente que el programador escribiera return null;: la JVM también podría exigir que si el control se cae al final de una función por otro medio que no sea explícito returno una throwdeclaración, nullse devuelve implícitamente a la persona que llama. IIRC C hace eso, excepto que el valor de retorno no está definido en ese caso (será el valor que esté en la ubicación utilizada para el valor de retorno en ese momento).
un CVn
2
La respuesta correcta a su pregunta en negrita es "una matriz ordenada".
Bryan Boettcher
2
@BryanBoettcher: ¿No leíste el resto?
Mehrdad
1
@Michael Kjörling: es una propiedad fundamental del lenguaje Java para evitar tales errores, es decir, no tener un "comportamiento indefinido" si olvida una returndeclaración. En cambio, aparece un error del compilador que le informa sobre su error. Insertar un implícito return null;sería mejor que el "comportamiento indefinido", pero no mucho. Eso sería útil solo cuando el tipo de retorno no tiene significado, pero ¿de dónde saca el compilador la conclusión de que el valor de retorno no tiene significado, cuando no lo es void?
Holger
2
@ MichaelKjörling: "Si returnrealmente no agrega ningún valor ...", bueno, como se dijo, no hay ningún indicador para el compilador de que una devolución no agregaría ningún valor, si no hay ningún voidtipo. En realidad es lo contrario, la primera suposición es que un método que declara un tipo de retorno quiere returnalgo útil de ese tipo. Y aún no hablamos sobre los tipos primitivos, que no tienen un nullvalor, por lo tanto, cualquier valor predeterminado inyectado por el compilador entraría en conflicto con el rango de valores de retorno potencialmente útiles.
Holger
7

Devolver un valor booleanque siempre es igual al trueque sugiere, no solo no tiene sentido (el valor de retorno no contiene información), sino que en realidad sería engañoso. La mayoría de las veces, es mejor usar tipos de retorno que solo contengan tanta información como esté realmente disponible; es por eso que en Java tiene booleanpara true/ en falselugar de devolver un intcon 4 mil millones de valores posibles. Del mismo modo, devolver a booleancon dos valores posibles donde solo hay un valor posible ("éxito genérico"), sería engañoso.

También agregaría una sobrecarga de rendimiento innecesaria.

Si un método se usa solo por su efecto secundario, no hay nada que devolver, y el tipo de retorno voidrefleja exactamente eso. Los posibles errores raros se pueden señalar mediante excepciones.

Una cosa que podría mejorarse sería crear voidun tipo real, como el de Scala Unit. Esto resolvería algunos problemas, como el manejo de genéricos.

Michał Kosmulski
fuente
Dato curioso: List.addsiempre regresa true, pero eso es por ser compatible con el contrato más amplio de Collection.add, por lo que Set.addpuede regresar trueo false.
Holger
4

Java es un lenguaje relativamente antiguo. Y en 1995 (cuando se creó) y poco después, los programadores estaban muy preocupados por la cantidad de tiempo que un proceso tomó el control del procesador, así como el consumo de memoria. Devolver el vacío eliminaría algunos ciclos de reloj y un poco de consumo de memoria de una llamada a función porque no tendría que poner el valor de retorno en la pila, y no tendría que eliminarlo posteriormente.

El código eficiente no le devuelve algo que nunca usaría y, por lo tanto, anular algo que tiene un valor de retorno sin sentido es una práctica mucho mejor en lugar de devolver un valor de éxito.

El codificador perezoso
fuente
14
los programadores que estaban muy preocupados por la velocidad no usarían Java en estos días de todos modos, porque las primeras implementaciones de Java fueron notoriamente lentas. Tan lento que muchas personas que actualmente no trabajan con él todavía tienen la impresión de que Java es sinónimo de gordo y lento.
Sarge Borsch
8
@SargeBorsch me recuerda una vieja broma de programación de hace 20 años. "TOC Toc." "¿Quién está ahí?" ... ... ... ... pausa muy larga ... ... ... "Java"
Dan Neely
44
@DanNeely y algún tiempo después, un automóvil impulsado por IA se estrella en una pared a toda velocidad, sin siquiera tratar de usar los frenos. Fue escrito en Java. ¡Entonces, Java no frena ahora! (fue un juego de palabras en ruso, no puedo traducirlo al inglés muy bien)
Sarge Borsch
2
@SargeBorsch Lo que tienes funciona como un juego de palabras en inglés, incluso si pierde algunos matices en la traducción. Y traducir juegos de palabras es probablemente más difícil que la poesía.
Dan Neely
3
Los buenos programadores todavía están preocupados por el rendimiento y el consumo de memoria, lo que hace que pedir a las personas que devuelvan basura sea relevante incluso hoy.
Sklivvz
2

He leído argumentos que sugieren que la segunda opción (retorno this) debería ser el enfoque en lugar de void. Esta es una idea bastante buena, pero el enfoque de estilo de constructor no era popular en el momento en que se creó Java. Si era tan popular en ese momento como lo es ahora, ese podría haber sido el enfoque adoptado. Volver nulles una muy mala idea de la OMI. Desearía que nullni siquiera estuviera en el idioma.

El problema ahora es que si un método devuelve una instancia de su propio tipo, no está claro si es un objeto nuevo o el mismo. A menudo no importa (o no debería), pero otras veces sí importa. Supongo que el lenguaje podría cambiarse para devolver implícitamente los thismétodos nulos. El único problema que puedo pensar con eso en este momento es que si el método fue cambiado más tarde para devolver un nuevo objeto del mismo tipo, no habría advertencias de compilación, pero tal vez eso no sea gran cosa. El sistema de tipos actual no se preocupa por estos tipos de cambios ahora. La forma en que esto interactúa con la herencia / interfaces necesitaría cierta consideración, pero permitiría que las API antiguas sin un estilo de generador se llamaran fácilmente como si lo hicieran.

JimmyJames
fuente
2
@Cody Buen punto, esto no se aplicaría a los métodos estáticos.
JimmyJames
14
@ marisbest2 ¿Qué argumento?
8bittree
3
Bueno, si nullno fuera parte del lenguaje, las personas estarían usando todo tipo de valores centinela que significan "ignore este argumento / valor de retorno, no contiene ningún valor sensible". El problema con nullno es la cosa en sí, solo que tiende a usarse incorrectamente. Apostaría a que este problema sólo sería exagerado si el estandarizada nullsería reemplazado con una gran variedad de soluciones a medida, donde cada aplicación se comportaría de manera diferente en silencio como ignorar el uso, o lanzar excepciones especiales en lugar de una excepción de puntero nulo standart, etc
cmaster
2
@cmaster un reemplazo obvio para nulles un tipo opcional adecuado (como, Maybeen Haskell). El problema con los valores nulos en Java es que no hay una forma sensata de decidir de inmediato si un valor es anulable en la práctica o no. Esto lleva a ambos: programación innecesariamente defensiva (verificar nulos donde no tiene sentido, lo que aumenta el porcentaje de popó en el código) y NPE accidentales en producción (si olvidó verificar dónde era necesario). Sí, se resuelve parcialmente mediante anotaciones, pero son opcionales, no siempre se verifican, etc. Por lo tanto, no lo salvarán en los límites con código de terceros.
Sarge Borsch
2
@SargeBorsch Estoy de acuerdo con eso :-) Mi objeción es contra un idioma sin una forma estándar de expresar un "por favor ignore este valor". nullfunciona bien para esto (fine = simple), pero los valores opcionales estandarizados con semántica sólida son definitivamente otra buena opción (fine = safe).
cmaster
2

Otro aspecto:

Java es un lenguaje estático con características bastante estrictas. Esto significa que muchas cosas que serían bastante abiertas o dinámicas en otros idiomas (c / f Ruby, Lisp, etc.) están estrictamente determinadas.

Esta es una decisión general de diseño. El "por qué" es difícil de responder (bueno, ¡porque los diseñadores del lenguaje pensaron que sería bueno!). El "para qué" es bastante claro: permite que el compilador detecte muchos errores, que generalmente es una característica bastante buena para cualquier idioma. En segundo lugar, hace que sea relativamente fácil razonar sobre el idioma. Por ejemplo, es relativamente fácil crear pruebas formales de corrección en (subconjuntos de) el lenguaje Java; en comparación, eso sería prácticamente imposible en un lenguaje dinámico como Ruby et al.

Este pensamiento impregna el lenguaje, por ejemplo, la declaración forzada de posibles excepciones que puede arrojar un método, el tipo separado de interfacevs. classpara evitar una herencia múltiple ambigua, y así sucesivamente. Por lo que es (un lenguaje del mundo real imperativo estático OOP con un fuerte enfoque en el manejo de errores en tiempo de compilación), esas cosas son en realidad bastante elegantes y poderosas. Se acercan más a los lenguajes teóricos (científicos) que están hechos a propósito solo para explorar algunos de estos problemas que cualquier otro lenguaje del mundo real antes (en ese momento, eso sí).

Entonces. Tener un voidtipo estricto es un mensaje claro: este método no devuelve nada, punto. Es lo que es. Reemplazarlo por la aplicación para que siempre devuelva algo conduciría a un comportamiento mucho más dinámico (como en Ruby, donde cada def tiene un valor de retorno explícito o implícito, siempre), lo que sería malo para la demostrabilidad y el razonamiento; o para una hinchazón masiva usando algún otro mecanismo aquí.

(Y NB, Ruby (por ejemplo) maneja esto de manera diferente, y su solución sigue siendo exactamente tan aceptable como la de Java, porque tiene una filosofía completamente diferente. Por ejemplo, arroja completamente la probabilidad y la razonabilidad por completo mientras pone un gran enfoque en extremadamente alta expresividad del lenguaje.)

AnoE
fuente