Java: plusieurs déclarations de classe dans un seul fichier

237

En Java, vous pouvez définir plusieurs classes de niveau supérieur dans un seul fichier, à condition qu'au plus l'une d'entre elles soit publique (voir JLS §7.6 ). Voir ci-dessous par exemple.

  1. Y at - il un nom bien rangé pour cette technique (analogue à inner, nested, anonymous)?

  2. Le JLS indique que le système peut appliquer la restriction selon laquelle ces classes secondaires ne peuvent pas être referred to by code in other compilation units of the package, par exemple, elles ne peuvent pas être traitées comme des packages privés. Est-ce vraiment quelque chose qui change entre les implémentations Java?

par exemple, PublicClass.java:

package com.example.multiple;

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

class PrivateImpl {
    int implementationData;
}
Michael Brewer-Davis
la source
11
+1 Bonnes questions. Je n'ai jamais vraiment réfléchi à la question, car il n'est presque jamais nécessaire de le faire.
Michael Myers
12
notez qu'il s'agit d'une caractéristique résiduelle; cela n'aurait jamais été possible si java avait eu des classes imbriquées depuis le début.
Kevin Bourrillion

Réponses:

120

Mon nom suggéré pour cette technique (y compris plusieurs classes de niveau supérieur dans un seul fichier source) serait "mess". Sérieusement, je ne pense pas que ce soit une bonne idée - j'utiliserais plutôt un type imbriqué dans cette situation. Ensuite, il est toujours facile de prédire dans quel fichier source il se trouve. Je ne pense pas cependant qu'il existe un terme officiel pour cette approche.

Quant à savoir si cela change réellement entre les implémentations - j'en doute fortement, mais si vous évitez de le faire en premier lieu, vous n'aurez jamais besoin de vous en soucier :)

Jon Skeet
la source
71
Je ne suis pas le downvoter, mais le fait que cette réponse puisse être qualifiée de "normative" (c'est-à-dire "vous devriez" au lieu de "en fait ... cependant ...") est la raison la plus probable obtenir un downvote je pense. Il ne répond en fait à aucune des questions. Comme lever une exception non pertinente au lieu de renvoyer quoi que ce soit / lever une exception qui contient des informations sur des faits réels au lieu d'opinions.
n611x007
6
J'ai trouvé ce que je pense être une exception mineure à la suggestion de @JonSkeet d'utiliser un type imbriqué (avec lequel je serais autrement d'accord): si la classe principale est générique et le paramètre de type est la deuxième classe, la deuxième classe ne peut pas être imbriqué. Et si les deux classes sont étroitement couplées (comme PublicClass et PrivateImpl dans la question), je pense que c'est une bonne idée de mettre PrivateImpl en tant que classe de niveau supérieur dans le même fichier.
jfritz42
6
@BoomerRogers: Non, ce n'est certainement pas la "base de base de la programmation basée sur les composants". Si vous programmez contre un composant, pourquoi vous soucieriez-vous de l'organisation du code source? (Personnellement, je préfère l' injection de dépendances plutôt que le modèle de localisateur de services, mais c'est une question différente.) API distincte et organisation du code source dans votre esprit - ce sont des choses très différentes.
Jon Skeet
1
@ JonSkeet Permettez-moi de reformuler: Votre "réponse" est une opinion personnelle non pertinente. (C'est-à-dire que les réponses comme "mess" et "j'en doute" ont peu de valeur.) Donc, votre message ne répond à aucune des 2 questions posées. Vérifiez la réponse des lubrifiants polygéniques, et vous verrez qu'il parvient à répondre aux deux.
bvdb
1
@bvdb: (Et il y a beaucoup de choses qui sont de mauvaises pratiques mais autorisées par la spécification. J'exhorte les gens à ne pas écrire public int[] foo(int x)[] { return new int[5][5]; }aussi bien, même si c'est valide.)
Jon Skeet
130

javac n'interdit pas activement cela, mais il a une limitation qui signifie à peu près que vous ne voudriez jamais faire référence à une classe de niveau supérieur à partir d'un autre fichier à moins qu'elle ait le même nom que le fichier dans lequel elle se trouve.

Supposons que vous ayez deux fichiers, Foo.java et Bar.java.

Foo.java contient:

  • classe publique Foo

Bar.java contient:

  • Bar public class
  • classe Baz

Disons également que toutes les classes sont dans le même package (et les fichiers sont dans le même répertoire).

Que se passe-t-il si Foo.java fait référence à Baz mais pas à Bar et que nous essayons de compiler Foo.java? La compilation échoue avec une erreur comme celle-ci:

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

Cela a du sens si vous y réfléchissez. Si Foo.java fait référence à Baz, mais qu'il n'y a pas de Baz.java (ou Baz.class), comment javac peut-il savoir dans quel fichier source chercher?

Si vous dites à la place à javac de compiler Foo.java et Bar.java en même temps, ou même si vous aviez déjà compilé Bar.java (en laissant la Baz.class où javac peut le trouver), cette erreur disparaît. Cela rend cependant votre processus de construction très peu fiable et instable.

Parce que la limitation réelle, qui ressemble plus à «ne fait pas référence à une classe de niveau supérieur d'un autre fichier, sauf si elle a le même nom que le fichier dans lequel elle se trouve ou si vous faites également référence à une classe qui se trouve dans le même fichier nommé la même chose que le fichier "est un peu difficile à suivre, les gens adoptent généralement la convention beaucoup plus simple (bien que plus stricte) de ne mettre qu'une classe de niveau supérieur dans chaque fichier. C'est aussi mieux si jamais vous changez d'avis quant à savoir si une classe doit être publique ou non.

Parfois, il y a vraiment une bonne raison pour laquelle tout le monde fait quelque chose d'une manière particulière.

Laurence Gonsalves
la source
Maven fait-il quelque chose pour rendre la compilation fiable?
Aleksandr Dubinsky
23

Je crois que vous appelez simplement PrivateImplce que c'est: a non-public top-level class. Vous pouvez également le déclarer non-public top-level interfaces.

par exemple, ailleurs sur SO: classe de premier niveau non publique vs classe imbriquée statique

En ce qui concerne les changements de comportement entre les versions, il y a eu cette discussion sur quelque chose qui "fonctionnait parfaitement" dans 1.2.2. mais a cessé de fonctionner dans 1.4 sur le forum de sun: Java Compiler - impossible de déclarer une classe de premier niveau non publique dans un fichier .

polygénelubrifiants
la source
1
Mon seul problème avec cela est que vous pouvez avoir une non-public top level classseule classe dans un fichier, donc cela ne traite pas de la multiplicité.
Michael Brewer-Davis
Je comprends la préoccupation, mais comme vous pouvez le voir, c'est une terminologie que d'autres ont utilisée historiquement. Si je dois inventer mon propre terme, je l'appellerai probablement secondary top level types.
polygenelubricants
7

Vous pouvez avoir autant de cours que vous le souhaitez

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
la source
1
@Nenotlep Lorsque vous effectuez une "amélioration de la mise en forme", veillez également à ce qu'il ne dérange pas le code lui-même, comme la suppression des barres obliques inverses.
Tom
3
Cela ne répond pas à la question.
ᴠɪɴᴄᴇɴᴛ
4

1.Y a-t-il un nom bien rangé pour cette technique (analogue à intérieur, imbriqué, anonyme)?

Démo multi-classe à fichier unique.

2. Le JLS indique que le système peut appliquer la restriction selon laquelle ces classes secondaires ne peuvent pas être référencées par du code dans d'autres unités de compilation du package, par exemple, elles ne peuvent pas être traitées comme des packages privés. Est-ce vraiment quelque chose qui change entre les implémentations Java?

Je ne connais aucun qui n'a pas cette restriction - tous les compilateurs basés sur des fichiers ne vous permettront pas de faire référence à des classes de code source dans des fichiers qui ne sont pas nommés de la même manière que le nom de la classe. (si vous compilez un fichier multi-classes et placez les classes sur le chemin des classes, alors n'importe quel compilateur les trouvera)

Pete Kirkham
la source
1

Selon Effective Java 2nd edition (Item 13):

"Si une classe (ou interface) de niveau supérieur de package privé est utilisée par une seule classe, envisagez de faire de la classe de niveau supérieur une classe imbriquée privée de la seule classe qui l'utilise (article 22). Cela réduit son accessibilité de tous les classes de son package à la classe qui l'utilise. Mais il est beaucoup plus important de réduire l'accessibilité d'une classe publique gratuite qu'une classe de niveau supérieur package-private: ... "

La classe imbriquée peut être statique ou non statique selon que la classe membre a besoin d'accéder à l'instance englobante (article 22).

rezzy
la source
L'OP ne pose pas de questions sur les classes imbriquées.
charmoniumQ
0

Oui, vous pouvez, avec des membres statiques publics sur une classe publique externe, comme ceci:

public class Foo {

    public static class FooChild extends Z {
        String foo;
    }

    public static class ZeeChild extends Z {

    }

}

et un autre fichier qui fait référence à ce qui précède:

public class Bar {

    public static void main(String[] args){

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

    }
}

mettez-les dans le même dossier. Compiler avec:

javac folder/*.java

et courir avec:

 java -cp folder Bar
Alexander Mills
la source
Cet exemple ne répond pas à la question. Vous donnez un exemple de classes statiques imbriquées, ce qui n'est pas la même chose que d'avoir deux classes de niveau supérieur définies dans le même fichier.
Pedro García Medina
0

Juste pour info, si vous utilisez Java 11+, il y a une exception à cette règle: si vous exécutez votre fichier java directement ( sans compilation ). Dans ce mode, il n'y a aucune restriction sur une seule classe publique par fichier. Cependant, la classe avec la mainméthode doit être la première du fichier.

ZhekaKozlov
la source
-5

Non, tu ne peux pas. Mais c'est très possible à Scala:

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