Este artículo se 深入Java虚拟机(四)
basa en algunas ideas, desde otro aspecto, para estudiar el problema de la carga de clases.
Programación orientada a interfaces
Una interfaz para obtener el saldo de la billetera se define en el catálogo de ingeniería del proyecto, y como una persona común, regresará a 50 océanos para que usted coma, beba y juegue.
public interface Pocket {
int getBalance();
}
public class NormalUser implements Pocket{
@Override
public int getBalance() {
return 50;
}
}
Compilé otra versión en el escritorio NormalUser
y agregué una cuota a mi billetera.
public class NormalUser implements Pocket{
@Override
public int getBalance() {
return 50000;
}
}
Ahora quiero mejorar la calidad de vida, así que hice lo siguiente:
Personalice un cargador de clases en el proyecto:
public class LocalClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String path = "/Users/lijie/Desktop/NormalUser.class";
try {
FileInputStream ins = new FileInputStream(path);
int size = ins.available();
byte[] clazzBytes = new byte[size];
if (ins.read(clazzBytes) > 0) {
return defineClass("hua.lee.classloader.NormalUser", clazzBytes, 0, size);
} else {
throw new ClassNotFoundException();
}
} catch (IOException e) {
e.printStackTrace();
}
throw new ClassNotFoundException();
}
}
LocalClassLoader
La escritura es relativamente rígida, pero la idea de ganar dinero está ahí. . . .
LocalClassLoader
Reescribir el findClass
método no se anula loadClass
, por lo que 双亲委托机制
también es efectivo. Para los tipos que no se pueden encontrar en el findClass
sistema, vendré aquí, y en el método, NormalUser
cargaré el escritorio .
Veamos el código de prueba:
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> clazz = Class.forName("hua.lee.classloader.NormalUser");
System.out.println("系统自带 ClassLoader=" + clazz.getClassLoader());
Pocket pocket = (Pocket) clazz.newInstance();
System.out.println("系统自带:"+pocket.getBalance());
//没有重写 loadClass,维持双亲委派机制
LocalClassLoader lcl = new LocalClassLoader();
//我们加个后缀名,这样双亲委托查找失败了,就调用LocalClassLoader.findClass 方法加载
//其实 findClass 方法还是加载 NormalUser类,只是换了个位置和方式:
//defineClass("hua.lee.classloader.NormalUser", clazzBytes, 0, size);
Class<?> clazzOut02 = lcl.loadClass("hua.lee.classloader.NormalUser_Temp");
//双亲委托机制先检查当前类加载器中有没有这个类的 Class 实例,没有的话再搜寻父类,
//查找到 hua.lee.classloader.NormalUser 的 Class 实例,就返回该对象
Class<?> clazzOut01 = lcl.loadClass("hua.lee.classloader.NormalUser");
System.out.println("clazzOut01 自定义 ClassLoader=" + clazzOut01.getClassLoader());
pocket = (Pocket) clazzOut01.newInstance();
System.out.println("clazzOut01 自定义加载器获取余额:"+pocket.getBalance());
System.out.println("clazzOut02 自定义 ClassLoader=" + clazzOut02.getClassLoader());
pocket = (Pocket) clazzOut02.newInstance();
System.out.println("clazzOut02 自定义加载器获取余额:"+pocket.getBalance());
System.out.println(clazz==clazzOut01);
System.out.println(clazz==clazzOut02);
System.out.println(clazzOut01==clazzOut02);
}
La salida de la consola es así:
系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
clazzOut01 自定义加载器获取余额:50000
clazzOut02 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
clazzOut02 自定义加载器获取余额:50000
false //clazz==clazzOut01
false //clazz==clazzOut02
true //clazzOut01==clazzOut02
Siento que he encontrado la manera de hacerme rico. No 加载约束
lo vi en la máquina virtual .
Programación abstracta
El camino anterior hacia la riqueza parece ser muy sencillo, así que intentemos de la siguiente manera:
public abstract class Pocket {
abstract int getBalance();
}
public class NormalUser extends Pocket{
@Override
public int getBalance() {
return 50;
}
}
Del mismo modo, compilé una versión enriquecida en el escritorio:
public class NormalUser extends Pocket{
@Override
public int getBalance() {
return 50000;
}
}
LocalClassLoader
Y el código de prueba no ha cambiado, pero el resultado impreso es así:
系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
Exception in thread "main" java.lang.AbstractMethodError
at hua.lee.classloader.ClassLoaderTest.main(ClassLoaderTest.java:25)
¿Existe alguna diferencia entre el análisis de la máquina virtual 抽象类实现
y 接口实现
el tiempo ?
Especulación inmadura
El primer 面向接口
modo debería evitar las restricciones de carga, o la máquina virtual está diseñada para admitir interfaces como esta.
Ahora que se le solicita AbstractMethodError
, cambiaré el código de prueba, ya no usaré clases abstractas Pocket
, apuntaré directamente a NormalUser
:
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> clazz = Class.forName("hua.lee.classloader.NormalUser");
System.out.println("系统自带 ClassLoader=" + clazz.getClassLoader());
//请注意此处⬇️
NormalUser pocket = (NormalUser) clazz.newInstance();
//请注意此处⬆️
System.out.println("系统自带:"+pocket.getBalance());
//没有重写 loadClass,维持双亲委派机制
LocalClassLoader lcl = new LocalClassLoader();
//我们加个后缀名,这样双亲委托查找失败了,就调用LocalClassLoader.findClass 方法加载
//其实 findClass 方法还是加载 NormalUser类,只是换了个位置和方式:
//defineClass("hua.lee.classloader.NormalUser", clazzBytes, 0, size);
Class<?> clazzOut02 = lcl.loadClass("hua.lee.classloader.NormalUser_Temp");
//双亲委托机制会自动搜寻父类,
//查找到 hua.lee.classloader.NormalUser 的 Class 实例,就返回该对象
Class<?> clazzOut01 = lcl.loadClass("hua.lee.classloader.NormalUser");
System.out.println("clazzOut01 自定义 ClassLoader=" + clazzOut01.getClassLoader());
pocket = (NormalUser) clazzOut01.newInstance();
System.out.println("clazzOut01 自定义加载器获取余额:"+pocket.getBalance());
System.out.println("clazzOut02 自定义 ClassLoader=" + clazzOut02.getClassLoader());
pocket = (NormalUser) clazzOut02.newInstance();
System.out.println("clazzOut02 自定义加载器获取余额:"+pocket.getBalance());
System.out.println(clazz==clazzOut01);
System.out.println(clazz==clazzOut02);
System.out.println(clazzOut01==clazzOut02);
}
}
Llegó la sorpresa y el resultado fue el siguiente:
系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
Exception in thread "main" java.lang.ClassCastException: hua.lee.classloader.NormalUser cannot be cast to hua.lee.classloader.NormalUser
at hua.lee.classloader.ClassLoaderTest.main(ClassLoaderTest.java:24)
Esta anomalía indica que un problema que cargamos de manera personalizada hua.lee.classloader.NormalUser
no es lo que quiere la máquina virtual hua.lee.classloader.NormalUser
.
La primera pregunta: ¿Cómo NormalUser
sabe la máquina virtual que estos dos son diferentes?
Tiene 定义类装载器
algo que ver con, si el tipo con el mismo nombre completo no es el mismo 定义类装载器
, la máquina virtual lo considerará como no del mismo tipo. (De acuerdo con las restricciones de carga, se debe informar un error)
¿Cuál es la definición de cargador de clases? Es el ClassLoader el que realmente carga el archivo de clase
La segunda pregunta: ¿Por qué la interfaz puede funcionar así?
Eso es porque 面向接口实现
la versión no nombra explícitamente el tipo NormalUser
. Las conversiones de tipos se Pocket
realizan según el tipo. La máquina virtual encuentra automáticamente la implementación del tipo de interfaz. Siempre que se Pocket
realice.
Si lo cambia pocket = (Pocket) clazzOut01.newInstance();
a pocket = (NormalUser) clazzOut01.newInstance();
, 面向接口
la versión también se informará en este momento ClassCastException
.
Para la máquina virtual, en la clase de implementación de la interfaz, no le importa la fuente de su clase de implementación (no importa qué cargador la cargue), solo se ajusta a la definición de la interfaz.
Pero cuando especifica la conversión de tipos, tiene que realizar una verificación de tipos.
La tercera pregunta: ¿Por qué no funcionan las clases abstractas?
Primer AA java.lang.AbstractMethodError
:
Cuando Pocket
es una clase abstracta, de acuerdo con la definición de la clase abstracta, debe haber una clase de implementación específica para ejecutar.
Para pocket = (Pocket) clazzOut01.newInstance();
esta oración, Pocket
la conversión de tipo forzada es exitosa (porque el Poket
tipo de análisis necesita ejecutar la 双亲委派
lógica, después del análisis, se encuentra que todos son iguales Poket
), pero al buscar la clase de implementación, encontré que el clazzOut01.newInstance()
tipo correspondiente de este objeto NormalUser
no es el que quiero, virtual La máquina piensa que no hay una implementación correspondiente, por lo que cuando llega la ejecución pocket.getBalance()
, la máquina virtual piensa que el programa debería ser Pocket
un método de ejecución puramente abstracto y reporta un error java.lang.AbstractMethodError
.
Si se entiende lo anterior, entonces java.lang.ClassCastException
es fácil de entender:
Para pocket = (NormalUser) clazzOut01.newInstance();
esta oración, la máquina virtual clazzOut01.newInstance()
forzó la conversión del objeto a NormalUser
, y aquí está la respuesta a la primera pregunta: clazzOut01
la conversión representativa NormalUser
y forzada NormalUser
no es del mismo tipo, la máquina virtual reporta directamente un error
Conclusión
¿Por qué esto tiene un final?
Debido a 不成熟的推测
que es realmente el resultado de mi propio YY, no puedo garantizar que sea verdadero y efectivo. Si hay errores, no dudes en aclararme.
El próximo artículo continuará Java虚拟机(五)垃圾收集
(siempre siento que tengo que escribir sobre la clasificación de la basura, del tipo que los cerdos no pueden comer)