Hay un capítulo en la Especificación de la máquina virtual Java que enumera específicamente los mnemónicos del código de operación, y el enlace correspondiente es: Especificación de la máquina virtual de Java: Capítulo 7. Mnemónicos del código de operación por código de operación
Entre ellos, los códigos de operación relacionados con las llamadas a métodos son:
decimal | hexadecimal | mnemotécnico | ilustrar |
---|---|---|---|
182 | (0xb6) | invocarvirtual | Invocar un método de instancia de una clase; |
183 | (0xb7) | invocar especial | Llamar a métodos de instancia especiales, como constructores, métodos de superclase y privados. |
184 | (0xb8) | invocadorestático | llamar al método estático |
185 | (0xb9) | invocar interfaz | método de interfaz de llamada |
186 | (0xba) | invocar dinámica | llamada a método dinámico |
Presentémoslo en detalle a través de ejemplos prácticos.
Por favor vea el código:
package com.cncounter.opcode;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 演示invoke操作码
*/
public class DemoInvokeOpcode {
public static void testMethodInvoke() {
// 183; invokespecial
HashMap<String, String> hashMap = new HashMap<String, String>(100);
// 182; invokevirtual
hashMap.put("name", "tiemao");
// 赋值给Map接口引用
Map<String, String> map = hashMap;
// 185; invokeinterface
map.putIfAbsent("url", "https://renfufei.blog.csdn.net");
// 使用lambda
List<String> upperKeys = map.keySet().stream()
// 186; invokedynamic
.map(i -> i.toUpperCase())
.collect(Collectors.toList());
// 184; invokestatic
String str = String.valueOf(upperKeys);
// 182; invokevirtual
System.out.println(str);
}
public static void main(String[] args) {
// 184; invokestatic
testMethodInvoke();
}
}
El resultado después de ejecutar el método principal es:
[NAME, URL]
Podemos compilar y descompilar con los siguientes comandos:
# 查看JDK工具的帮助信息
javac -help
javap -help
# 带调试信息编译
javac -g DemoInvokeOpcode.java
# 反编译
javap -v DemoInvokeOpcode.class
# 因为带了package, 所以执行时需要注意路径:
cd ../../..
java com.cncounter.opcode.DemoInvokeOpcode
Después de compilar javac, puede ver que solo se genera un archivo DemoInvokeOpcode.class
. Aquí es donde las lambdas se diferencian de las clases internas.
La información de código de bytes generada por la herramienta de descompilación javap es mucha, y se extrae la parte del método testMethodInvoke que más nos preocupa:
public static void testMethodInvoke();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=4, args_size=0
0: new #2 // class java/util/HashMap
3: dup
4: bipush 100
6: invokespecial #3 // Method java/util/HashMap."<init>":(I)V
9: astore_0
10: aload_0
11: ldc #4 // String name
13: ldc #5 // String tiemao
15: invokevirtual #6 // Method java/util/HashMap.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
18: pop
19: aload_0
20: astore_1
21: aload_1
22: ldc #7 // String url
24: ldc #8 // String https://renfufei.blog.csdn.net
26: invokeinterface #9, 3 // InterfaceMethod java/util/Map.putIfAbsent:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
31: pop
32: aload_1
33: invokeinterface #10, 1 // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
38: invokeinterface #11, 1 // InterfaceMethod java/util/Set.stream:()Ljava/util/stream/Stream;
43: invokedynamic #12, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function;
48: invokeinterface #13, 2 // InterfaceMethod java/util/stream/Stream.map:(Ljava/util/function/Function;)Ljava/util/stream/Stream;
53: invokestatic #14 // Method java/util/stream/Collectors.toList:()Ljava/util/stream/Collector;
56: invokeinterface #15, 2 // InterfaceMethod java/util/stream/Stream.collect:(Ljava/util/stream/Collector;)Ljava/lang/Object;
61: checkcast #16 // class java/util/List
64: astore_2
65: aload_2
66: invokestatic #17 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
69: astore_3
70: getstatic #18 // Field java/lang/System.out:Ljava/io/PrintStream;
73: aload_3
74: invokevirtual #19 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
77: return
LineNumberTable:
line 15: 0
line 17: 10
line 19: 19
line 21: 21
line 23: 32
line 25: 48
line 26: 53
line 28: 65
line 30: 70
line 31: 77
LocalVariableTable:
Start Length Slot Name Signature
10 68 0 hashMap Ljava/util/HashMap;
21 57 1 map Ljava/util/Map;
65 13 2 upperKeys Ljava/util/List;
70 8 3 str Ljava/lang/String;
LocalVariableTypeTable:
Start Length Slot Name Signature
10 68 0 hashMap Ljava/util/HashMap<Ljava/lang/String;Ljava/lang/String;>;
21 57 1 map Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;
65 13 2 upperKeys Ljava/util/List<Ljava/lang/String;>;
Una explicación sencilla es la siguiente:
- Para llamar a un método estático de una clase, se utiliza la instrucción invokestatic.
- Al llamar a un método a través de una referencia de interfaz, se compilará directamente en la instrucción invokeinterface.
- La llamada a un constructor se compilará en una instrucción especial de invocación, que por supuesto también incluye la llamada a un método privado, así como a un método de superclase visible.
- Si el tipo al que hace referencia la variable es una clase concreta, el compilador usa invokevirtual para invocar métodos en los niveles público, protegido y visible del paquete.
- JDK7 ha agregado una nueva
invokedynamic
instrucción para admitir el "lenguaje de tipo dinámico" (Dynamically TypedLanguage, la expresión lambda introducida desde JDK8, que se compilará en esta instrucción cuando se use.
Para obtener más artículos, consulte el proyecto de traducción de artículos en GitHub: https://github.com/cncounter/translation
Al mismo tiempo, ¡a todos, por favor, dale me gusta a Star para apoyar!
Enlace original: Artículo de 2020: 41. JVM en profundidad: ejemplos detallados de códigos de operación relacionados con la invocación