Por que o Java proíbe campos estáticos em classes internas?

85
class OuterClass {
 class InnerClass {
  static int i = 100; // compile error
  static void f() { } // compile error
 }
} 

Embora não seja possível acessar o campo estático com OuterClass.InnerClass.i, se eu quiser registrar algo que deve ser estático, por exemplo, o número de objetos InnerClass criados, seria útil tornar esse campo estático. Então, por que Java proíbe campos / métodos estáticos em classes internas?

EDITAR: Eu sei como deixar o compilador feliz com a classe aninhada estática (ou classe interna estática), mas o que eu quero saber é por que o java proíbe campos / métodos estáticos dentro de classes internas (ou classe interna comum) de design de linguagem e aspectos de implementação, se alguém souber mais sobre isso.

Jichao
fonte
3
Meu exemplo favorito é ter um Logger apenas para a classe interna. Não pode ser estático como todos os outros Loggers.
Piotr Findeisen
Desde Java 16, já que não é mais o caso - veja esta resposta .
Nicolai Parlog

Respostas:

32

A ideia por trás das classes internas é operar no contexto da instância envolvente. De alguma forma, permitir variáveis ​​e métodos estáticos contradiz essa motivação?

8.1.2 Classes internas e instâncias encerradas

Uma classe interna é uma classe aninhada que não é declarada estática explícita ou implicitamente. As classes internas não podem declarar inicializadores estáticos (§8.7) ou interfaces de membro. As classes internas não podem declarar membros estáticos, a menos que sejam campos de constantes de tempo de compilação (§15.28).

Gregory Pakosz
fonte
19
talvez seja apenas decidido assim
Gregory Pakosz
3
você não pode instanciar o interno não estático sem uma referência pai, mas ainda pode inicializá- lo.
Skaffman
Se os ClassLoaders mantiverem um cache que diz "A Classe X foi inicializada", então sua lógica não pode ser usada para inicializar múltiplas instâncias de Class [objetos que representam] X (que é o que é necessário quando objetos Class devem ser instanciados como classes internas dentro de vários objetos distintos).
Erwin Smout
@skaffman Ainda não faz sentido. As propriedades estáticas da classe interna serão inicializadas apenas uma vez, então qual seria o problema? No momento, tenho um hashmap estático e cerca de 4 métodos que manipulam apenas esse mapa, tornando-o mais ideal para agrupar tudo em uma classe interna. No entanto, o hashmap estático agora teria que viver fora, e possivelmente outras coisas relacionadas, o que é simplesmente estúpido. Qual seria o problema de ter propriedades estáticas inicializadas?
mmm
Desde Java 16, já que não é mais o caso - veja esta resposta .
Nicolai Parlog
56

o que eu quero saber é por que o java proíbe campos / métodos estáticos dentro de classes internas

Porque essas classes internas são classes internas de "instância". Ou seja, eles são como um atributo de instância do objeto envolvente.

Visto que são classes de "instância", não faz nenhum sentido permitir staticrecursos, pois staticfoi criado para funcionar sem uma instância em primeiro lugar.

É como se você tentasse criar um atributo estático / instância ao mesmo tempo.

Veja o seguinte exemplo:

class Employee {
    public String name;
}

Se você criar duas instâncias de funcionário:

Employee a = new Employee(); 
a.name = "Oscar";

Employee b = new Employee();
b.name = "jcyang";

Fica claro porque cada um tem seu valor pelo imóvel name, certo?

O mesmo acontece com a classe interna; cada instância de classe interna é independente da outra instância de classe interna.

Portanto, se você tentar criar um counteratributo de classe, não haverá como compartilhar esse valor entre duas instâncias diferentes.

class Employee {
    public String name;
    class InnerData {
        static count; // ??? count of which ? a or b? 
     }
}

Quando você cria a instância ae bno exemplo acima, qual seria o valor correto para a variável estática count? Não é possível determiná-lo, porque a existência da InnerDataclasse depende totalmente de cada um dos objetos envolventes.

É por isso que, quando a classe é declarada como static, ela não precisa mais de uma instância viva para viver a si mesma. Agora que não há dependência, você pode declarar livremente um atributo estático.

Eu acho que isso parece reiterativo, mas se você pensar sobre as diferenças entre atributos de instância e classe, fará sentido.

OscarRyz
fonte
4
Vou comprar sua explicação para as propriedades estáticas da classe interna, mas como @skaffman aponta em um comentário à minha resposta, e os métodos estáticos ? Parece que os métodos devem ser permitidos sem exigir que eles sejam separados de qualquer instância. Na verdade, em java, você pode chamar métodos estáticos em instâncias (embora seja considerado um estilo ruim). BTW: Eu perguntei a um colega para tentar compilar o código do OP como C # e faz compilação. Portanto, o C # aparentemente permite isso, o que demonstra que o que o OP deseja fazer não viola algum princípio OO fundamental.
Asaph
2
Acontece exatamente o mesmo com os métodos. O ponto aqui não são os atributos ou os métodos sendo statis ou não, mas o fato da própria classe interna ser uma instância "stuff". Quer dizer, o problema aqui é que uma instância dessa classe interna não existe até que a classe externa seja criada. Portanto, qual método seria despachado então se não houvesse nada. Você acertaria no ar apenas porque precisará de uma instância em primeiro lugar.
OscarRyz
1
Sobre C # ... bem. Não é válido OO apenas porque o C # permite, não quero dizer que seja errado, mas o C # inclui vários paradigmas para tornar o desenvolvimento mais fácil mesmo com o custo da consistência (você tem que aprender coisas novas com cada versão do .NET) e permite este e outros tipos de coisas, entre outras. Eu acho isso uma coisa boa. Se a comunidade achar que um recurso extra é legal o suficiente, C # pode tê-lo no futuro.
OscarRyz
2
@OscarRyz Por que você precisa de instâncias da classe interna para usar seus métodos / campos estáticos ? E um exemplo de método estático [útil] na classe interna é o método auxiliar privado.
Leonid Semyonov
1
Com o uso de final, campos estáticos são permitidos na classe interna em java. Como você explica esse cenário?
945
34

InnerClassnão pode ter staticmembros porque pertence a uma instância (de OuterClass). Se você declarar InnerClasscomo staticdesanexá-lo da instância, seu código será compilado.

class OuterClass {
    static class InnerClass {
        static int i = 100; // no compile error
        static void f() { } // no compile error
    }
}

BTW: você ainda será capaz de criar instâncias de InnerClass. staticneste contexto, permite que isso aconteça sem uma instância envolvente de OuterClass.

Asaph
fonte
6
InnerClassque não pertencem a OuterClass, casos de ele fazer. As duas classes em si não têm essa relação. A questão de por que você não pode ter métodos estáticos InnerClassainda permanece.
Skaffman
10

Na verdade, você pode declarar campos estáticos se eles forem constantes e forem escritos em tempo de compilação.

class OuterClass {
    void foo() {
        class Inner{
            static final int a = 5; // fine
            static final String s = "hello"; // fine
            static final Object o = new Object(); // compile error, because cannot be written during compilation
        }
    }
}
vmolchanov
fonte
8
  1. A sequência de inicialização da classe é um motivo crítico.

Como as classes internas são dependentes da instância da classe envolvente / Outer, a classe Outer precisa ser inicializada antes da inicialização da classe Inner.
Isso é o que JLS diz sobre a inicialização da classe. O que precisamos é, a classe T será inicializada se

  • Um campo estático declarado por T é usado e o campo não é uma variável constante.

Portanto, se a classe interna tiver um acesso de campo estático, isso causará a inicialização da classe interna, mas não garantirá que a classe envolvente seja inicializada.

  1. Isso violaria algumas regras básicas . você pode pular para a última seção (para two cases) para evitar coisas noob

Uma coisa sobre , quando alguma é, ela se comportará como uma classe normal em todos os sentidos e está associada à classe Externa.static nested classnested classstatic

Mas o conceito de Inner class/ é será associado à classe externa / envolvente. Observe o associado à instância, não à classe. Agora, associar a instância significa claramente que (a partir do conceito de variável de instância ) ela existirá dentro de uma instância e será diferente entre as instâncias. non-static nested classinstance

Agora, quando tornamos algo estático, esperamos que seja inicializado quando a classe estiver sendo carregada e deve ser compartilhado entre todas as instâncias. Mas por serem não estáticas, mesmo as próprias classes internas ( você pode definitivamente esquecer a instância da classe interna por enquanto ) não são compartilhadas com todas as instâncias da classe externa / envolvente ( pelo menos conceitualmente ), então como podemos esperar que alguma variável da classe interna será compartilhado entre todas as instâncias da classe interna.

Portanto, se o Java permitir o uso de variáveis ​​estáticas dentro de uma classe aninhada não estática. haverá dois casos .

  • Se for compartilhado com todas as instâncias da classe interna, violará o conceito de context of instance(variável de instância). Então é um NÃO.
  • Se não for compartilhado com todas as instâncias, ele violará o conceito de ser estático. Novamente NÃO.
Saif
fonte
5

Aqui está a motivação que considero mais adequada para este "limite": você pode implementar o comportamento de um campo estático de uma classe interna como um campo de instância do objeto externo; Portanto, você não precisa de campos / métodos estáticos . O comportamento que quero dizer é que todas as instâncias de classe interna de algum objeto compartilham um campo (ou método).

Então, suponha que você queira contar todas as instâncias de classe interna, você faria:

public class Outer{
    int nofInner; //this will count the inner class 
                  //instances of this (Outer)object
                  //(you know, they "belong" to an object)
    static int totalNofInner; //this will count all 
                              //inner class instances of all Outer objects
    class Inner {
        public Inner(){
            nofInner++;
            totalNofInner++;
        }
    }
}
ianos
fonte
2
Mas a questão é: qual é a razão para permitir campos estáticos quando eles são declarados finalentão?
Solace
se você olhar em [ stackoverflow.com/a/1954119/1532220] (OscarRyzs resposta acima): Sua motivação é que um valor não pode ser associado à variável. Claro, se a variável for final, você pode saber facilmente qual valor atribuir (você deve saber).
ianos de
2

Em palavras simples, classes internas não estáticas são variáveis ​​de instância para classe externa e são criadas apenas quando uma classe externa é criada e um objeto de classe externa é criado em tempo de execução, enquanto variáveis ​​estáticas são criadas no tempo de carregamento da classe. Portanto, a classe interna não estática é algo do tempo de execução, por isso que a estática não faz parte de uma classe interna não estática.

NOTA: trate as classes internas sempre como uma variável para uma classe externa, elas podem ser estáticas ou não estáticas como quaisquer outras variáveis.

Mannu
fonte
Mas a classe interna pode ter static finalconstantes.
Chovendo em
1

Porque causaria ambigüidade no significado de "estático".

As classes internas não podem declarar membros estáticos que não sejam constantes de tempo de compilação. Haveria uma ambigüidade sobre o significado de "estático". Isso significa que há apenas uma instância na máquina virtual? Ou apenas uma instância por objeto externo? Os designers da linguagem decidiram não resolver esse problema.

Retirado de "Core Java SE 9 for the Impacient" por Cay S. Horstmann. Pág. 90 Capítulo 2.6.3

aj hrishikesh
fonte
0

Do Java 16 em diante, esse não é mais o caso. Citando JEP 395 (sobre a finalização dos registros):

Relaxe a antiga restrição pela qual uma classe interna não pode declarar um membro que seja explícita ou implicitamente estático. Isso se tornará válido e, em particular, permitirá que uma classe interna declare um membro que é uma classe de registro.

Na verdade, o código a seguir pode ser compilado com Java 16 (tentado com 16.ea.27):

public class NestingClasses {

    public class NestedClass {

        static final String CONSTANT = new String(
                "DOES NOT COMPILE WITH JAVA <16");

        static String constant() {
            return CONSTANT;
        }

    }

}
Nicolai Parlog
fonte
-1

Eu acho que é para consistência. Embora não pareça haver nenhuma limitação técnica para isso, você não seria capaz de acessar membros estáticos da classe interna de fora, ou seja, OuterClass.InnerClass.iporque a etapa do meio não é estática.

Chochos
fonte
Mas a classe interna pode ter static finalconstantes.
Chovendo em