写在最前:本笔记全程参考《Java核心技术卷I》,添加了本人对反射的一些使用心得
反射
1、反射概述
反射机制的作用
能够分析类能力的程序称为反射(reflective),它可以用来:
- 在运行时分析类
- 在运行时检查对象
- 实现泛型数组操作代码
- 利用Method对象调用方法
反射机制重要的相关的类
java.lang.Class
代表整个字节码,代表一个类java.lang.reflect.Method
代表字节码中的方法字节码,java.lang.reflect.Constructor
方便字节码中的构造方法字节码java.lang.reflect.Field
代表字节码中的属性字节码,代表类中的成员变量
2、类的反射与Class类
2.1 Class类概述
在程序运行期间,Java运行时始终为所有对象维护一个运行时类型标识。这个信息会跟踪每一个对象所属的类。虚拟机利用运行时类型信息选择要执行的正确方法。
可以用一个特殊的类访问这些信息,保存这些信息的类为Class。Object类中的Class方法将返回一个Class类型实例
Class是一个泛型类,例如Employee的类型是Class<Employee>
虚拟机为每个类型管理一个唯一的Class对象,可以使用==
判断是否相等。如果两个对象的类不同,那么结果为false(即使是父类与子类也会为false,这个与instanceof
不同)
由于历史原因,对数组类型使用getName
方法会返回奇怪的名字(书中原话,例如int[].class.getName()
返回"[I"
)
2.2 类加载
类加载机制
在程序启动时,包含main方法的类将被加载,进而加载该类所需要的其他类,依此类推。对于一个大型应用程序来说,这会花费很长时间,用户可能会感到不爽。
可以确保main方法的类没有显示引用其他类,然后显示一个启动画面,接着再通过Class.forName
利用字符串(使用字符串,编译器不知道要加载这个类)手动强制加载其他类,
类加载器概述
三种类加载器:
- 启动类加载器
- 扩展类加载器
- 应用类加载器
代码在开始执行前会将所有需要的类加载到JVM中。
- 首先通过启动类加载器加载
- 启动类加载器专门加载:
Java\jdk1.8….\jre\lib\rt.jar
rt.jar
中都是JDK
最核心的类库
- 启动类加载器专门加载:
- 如果启动类加载器加载不到需要的类,就通过扩展类加载器加载
- 扩展类加载器专门加载
Java\jdk1.8…..\jre\lib\ext\*.jar
- 如果扩展类加载器没有加载到,那么就要用到应用类加载器
- 扩展类加载器专门加载
- 应用类加载器专门加载:
classpath
中的jar包(class文件)
Java为了保证类加载的安全,使用了双亲委派机制。固定先从jdk中的默认类加载。
2.3 通过反射获取类
获取类的字节码
要操作一个类的字节码,首先要获取这个类的字节码。以下是三种方法:
-
Class.forName("完整包名+类名")
- 该方法是一个静态方法
- 这个方法会使类加载,使静态代码块执行
- 方法的参数是一个字符串,字符串需要一个完整的包名
- 该方法需要处理ClassNotFoundExcption异常
- 获取java.lang.Class的实例
Class c1 = Class.forName("java.Lang.String");
-
Class x = 对象.getClass();
- java中任何一个对象都有一个方法getClass
- 字节码文件装载到JVM中只装载一份
- 该方法获取的Class属性与上一个方法获取的属性内存地址相同
- 示例:
String s = "abc"; Class x = s.getClass(); //x代表String.class字节码文件 x代表String类型
-
Java语言中任意一种类型(包括基本数据类型)都有.class属性
Class s1 = String.class; Class s2 = int.class;
2.4 通过Class类反射获取字符、方法和构造器
- Class类中的
getFields
、getMethods
、getConstructors
方法将分别返回这个类支持的公共字段、方法和构造器数组(包括父类的公共成员) - Class类中的
getDeclaredFields
、getDeclaredMethods
、getDeclaredConstructors
方法将返回类中声明的全部字段、方法和构造器组成的数组,包括私有成员、包成员和受保护成员,但不包括父类的成员
2.5 获取父类和接口
Class c = Class.forName("完整类名");
//获取父类
Class superClass = c.getSuperclass();
//获取类实现的所有接口
Class[] interfaces = c.getInterfaces();
2.6 获取类路径下文件的绝对路径
资源
类通常有一些关联的数据文件,在Java中统称为资源:
- 图像和声音文件
- 包含消息字符串和按钮标签的文本文件
使用背景
一个类的储存路径默认是在project的根下。代码文件离开了根,换到了其他地方,它的当前路径就不是project的根了。这时这个路径就无效了。我们希望获取一个通用路径,在文件移动后依然可以获取它的位置(且该方法是通用的)
使用一下通用方式的前提是:该方法要在src
类路径下
代码
String path = Thread.currentThread().getContextClassLoader().getResource(类文件名).getPath();
Thread.currentThread()
获取当前线程getContextClassLoader()
是线程对象的方法,可以获取当前线程的类加载器对象getResource(类文件名)
这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源- 获取到的是绝对路径
示例:IO流获取properties文件创建对象
首先在Properties文件中配置属性:className = 包名;
InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("文件名");
Properties pro = new Properties();
pro.load(reader);
reader.close();
String className = pro.getProperty("className"); // 通过配置的属性值获取包名
Class c = Class.forName();
Object obj = c.getDeclaredConstructor().newInstance()
2.8 自定义对象解析器
注:代码是《核心技术卷I》里的原代码
public class ObjectAnalyzer {
private ArrayList<Object> visited = new ArrayList<Object>();
public String toString(Object obj) throws IllegalAccessException {
if (obj == null) {
return "null";
}
if (visited.contains(obj)) {
return "...";
}
visited.add(obj);
Class cl = obj.getClass();
// 字符串原样输出
if (cl == String.class) {
return (String) obj;
}
/*
数组:
一位数组:int[]{0,1,2,3,4,5}
二维数组:class [I[]{int[]{0,1,2,3,4,5},int[]{6,7,8,9,10}}
*/
if (cl.isArray()) {
StringBuilder r = new StringBuilder(cl.getComponentType() + "[]{");
for (int i = 0; i < Array.getLength(obj); i++) {
if (i > 0) {
r.append(",");
}
Object val = Array.get(obj, i);
if (cl.getComponentType().isPrimitive()) {
r.append(val);
} else {
r.append(toString(val));
}
}
return r + "}";
}
/*
对象:(VIP继承了User)
User[name=null,id=0,password=null,sex=false]
User[name=name,id=11100,password=123123,sex=true]
VIP[level=3][name=VIP,id=22100,password=123123,sex=true]
*/
StringBuilder r = new StringBuilder(cl.getName());
do {
r.append("[");
Field[] fields = cl.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
for (Field f : fields) {
if (!r.toString().endsWith("[")) {
r.append(",");
}
r.append(f.getName()).append("=");
Class t = f.getType();
Object val = f.get(obj);
if (t.isPrimitive()) {
r.append(val);
} else {
r.append(toString(val));
}
}
r.append("]");
cl = cl.getSuperclass();
} while (cl != null && cl != Object.class);
return r.toString();
}
}
3、 字段的反射与Field类
- Feild直译为字段,其实就是属性/成员。
- Feild对象包括:属性的访问权限;属性的数据类型;属性名。
3.1 方法使用
// 获取整个类
Class studentClass = CLass.forName("类路径");
// 获取完整类名:
String className = studentClass.getName();
// 获取简类名:
String simpleName = studentClass.getSimpleName();
// 获取类中所有公开的Field
Field[] fields = studentClass.getFields();
// 获取某一个field
Field f = files[0];
// 获取属性名
String fieldName = f.getName();
// 获取类中所有的Field
Field[] fs = studentClass.getDeclaredFields();
for(Field field : fs){
// 通过field对象获取属性名
field.getName();
// 通过field对象获取属性类型
Class fildType = field.getType(); // 返回CLass类型的数据
String fName = fieldType.getName();
// 获取属性的访问修饰符列表(Modifier即“修饰符”)
// 返回的是访问修饰符的代号!(int类型)
int i = filed.getModifiers();
String modifeirString = Modifier.toString(i);
}
3.2 方法小结
-
getModifiers
方法将返回一个整数,描述这个类的构造器、方法或字段的修饰符列表。使用Modifier类中的方法来分析这个返回值 -
Modifier类方法:
-
static String toString(int modifiers)
返回一个字符串,对于modifiers中描述的修饰符列表
-
static boolean isAbstract(int modifiers)
-
static boolean isFinal(int modifiers)
-
类似的还有
isInterface
、isPrivate
、isProtected
、isPublic
、isStatic
、isStrict
、isSynchronized
、isVolatile
-
3.3 示例:打印字段
// 拼接字符串
StringBuilder s = new StringBuilder();
Class studentClass = Class.forName("java.lang.String");
// 类的修饰符和类名 public final class String {
s.append(Modifier.toString(studentClass.getModifiers())
+ " class "
+ studentClass.getSimpleName()
+ " {\n");
Field[] fields = studentClass.getDeclaredFields();
// 打印属性 例如:private static final long serialVersionUID;
for(Field field: fields){
s.append("\t");
s.append(Modifier.toString(field.getModifiers()));
s.append(" ");
s.append(field.getType().getSimpleName());
s.append(" ");
s.append(field.getName());
s.append(";\n");
}
s.append("}");
System.out.println(s);
结果:
public final class String {
private final char[] value;
private int hash;
private static final long serialVersionUID;
private static final ObjectStreamField[] serialPersistentFields;
public static final Comparator CASE_INSENSITIVE_ORDER;
}
3.4 利用反射给属性赋值
属性赋值三要素:对象,对象的属性名,属性值
使用反射访问对象属性:
Class studentClass = Class.forName("类名");
Object obj = studentClass.newInstance();
//获取学生类的No属性
Field noField = studentClass.getDeclareField("no");
//给obj对象(Student对象)no赋值
noField.set(obj, 属性值);
// 获取字段属性值
Object value = noField.get(obj);
不能访问私有属性,但是可以利用反射机制打破封装,将私有属性改为可访问的。
Field nameField = studentClass.getDeclareField("name");
//设置为可访问的,打破封装
nameField.setAccessible(true);
4、方法的反射与Method类
4.1 方法使用
Class targetClass = Class.forName("完整类名");
//获取所有的Method(包括私有的)
Method[] methods = targetClass.getDeclaredMethods();
for(Method method: methods){
//获取方法返回值类型
method.getReturnType().getSimpleName();
//获取修饰符列表
Modifier.toString(method.getModifiers());
//获取方法参数的修饰符列表
CLass[] parameterTypes = method.getParameterTypes().getSimpleName();
}
4.2 通过反射调用方法
method.invoke(obj, 0~多个参数)
:
- 第一个参数obj是被反射的对象,后续参数是被执行的方法的参数
- 静态方法第一个参数可设置为null
//获取Method
Method method = targetClass.getDeclaredMethod("方法名", 0~多个方法参数);
//调用(invoke)方法
Object returnValue= method.invoke(obj, 0~多个参数)
4.3 方法小结
-
Class类的方法:
getDeclaredMethods()
获取所有的Method(包括私有的),但是不获取继承来的方法
-
Method类方法:
getReturnType().getSimpleName();
获取方法返回值类型
-
Method类方法:
public Object invoke(Object obj, Object... args)
调用方法。第一个参数obj是被反射的对象,后续参数是被执行的方法的参数;静态方法第一个参数可设置为null
-
Method类方法:
Method getMethod(String name, Class... parameterTypes)
通过方法名和参数列表获取方法对象(也就是要提供方法签名)
5、 构造方法的反射和Constructor类
获取所有构造方法
Contructor[] constructors = targetClass.getDeclaredConstructors();
通过反射创建对象
下方该方法只能调用无参构造方法实例化对象,且在jdk9后过时
Class c = Class.forName("完整包名")
Object obj = c.newInstance(); //jdk9后过时
代替方法:通过构造方法Constructor类创建对象
如果
getDeclaredConstructor
方法不加参数,返回无参构造方法
// 无参构造
Class.forName("完整包名").getDeclaredConstructor().newInstance()
// 有参,提供参数列表,指定构造方法:
Class c = Class.forName("完整类名");
Constructor con = c.getDeclaredConstructor(int.class, String.class);
//调用有参构造方法
con.newInstance(100, "abc");
当然,有
getDeclaredConstructor()
方法也会有getConstructor()
方法,和Method
是一样的
6、反射与数组
6.1 问题引入:copyOf方法
Array类中有一个copyOf
方法,可以扩展一个已满的数组:
Employee[] staff = new Employee[100];
// 经过一些操作后数组已满
// 扩展为两倍长度的数组:
staff = Arrays.copyOf(a, 2 * a.length);
如果编写这个方法呢?以下方法如果传入Employee[]
数组,企图扩容,会出现ClassCaseException
:
public static Object[] copyOf(Object[] a, int newLength) {
Object[] newArray = new Object[newLength];
System.arraycopy(a, 0, newArray, Math.min(a.length, newLength));
return newArray;
}
原因是 java数组会记住每一个元素的类型,即创建数组时new
表达式使用的元素类型。
如果将一个Employee[]
数组临时转为Object[]
,之后又强转为Employee[]
数组,是可行的。但是此处,newArray
的元素类型是Object
而不是Employee
,不能把Object[]
强转为Employee[]
数组
也就是说,我们需要动态创建一个新数组,在new
的时候就获取它的元素类型。这就需要用到反射!
6.2 使用反射获取数组类型
需要使用java.lang.reflect
包中Array
类的一些方法:
- 构造一个新数组:
Object newArray = Array.newInstance(componentType, newLength);
- 获取数组长度:
Array.getLength(array);
- 获取数组类型:使用Class类的
cl.getComponentType()
方法
为了使数组可以用于基本数据类型数组(如int[]
),需要将参数和返回值设置为Object
,而不是Object[]
(后者只能用于对象数组,而不能用于基本数据类型数组)
具体代码如下:
public static Object copyOf(Object a, int newLength) {
Class cl = a.getClass();
if (!cl.isArray()) {
return null;
}
Class componentType = cl.getComponentType();
int lenth = Array.getLength(a);
Object newArray = Array.newInstance(componentType, newLength);
// arraycopy最后一个参数指定要复制的长度
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
return newArray;
}
public static void main(String[] args) {
int[] a = {1,2,3,4,5};
int[] o = (int[]) copyOf(a, 10); // [1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
}
7、反射与注解
- 判断属性上是否加了
AnnotNote
注解
if(field.isAnnotationPresent(AnnotNote.class)) {
//...
}