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
обратите внимание, что это остаточная особенность; это никогда не было бы возможно, если бы у java были вложенные классы с самого начала.
Кевин Бурриллион

Ответы:


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 на форуме Sun. 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

Согласно изданию Effective 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

Просто к сведению, если вы используете Java 11+, есть исключение из этого правила: если вы запускаете свой Java-файл напрямую ( без компиляции ). В этом режиме нет ограничений на один публичный класс на файл. Однако класс с mainметодом должен быть первым в файле.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.