Enquanto pesquisava na Especificação da linguagem Java para responder a esta pergunta , aprendi que
Antes de uma classe ser inicializada, sua superclasse direta deve ser inicializada, mas as interfaces implementadas pela classe não são inicializadas. Da mesma forma, as superinterfaces de uma interface não são inicializadas antes de a interface ser inicializada.
Por curiosidade própria, experimentei e, como era de se esperar, a interface InterfaceType
não foi inicializada.
public class Example {
public static void main(String[] args) throws Exception {
InterfaceType foo = new InterfaceTypeImpl();
foo.method();
}
}
class InterfaceTypeImpl implements InterfaceType {
@Override
public void method() {
System.out.println("implemented method");
}
}
class ClassInitializer {
static {
System.out.println("static initializer");
}
}
interface InterfaceType {
public static final ClassInitializer init = new ClassInitializer();
public void method();
}
Este programa imprime
implemented method
No entanto, se a interface declara um default
método, a inicialização ocorre. Considere a InterfaceType
interface fornecida como
interface InterfaceType {
public static final ClassInitializer init = new ClassInitializer();
public default void method() {
System.out.println("default method");
}
}
então o mesmo programa acima iria imprimir
static initializer
implemented method
Em outras palavras, o static
campo da interface é inicializado ( etapa 9 no Procedimento de inicialização detalhado ) e o static
inicializador do tipo que está sendo inicializado é executado. Isso significa que a interface foi inicializada.
Não consegui encontrar nada no JLS que indicasse que isso deveria acontecer. Não me entenda mal, eu entendo que isso deve acontecer caso a classe de implementação não forneça uma implementação para o método, mas e se fornecer? Esta condição está faltando na Especificação da linguagem Java, eu perdi algo ou estou interpretando incorretamente?
fonte
interface
em Java não deveria definir nenhum método concreto. Portanto, estou surpreso que oInterfaceType
código foi compilado.default
métodos .Respostas:
Este é um assunto muito interessante!
Parece que a seção 12.4.1 do JLS deve cobrir isso definitivamente. No entanto, o comportamento do Oracle JDK e OpenJDK (javac e HotSpot) difere do que está especificado aqui. Em particular, o Exemplo 12.4.1-3 desta seção cobre a inicialização da interface. O exemplo a seguir:
Sua produção esperada é:
e, de fato, obtenho a saída esperada. No entanto, se um método padrão for adicionado à interface
I
,a saída muda para:
que indica claramente que interface
I
está sendo inicializada onde não estava antes! A mera presença do método padrão é suficiente para acionar a inicialização. O método padrão não precisa ser chamado, substituído ou mesmo mencionado, nem a presença de uma inicialização de gatilho de método abstrato.Minha especulação é que a implementação do HotSpot queria evitar adicionar verificação de inicialização de classe / interface no caminho crítico da
invokevirtual
chamada. Antes do Java 8 e dos métodos padrão,invokevirtual
nunca poderia terminar executando o código em uma interface, então isso não aconteceu. Pode-se pensar que isso faz parte do estágio de preparação de classe / interface ( JLS 12.3.2 ), que inicializa coisas como tabelas de métodos. Mas talvez isso tenha ido longe demais e, acidentalmente, tenha feito a inicialização completa.Eu levantei essa questão na lista de discussão de devedores do compilador OpenJDK. Houve uma resposta de Alex Buckley (editor do JLS) em que ele levanta mais questões direcionadas às equipes de implementação de JVM e lambda. Ele também observa que há um bug na especificação aqui onde diz "T é uma classe e um método estático declarado por T é invocado" também deve se aplicar se T for uma interface. Portanto, pode ser que haja bugs de especificação e de HotSpot aqui.
Divulgação : Eu trabalho para a Oracle em OpenJDK. Se as pessoas acharem que isso me dá uma vantagem injusta em obter a recompensa associada a essa pergunta, estou disposto a ser flexível quanto a isso.
fonte
A interface não é inicializada porque o campo constante
InterfaceType.init
, que está sendo inicializado por um valor não constante (chamada de método), não é usado em nenhum lugar.É sabido em tempo de compilação que o campo constante da interface não é usado em nenhum lugar, e a interface não contém nenhum método padrão (em java-8), portanto não há necessidade de inicializar ou carregar a interface.
A interface será inicializada nos seguintes casos,
No caso de métodos padrão , você está implementando
InterfaceType
. Portanto, IfInterfaceType
conterá quaisquer métodos padrão, Ele será INERIDO (usado) na implementação da classe. E a inicialização entrará em cena.Porém, se você estiver acessando o campo constante da interface (que é inicializado de maneira normal), a inicialização da interface não é necessária.
Considere o seguinte código.
No caso acima, a Interface será inicializada e carregada porque você está usando o campo
InterfaceType.init
.Não estou dando o exemplo de método padrão, pois você já deu isso em sua pergunta.
A especificação e o exemplo da linguagem Java são fornecidos em JLS 12.4.1 (o exemplo não contém métodos padrão).
Não consigo encontrar JLS para métodos padrão, pode haver duas possibilidades
fonte
default
método e uma classe que implementa a interface for inicializada.O arquivo instanceKlass.cpp do OpenJDK contém o método de inicialização
InstanceKlass::initialize_impl
que corresponde ao Procedimento de inicialização detalhado no JLS, que é encontrado analogamente no Initialization seção na Especificação JVM.Ele contém uma nova etapa que não é mencionada no JLS e não no livro JVM que é referido no código:
Portanto, essa inicialização foi implementada explicitamente como uma nova Etapa 7.5 . Isso indica que essa implementação seguiu algumas especificações, mas parece que a especificação escrita no site não foi atualizada de acordo.
EDITAR: Como referência, o commit (a partir de outubro de 2012!) Onde a respectiva etapa foi incluída na implementação: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362
EDIT2: Coincidentemente, encontrei este documento sobre métodos padrão em ponto de acesso que contém uma observação interessante no final:
fonte
Vou tentar argumentar que uma inicialização de interface não deve causar nenhum efeito colateral de canal lateral dos quais os subtipos dependem, portanto, se isso é um bug ou não, ou de qualquer maneira que o Java o conserte, não deve importar o aplicativo no qual as interfaces de ordem são inicializadas.
No caso de a
class
, é bem aceito que pode causar efeitos colaterais dos quais dependem as subclasses. Por exemploQualquer subclasse de
Foo
esperaria ver $ 1000 no banco, em qualquer lugar do código da subclasse. Portanto, a superclasse é inicializada antes da subclasse.Não deveríamos fazer a mesma coisa com as superintefaces também? Infelizmente, a ordem das superinterfaces não deve ser significativa, portanto, não há uma ordem bem definida para inicializá-las.
Portanto, é melhor não estabelecermos esse tipo de efeito colateral nas inicializações de interface. Afinal,
interface
não se destina a esses recursos (campos / métodos estáticos) que acumulamos por conveniência.Portanto, se seguirmos esse princípio, não haverá preocupação para nós em que ordem as interfaces são inicializadas.
fonte