一、反射
(一)类Class
1 . Class的概念
我们知道在Java中除了基本类型之外的其他类型都是class(包括interface),例如:String,Object,Runnable,Exception等。class的本质其实就是数据类型,Java规定class/interface的数据类型是Class。
当我们把一个类型的实例赋值给一个数据类型的变量时,必须严格按照数据类型进行赋值,无继承关系的数据类型无法赋值,编写不符合数据类型要求的代码,编译器就会发生报错。
(1)JVM持有的每一个Class类型都指向一个数据类型(class或interface)
例如,JVM在加载String类时,首先会读取String.class文件,然后为String类创建一个Class实例(这个过程是JVM在其内部实现)
(2)JVM为每个加载的class创建对应的Class实例,并在实例中保存该class的所有信息
(3)如果获取了某个Class实例,则可以获取到该实例对应的class的所有信息
例如:name,package,super,interface,field,method等
(4)通过Class实例获取class信息的方法称为反射(Reflection)
2 . 如何获取class的Class实例
(1)Type.class
Class cls = String.class;
//返回String类对应的Class实例
(2)getClass()
String s = "Hello";
Class cls = s.getClass();
//对实例变量调用getClass方法来返回Class实例
(3)Class.forName()
Class cls = Class.forName("java.lang.String");
//利用Class提供的静态方法forName()传入完整类名得到对应的Class
2 . 比较Class实例
在JVM中,一个class只对应唯一的一个Class实例,可以使用==操作符比较两个Class实例。例如:
Class cls1 = String.class;
String s = "Hello";
Class cls2 = s.getClass();
Class cls3 = Class.forName("java.lang.String");
boolean b1 = cls1 == cls2;//true
boolean b2 = cls2 == cls3;//true
Class实例比较和instanceof比较的差别:
(1)当我们使用instanceof进行比较时,不但匹配当前的类型,还匹配当前类型的子类。例如:
Integer n = new Integer(123);
boolean b3 = n instanceof Integer;//true
boolean b4 = n instanceof Number;//true
//因为Integer是Number的子类,所以判断结果都为true
(2)当我们使用==操作符对Class实例进行比较时,由于Class实例只能精确的判断数据类型,不能进行子类的比较。例如:
boolean b1 = n.getClass() == Integer.class;//true
boolean b2 = n.getClass() == Number.class;//false
3 . 反射的目的
反射的目的是当获得某个Object实例时,我们可以获取该Object的class信息
(1)从Class实例获取class信息:
①获取完整类名:getName()
②获取简单类名:getSimpleName()
③获取包名:getPackage()
Class cls = String.class;
String fname = cls.getName();//"java.lang.String"
String sname = cls.getSimpleName();//"String"
String pkg = cls.getPackage();//"java.lang"
(2)从Class实例判断class类型:
①判断数据类型:isinterface()
②判断枚举类型:isEnum()
③判断数组类型:isArray()
④判断基本类型:isPrimitive()
Runnable.class.isinterface();//true
java.time.Month.class.isEnum();//true
String[].class.isArray();//true
int.classisPrimitive();//true
(3)通过Class创建class实例:
Class cls = String.class;
String s = (String) cls.newInstance();
注意:在这里我们只能调用默认构造方法,带有参数的构造方法无法通过此方式进行调用
(4)动态加载
利用JVM动态加载class的特性:
可以在运行期间根据条件加载不同的类
例如:
如果我们要实现Commons Logging这个功能
首先我们判断org.apache.logging.log4j.Logger这个类是否存在
利用if-else语句,如果这个类存在就优先使用Log4j,如果不存在则使用JVM自带的JDKLog
//Commons Logging优先使用Log4j
LogFactory factory;
if(isClassPresent("org.apache.logging.log4j.Logger")){
factory = createLog4j;
} else {
factory = createJDKLog();
}
//在JVM执行这段代码时,并没有引用Log4j的Class本身,在这种情况下即使没有放入Log4j的jar包,代码也依然会执行
而判断一个Class是否存在,我们只需要使用Class.forName()方法,然后捕获Class Not Found异常就可以
boolean isClassPresent(String name) {
try {
Class.forName(name);
return true;
} catch (Exception e) {
return false;
}
}
这就是我们只需要把Log4j的jar包放入classpath中,Commons Logging就可以直接使用Log4j的原因。
(二)访问字段
1 . 通过Class实例获取field信息
(1)获取某个public的field(包括父类):getField(Name)
(2)获取当前某各类的field(不包括父类):getDeclaredField(Name)
(3)获取所有public的field(包括父类):getFields()
(4)获取当前类的所有field(不包括父类):getDeclaredFields()
2 . Field对象包含一个field的所有信息
(1)获得field字段的名称:getName()
(2)获得field字段定义的类型:getType()
(3)获得field字段定义的修饰符:getModifiers()
Integer n = new Integer(123);
Class cls = n.getClass();
Field[] fs = cls.getFields();
for(Field f : fs) {
f.getName();//field name
f.getType();//field type
f.getModifiers();//modifiers
}
3 . 获取和设置field的值
(1)获取一个实例的该字段的值:get(Object)
Integer n = new Integer(123);
Class cls = n.getClass();
Field f = cls.getDeclaredField("Value");
f.get(n);//123,相当于n.Value
(2)设置一个实例的该字段的值:set(Object)
Integer n = new Integer(123);
Class cls = n.getClass();
Field f = cls.getDeclaredField("Value");
f.set(n, 456);//123,相当于n.Value = 456
4 . 访问非public字段
通过反射访问Field需要通过SecurityManager设置的规则。
通过设置setAccessible(true)来访问非public字段。
setAccessible(true)
如果定义了SecurityManager,SecurityManager的规则将会阻止对该field设置Accessible
(三)调用方法
1 . 通过Class实例获取method信息
(1)获取某个public的method(包括父类):getMethod(name,Class…)
(2)获取当前类的某个method(不包括父类):getDeclaredMethod(Name,Class…)
(3)获取所有public的method(包括父类):getMethods()
(4)获取当前类的所有的method(不包括父类):getDeclaredMethods()
2 . Method对象包含一个method的所有信息
(1)返回method的名称:getName()
(2)返回method的返回类型:getReturnType()
(3)返回method的参数类型:getParamaterType()
(4)返回method的修饰符:getModifiers()
Integer n = new Integer(123);
Class cls = n.getClass();
Method[] ms = cls.getMethods();
for(Method m : ms) {
m.getName();//method name
//return type:
Class ret = m.getReturnType();//
//parameter types:
Class[] params = m.getParam