Java: Várias declarações de classe em um arquivo

237

Em Java, você pode definir várias classes de nível superior em um único arquivo, desde que no máximo uma delas seja pública (consulte JLS §7.6 ). Veja abaixo, por exemplo.

  1. Existe um nome arrumado para esta técnica (análogo a inner, nested, anonymous)?

  2. O JLS diz que o sistema pode impor a restrição de que essas classes secundárias não podem ser referred to by code in other compilation units of the package, por exemplo, elas não podem ser tratadas como privadas de pacotes. Isso é realmente algo que muda entre implementações Java?

por exemplo, PublicClass.java:

package com.example.multiple;

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

class PrivateImpl {
    int implementationData;
}
Michael Brewer-Davis
fonte
11
+1 Boas perguntas. Eu realmente nunca pensei muito sobre o assunto, já que quase nunca é necessário fazer isso.
Michael Myers
12
note que esta é uma característica vestigial; nunca seria possível se o java tivesse aninhado classes desde o início.
Kevin Bourrillion

Respostas:

120

Meu nome sugerido para esta técnica (incluindo várias classes de nível superior em um único arquivo de origem) seria "bagunça". Sério, não acho que seja uma boa ideia - eu usaria um tipo aninhado nessa situação. Ainda é fácil prever em qual arquivo fonte ele está. Não acredito que exista um termo oficial para essa abordagem.

Quanto a saber se isso realmente muda entre implementações - duvido muito, mas se você evitar fazê-lo em primeiro lugar, nunca precisará se preocupar :)

Jon Skeet
fonte
71
Não sou a favor do voto negativo, mas o fato de que essa resposta pode ser chamada de "normativa" (ou seja, "você deveria" em vez de "de fato ... no entanto ...") é a razão mais provável para isso. obter um voto negativo, eu acho. Na verdade, não responde a nenhuma das perguntas. Como criar uma exceção irrelevante em vez de retornar qualquer coisa / criar uma exceção que tenha informações sobre fatos reais em vez de opiniões.
N611x007
6
Eu achei o que eu acho que é uma exceção menor à sugestão do @JonSkeet de usar um tipo aninhado (com o qual eu concordaria): se a classe principal é genérica e o parâmetro type é a segunda classe, a segunda classe não pode ser aninhado. E se as duas classes estiverem fortemente acopladas (como PublicClass e PrivateImpl na pergunta), acho uma boa ideia colocar PrivateImpl como uma classe de nível superior no mesmo arquivo.
Jritz42
6
@BoomerRogers: Não, essa definitivamente não é a "base principal da programação baseada em componentes". Se você está programando em um componente, por que você se importaria com a organização do código-fonte? (Pessoalmente, prefiro a injeção de dependência do que o padrão do localizador de serviço, mas isso é uma questão diferente.) Separar a organização da API e do código-fonte em sua mente - são coisas muito diferentes.
Jon Skeet
1
@ JonSkeet Deixe-me reformular: Sua "resposta" é uma opinião pessoal irrelevante. (ou seja, respostas como "bagunça" e "duvido que" tenham pouco valor.) Portanto, sua postagem não responde a nenhuma das duas perguntas feitas. Verifique a resposta dos poligenelubricants e você verá que ele consegue responder a ambos.
bvdb
1
@bvdb: (E há muitos . coisas que são ruins prática, mas permitido pela especificação Gostaria de exortar as pessoas a não escrever public int[] foo(int x)[] { return new int[5][5]; }bem, mesmo que isso é válido.)
Jon Skeet
130

O javac não proíbe isso ativamente, mas tem uma limitação que praticamente significa que você nunca desejaria consultar uma classe de nível superior de outro arquivo, a menos que tenha o mesmo nome do arquivo em que está.

Suponha que você tenha dois arquivos, Foo.java e Bar.java.

Foo.java contém:

  • classe pública Foo

Bar.java contém:

  • Bar de classe pública
  • classe Baz

Digamos também que todas as classes estão no mesmo pacote (e os arquivos estão no mesmo diretório).

O que acontece se Foo.java se referir a Baz, mas não Bar, e tentarmos compilar Foo.java? A compilação falha com um erro como este:

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

Isso faz sentido se você pensar sobre isso. Se Foo.java se referir ao Baz, mas não houver Baz.java (ou Baz.class), como o javac pode saber em qual arquivo de origem procurar?

Se você instruir o javac a compilar Foo.java e Bar.java ao mesmo tempo, ou mesmo se você tiver compilado o Bar.java anteriormente (deixando a classe Baz.classe onde o javac pode encontrá-lo), esse erro desaparecerá. Isso faz com que seu processo de construção pareça muito pouco confiável e esquisito.

Como a limitação real, que é mais parecida com "não se refere a uma classe de nível superior de outro arquivo, a menos que tenha o mesmo nome do arquivo em que está ou você também esteja se referindo a uma classe que está no mesmo arquivo que é nomeado a mesma coisa que o arquivo "é meio difícil de seguir, as pessoas geralmente seguem a convenção muito mais direta (embora mais rigorosa) de apenas colocar uma classe de nível superior em cada arquivo. Isso também é melhor se você mudar de idéia sobre se uma classe deve ser pública ou não.

Às vezes, há realmente uma boa razão pela qual todo mundo faz algo de uma maneira específica.

Laurence Gonsalves
fonte
O Maven faz alguma coisa para tornar a compilação confiável?
Aleksandr Dubinsky
23

Eu acredito que você simplesmente chama PrivateImplo que é: a non-public top-level class. Você também pode declarar non-public top-level interfacestambém.

por exemplo, em outro lugar no SO: classe não pública de nível superior versus classe aninhada estática

Quanto às mudanças de comportamento entre as versões, houve essa discussão sobre algo que "funcionou perfeitamente" no 1.2.2. mas parou de funcionar no 1.4 no fórum da sun: Java Compiler - incapaz de declarar classes de nível superior não públicas em um arquivo .

poligenelubricants
fonte
1
Meu único problema com isso é que você pode ter non-public top level classa única classe em um arquivo, para que não lide com a multiplicidade.
Michael Brewer-Davis
Entendo a preocupação, mas como você pode ver, essa é uma terminologia que outras pessoas usaram historicamente. Se eu tiver que criar meu próprio mandato, provavelmente chamarei secondary top level types.
polygenelubricants
7

Você pode ter quantas aulas quiser dessa maneira

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
fonte
1
@Nenotlep Quando você faz uma "formatação aprimorada", lembre-se também de não mexer com o código, como remover barras invertidas.
Tom
3
Isso não responde à pergunta.
ᴠɪɴᴄᴇɴᴛ
4

1. Existe um nome arrumado para esta técnica (análoga a interna, aninhada, anônima)?

Demonstração de arquivo único de várias classes.

2. O JLS diz que o sistema pode impor a restrição de que essas classes secundárias não podem ser referidas por código em outras unidades de compilação do pacote, por exemplo, elas não podem ser tratadas como privadas do pacote. Isso é realmente algo que muda entre implementações Java?

Não conheço nenhum que não tenha essa restrição - todos os compiladores baseados em arquivos não permitirão que você se refira às classes de código-fonte em arquivos que não tenham o mesmo nome que a classe. (se você compilar um arquivo com várias classes e colocar as classes no caminho da classe, qualquer compilador as encontrará)

Pete Kirkham
fonte
1

De acordo com a Effective Java 2nd edition (Item 13):

"Se uma classe de nível superior (ou interface) privada de pacote for usada por apenas uma classe, considere tornar a classe de nível superior uma classe aninhada privada da única classe que a utiliza (Item 22). Isso reduz sua acessibilidade de todos as classes em seu pacote para a classe que o utiliza. Mas é muito mais importante reduzir a acessibilidade de uma classe pública gratuita do que uma classe de nível superior privada do pacote: ... "

A classe aninhada pode ser estática ou não estática, com base no fato de a classe membro precisar acessar a instância anexa (Item 22).

rezzy
fonte
O OP não está perguntando sobre classes aninhadas.
usar o seguinte código
0

Sim, você pode, com membros estáticos públicos em uma classe pública externa, assim:

public class Foo {

    public static class FooChild extends Z {
        String foo;
    }

    public static class ZeeChild extends Z {

    }

}

e outro arquivo que faz referência ao acima:

public class Bar {

    public static void main(String[] args){

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

    }
}

coloque-os na mesma pasta. Ajuntar com:

javac folder/*.java

e corra com:

 java -cp folder Bar
Alexander Mills
fonte
Esse exemplo não responde à pergunta. Você está dando um exemplo de classes estáticas aninhadas, que não é o mesmo que ter duas classes de nível superior definidas no mesmo arquivo.
Pedro García Medina
0

Apenas para sua informação, se você estiver usando o Java 11+, há uma exceção a esta regra: se você executar o seu arquivo java diretamente ( sem compilação ). Nesse modo, não há restrições para uma única classe pública por arquivo. No entanto, a classe com o mainmétodo deve ser a primeira no arquivo.

ZhekaKozlov
fonte
-5

Não. Você não pode. Mas é muito possível em Scala:

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