《Java核心技术卷I》java反射 笔记

写在最前:本笔记全程参考《Java核心技术卷I》,添加了本人对反射的一些使用心得

反射

1、反射概述

反射机制的作用

能够分析类能力的程序称为反射(reflective),它可以用来:

  1. 在运行时分析类
  2. 在运行时检查对象
  3. 实现泛型数组操作代码
  4. 利用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利用字符串(使用字符串,编译器不知道要加载这个类)手动强制加载其他类,

类加载器概述

三种类加载器:

  1. 启动类加载器
  2. 扩展类加载器
  3. 应用类加载器

代码在开始执行前会将所有需要的类加载到JVM中。

  1. 首先通过启动类加载器加载
    1. 启动类加载器专门加载:Java\jdk1.8….\jre\lib\rt.jar
    2. rt.jar中都是JDK最核心的类库
  2. 如果启动类加载器加载不到需要的类,就通过扩展类加载器加载
    1. 扩展类加载器专门加载Java\jdk1.8…..\jre\lib\ext\*.jar
    2. 如果扩展类加载器没有加载到,那么就要用到应用类加载器
  3. 应用类加载器专门加载:classpath中的jar包(class文件)

Java为了保证类加载的安全,使用了双亲委派机制。固定先从jdk中的默认类加载。

2.3 通过反射获取类

获取类的字节码

要操作一个类的字节码,首先要获取这个类的字节码。以下是三种方法:

  1. Class.forName("完整包名+类名")

    1. 该方法是一个静态方法
    2. 这个方法会使类加载,使静态代码块执行
    3. 方法的参数是一个字符串,字符串需要一个完整的包名
    4. 该方法需要处理ClassNotFoundExcption异常
    5. 获取java.lang.Class的实例
      Class c1 = Class.forName("java.Lang.String");
  2. Class x = 对象.getClass();

    1. java中任何一个对象都有一个方法getClass
    2. 字节码文件装载到JVM中只装载一份
    3. 该方法获取的Class属性与上一个方法获取的属性内存地址相同
    4. 示例:
      String s = "abc";
      Class x = s.getClass(); //x代表String.class字节码文件 x代表String类型
      
  3. Java语言中任意一种类型(包括基本数据类型)都有.class属性

    Class s1 = String.class;
    Class s2 = int.class;
    

2.4 通过Class类反射获取字符、方法和构造器

  • Class类中的getFieldsgetMethodsgetConstructors方法将分别返回这个类支持的公共字段、方法和构造器数组(包括父类的公共成员)
  • Class类中的getDeclaredFieldsgetDeclaredMethodsgetDeclaredConstructors方法将返回类中声明的全部字段、方法和构造器组成的数组,包括私有成员、包成员和受保护成员,但不包括父类的成员

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();
  1. Thread.currentThread()获取当前线程
  2. getContextClassLoader() 是线程对象的方法,可以获取当前线程的类加载器对象
  3. getResource(类文件名)这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
  4. 获取到的是绝对路径

示例: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 方法小结

  1. getModifiers方法将返回一个整数,描述这个类的构造器、方法或字段的修饰符列表。使用Modifier类中的方法来分析这个返回值

  2. Modifier类方法:

    1. static String toString(int modifiers)

      返回一个字符串,对于modifiers中描述的修饰符列表

    2. static boolean isAbstract(int modifiers)

    3. static boolean isFinal(int modifiers)

    4. 类似的还有isInterfaceisPrivateisProtectedisPublicisStaticisStrictisSynchronizedisVolatile

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~多个参数)

  1. 第一个参数obj是被反射的对象,后续参数是被执行的方法的参数
  2. 静态方法第一个参数可设置为null
//获取Method
Method method = targetClass.getDeclaredMethod("方法名", 0~多个方法参数);
//调用(invoke)方法
Object returnValue= method.invoke(obj, 0~多个参数)

4.3 方法小结

  1. Class类的方法:getDeclaredMethods()

    获取所有的Method(包括私有的),但是不获取继承来的方法

  2. Method类方法:getReturnType().getSimpleName();

    获取方法返回值类型

  3. Method类方法:public Object invoke(Object obj, Object... args)

    调用方法。第一个参数obj是被反射的对象,后续参数是被执行的方法的参数;静态方法第一个参数可设置为null

  4. 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类的一些方法:

  1. 构造一个新数组:Object newArray = Array.newInstance(componentType, newLength);
  2. 获取数组长度:Array.getLength(array);
  3. 获取数组类型:使用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)) {
    //...
}

参考:自定义注解,根据反射拿到被注解修饰的类,方法,属性

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值