平台无关性
Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同平台上运行不需要重新编译,Java虚拟机在执行字节码的时候,把字节码转换为具体平台上的机器码。
反射
示例代码如下:
首先编写一个类:
public class ReflectTest {
private String name;
public String getName(){
return name;
}
public void publicMethod(String val){
System.out.println("enter publicMethod:"+val);
}
private void privateMethod(String val){
System.out.println("enter privateMethod:"+val);
}
}
里面定义了私有属性,私有方法和公有方法。下面通过反射机制来访问这些属性和方法。
// 1. 获取class对象
Class rc = Class.forName("com.example.demo.ReflectTest");
// 2. 创建反射类实例
ReflectTest reflectTest = (ReflectTest) rc.newInstance();
// 3. 获取类名
System.out.println("Class Name is :"+rc.getName());
// 4. getDeclaredMethod()可以获取除了父类和接口外的所有方法(只要是它自己定义的方法)
Method privateMethod = rc.getDeclaredMethod("privateMethod",String.class);
privateMethod.setAccessible(true);
// 5. 执行私有方法
privateMethod.invoke(reflectTest,"hello");
// 6.getMethod()只能获取有访问权限的方法,私有方法不行
Method publicMethod = rc.getMethod("publicMethod", String.class);
publicMethod.invoke(reflectTest,"world");
//7. 获取和修改私有属性
Field name = rc.getDeclaredField("name");
name.setAccessible(true);
name.set(reflectTest,"yun");
// 7.1 打印设置的属性
Method getNameMethod = rc.getMethod("getName");
System.out.println(getNameMethod.invoke(reflectTest));
getDeclaredMethod()
方法和getMethod()
方法的区别在于:
-
getDeclaredMethod()
可以获取除了父类和接口定义的方法以外的所有方法 -
getMethod()
只可以获取有访问权限的方法,如private方法不能获取
类的加载过程
ClassLoader的种类
BootStrapClassLoader::C++编写,加载核心库java.*
ExtClassLoader:Java编写,加载扩展库javax.*
AppClassLoader:Java编写,加载程序所在目录
自定义ClassLoader:Java编写,定制化加载
BootStrapClassLoader
BootStrapClassLoader作为核心的类加载器,加载出核心库给应用使用,开发者一般调用不到这个类。
ExtClassLoader
Java编写,加载扩展库javax.*。从其源码可以看出,它只获取java.ext.dirs
目录下的class文件:
private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
......
AppClassLoader
Java编写,加载程序所在目录,也就是我们项目中自己编写生成的class文件。从源码中可以看出,它会去加载
java.class.path
目录下的class文件:
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
......
自定义ClassLoader
在某些特殊情况下,可能需要自定义类加载器,下面是一个示例:
首先创建一个类加载器:
public class MyClassLoader extends ClassLoader {
private String path;
private String classLoaderName;
public MyClassLoader(String path,String classLoaderName){
this.path = path;
this.classLoaderName = classLoaderName;
}
@Override
public Class findClass(String name){
byte[] b = loadClassData(name);
return defineClass(name,b,0,b.length);
}
private byte[] loadClassData(String name){
name = path+name+".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try{
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i=0;
while((i=in.read()) != -1){
out.write(i);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try{
out.close();
in.close();
} catch (Exception e){
e.printStackTrace();
}
}
return out.toByteArray();
}
}
自定义类加载器最主要的就是实现findClass()
这个方法,里面的逻辑就是获取class文件,将其以字节数组的形式传给defineClass()
方法去执行(这个方法父类已经实现,不需要我们再去实现)。
接下来我们在桌面上创建一个类:
public class Test {
static{
System.out.println("hello world");
}
}
并将其编译出Test.class文件,准备加载。
最后编写测试类测试:
MyClassLoader myClassLoader = new MyClassLoader("C:\\Users\\Administrator\\Desktop\\","myClassLoader");
Class c = myClassLoader.loadClass("Test");
c.newInstance();
经过测试,我们自定义的类加载器已经可以正常加载class文件,并创建类的对象了,静态代码块的打印也正常显示了。
loadClass
和ForName
的区别
前面我们知道在反射的时候使用loadClass来加载类:
// 1. 获取class对象
Class rc = Class.forName("com.example.demo.ReflectTest");
// 2. 创建反射类实例
ReflectTest reflectTest = (ReflectTest) rc.newInstance();
而我们在使用自定义类加载器的时候使用ForName来加载:
MyClassLoader myClassLoader = new MyClassLoader("C:\\Users\\Administrator\\Desktop\\","myClassLoader");
Class c = myClassLoader.loadClass("Test");
c.newInstance();
那使用Class.forName()
和myClassLoader.loadClass()
这两种方式加载的区别是什么呢?
- 使用
ClassLoader.loadClass()
加载时,只对类进行加载操作,即类的加载第一阶段:将class文件加载到内存中,并未对类进行初始化等其他操作,所以类的static静态代码块中代码并未得到执行 - 而使用
Class.forName()
加载时,对类的加载三个节点都会执行,即加载,链接,初始化。所以类定义的静态代码块也会执行。
类加载器的双亲委派机制
四种类加载器相互协作。判断一个Class是否被加载过的逻辑是:首先判断自定义加载器是否已经加载过,如果没有再委派给它的父加载器去查看,一直往上进行判断。
为什么使用双亲委派机制去加载类:
- 避免多个类加载器重复加载,之前某个加载器加载过就不需要再重复加载了。
双亲委派机制体现在ClassLoader类的源码中:
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);
......
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}