A execução de um programa Java requer duas etapas: compilação e execução (interpretação). Ao mesmo tempo, Java é uma linguagem de programação orientada a objetos. Quando a subclasse e a classe pai têm o mesmo método, e a subclasse substitui o método da classe pai, quando o programa chama o método em tempo de execução, ele deve chamar o método da classe pai ou o método substituído da subclasse? deve ser a pergunta quando aprendemos Java pela primeira vez. Aqui primeiro determinaremos qual método chamar ou a operação de variáveis é chamada de ligação.
Existem dois métodos de ligação em Java, um é a ligação estática, também chamada de ligação antecipada. A outra é a ligação dinâmica, também conhecida como ligação tardia.
Comparação de diferenças
1. A ligação estática ocorre em tempo de compilação e a ligação dinâmica ocorre em tempo de execução.
2. Use variáveis ou métodos modificados com private, static ou final e use vinculação estática. Os métodos virtuais (métodos que podem ser substituídos por subclasses) serão vinculados dinamicamente com base no objeto de tempo de execução.
3. A ligação estática é concluída usando informações de classe, enquanto a ligação dinâmica precisa ser concluída usando informações de objeto.
4. O método sobrecarregado é concluído usando ligação estática, enquanto o método de substituição é concluído usando ligação dinâmica.
Exemplo de método sobrecarregado
Aqui está um exemplo de métodos sobrecarregados.
Copie o código do código da seguinte forma:
classe pública TestMain {
public static void main(String[] args) {
Stringstr = new String();
chamador chamador = new chamador();
chamador.call(str);
}
Chamador de classe estática {
chamada pública void(Objeto obj) {
System.out.println("uma instância de objeto no chamador");
}
chamada pública nula(String str) {
System.out.println("uma instância de String no chamador");
}
}
}
O resultado da execução é
Copie o código do código da seguinte forma:
22:19 $javaTestMain
uma instância de String no Caller
No código acima, existem duas implementações sobrecarregadas do método de chamada. Uma recebe um objeto do tipo Object como parâmetro e a outra recebe um objeto do tipo String como parâmetro. str é um objeto String e todos os métodos de chamada que recebem parâmetros do tipo String serão chamados. A ligação aqui é uma ligação estática baseada no tipo de parâmetro em tempo de compilação.
verificar
Apenas olhar para a aparência não pode provar que a ligação estática foi executada. Você pode verificar isso usando javap para compilá-lo.
Copie o código do código da seguinte forma:
22:19 $ javap -c TestMain
Compilado de "TestMain.java"
classe pública TestMain {
publicTestMain();
Código:
0: aload_0
1: invocaespecial #1 // Método java/lang/Object."<init>":()V
4: retorno
public static void main(java.lang.String[]);
Código:
0: novo #2 // classe java/lang/String
3: idiota
4: invocaespecial #3 // Método java/lang/String."<init>":()V
7: uma loja_1
8: novo #4 // classe TestMain$Caller
11: dup
12: invocaespecial #5 // Método TestMain$Caller."<init>":()V
15: uma loja_2
16: carga_2
17: aload_1
18: invocavirtual #6 // Método TestMain$Caller.call:(Ljava/lang/String;)V
21: retorno
}
Eu vi esta linha 18: invocavirtual #6 // O método TestMain$Caller.call:(Ljava/lang/String;)V está de fato vinculado estaticamente, o que confirma que o método chamador que recebe um objeto String como parâmetro é chamado.
Exemplo de substituição de um método
Copie o código do código da seguinte forma:
classe pública TestMain {
public static void main(String[] args) {
Stringstr = new String();
chamador chamador = new SubCaller();
chamador.call(str);
}
Chamador de classe estática {
chamada pública nula(String str) {
System.out.println("uma instância de String no Caller");
}
}
classe estática SubCaller estende Caller {
@Substituir
chamada pública nula(String str) {
System.out.println("uma instância de String em SubCaller");
}
}
}
O resultado da execução é
Copie o código do código da seguinte forma:
22:27 $javaTestMain
uma instância de String em SubCaller
No código acima, há uma implementação do método call em Caller. SubCaller herda Caller e reescreve a implementação do método call. Declaramos uma variável callerSub do tipo Caller, mas esta variável aponta para um objeto SubCaller. De acordo com os resultados, pode-se observar que ele chama a implementação do método de chamada do SubCaller ao invés do método de chamada do Caller. A razão para esse resultado é que a ligação dinâmica ocorre em tempo de execução e durante o processo de ligação é necessário determinar qual versão da implementação do método de chamada chamar.
verificar
A ligação dinâmica não pode ser verificada diretamente usando javap, e se for provado que a ligação estática não é executada, significa que a ligação dinâmica é executada.
Copie o código do código da seguinte forma:
22:27 $ javap -c TestMain
Compilado de "TestMain.java"
classe pública TestMain {
publicTestMain();
Código:
0: aload_0
1: invocaespecial #1 // Método java/lang/Object."<init>":()V
4: retorno
public static void main(java.lang.String[]);
Código:
0: novo #2 // classe java/lang/String
3: idiota
4: invocaespecial #3 // Método java/lang/String."<init>":()V
7: uma loja_1
8: novo #4 // classe TestMain$SubCaller
11: dup
12: invocaespecial #5 // Método TestMain$SubCaller."<init>":()V
15: uma loja_2
16: carga_2
17: aload_1
18: invocavirtual #6 // Método TestMain$Caller.call:(Ljava/lang/String;)V
21: retorno
}
Como resultado acima, 18: invocavirtual #6 // Método TestMain$Caller.call:(Ljava/lang/String;)V Este é TestMain$Caller.call em vez de TestMain$SubCaller.call, porque a sub-rotina de chamada não pode ser determinada em tempo de compilação A classe ainda é a implementação da classe pai, portanto, só pode ser tratada por ligação dinâmica em tempo de execução.
Quando recarregar encontra reescrever
O exemplo a seguir é um pouco anormal. Existem duas sobrecargas do método call na classe Caller. O que é mais complicado é que SubCaller integra Caller e substitui esses dois métodos. Na verdade, esta situação é uma situação composta das duas situações acima.
O código a seguir executará primeiro a ligação estática para determinar o método de chamada cujo parâmetro é um objeto String e, em seguida, executará a ligação dinâmica em tempo de execução para determinar se deve executar a implementação de chamada da subclasse ou da classe pai.
Copie o código do código da seguinte forma:
classe pública TestMain {
public static void main(String[] args) {
Stringstr = new String();
Chamador callerSub = new SubCaller();
callerSub.call(str);
}
Chamador de classe estática {
chamada pública void(Objeto obj) {
System.out.println("uma instância de objeto no chamador");
}
chamada pública nula(String str) {
System.out.println("uma instância de String no chamador");
}
}
classe estática SubCaller estende Caller {
@Substituir
chamada pública void(Objeto obj) {
System.out.println("uma instância de objeto em SubCaller");
}
@Substituir
chamada pública nula(String str) {
System.out.println("uma instância de String em SubCaller");
}
}
}
O resultado da execução é
Copie o código do código da seguinte forma:
22:30 $javaTestMain
uma instância de String em SubCaller
verificar
Como foi apresentado acima, postarei apenas os resultados da descompilação aqui.
Copie o código do código da seguinte forma:
22:30 $ javap -c TestMain
Compilado de "TestMain.java"
classe pública TestMain {
publicTestMain();
Código:
0: aload_0
1: invocaespecial #1 // Método java/lang/Object."<init>":()V
4: retorno
public static void main(java.lang.String[]);
Código:
0: novo #2 // classe java/lang/String
3: idiota
4: invocaespecial #3 // Método java/lang/String."<init>":()V
7: uma loja_1
8: novo #4 // classe TestMain$SubCaller
11: dup
12: invocaespecial #5 // Método TestMain$SubCaller."<init>":()V
15: uma loja_2
16: carga_2
17: aload_1
18: invocavirtual #6 // Método TestMain$Caller.call:(Ljava/lang/String;)V
21: retorno
}
Perguntas curiosas
Não é possível usar vinculação dinâmica?
Na verdade, em teoria, a ligação de certos métodos também pode ser alcançada por ligação estática. por exemplo:
Copie o código do código da seguinte forma:
public static void main(String[] args) {
Stringstr = new String();
chamador final callerSub = new SubCaller();
callerSub.call(str);
}
Por exemplo, aqui callerSub contém o objeto de subCaller e a variável callerSub é final, e o método de chamada é executado imediatamente. Em teoria, o compilador pode saber que o método de chamada de SubCaller deve ser chamado por meio de análise suficiente do código.
Mas por que não há ligação estática?
Suponha que nosso Caller herde da classe BaseCaller de uma determinada estrutura, que implementa o método de chamada, e BaseCaller herde de SuperCaller. O método call também é implementado no SuperCaller.
Suponha que BaseCaller e SuperCaller em um determinado framework 1.0
Copie o código do código da seguinte forma:
classe estática SuperCaller {
chamada pública void(Objeto obj) {
System.out.println("uma instância de objeto no SuperCaller");
}
}
classe estática BaseCaller estende SuperCaller {
chamada pública void(Objeto obj) {
System.out.println("uma instância de objeto em BaseCaller");
}
}
Implementamos isso usando o framework 1.0. Caller herda de BaseCaller e chama o método super.call.
Copie o código do código da seguinte forma:
classe pública TestMain {
public static void main(String[] args) {
Objeto obj = new Objeto();
SuperCaller callerSub = new SubCaller();
callerSub.call(obj);
}
Chamador de classe estática estende BaseCaller{
chamada pública void(Objeto obj) {
System.out.println("uma instância de objeto no chamador");
super.call(obj);
}
chamada pública nula(String str) {
System.out.println("uma instância de String no chamador");
}
}
classe estática SubCaller estende Caller {
@Substituir
chamada pública void(Objeto obj) {
System.out.println("uma instância de objeto em SubCaller");
}
@Substituir
chamada pública nula(String str) {
System.out.println("uma instância de String em SubCaller");
}
}
}
Em seguida, compilamos o arquivo de classe com base na versão 1.0 desta estrutura, assumindo que a ligação estática pode determinar que o super.call do Caller acima seja implementado como BaseCaller.call.
Então assumimos novamente que BaseCaller não reescreve o método de chamada do SuperCaller na versão 1.1 desta estrutura. Então a suposição acima de que a implementação da chamada que pode ser vinculada estaticamente causará problemas na versão 1.1, porque super.call deve usar SuperCall na versão. 1.1. implementação do método de chamada, em vez de assumir que a implementação do método de chamada do BaseCaller é determinada por ligação estática.
Portanto, algumas coisas que podem realmente ser vinculadas estaticamente são simplesmente vinculadas dinamicamente em consideração à segurança e à consistência.
Inspiração de otimização obtida?
Como a vinculação dinâmica precisa determinar qual versão da implementação do método ou variável será executada em tempo de execução, ela consome mais tempo do que a vinculação estática.
Portanto, sem afetar o design geral, podemos considerar a modificação de métodos ou variáveis com private, static ou final.