Java不是动态语言,但它却有着一个非常突出的动态相关机制-反射。用在Java身上表现为我们可以于运行时加载、探知、使用编译期间完全未知的class。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造,并生成其对象实体、对其fields设值、调用其methods。
反射是JDK5.0提供的java新特性,反射的出现打破了java一些常规的规则,反射可以强行查看一个类的私有信息,在不了解一个类的情况下可以查看并创建类的实例并调用方法。简单的来说,反射机制指的是程序在运行时能够获取自身的信息。在java中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息。 反射将这些信息包装成类,提供给用户使用。
一、Class类
每一个类都可以看做是一个Class实例,这是反射机制的基石。Class类本身不具备构造函数,因此不存在new Class()的情况,可以采用class.forname()方法创建Class实例。Class类常见的方法有:
1、static Class forName(String className) :返回与给定字符串名的类或接口的相关联的Class对象。
2、Class getClass() :返回的是Object运行时的类,即返回Class对象即字节码对象
3、Constructor getConstructor() :返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。
4、Construtor[] getConstructors() :返回给累的所有构造方法。
5、Field getField(String name) :返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段。
6、Field[] getFields() :返回包含某些Field对象的数组,表示所代表类中的成员字段。
7、Method getMethod(String name,Class… parameterTypes) : 返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。
8、Method[] getMehtods() : 返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。
9、T newInstance() 创建此Class对象所表示的类的一个新实例
二、通过反射创建一个类实例,并查询构造方法,查询该类具有的Field,methods
package com.amorvos.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.amorvos.Bean.Persion;
public class RefDemo {
public static void main(String[] args) throws Exception {
// static Class forName(String className) :返回与给定字符串名的类或接口的相关联的Class对象。
Class clzz = Class.forName("com.amorvos.Bean.Persion");
// Class getClass() :返回的是Object运行时的类,即返回Class对象即字节码对象
System.out.println(clzz.getClass().toString());
// Construtor[] getConstructors() :返回类的所有构造方法。
Constructor[] constructors = clzz.getDeclaredConstructors();
for (Constructor con : constructors) {
System.out.println(con.toString());
}
Persion p = (Persion) clzz.newInstance();
// Method[] getMehtods() : 返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。
Method[] methods = clzz.getDeclaredMethods();
for (Method me : methods) {
System.out.println(me.toString());
}
// Field[] getFields() :返回包含Field对象的数组,表示所代表类中的成员字段。
Field[] fields = clzz.getDeclaredFields();
for (Field fd : fields) {
//暴力反射
fd.setAccessible(true);
System.out.println(fd.get(p));
}
// Constructor<T> getConstructor(Class<?>... parameterTypes)
// 指定初始化参数类型创建类实例
// Method getMethod(String name,Class… parameterTypes) :
// 返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。
Object obj = clzz.getConstructor(int.class, int.class, String.class)
.newInstance(1, 1, "lisi");
clzz.getMethod("toString", null).invoke(obj, null);
}
}
三、通过反射返回一个类实现的接口名称:
<span style="font-size:14px;">package com.amorvos.reflect;
interface chinese {
public static final String name = "Rollen";
public static int age = 20;
public void sayChina();
public void sayHello(String name, int age);
}
class Person implements chinese {
public Person() {
}
public Person(String gender) {
this.gender = gender;
}
public String getSex() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public void sayChina() {
System.out.println("hello ,china");
}
public void sayHello(String name, int age) {
System.out.println(name + " " + age);
}
private String gender;
}
public class demo2 {
public static void main(String[] args) {
Class<?> demo = null;
try {
demo = Class.forName("com.amorvos.reflect.Person");
} catch (Exception e) {
e.printStackTrace();
}
Class<?> intes[] = demo.getInterfaces();
for (int i = 0; i < intes.length; i++) {
System.out.println("实现的接口 " + intes[i].getName());
}
}
}
</span>
四、类加载器
类加载器本身也是Java类,因为它是Java类,本身也需要加载器加载,显然必须有第一个类加载器而不是java类的,这正是BootStrap。它是嵌套在Java虚拟机内核中的,已启动即出现在虚拟机中,是用c++写的一段二进制代码。所以不能通过java程序获取其名字,若果获取只能是null。
Java虚拟机中可安装多个类加载器,系统默认的有三个主要的,每个类负责加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader。Java虚拟机中的所有类加载器采用子父关系的树形结构进行组织,在实例化每个类加载器对象或默认采用系统类加载器作为其父级类加载器。
当Java虚拟机要加载一个类时,首先,当前线程的类加载器去加载线程中的第一个类。若A引用类B(继承或者使用了B),Java虚拟机将使用加载类的类加载器来加载类B。还可直接调用ClassLoader的LoaderClass()方法,来制定某个类加载器去加载某个类。
每个ClassLoader本身只能分别加载特定位置和目录中的类,但他们可以委托其他类的加载器去加载,这就是类加载器的委托模式,类加载器一级级委托到BootStrap类加载器,当BootStrap在指定目录中没有找到要加载的类时,无法加载当前所要加载的类,就会一级级返回子孙类加载器,进行真正的加载,每级都会先到自己相应指定的目录中去找,有没有当前的类;直到退回到最初的类装载器的发起者时,如果它自身还未找到,未完成类的加载,那就报告ClassNoFoundException的异常。
简单说,就是先由发起者将类一级级委托为BootStrap,从父级开始找,找到了直接返回,没找到再返回给其子级找,直到发起者,再没找到就报异常。
五、自定义类加载器
自定义的类加载器必须继承抽象类ClassLoader,要覆写其中的findClass(String name)方法,而不用覆写loadClass()方法。loadClass()中调用了findClass(String name)这个方法,此方法返回的就是去寻找父级的类加载器。在loadClass()内部是会先委托给父级,当父级找到后就会调用findClass(String name)方法,而找不到时就会用子级的类加载器,再找不到就报异常了,所以只需要覆写findClass方法,那么就具有了实现用自定义的类加载器加载类的目的。
package com.amorvos.classLoad;
import java.io.*;
public class MyClassLoader extends ClassLoader {
public static void main(String[] args) throws Exception {
// 传入两个参数,源和目标
String scrPath = args[0];
String destDir = args[1];
// 将数据读取到输入流中,并写入到输出流中
FileInputStream fis = new FileInputStream(scrPath);
String destFileName = scrPath.substring(scrPath.lastIndexOf('\\') + 1);
String destPath = destDir + "\\" + destFileName;
FileOutputStream fos = new FileOutputStream(destPath);
// 加密数据
cypher(fis, fos);
fis.close();
fos.close();
}
// 定义加密数据的方法
private static void cypher(InputStream ips, OutputStream ops)
throws Exception {
int b = 0;
while ((b = ips.read()) != -1) {
ops.write(b ^ 0xff);
}
}
// 定义全局变量
private String classDir;
@SuppressWarnings("deprecation")
@Override
// 覆写findClass方法,自定义类加载器
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir + "\\" + name + ".class";
try {
// 将要加载的文件读取到流中,并写入字节流中
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//数据解密
cypher(fis, bos);
fis.close();
byte[] bytes = bos.toByteArray();
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
// 如果没找到类,则用父级类加载器加载
return super.findClass(name);
}
// 构造函数
public MyClassLoader() {
}
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
}