Java: deklaracje wielu klas w jednym pliku

238

W Javie możesz zdefiniować wiele klas najwyższego poziomu w jednym pliku, pod warunkiem, że co najwyżej jedna z nich jest publiczna (patrz JLS §7.6 ). Zobacz na przykład poniżej.

  1. Czy istnieje schludny nazwa dla tej techniki (analogiczny do inner, nested, anonymous)?

  2. JLS mówi, że system może wymusić ograniczenie, że te klasy drugorzędne nie mogą być referred to by code in other compilation units of the package, np. Nie mogą być traktowane jako prywatne. Czy to naprawdę coś zmienia się między implementacjami Java?

np. PublicClass.java:

package com.example.multiple;

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

class PrivateImpl {
    int implementationData;
}
Michael Brewer-Davis
źródło
11
+1 dobre pytania. Nigdy tak naprawdę nie zastanawiałem się nad tym, ponieważ prawie nigdy nie jest to konieczne.
Michael Myers
12
zauważ, że jest to cecha szczątkowa; nigdy nie byłoby to możliwe, gdyby java od początku zagnieżdżała klasy.
Kevin Bourrillion

Odpowiedzi:

120

Moja sugerowana nazwa tej techniki (w tym wielu klas najwyższego poziomu w jednym pliku źródłowym) to „bałagan”. Poważnie, nie sądzę, że to dobry pomysł - zamiast tego użyłbym typu zagnieżdżonego. Wciąż łatwo jest przewidzieć, w którym pliku źródłowym jest. Nie sądzę jednak, aby istniało oficjalne określenie tego podejścia.

Jeśli chodzi o to, czy to rzeczywiście zmienia się między implementacjami - bardzo w to wątpię, ale jeśli unikniesz tego, nigdy nie będziesz musiał się tym przejmować :)

Jon Skeet
źródło
71
Nie jestem zwolennikiem, ale fakt, że ta odpowiedź jest czymś, co można nazwać „normatywnym” (tj. „Powinieneś” zamiast „w rzeczywistości ... jednak ...”) jest najbardziej prawdopodobnym powodem, dla którego myślę, że zdobądź głos. W rzeczywistości nie odpowiada na żadne z pytań. Jak zgłaszanie nieistotnego wyjątku zamiast zwracania czegokolwiek / zgłaszanie wyjątku zawierającego informacje o faktach zamiast opinii.
n611x007
6
Znalazłem coś, co uważam za niewielki wyjątek od sugestii @JonSkeet, aby użyć typu zagnieżdżonego (z czym w innym przypadku zgodziłbym się): jeśli główna klasa jest ogólna, a parametr type to druga klasa, druga klasa nie może być zagnieżdżonym. A jeśli obie klasy są ze sobą ściśle powiązane (jak w pytaniu PublicClass i PrivateImpl), myślę, że warto umieścić PrivateImpl jako klasę najwyższego poziomu w tym samym pliku.
jfritz42
6
@BoomerRogers: Nie, zdecydowanie nie jest to „podstawowa podstawa programowania opartego na komponentach”. Jeśli programujesz przeciwko komponentowi, dlaczego miałbyś przejmować się organizacją kodu źródłowego? (Osobiście wolę wstrzykiwanie zależności niż wzorzec lokalizatora usług, ale to inna sprawa.) Oddzielny interfejs API i organizacji kodu źródłowego w twojej głowie - to bardzo różne rzeczy.
Jon Skeet
1
@JonSkeet Pozwólcie, że sformułuję: Twoja „odpowiedź” to osobista nieistotna opinia. (tzn. odpowiedzi typu „bałagan” i „wątpię” mają niewielką wartość.) Zatem twój post nie odpowiada na żadne z 2 postawionych pytań. Sprawdź odpowiedź wieloskładnikowych środków smarnych, a zobaczysz, że uda mu się odpowiedzieć na oba.
bvdb
1
@bvdb: (I jest wiele rzeczy, które są złymi praktykami, ale są dozwolone przez specyfikację. Zachęcam ludzi, żeby nie pisali public int[] foo(int x)[] { return new int[5][5]; }też, nawet jeśli to jest ważne.)
Jon Skeet
130

javac nie aktywnie tego zabrania, ale ma ograniczenia, które w zasadzie oznaczają, że nigdy nie chciałbyś odwoływać się do klasy najwyższego poziomu z innego pliku, chyba że ma taką samą nazwę jak plik, w którym się znajduje.

Załóżmy, że masz dwa pliki, Foo.java i Bar.java.

Foo.java zawiera:

  • klasa publiczna Foo

Bar.java zawiera:

  • Bar klasy publicznej
  • klasa Baz

Powiedzmy również, że wszystkie klasy są w tym samym pakiecie (a pliki znajdują się w tym samym katalogu).

Co się stanie, jeśli Foo.java odnosi się do Baz, ale nie Bar, i spróbujemy skompilować Foo.java? Kompilacja kończy się niepowodzeniem z takim błędem:

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

Ma to sens, jeśli się nad tym zastanowić. Jeśli Foo.java odnosi się do Baz, ale nie ma Baz.java (lub Baz.class), to w jaki sposób javac może wiedzieć, w którym pliku źródłowym szukać?

Jeśli zamiast tego powiesz javacowi, aby skompilował Foo.java i Bar.java w tym samym czasie, lub nawet jeśli wcześniej skompilowałeś Bar.java (pozostawiając Baz.class, gdzie javac może go znaleźć), to ten błąd zniknie. To sprawia, że ​​proces kompilacji wydaje się bardzo zawodny i łuszczący się.

Ponieważ rzeczywiste ograniczenie, które jest bardziej jak „nie odnosi się do klasy najwyższego poziomu z innego pliku, chyba że ma taką samą nazwę jak plik, w którym się znajduje, lub odnosi się to również do klasy, która znajduje się w tym samym pliku o nazwie to samo, co plik „jest trochę trudny do naśladowania, ludzie zwykle stosują o wiele prostszą (choć bardziej rygorystyczną) konwencję polegającą na umieszczeniu tylko jednej klasy najwyższego poziomu w każdym pliku. Jest to również lepsze, jeśli kiedykolwiek zmienisz zdanie na temat tego, czy klasa powinna być publiczna, czy nie.

Czasami naprawdę istnieje dobry powód, dla którego każdy robi coś w określony sposób.

Laurence Gonsalves
źródło
Czy Maven robi wszystko, aby kompilacja była niezawodna?
Aleksandr Dubinsky
23

Uważam po prostu zadzwonić PrivateImpl, co to jest: a non-public top-level class. Możesz również zadeklarować non-public top-level interfaces.

np. gdzie indziej na SO: niepubliczna klasa najwyższego poziomu vs. statyczna klasa zagnieżdżona

Jeśli chodzi o zmiany w zachowaniu między wersjami, była dyskusja na temat czegoś, co „działało idealnie” w 1.2.2. ale przestał działać w 1.4 na forum Sun: Kompilator Java - nie można zadeklarować w pliku niepublicznych klas najwyższego poziomu .

środki smarujące wielotlenowe
źródło
1
Moim jedynym problemem jest to, że możesz mieć non-public top level classjedyną klasę w pliku, więc to nie rozwiązuje problemu wielości.
Michael Brewer-Davis,
Rozumiem obawy, ale jak widzicie, jest to terminologia, której inni używali w przeszłości. Jeśli będę musiał wymyślić swój własny termin, prawdopodobnie go nazywam secondary top level types.
polygenelubrykanty
7

Możesz mieć tyle zajęć, ile chcesz

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
źródło
1
@Nenotlep Kiedy wykonujesz „poprawianie formatowania”, pamiętaj również, aby nie zadzierał z samym kodem, jak na przykład usuwanie ukośników odwrotnych.
Tom
3
To nie odpowiada na pytanie.
ᴠɪɴᴄᴇɴᴛ
4

1. Czy istnieje schludna nazwa tej techniki (analogiczna do wewnętrznej, zagnieżdżonej, anonimowej)?

Wieloklasowe demo jednego pliku.

2. JLS mówi, że system może egzekwować ograniczenie, że do tych klas drugorzędnych nie można odwoływać się przez kod w innych jednostkach kompilacji pakietu, np. Nie można ich traktować jako pakiet prywatny. Czy to naprawdę coś zmienia się między implementacjami Java?

Nie znam żadnych, które nie mają tego ograniczenia - wszystkie kompilatory oparte na plikach nie pozwolą ci odwoływać się do klas kodu źródłowego w plikach, które nie mają takich samych nazw jak nazwa klasy. (jeśli skompilujesz plik wielu klas i umieścisz klasy na ścieżce klasy, to dowolny kompilator je znajdzie)

Pete Kirkham
źródło
1

Według Effective Java 2. wydanie (pozycja 13):

„Jeśli klasa najwyższego poziomu (lub interfejs) z prywatnego pakietu jest używana tylko przez jedną klasę, rozważ uczynienie z klasy najwyższego poziomu prywatnej zagnieżdżonej klasy jedynej klasy, która jej używa (pozycja 22). Zmniejsza to jej dostępność ze wszystkich klas w pakiecie do jednej klasy, która go używa. Ale o wiele ważniejsze jest ograniczenie dostępności nieodpłatnej klasy publicznej niż prywatna klasa najwyższego poziomu w pakiecie: ... ”

Zagnieżdżona klasa może być statyczna lub niestatyczna w zależności od tego, czy klasa członka potrzebuje dostępu do otaczającej instancji (pozycja 22).

rezzy
źródło
OP nie pyta o zagnieżdżone klasy.
charmoniumQ
0

Tak, możesz, z publicznymi statycznymi członkami zewnętrznej klasy publicznej, tak jak:

public class Foo {

    public static class FooChild extends Z {
        String foo;
    }

    public static class ZeeChild extends Z {

    }

}

i inny plik, który odwołuje się do powyższego:

public class Bar {

    public static void main(String[] args){

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

    }
}

umieść je w tym samym folderze. Połącz z:

javac folder/*.java

i biegnij z:

 java -cp folder Bar
Alexander Mills
źródło
Ten przykład nie odpowiada na pytanie. Podajesz przykład zagnieżdżonych klas statycznych, co nie jest tym samym, co zdefiniowanie dwóch klas najwyższego poziomu w tym samym pliku.
Pedro García Medina
0

Tylko do Twojej wiadomości, jeśli używasz Java 11+, istnieje wyjątek od tej reguły: jeśli uruchamiasz plik Java bezpośrednio ( bez kompilacji ). W tym trybie nie ma ograniczeń co do jednej klasy publicznej na plik. Jednak klasa z mainmetodą musi być pierwsza w pliku.

ZhekaKozlov
źródło
-5

Nie, nie możesz. Ale jest bardzo możliwe w Scali:

class Foo {val bar = "a"}
class Bar {val foo = "b"}
Michal Štein
źródło