Java: Декларації декількох класів в одному файлі


237

У Java ви можете визначити кілька класів вищого рівня в одному файлі, за умови, що щонайменше один із них є загальнодоступним (див. JLS §7.6 ). Дивіться, наприклад, нижче.

  1. Є акуратне назва цієї техніки (аналогічно inner, nested, anonymous)?

  2. JLS говорить, що система може застосовувати обмеження, яке не може бути в цих середніх класах referred to by code in other compilation units of the package, наприклад, вони не можуть розглядатися як приватні пакети. Це дійсно щось, що змінюється між реалізаціями Java?

наприклад, PublicClass.java:

package com.example.multiple;

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

class PrivateImpl {
    int implementationData;
}

11
+1 Хороші запитання. Я ніколи насправді не задумувався над цим питанням, оскільки це майже ніколи не потрібно робити.
Майкл Майерс

12
зауважте, що це вестигійна особливість; це ніколи не було б можливим, якби ява мала вкладені заняття з самого початку.
Кевін Бурліон

Відповіді:


120

Моя запропонована назва цієї методики (включаючи кілька класів верхнього рівня в одному вихідному файлі) буде "безлад". Якщо серйозно, я не думаю, що це гарна ідея - я б замість цього використовував вкладений тип. Тоді все-таки легко передбачити, у якому вихідному файлі він знаходиться. Хоча я не вірю, що для цього підходу є офіційний термін.

Що стосується того, чи насправді це змінюється між реалізаціями - я дуже сумніваюся в цьому, але якщо ви уникаєте робити це в першу чергу, вам ніколи не потрібно буде дбати :)


71
Я не переслідувач, але факт, що ця відповідь - це те, що можна було б назвати "нормативним" (тобто. "Ти повинен" замість "насправді ... однак ...") є найбільш ймовірною причиною цього Отримати знижку я думаю. Він насправді не відповідає на жодне з питань. Як і підняття нерелевантного винятку замість того, щоб повертати що-небудь / підняти виняток, який містить інформацію про фактичні факти замість думок.
n611x007

6
Я виявив, що, на мою думку, є незначним винятком із пропозиції @JonSkeet щодо використання вкладеного типу (з яким я б інакше погодився): якщо основний клас є загальним, а параметр типу - другим класом, другий клас не може бути вкладеним. І якщо два класи щільно з'єднані (як PublicClass та PrivateImpl у питанні), я думаю, що це гарна ідея помістити PrivateImpl як клас вищого рівня в одному файлі.
jfritz42

6
@BoomerRogers: Ні, це, безумовно, не є "основною основою програмування на основі компонентів". Якщо ви програмуєте на компонент, чому б вам було все одно, як організовано вихідний код? (Особисто я віддаю перевагу введенню залежності, а не схемі локатора служб, але це вже інше питання.) Окремий API та організація вихідного коду на увазі - вони дуже різні речі.
Джон Скіт

1
@JonSkeet Дозвольте перефразувати: Ваша "відповідь" - це особиста неактуальна думка. (тобто відповіді типу "безлад" та "я сумніваюсь у цьому" мають мало значення.) Отже, ваша публікація не відповідає на жодне з 2-х поставлених питань. Перевірте відповідь полігенмастильних матеріалів, і ви побачите, що йому вдається відповісти на обидва.
bvdb

1
@bvdb: (І є багато речей, які є поганою практикою, але дозволені спец.. Я б закликав людей не писати public int[] foo(int x)[] { return new int[5][5]; }, навіть якщо це дійсно.)
Джон Скіт

130

javac не забороняє цього активно, але у нього є обмеження, яке в значній мірі означає, що ви ніколи не хочете посилатися на клас вищого рівня з іншого файлу, якщо він не має того самого імені, що і файл, в якому він знаходиться.

Припустимо, у вас є два файли, Foo.java і Bar.java.

Foo.java містить:

  • громадський клас Foo

Bar.java містить:

  • Бар публічного класу
  • клас Баз

Скажемо також, що всі класи знаходяться в одному пакеті (а файли - в одному каталозі).

Що станеться, якщо Foo.java посилається на Baz, але не на Bar, і ми спробуємо скласти Foo.java? Компіляція не вдається з такою помилкою:

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

Це має сенс, якщо ви подумаєте про це. Якщо Foo.java посилається на Baz, але немає Baz.java (або Baz.class), як javac може знати, у якому вихідному файлі шукати?

Якщо ви замість цього скажете javac одночасно збирати Foo.java і Bar.java або навіть якщо ви раніше компілювали Bar.java (залишаючи Baz.class, де javac може його знайти), ця помилка усувається. Однак це робить процес збирання дуже ненадійним та неохайним.

Тому що фактичне обмеження, яке більше нагадує "не посилайтесь на клас вищого рівня з іншого файлу, якщо він не має того самого імені, що і файл, у якому він є, або ви також посилаєтесь на клас, який знаходиться в тому самому файлі, який названий те саме, що за файлом "важко дотримуватися, люди зазвичай йдуть із набагато простішою (хоча суворішою) умовою просто введення одного класу верхнього рівня у кожен файл. Це також краще, якщо ви коли-небудь передумаєте, чи має бути клас загальнодоступним чи ні.

Іноді дійсно є вагома причина, чому кожен робить щось певним чином.


Чи робить Maven щось для того, щоб зробити компіляцію надійною?
Олександр Дубінський

23

Я вважаю, ви просто називаєте, PrivateImplщо це: а non-public top-level class. Ви також можете заявити non-public top-level interfaces.

наприклад, в іншому місці SO: Непублічний клас вищого рівня та статичний вкладений клас

Щодо змін у поведінці між версіями, то в 1.2.2 обговорювалося щось, що "справно працювало". але перестала працювати в 1.4 на форумі ВС: Java Compiler - не в змозі оголосити файл, який не є загальнодоступним класом вищого рівня у файлі .


1
Єдине моє питання з цим полягає в тому, що у вас може non-public top level classбути єдиний клас у файлі, тому він не вирішує кратність.
Майкл Брюер-Девіс

Я розумію стурбованість, але, як ви бачите, це термінологія, яку інші історично використовували. Якщо мені доведеться скласти власний термін, я, мабуть, його назву secondary top level types.
полігенмастильні матеріали

7

Ви можете мати стільки занять, скільки хочете

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");
    }
}

1
@Nenotlep Коли ви робите "покращення форматування", будь ласка, подбайте про те, щоб він не змішувався з самим кодом, як-от видалення косої риски.
Том

3
Це не відповідає на запитання.
ᴠɪɴᴄᴇɴᴛ

4

1.Чи є охайна назва цієї методики (аналогічна внутрішній, вкладеній, анонімній)?

Демонстрація однокласних багатокласних демонстрацій.

2. JLS стверджує, що система може застосовувати обмеження, що ці вторинні класи не можуть бути передані кодом в інших підрозділах компіляції пакету, наприклад, вони не можуть розглядатися як приватні пакети. Це дійсно щось, що змінюється між реалізаціями Java?

Я не знаю жодного, у якого немає цього обмеження - усі компілятори на основі файлів не дозволять вам посилатися на класи вихідного коду у файлах, які не названі так само, як ім'я класу. (якщо ви компілюєте багатокласовий файл і кладете класи на шлях до класу, то будь-який компілятор знайде їх)


1

Відповідно до ефективної Java 2-го видання (Пункт 13):

"Якщо пакет-приватний клас вищого рівня (або інтерфейс) використовується лише одним класом, подумайте про те, щоб клас верхнього рівня був приватним вкладеним класом єдиного класу, який ним користується (Пункт 22). Це знижує його доступність від усіх класи у своєму пакеті до одного класу, який ним користується. Але набагато важливіше знизити доступність безоплатно публічного класу, ніж приватний клас вищого рівня: "

Вкладений клас може бути статичним або нестатичним на основі того, чи потрібен клас-члену доступ до екземпляра, що вкладається (пункт 22).


ОП не питає про вкладені класи.
charmoniumQ

0

Так, ви можете із загальнодоступними статичними членами на зовнішньому публічному класі так:

public class Foo {

    public static class FooChild extends Z {
        String foo;
    }

    public static class ZeeChild extends Z {

    }

}

та ще один файл, на який посилається вище:

public class Bar {

    public static void main(String[] args){

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

    }
}

помістіть їх у ту саму папку. Зібрати:

javac folder/*.java

і бігти з:

 java -cp folder Bar

Цей приклад не дає відповіді на запитання. Ви наводите приклад вкладених статичних класів, що не те саме, що два класи верхнього рівня, визначені в одному файлі.
Педро Гарсія Медіна

0

Просто FYI, якщо ви використовуєте Java 11+, є виняток із цього правила: якщо ви запускаєте файл Java безпосередньо ( без компіляції ). У цьому режимі не існує обмежень на один загальнодоступний клас на файл. Однак клас із mainметодом повинен бути першим у файлі.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.