Java: múltiples declaraciones de clase en un archivo

237

En Java, puede definir varias clases de nivel superior en un solo archivo, siempre que, como máximo, una de ellas sea pública (consulte JLS §7.6 ). Ver abajo por ejemplo.

  1. ¿Hay un nombre para esta técnica ordenada (análogo a inner, nested, anonymous)?

  2. El JLS dice que el sistema puede imponer la restricción de que estas clases secundarias no pueden ser referred to by code in other compilation units of the package, por ejemplo, no pueden tratarse como paquetes privados. ¿Es eso realmente algo que cambia entre las implementaciones de Java?

por ejemplo, PublicClass.java:

package com.example.multiple;

public class PublicClass {
    PrivateImpl impl = new PrivateImpl();
}

class PrivateImpl {
    int implementationData;
}
Michael Brewer-Davis
fuente
11
+1 Buenas preguntas. Realmente nunca he pensado mucho en el asunto, ya que casi nunca es necesario hacer esto.
Michael Myers
12
tenga en cuenta que esta es una característica vestigial; nunca hubiera sido posible si Java hubiera tenido clases anidadas desde el principio.
Kevin Bourrillion

Respuestas:

120

Mi nombre sugerido para esta técnica (incluidas múltiples clases de nivel superior en un solo archivo fuente) sería "desastre". En serio, no creo que sea una buena idea, en su lugar usaría un tipo anidado en esta situación. Entonces todavía es fácil predecir en qué archivo fuente está. Sin embargo, no creo que haya un término oficial para este enfoque.

En cuanto a si esto realmente cambia entre implementaciones, lo dudo mucho, pero si evita hacerlo, en primer lugar, nunca tendrá que preocuparse :)

Jon Skeet
fuente
71
No soy el votante negativo, pero el hecho de que esta respuesta sea algo que podría llamarse "normativo" (es decir, "deberías" en lugar de "de hecho ... sin embargo ...") es la razón más probable para que conseguir un voto negativo, creo. En realidad no responde ninguna de las preguntas. Como plantear una excepción irrelevante en lugar de devolver algo / plantear una excepción que tenga información sobre hechos reales en lugar de opiniones.
n611x007
66
Encontré lo que creo que es una excepción menor a la sugerencia de @JonSkeet de usar un tipo anidado (con el que de otra manera estaría de acuerdo): si la clase principal es genérica y el parámetro tipo es la segunda clase, la segunda clase no puede estar anidado Y si las dos clases están estrechamente acopladas (como PublicClass y PrivateImpl en la pregunta), creo que es una buena idea colocar PrivateImpl como una clase de nivel superior en el mismo archivo.
jfritz42
66
@BoomerRogers: No, definitivamente esta no es la "base central de la programación basada en componentes". Si está programando contra un componente, ¿por qué le importaría cómo está organizado el código fuente? (Personalmente, prefiero la inyección de dependencia en lugar del patrón del localizador de servicios, pero ese es un asunto diferente). Separe la API y la organización del código fuente en su mente: son cosas muy diferentes.
Jon Skeet
1
@ JonSkeet Permítanme reformular: su "respuesta" es una opinión personal irrelevante. (es decir, las respuestas como "desorden" y "lo dudo" tienen poco valor). Por lo tanto, su publicación no responde a ninguna de las 2 preguntas planteadas. Verifique la respuesta de los poligenel lubricantes, y verá que él logra responder a ambos.
bvdb
1
@bvdb: (Y hay muchas cosas que son malas prácticas pero están permitidas por la especificación. Exhortaría a las personas a que no escriban public int[] foo(int x)[] { return new int[5][5]; }tan bien, aunque sea válido).
Jon Skeet
130

javac no prohíbe esto activamente, pero tiene una limitación que significa que nunca querrá referirse a una clase de nivel superior de otro archivo a menos que tenga el mismo nombre que el archivo en el que se encuentra.

Supongamos que tiene dos archivos, Foo.java y Bar.java.

Foo.java contiene:

  • clase pública Foo

Bar.java contiene:

  • Bar de clase pública
  • clase Baz

Digamos también que todas las clases están en el mismo paquete (y los archivos están en el mismo directorio).

¿Qué sucede si Foo.java se refiere a Baz pero no a Bar e intentamos compilar Foo.java? La compilación falla con un error como este:

Foo.java:2: cannot find symbol
symbol  : class Baz
location: class Foo
  private Baz baz;
          ^
1 error

Esto tiene sentido si lo piensas. Si Foo.java se refiere a Baz, pero no hay Baz.java (o Baz.class), ¿cómo puede saber javac en qué archivo fuente buscar?

Si, en cambio, le dice a javac que compile Foo.java y Bar.java al mismo tiempo, o incluso si previamente compiló Bar.java (dejando la clase Baz.class donde javac puede encontrarlo), entonces este error desaparece. Sin embargo, esto hace que su proceso de construcción se sienta muy poco confiable y poco fiable.

Debido a que la limitación real, que es más como "no se refiere a una clase de nivel superior de otro archivo a menos que tenga el mismo nombre que el archivo en el que se encuentra o que también se refiera a una clase que está en ese mismo archivo que se llama lo mismo que el archivo "es un poco difícil de seguir, la gente suele seguir la convención mucho más directa (aunque más estricta) de simplemente poner una clase de nivel superior en cada archivo. Esto también es mejor si alguna vez cambia de opinión acerca de si una clase debe ser pública o no.

A veces hay una buena razón por la cual todos hacen algo de una manera particular.

Laurence Gonsalves
fuente
¿Maven hace algo para que la compilación sea confiable?
Aleksandr Dubinsky
23

Creo que simplemente llamas PrivateImpllo que es: a non-public top-level class. También puedes declarar non-public top-level interfacestambién.

por ejemplo, en otra parte de SO: clase de nivel superior no pública versus clase anidada estática

En cuanto a los cambios en el comportamiento entre versiones, hubo una discusión sobre algo que "funcionó perfectamente" en 1.2.2. pero dejó de funcionar en 1.4 en el foro de sun: Compilador de Java: no se pueden declarar clases de nivel superior no públicas en un archivo .

poligenelubricantes
fuente
1
Mi único problema con esto es que puede non-public top level classser la única clase en un archivo, por lo que no aborda la multiplicidad.
Michael Brewer-Davis
Entiendo la preocupación, pero como puede ver, esta es una terminología que otros han usado históricamente. Si tengo que inventar mi propio término, probablemente lo llamaré secondary top level types.
polygenelubricants
7

Puedes tener tantas clases como desees así

public class Fun {
    Fun() {
        System.out.println("Fun constructor");
    }
    void fun() {
        System.out.println("Fun mathod");
    }
    public static void main(String[] args) {
        Fun fu = new Fun();
        fu.fun();
        Fen fe = new Fen();
        fe.fen();
        Fin fi = new Fin();
        fi.fin();
        Fon fo = new Fon();
        fo.fon();
        Fan fa = new Fan();
        fa.fan();
        fa.run();
    }
}

class Fen {
    Fen() {
        System.out.println("fen construuctor");

    }
    void fen() {
        System.out.println("Fen method");
    }
}

class Fin {
    void fin() {
        System.out.println("Fin method");
    }
}

class Fon {
    void fon() {
        System.out.println("Fon method");
    } 
}

class Fan {
    void fan() {
        System.out.println("Fan method");
    }
    public void run() {
        System.out.println("run");
    }
}
Denis
fuente
1
@Nenotlep Cuando realice una "mejora de formato", también tenga cuidado de que no interfiera con el código en sí, como eliminar las barras invertidas.
Tom
3
Eso no responde la pregunta.
ᴠɪɴᴄᴇɴᴛ
4

1. ¿Hay un nombre ordenado para esta técnica (análogo a anónimo interno, anidado)?

Demostración de archivo único de varias clases.

2. El JLS dice que el sistema puede imponer la restricción de que no se puede hacer referencia a estas clases secundarias por código en otras unidades de compilación del paquete, por ejemplo, no se pueden tratar como paquete privado. ¿Es eso realmente algo que cambia entre las implementaciones de Java?

No conozco ninguno que no tenga esa restricción: todos los compiladores basados ​​en archivos no le permitirán referirse a las clases de código fuente en archivos que no tienen el mismo nombre que la clase. (si compila un archivo de varias clases y coloca las clases en la ruta de clase, cualquier compilador las encontrará)

Pete Kirkham
fuente
1

Según Effective Java 2nd edition (Item 13):

"Si una clase de nivel superior de paquete privado (o interfaz) es utilizada por una sola clase, considere hacer de la clase de nivel superior una clase privada anidada de la única clase que la usa (Elemento 22). Esto reduce su accesibilidad de todos las clases en su paquete a la clase que lo usa. Pero es mucho más importante reducir la accesibilidad de una clase pública gratuita que una clase de nivel superior de paquete privado: ... "

La clase anidada puede ser estática o no estática en función de si la clase miembro necesita acceso a la instancia de cierre (Elemento 22).

rezzy
fuente
El OP no pregunta sobre clases anidadas.
charmoniumQ
0

Sí, puede, con miembros públicos estáticos en una clase pública externa, así:

public class Foo {

    public static class FooChild extends Z {
        String foo;
    }

    public static class ZeeChild extends Z {

    }

}

y otro archivo que hace referencia a lo anterior:

public class Bar {

    public static void main(String[] args){

        Foo.FooChild f = new Foo.FooChild();
        System.out.println(f);

    }
}

ponlos en la misma carpeta. Compilar con:

javac folder/*.java

y correr con:

 java -cp folder Bar
Alexander Mills
fuente
Ese ejemplo no responde la pregunta. Está dando un ejemplo de clases estáticas anidadas, que no es lo mismo que tener dos clases de nivel superior definidas en el mismo archivo.
Pedro García Medina
0

Solo para su información, si está utilizando Java 11+, hay una excepción a esta regla: si ejecuta su archivo java directamente ( sin compilación ). En este modo, no hay restricción en una sola clase pública por archivo. Sin embargo, la clase con el mainmétodo debe ser la primera en el archivo.

ZhekaKozlov
fuente
-5

No. No puedes. Pero es muy posible en Scala:

class Foo {val bar = "a"}
class Bar {val foo = "b"}
Michal Štein
fuente