¿Por qué Java no permite que las definiciones de funciones estén presentes fuera de la clase?


22

A diferencia de C ++, en Java, no podemos tener solo declaraciones de funciones en la clase y definiciones fuera de la clase. ¿Por que es esto entonces?

¿Es para enfatizar que un solo archivo en Java debe contener solo una clase y nada más?



1
Por definiciones, ¿se refiere a propiedades o firmas de métodos de los archivos de encabezado?
Deco el

Una buena respuesta satírica a su pregunta: steve-yegge.blogspot.com/2006/03/…
Brandon

Respuestas:


33

La diferencia entre C ++ y Java está en lo que los lenguajes consideran su unidad de enlace más pequeña.

Debido a que C fue diseñado para coexistir con el ensamblaje, esa unidad es la subrutina llamada por una dirección. (Esto es cierto para otros idiomas que se compilan en archivos de objetos nativos, como FORTRAN). En otras palabras, un archivo de objetos que contiene una función foo()tendrá un símbolo llamado _fooque se resolverá en una dirección como0xdeadbeefdurante el enlace. Eso es todo lo que hay. Si la función es tomar argumentos, depende de la persona que llama asegurarse de que todo lo que la función espera esté en orden antes de llamar a su dirección. Normalmente, esto se hace apilando cosas en la pila, y el compilador se encarga del trabajo duro y se asegura de que los prototipos coincidan. No hay comprobación de esto entre archivos de objeto; Si subes el enlace de la llamada, la llamada no saldrá según lo planeado y no recibirás una advertencia al respecto. A pesar del peligro, esto hace posible que los archivos de objetos compilados de varios idiomas (incluido el ensamblado) se vinculen entre sí en un programa funcional sin mucho alboroto.

C ++, a pesar de toda su fantasía adicional, funciona de la misma manera. El compilador calza espacios de nombres, clases y métodos / miembros / etc. dentro de esta convención al acoplar el contenido de las clases en nombres únicos que se separan de una manera que los hace únicos. Por ejemplo, un método como Foo::bar(int baz)podría ser destruido _ZN4Foo4barEicuando se coloca en un archivo de objeto y una dirección como 0xBADCAFEen tiempo de ejecución. Esto depende completamente del compilador, por lo que si intenta vincular dos objetos que tienen diferentes esquemas de manipulación, no tendrá suerte. Por muy feo que sea, significa que puede usar un extern "C"bloque para deshabilitar la manipulación, lo que hace posible que el código C ++ sea fácilmente accesible a otros idiomas. C ++ heredó la noción de funciones de libre flotación de C, en gran parte porque el formato de objeto nativo lo permite.

Java es una bestia diferente que vive en un mundo aislado con su propio formato de archivo de objeto, el .classarchivo. Los archivos de clase contienen una gran cantidad de información sobre su contenido que permite al entorno hacer cosas con clases en tiempo de ejecución con las que el mecanismo de enlace nativo ni siquiera podía soñar. Esa información tiene que comenzar en alguna parte, y ese punto de partida es elclass. La información disponible permite que el código compilado se describa a sí mismo sin la necesidad de archivos separados que contengan una descripción en el código fuente como lo haría en C, C ++ u otros lenguajes. Eso le brinda todos los lenguajes de beneficios de seguridad de tipo que usan la falta de vinculación nativa, incluso en tiempo de ejecución, y es lo que le permite extraer una clase arbitraria de un archivo usando la reflexión y usarla con una falla garantizada si algo no coincide .

Si aún no lo ha descubierto, toda esta seguridad viene con una compensación: todo lo que enlace a un programa Java debe ser Java. (Por "enlace", quiero decir que cada vez que algo en un archivo de clase se refiere a algo en otro). Puede enlazar (en el sentido nativo) al código nativo usando JNI , pero hay un contrato implícito que dice que si rompe el lado nativo Eres dueño de ambas piezas.

Java era grande y no particularmente rápido en el hardware disponible cuando se introdujo por primera vez, al igual que Ada había sido en la década anterior. Solo Jim Gosling puede decir con certeza cuáles fueron sus motivaciones al hacer la unidad de vinculación más pequeña de la clase Java, pero tendría que adivinar que la complejidad adicional que agregaría flotadores libres podría haber sido un factor decisivo.


enlace roto, obtener enlace de archivo web
noɥʇʎԀʎzɐɹƆ

14

Creo que la respuesta es, según Wikipedia, que Java fue diseñado para ser simple y orientado a objetos. Las funciones están destinadas a operar en las clases en las que están definidas. Con esa línea de pensamiento, tener funciones fuera de una clase no tiene sentido. Voy a llegar a la conclusión de que Java no lo permite porque no encajaba con OOP puro.

Una búsqueda rápida en Google para mí no dio mucho en motivaciones de diseño de lenguaje Java.


66
¿La existencia de tipos primitivos no excluye a Java de ser "OOP puro"?
Radu Murzea

55
@SoboLAN: Sí, y agregar funciones lo haría aún menos "POO puro".
Giorgio

8
@SoboLAN que depende de su definición de "POO puro". En algún momento todo es una combinación de bits en la memoria del ordenador después de todo ...
jwenting

3
@jwenting: Bueno, el propósito de la abstracción en los lenguajes de programación es ocultar los bits subyacentes tanto como sea posible. Cuanto más pueda ver esos bits en su programa, más debería comenzar a pensar que su lenguaje de programación ofrece abstracciones permeables (a menos que esté construido cerca del metal a propósito). Entonces, sí, todo se reduce a manipular bits, pero los diferentes lenguajes ofrecen diferentes niveles de abstracción, de lo contrario no sería posible distinguir el ensamblaje de un lenguaje de nivel superior.
Giorgio

2
Depende de su definición de "OOP puro": cada valor de datos es un objeto, y cada operación de datos es la invocación del método de resultado obtenido al pasar mensajes. Que yo sepa, no hay nada más.
Giorgio

11

La verdadera pregunta es ¿cuál sería el mérito de continuar haciendo las cosas de la manera C ++ y cuál fue el propósito original del archivo de encabezado? La respuesta breve es que el estilo del archivo de encabezado permitió tiempos de compilación más rápidos en proyectos grandes en los que muchas clases podrían hacer referencia al mismo tipo. Esto no es necesario en JAVA y .NET debido a la naturaleza de los compiladores.

Vea esta respuesta aquí: ¿Son realmente buenos los archivos de encabezado?


1
+1, aunque he escuchado a personas decir que les gusta tener la separación entre la interfaz pública y la implementación privada que proporcionan los archivos de encabezado. Esas personas están equivocadas, por supuesto. ;)
vaughandroid

66
@Baqueta En Java puedes lograr esto con interfacey class:) ¡No necesitas encabezados!
Andres F.

1
Nunca entenderé la conveniencia de tener que mirar 3+ archivos para entender cómo funciona una instancia de clase simple.
Erik Reppen

@ErikReppen normalmente, la interfaz (o el archivo de encabezado en C) es lo que los clientes obtienen en forma legible por el usuario para escribir sus soluciones, el resto se proporciona solo en forma binaria (por supuesto, en Java, no hay necesidad de proporcionar las fuentes de las interfaces, archivos de clase y javadoc harán).
Jwenting

@jwenting No había pensado en ese caso. Estoy más acostumbrado a pensar en el próximo bastardo pobre que tiene que mantener esta tonta base de código, porque con demasiada frecuencia ese soy yo en el siguiente trabajo apuñalando mis ojos después de pasar horas mirando algún tipo de interfaz extraña y feliz sub / superclase arquitectura redonda.
Erik Reppen

3

Un archivo Java representa una clase. Si tuviera un procedimiento fuera de la clase, ¿cuál sería el alcance? ¿Sería global? ¿O pertenecería a la clase que representa el archivo Java?

Presumiblemente, lo pones en ese archivo Java en lugar de otro archivo por una razón, porque va con esa clase más que cualquier otra clase. Si un procedimiento fuera de una clase en realidad se asoció con esa clase, ¿por qué no forzarlo a ir dentro de esa clase a la que pertenece? Java maneja esto como un método estático dentro de la clase.

Si se permitiera un procedimiento de clase externa, presumiblemente no tendría acceso especial a la clase en cuyo archivo se declaró, limitando así a una función de utilidad que no cambia ningún dato.

El único inconveniente posible de esta limitación de Java es que si realmente tiene procedimientos globales que no están asociados con ninguna clase, termina creando una clase MyGlobals para contenerlos e importa esa clase en todos sus otros archivos que usan esos procedimientos .

De hecho, el mecanismo de importación de Java necesita esta restricción para funcionar. Con todas las API disponibles, el compilador de Java necesita saber exactamente qué compilar y contra qué compilar, por lo tanto, las declaraciones de importación explícitas en la parte superior del archivo. Sin tener que agrupar sus globales en una clase artificial, ¿cómo le diría al compilador de Java que compile sus globales y no todos y cada uno de los globales en su classpath? ¿Qué pasa con la colisión del espacio de nombres donde tienes un doStuff () y alguien más tiene un doStuff ()? No funcionaria. Obligarlo a especificar MyClass.doStuff () y YourClass.doStuff () soluciona estos problemas. Obligar a sus procedimientos a ir dentro de MyClass en lugar de afuera solo aclara esta restricción y no impone restricciones adicionales en su código.

Java se equivocó en varias cosas: la serialización tiene tantas pequeñas verrugas que es casi demasiado difícil ser útil (piense en SerialVersionUID). También se puede usar para romper singletons y otros patrones de diseño comunes. El método clone () en Object debe dividirse en deepClone () y shallowClone () y debe ser de tipo seguro. Todas las clases de API podrían haberse hecho inmutables por defecto (como están en Scala). Pero la restricción de que todos los procedimientos deben pertenecer a una clase es buena. Sirve principalmente para simplificar y aclarar el idioma y su código sin imponer restricciones onerosas.


3

Creo que la mayoría de las personas que respondieron y sus votantes han entendido mal la pregunta. Refleja que no conocen C ++.

"Definición" y "Declaración" son palabras con un significado muy específico en C ++.

El OP no significa cambiar la forma en que funciona Java. Esta es una pregunta puramente sobre la sintaxis. Creo que es una pregunta válida.

En C ++ hay dos formas de definir una función miembro.

La primera es la manera de Java. Simplemente coloque todo el código dentro de las llaves:

class Box {
public:
    // definition of member function
    void change(int newInt) { 
        this._m = newInt;
    }
private:
    int _m
}

Segunda forma:

class Box {
public:  
    // declaration of member function
    void change(int newInt); 
private:
    int _m
}

// definition of member function
// this can be in the same file as the declaration
void Box::change(int newInt) {
    this._m = newInt;
}

Ambos programas son iguales. La función changesigue siendo una función miembro: no existe fuera de la clase. Además, la definición de clase DEBE incluir los nombres y tipos de TODAS las funciones y variables miembro, al igual que en Java.

Jonathan Henson tiene razón en que este es un artefacto de la forma en que funcionan los encabezados en C ++: le permite colocar declaraciones en el archivo de encabezado e implementaciones en un archivo .cpp separado para que su programa no viole la ODR (regla de una definición). Pero tiene ventajas aparte de eso: le permite ver la interfaz de una clase grande de un vistazo.

En Java puede aproximar este efecto con clases o interfaces abstractas, pero no pueden tener el mismo nombre que la clase de implementación, lo que lo hace bastante torpe.


y eso muestra que ambos no entienden a las personas ni a Java. Puede usar el mismo nombre para la interfaz y la implementación, siempre que no estén en el mismo paquete.
Jwenting

Considero que el paquete (o espacio de nombres, o clase externa) es parte del nombre.
Erik van Velzen

2

Creo que es un artefacto del mecanismo de carga de clases. Cada archivo de clase es un contenedor para un objeto cargable. No hay lugar "fuera" de los archivos de clase.


1
No veo por qué organizar el código fuente en unidades separadas evitaría mantener el formato del archivo de clase tal como está.
Mat

Existe una correspondencia 1: 1 entre los archivos de clase y los archivos de origen, que es una de las mejores decisiones de diseño en el sistema general.
ddyer

@ddyer ¿Nunca has visto Foo $ 2 $ 1.clase entonces? (consulte los nombres de los archivos de clase de clase interna de Java )

¿Eso lo convertiría en un mapeo "sobre"? en cualquier caso, cada clase se genera compilando exactamente un archivo fuente, lo cual es una buena decisión de diseño.
ddyer

0

C #, que es muy similar a Java, tiene este tipo de característica mediante el uso de métodos parciales, excepto que los métodos parciales son exclusivamente privados.

Métodos parciales: http://msdn.microsoft.com/en-us/library/6b0scde8.aspx

Clases y métodos parciales: http://msdn.microsoft.com/en-us/library/wa80x488.aspx

No veo ninguna razón por la que Java no pueda hacer lo mismo, pero probablemente solo se deba a si existe una necesidad percibida de la base de usuarios para agregar esta función al lenguaje.

La mayoría de las herramientas de generación de código para C # generan clases parciales para que el desarrollador pueda agregar fácilmente código escrito manualmente a la clase en un archivo separado, si lo desea.


0

C ++ requiere que todo el texto de la clase se compile como parte de cada unidad de compilación que use cualquiera de sus miembros o produzca alguna instancia. La única forma de mantener cuerdos los tiempos de compilación es hacer que el texto de la clase contenga, en la medida de lo posible, solo aquellas cosas que son realmente necesarias para sus consumidores. El hecho de que los métodos C ++ a menudo se escriben fuera de las clases que los contienen es un truco vil realmente desagradable motivado por el hecho de que requerir que el compilador procese el texto de cada método de clase una vez por cada unidad de compilación donde se usa la clase conduciría a tiempos de construcción locos.

En Java, los archivos de clase compilados contienen, entre otras cosas, información que es en gran medida equivalente a un archivo C ++ .h. Los consumidores de la clase pueden extraer toda la información que necesitan de ese archivo, sin tener que hacer que el compilador procese el archivo .java. A diferencia de C ++, donde el archivo .h contiene información que está disponible tanto para las implementaciones como para los clientes de las clases que contiene, el flujo en Java se invierte: el archivo que utilizan los clientes no es un archivo fuente utilizado en la compilación de código de clase, pero en su lugar es producido por el compilador utilizando información del archivo de código de clase. Debido a que no es necesario dividir el código de clase entre un archivo que contiene información que los clientes necesitan y un archivo que contiene la implementación, Java no permite tal división.


-1

Creo que parte de esto es que Java es en gran medida un lenguaje proteccionista que se ocupa del uso en equipos grandes. Las clases no se pueden sobrescribir o redefinir. Tiene 4 niveles de modificadores de acceso que definen muy específicamente cómo los métodos pueden y no pueden usarse. Todo es fuerte / estáticamente tipado para proteger a los desarrolladores de la falta de coincidencia de tipos hijinx causada por otros o por ellos mismos. Tener clases y funciones como sus unidades más pequeñas hace que sea mucho más fácil reinventar el paradigma de cómo diseñar una aplicación.

Compare con JavaScript, donde las funciones de primera clase de sustantivo / verbo de doble propósito llueven y pasan como un dulce de una piñata abierta, el constructor de funciones equivalente a la clase puede alterar su prototipo agregando nuevas propiedades o cambiando las antiguas para instancias que ya se han creado en cualquier momento, absolutamente nada le impide reemplazar cualquier construcción con su propia versión, hay como 5 tipos y se convierten y evalúan automáticamente en diferentes circunstancias y puede adjuntar nuevas propiedades a casi cualquier cosa:

function nounAndVerb(){
}
nounAndVerb.newProperty = 'egads!';

En definitiva se trata de nichos y mercados. JavaScript es casi tan apropiado y desastroso en manos de 100 desarrolladores (muchos de los cuales probablemente serán mediocres) como Java una vez estuvo en manos de pequeños grupos de desarrolladores que intentaban escribir la interfaz de usuario web con él. Cuando trabajas con 100 desarrolladores, lo último que quieres es que un chico reinvente el maldito paradigma. Cuando trabajas con UI o el desarrollo rápido es una preocupación más crítica, lo último que quieres es que te bloqueen en hacer cosas relativamente simples rápidamente, simplemente porque sería fácil hacerlo de manera muy estúpida si no tienes cuidado.

Al final del día, sin embargo, ambos son lenguajes de uso general populares, por lo que hay un poco de argumento filosófico allí. Mi mayor problema personal con Java y C # es que nunca he visto o trabajado con una base de código heredada donde la mayoría de los desarrolladores parecían haber entendido el valor de la OOP básica. Cuando entras al juego teniendo que envolver todo en una clase, tal vez sea más fácil asumir que estás haciendo POO en lugar de funcionar espaguetis disfrazados como cadenas masivas de clases de 3-5 líneas.

Dicho esto, no hay nada más horrible que JavaScript escrito por alguien que solo sabe lo suficiente como para ser peligroso y no tiene miedo de mostrarlo. Y creo que esa es la idea general. Ese tipo supuestamente tiene que jugar con aproximadamente las mismas reglas en Java. Sin embargo, yo diría que el bastardo siempre encontrará la manera.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.