Dependência do tempo de compilação vs tempo de execução - Java

95

Qual é a diferença entre dependências de tempo de compilação e tempo de execução em Java? Está relacionado ao caminho da classe, mas como eles diferem?

Kunal
fonte

Respostas:

82
  • Dependência de tempo de compilação : você precisa da dependência em seu CLASSPATHpara compilar seu artefato. Eles são produzidos porque você tem algum tipo de "referência" à dependência codificada em seu código, como chamar newalguma classe, estender ou implementar algo (direta ou indiretamente) ou uma chamada de método usando a reference.method()notação direta .

  • Dependência de tempo de execução : você precisa da dependência em seu CLASSPATHpara executar seu artefato. Eles são produzidos porque você executa o código que acessa a dependência (de forma codificada ou por meio de reflexão ou qualquer outra coisa).

Embora a dependência de tempo de compilação geralmente implique dependência de tempo de execução, você pode ter uma dependência apenas de tempo de compilação. Isso se baseia no fato de que o Java apenas vincula dependências de classe no primeiro acesso a essa classe, portanto, se você nunca acessar uma classe específica em tempo de execução porque um caminho de código nunca é percorrido, o Java irá ignorar a classe e suas dependências.

Exemplo disso

Em C.java (gera C.class):

package dependencies;
public class C { }

Em A.java (gera A.class):

package dependencies;
public class A {
    public static class B {
        public String toString() {
            C c = new C();
            return c.toString();
        }
    }
    public static void main(String[] args) {
        if (args.length > 0) {
            B b = new B();
            System.out.println(b.toString());
        }
    }
}

Neste caso, Atem uma dependência em tempo de compilação em Cthrough B, mas só terá uma dependência em tempo de execução em C se você passar alguns parâmetros durante a execução java dependencies.A, pois a JVM só tentará resolver Ba dependência de Cquando chegar a executar B b = new B(). Este recurso permite que você forneça em tempo de execução apenas as dependências das classes que você usa em seus caminhos de código e ignore as dependências do resto das classes no artefato.

gpeche
fonte
1
Sei que agora é uma resposta muito antiga, mas como a JVM pode não ter C como uma dependência de tempo de execução desde o início? Se for capaz de reconhecer "aqui está uma referência a C, hora de adicioná-la como uma dependência", então C já não é essencialmente uma dependência, uma vez que a JVM o reconhece e sabe onde está?
wearebob
@wearebob Poderia ter sido especificado dessa forma, eu acho, mas eles decidiram que a vinculação lenta era melhor e, pessoalmente, concordo pelo motivo declarado acima: permite que você use algum código se necessário, mas não o força a incluí-lo em sua implantação, se você não precisar dela. Isso é muito útil ao lidar com código de terceiros.
gpeche
Se eu tiver um jar implantado em algum lugar, ele já terá que conter todas as suas dependências. Ele não sabe se será executado com argumentos ou não (portanto, não sabe se C será usado ou não), portanto, teria que ter C disponível de qualquer maneira. Eu simplesmente não vejo como qualquer memória / tempo é salvo por não ter C no caminho de classe desde o início.
Wearebob
1
@wearebob um JAR não precisa incluir todas as suas dependências. É por isso que quase todos os aplicativos não triviais têm um diretório / lib ou similar contendo vários JARs.
gpeche
34

Um exemplo fácil é olhar para uma api como a servlet api. Para fazer seus servlets compilarem, você precisa de servlet-api.jar, mas no tempo de execução o contêiner de servlet fornece uma implementação de API de servlet, portanto, você não precisa incluir servlet-api.jar em seu caminho de classe de tempo de execução.

Martin Algesten
fonte
Para esclarecimento (isso me confundiu), se você estiver usando maven e construindo uma guerra, "servlet-api" é geralmente uma dependência "fornecida" em vez de uma dependência de "tempo de execução", o que faria com que fosse incluída na guerra, se Estou correto.
xdhmoore
2
'fornecido' significa incluir em tempo de compilação, mas não agrupá-lo no WAR ou em outra coleção de dependências. 'runtime' faz o oposto (não disponível na compilação, mas empacotado com o WAR).
KC Baltz
30

O compilador precisa do classpath correto para compilar chamadas para uma biblioteca (dependências de tempo de compilação)

A JVM precisa do classpath correto para carregar as classes na biblioteca que você está chamando (dependências de tempo de execução).

Eles podem ser diferentes de duas maneiras:

1) se sua classe C1 chama a classe de biblioteca L1 e L1 chama a classe de biblioteca L2, então C1 tem uma dependência de tempo de execução em L1 e L2, mas apenas uma dependência de tempo de compilação em L1.

2) se sua classe C1 instancia dinamicamente uma interface I1 usando Class.forName () ou algum outro mecanismo, e a classe de implementação para a interface I1 é a classe L1, então C1 tem uma dependência de tempo de execução em I1 e L1, mas apenas uma dependência de tempo de compilação em I1.

Outras dependências "indiretas" que são iguais para tempo de compilação e tempo de execução:

3) sua classe C1 estende a classe de biblioteca L1 e L1 implementa a interface I1 e estende a classe de biblioteca L2: C1 tem uma dependência de tempo de compilação em L1, L2 e I1.

4) sua classe C1 tem um método foo(I1 i1)e um método bar(L1 l1)onde I1 é uma interface e L1 é uma classe que recebe um parâmetro que é a interface I1: C1 tem uma dependência de tempo de compilação em I1 e L1.

Basicamente, para fazer algo interessante, sua classe precisa fazer interface com outras classes e interfaces no caminho de classe. O gráfico de classe / interface formado por esse conjunto de interfaces de biblioteca produz a cadeia de dependência do tempo de compilação. As implementações da biblioteca geram a cadeia de dependência do tempo de execução. Observe que a cadeia de dependência do tempo de execução é dependente do tempo de execução ou falha lenta: se a implementação de L1 às vezes depende da instanciação de um objeto da classe L2 e essa classe só é instanciada em um cenário específico, então não há dependência, exceto esse cenário.

Jason S
fonte
1
A dependência de tempo de compilação no exemplo 1 não deveria ser L1?
BalusC
Obrigado, mas como funciona o carregamento de classe em tempo de execução? Em tempo de compilação, é fácil de entender. Mas em tempo de execução, como ele age, em um caso em que tenho dois Jars de versões diferentes? Qual deles ele vai escolher?
Kunal
1
Tenho certeza que o classloader padrão pega o classpath e o percorre em ordem, então se você tiver dois jars no classpath que contêm a mesma classe (por exemplo, com.example.fooutils.Foo), ele usará aquele que é o primeiro no caminho de classe. Ou isso ou você obterá um erro declarando a ambigüidade. Mas se você quiser mais informações específicas para carregadores de classe, deve fazer uma pergunta separada.
Jason S
Acho que no primeiro caso, as dependências de tempo de compilação também devem estar lá em L2, ou seja, a sentença deve ser: 1) se sua classe C1 chama a classe de biblioteca L1 e L1 chama a classe de biblioteca L2, então C1 tem uma dependência de tempo de execução em L1 e L2, mas apenas uma dependência do tempo de compilação em L1 e L2. Isso é verdade, pois no tempo de compilação também quando o compilador java verifica L1, então ele também verifica todas as outras classes referenciadas por L1 (excluindo as dependências dinâmicas como Class.forName ("myclassname)) ... caso contrário, como ele verifica isso a compilação está funcionando bem. Explique se você tiver outra opinião
Rajesh Goel
1
Não. Você precisa ler sobre como a compilação e a vinculação funcionam em Java. A única preocupação do compilador, quando se refere a uma classe externa, é como usar essa classe, por exemplo, quais são seus métodos e campos. Não importa o que realmente acontece nos métodos dessa classe externa. Se L1 chama L2, isso é um detalhe de implementação de L1, e L1 já foi compilado em outro lugar.
Jason S
13

Na verdade, o Java não vincula nada em tempo de compilação. Ele apenas verifica a sintaxe usando as classes correspondentes que encontra no CLASSPATH. Não é até o tempo de execução que tudo é montado e executado com base no CLASSPATH naquele momento.

JOTN
fonte
Não é até o tempo de carregamento ... o tempo de execução é diferente do tempo de carregamento.
overexchange de
11

As dependências do tempo de compilação são apenas as dependências (outras classes) que você usa diretamente na classe que está compilando. As dependências de tempo de execução abrangem as dependências diretas e indiretas da classe que você está executando. Assim, as dependências de tempo de execução incluem dependências de dependências e quaisquer dependências de reflexão, como nomes de classe que você tem em a String, mas são usadas em Class#forName().

BalusC
fonte
Obrigado, mas como funciona o carregamento de classe em tempo de execução? Em tempo de compilação, é fácil de entender. Mas em tempo de execução, como ele age, em um caso em que tenho dois Jars de versões diferentes? Qual classe Class.forName () selecionaria no caso de várias classes de classes diferentes em um caminho de classe?
Kunal
O que corresponde ao nome, é claro. Se você realmente quer dizer "várias versões da mesma classe", então depende do carregador de classe. O "mais próximo" será carregado.
BalusC
Bem, eu acho que se você tiver A.jar com A, B.jar com B extends Ae C.jar com, C extends Bentão C.jar depende do tempo de compilação de A.jar, embora a dependência de C em A seja indireta.
gpeche
1
O problema em todas as dependências em tempo de compilação é a dependência da interface (se a interface é por meio de métodos de uma classe, ou por meio de métodos de uma interface, ou por meio de um método que contém um argumento que é uma classe ou interface)
Jason S
2

Para Java, a dependência do tempo de compilação é a dependência do código-fonte. Por exemplo, se a classe A chama um método da classe B, então A é dependente de B no momento da compilação, já que A precisa saber sobre B (tipo de B) a ser compilado. O truque aqui deve ser este: o código compilado ainda não é um código completo e executável. Inclui endereços substituíveis (símbolos, metadados) para as fontes que ainda não foram compiladas ou existentes em jars externos. Durante a vinculação, esses endereços devem ser substituídos por endereços reais na memória. Para fazer isso corretamente, símbolos / endereços corretos devem ser criados. E isso pode ser feito com o tipo da aula (B). Acredito que essa seja a principal dependência no momento da compilação.

A dependência do tempo de execução está mais relacionada ao fluxo de controle real. Envolve endereços de memória reais. É uma dependência que você tem quando seu programa está em execução. Você precisa de detalhes de classe B aqui, como implementações, não apenas as informações de tipo. Se a classe não existir, você obterá RuntimeException e a JVM será encerrada.

Ambas as dependências, geralmente e não devem, fluir na mesma direção. No entanto, isso é uma questão de design OO.

Em C ++, a compilação é um pouco diferente (não just-in-time), mas também tem um vinculador. Portanto, o processo pode ser considerado semelhante ao Java, eu acho.

stdout
fonte