反射

  • Post author:
  • Post category:其他


1. 概述

反射就是把java类中的各种成分映射成相应的java类。

1.1 类的加载概述

当程序要使用某个类的时候,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三步来实现对这个类进行初始化

1.1.1 加载

就是指将class文件读入内存,并为之创建一个Class对象,任何类被使用的时候,系统都会建立一个Class对象

1.1.2 连接

连接分为三步:

1. 验证:是否有正确的内部结构,并和其他类协调一致

2. 准备:负责为类的静态成员分配内存,并设置默认初始化值

3. 解析:将类的二进制数据中的符号应用替换为直接引用

1.1.3 初始化

就是初始化步骤

1.2 加载时机

1. 创建类的实例

2. 访问类的静态变量,或者为静态变量赋值

3. 调用静态类的方法

4. 使用反射方式强制创建某个类或者接口对应的java.lang.Class对象

5. 初始化某个类的子类

6. 直接使用java.exe命令来运行某个主类

1.3 类加载器的概述和分类

1.3.1 类加载器的概述

负责将.class文件加载到内存中,并为之生成对应的class对象。

1.3.2 类加器的分类

1. 根类加载器:Bootstrap ClassLoader

2. 扩展类加载器:Extension ClassLoader

3. 系统类加载器:System ClassLoader

1.3.3 类加载器的作用

根类加载器Bootstrap ClassLoader,也被称为引导类加载器,负责java核心类的加载,比如:System、String等。在JDK中JRE的lib目录下rt.jar文件中

扩展类加载器Extension ClassLoader,负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录

系统类加载器System ClassLoader,负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。

2. 反射的基石——Class

Class是从JDK1.2开始引入的特性,Class类代表了Java类,Java类用于描述一类事物的共性,该类事物决定了其所具有的属性,至于属性值,则由具体的实现类来确定,不同的实例对象有不同的属性值。

Java程序中用Class来描述各个类,Class描述了类的名字,访问属性,类的包名,字段名称列表,方法名称的列表等等。其实例对应各个类在内存中的字节码,一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象具有相同的类型,这个类型就是CLass,即

在java程序中各个java类都同属于一类事物,描述这类事物的java类名就是Class

2.1. 获取Class类型的三种方式

2.1.1 方式一

类名.class

例如:System.class

2.1.2 方式二

对象.getClass()

例如:new Date().getClass()

2.1.3 方式三

Class.forName(“类名”)

例如:CLass.forName(“java.util.Date”);

注意:

1. 该方式在实际中被大量运用于一些框架的反射中。

2. 该方法有两种加载方式,第一种,内存中已存在该字节码,直接返回对应的字节码的内存地址,第二种,虚拟机中不存在该字节码,首先类加载器会先加载该类的字节码到内存中缓存,同时方法返回该字节码对应的内存地址.

3. 同一个类对象在内存中只会存在一个字节码。

例如:

String str1 = "acc";
String str2 = new String();
Class c1 = str1.getClass();
Class c2 = str2.getClass();
Class c3= Class.forName("java.lang.String");
System.out.println(c1 == c2);   // true
System.out.println(c1 == c3);   // true

2.2. 九个预定义CLass实例对象

八个基本数据类型+void,他们都有对应的Class对象。

例如:int.class    void.class

2.3. 判断Class字节码类型

注意:基本数据类型的包装类都有一个TYPE常量,该常量表示其对应的基本数据类型的字节码

例如:int.class == Integer.TYPE

2.3.1 isPrimitive——判断基本数据类型

在java中,可以通过isPrimitive()来判断字节码是否为基本数据类型

例如:int.class.isPrimitive();

2.3.2 isArray——判断数组

int[].class.isArray();

3. 反射

3.1 构造方法的反射——Constructor

Constructor类代表了某个类中的构造方法

3.1.1 常用API

1. 得到某个类的构造方法集合——getConstructors

Constructor c[] = Class.forName("java.lang.String").getConstructors();

注意:对于数组的写法,中括号最好是写在类的后面。例如:Constructor[] c = xxx

2. 得到指定参数的构造方法——getConstructor

Constructor c11 = Class.forName("java.lang.String").getConstructor(StringBuffer.class);

3. 创建实例对象——newInstance

该方法是使用该类无参的构造函数创建对象,如果一个类不存在无参的构造函数,就不能使用它创建了。

Constructor c11 = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
String str = (String)c11.newInstance(new StringBuffer("abc"));
System.out.println(str.charAt(1));

4. 创建实例对象—— newInstance(Object … obj)

该方法是使用该类的有参数的构造函数来创建对象的。

Class clazz = Class.forName("com.bjc.Person");
Constructor c = clazz.getConstructor(String.class,int.class);
Person p = (Person)c.newInstance("张三",21);

3.2 成员变量的反射——Field

Field类代表了某个类中的一个成员变量

3.2.1 常用API

1. 获取所有的成员变量——getFields

ReflectPoint rp = new ReflectPoint(3, 5);
Field[] fields = rp.getClass().getFields();

2. 获取某一个非private类型的成员变量——getField

ReflectPoint rp = new ReflectPoint(3, 5);
Field fieldY = rp.getClass().getField("y");

3. 获取某个类型的Field的值——get

ReflectPoint rp = new ReflectPoint(3, 5);
Field fieldY = rp.getClass().getField("y");
Object object = fieldY.get(rp);
System.out.println(object);

注意:get的参数为一个实例对象

4. 获取所有私有的成员变量——getDeclaredFields

rp.getClass().getDeclaredFields();

5. 获取指定的私有成员变量——getDeclaredField

rp.getClass().getDeclaredField("y");

6. 设置私有变量可以被访问——setAccessible

Field declaredField = rp.getClass().getDeclaredField("y");
declaredField.setAccessible(true);

注意:如果要访问私有成员,需要设置setAccessible为true,否则会报错。

7. 设置成员变量值——set

Field declaredField = rp.getClass().getDeclaredField("y");
declaredField.set(rp,32);

8. 获取成员变量的类型——getType

Class type = fieldY.getType();

3.2.2 例子

1. 将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的“b”改成“a”

public static void change(Object obj) throws Exception {
	Field[] fields = obj.getClass().getFields();
	for(Field f : fields){
	if(f.getType() == String.class){ // 注意:字节码只有一份,所以这里用双等号语意更明确
			String strVal = (String)f.get(obj);
			String newStrVal = strVal.replaceAll("a", "b");
			f.set(obj,newStrVal); // 将新值设置到obj对象中
		}
	}
}

3.3 成员方法的反射——Method

Method类代表某个类中的一个成员方法

3.3.1 常用API

1. 得到类中的所有方法——getMethods

Method[] methods = Class.forName("java.lang.String").getMethods();

2. 得到类中的某一个方法——getMethod

2.1 获取有参方法

Class.forName("java.lang.String").getMethod("charAt", int.class);

2.2 获取无参方法

Class.forName("com.bjc.Person").getMethod("eat");

3. 执行方法——invoke

3.1 执行有参方法

Method method = Class.forName("java.lang.String").getMethod("charAt", int.class);
String str = "abc";
method.invoke(str, 1);

又如:

Class clazz = Class.forName("com.bjc.Person");
Constructor c = clazz.getConstructor(String.class,int.class);
Person p = (Person)c.newInstance("张三",22);

Method m = Class.forName("com.bjc.Person").getMethod("eat",int.class);
m.invoke(p,23);

该执行逻辑为,调用某个对象的某个方法,第一个参数为对象,第二个开始的为参数列表

3.2 执行无参方法

Class clazz = Class.forName("com.bjc.Person");
Constructor c = clazz.getConstructor(String.class,int.class);
Person p = (Person)c.newInstance("张三",22);

Method m = Class.forName("com.bjc.Person").getMethod("eat");
m.invoke(p);

注意:

1. 如果传递给Method对象的invoke()方法的第一个参数为Null,这表示该method对象对应的是一个静态方法。

例如:method.invoke(null, 1);

2. jdk1.4与jdk1.5invoke的区别是。

在1.5

public Object invoke(Object obj,Object ... args)

在1.4

public Object invoke(Object obj,Object[] args)

即,按1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,我们也可以这样使用

Method method = Class.forName("java.lang.String").getMethod("charAt", int.class);
String str = "abc";
method.invoke(str, new Oject[]{1});

3.3.2 对接收数组参数的成员方法的反射

我们知道,在jdk1.4中,数组中的每一个元素都对应一个参数,但是在jdk1.5中是按照一个整体来处理的,如果我们一个数组作为参数传递给invoke的时候,javac会按照1.4还是按照1.5来处理了?jdk1.5肯定是要兼容1.4的所以,所以javac会按照1.4的语法进行处理,即将数组打散成为若干个单独的参数,所以,数组类型的参数,不能直接使用数组(即下面的处理方式会出现参数类型不匹配的问题method.invoke(null,new String[]{“abc”,”bcd”}))。

那么,我们需要怎么处理数组参数了

两种方式

1. 方式一:

method.invoke(null,new Object[]{new String[]{"abc","bcd"}})

2. 方式二:

method.invoke(null,(Oject)new String[]{"ac","cd"})

3.4 数组的反射

1. 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

2. 代表数组的Class实例对象的getSupperClass()方法返回的父类为Object类对应的Class。

3. 基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用。

4. 非基本类型的一维数组,既可以当做Object[]类型使用,又可以作为Object[]类型使用。

例如:

public static void test02(){
		int[] a = new int[3];
		int[] b = new int[4];
		int[][] d = new int[2][3];
		String[] c = new String[3];
		System.out.println(a.getClass() == b.getClass());  // true
		//System.out.println(a.getClass() == d.getClass());	// false
		//System.out.println(a.getClass() == c.getClass());	// false
		System.out.println(a.getClass().getSuperclass().getName());	// java.lang.Object
		System.out.println(d.getClass().getSuperclass().getName());	// java.lang.Object
		System.out.println(c.getClass().getSuperclass().getName()); // java.lang.Object
}

3.5 泛型反射

泛型只存在于编译期,在运行期会被擦除掉,所以,我们可以使用反射越过泛型检查

public static void main(String[] args) throws Exception {
	ArrayList<Integer> aList = new ArrayList<Integer>();
	aList.add(1);
	aList.add(2);
	aList.add(3);
	// 利用反射,添加字符串到aList中
	Class clazz = aList.getClass();
	Method method = clazz.getMethod("add", Object.class);
	method.invoke(aList, "张三");
	System.out.println(aList.toString());
}

3.5.1  Constructor的泛型反射

构造函数Constructor的泛型反射,就是在Constructor上加入泛型,这样,通过其创建出来的对象就是该类型的对象了,不需要进行强制类型转换了。

例如:

public static void test02() throws Exception, SecurityException{
	Class clazz = String.class;
	Constructor<String> constructor = clazz.getConstructor(String.class);
	String newInstance = constructor.newInstance("abcde");
	System.out.println(newInstance);
}

3.5.2 通过反射获取函数形参列表中泛型的实际参数类型

例如:有一个函数

public static void test(List<String> list){
		
}

通过反射得到泛型String

// 通过反射获取方法
Method method = AnnotationTest.class.getMethod("test", List.class);
// 通过方法得到参数化类型数组
Type[] genericParameterTypes = method.getGenericParameterTypes();
// 因为只有一个参数,所以取第一个
Type genericParameterType = genericParameterTypes[0];
// 强转 ParameterizedType 是 Type的子类,表示参数化类型
ParameterizedType pt = (ParameterizedType)genericParameterType;
// 通过ParameterizedType得到实际参数类型数组
Type[] actualTypeArguments = pt.getActualTypeArguments();
// 取第一个类型
Type type = actualTypeArguments[0];
System.out.println(type);

3.5.3 通过反射获取类上的泛型类型

public BaseDaoImpl<T>{
	// 反射:第一步获取class
	Class clazz = this.getClass();	// this指正在被调用的那个类
	// 获取子类的父类的参数化类型CustomerDaoImpl extends BaseDapImpl<Customer>
	Type type = clazz.getGenericSuperclass();
	// 将Type类型强转为参数化类型
	ParameterizedType pType = (ParameterizedType)type;
	// 获取实际类型参数
	Type[] actualTypes = pType.getActualTypeArguments();
	// 这里只需要获取1个就可以了 ,因为Type接口的实现类是Class,所以,可以直接强转
	this.clazz = (Class) actualTypes[0];
}

注意:这种方式有一个前提,需要当前类继承泛型类

在例如:

定义泛型类

package com.bjc.annotation;

public class General<T> {
	T field;
	public void add(T t){
		
	}
	
	public T findById(Integer id){
		return field;
	}
	
	public void del(){
		
	}
}

继承该类

public class AnnotationTest extends General<Double>{
	public static void main(String[] args) throws Exception{
		new AnnotationTest().test();
	}
	
	public void test(){
		Type genericSuperclass = this.getClass().getGenericSuperclass();
		ParameterizedType p = (ParameterizedType)genericSuperclass;
		Type type = p.getActualTypeArguments()[0];
		System.out.println(type);
	}
}

打印结果为Double。

5. 反射一些应用

5.1 反射实现设置属性值

public static void setProperty(Object obj,String fieldName,Object value) throws Exception{
		Class class1 = obj.getClass();
		Field field = class1.getDeclaredField(fieldName);
		field.setAccessible(true);
		field.set(obj, value);
	}

测试:

package com.bjc.reflect;

import com.bjc.util.ReflectUtils;

public class ReflectDemo3 {
	public static void main(String[] args) throws Exception {
		Person p = new Person();
		p.setName("张三");
		p.setAge(23);
		System.out.println(p);
		ReflectUtils.setProperty(p, "name", "李四");
		System.out.println(p);
	}
}

class Person{
	private String name;
	private Integer age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
}

5.2 实现框架功能

模拟框架,将类交给配置文件管理

配置文件:config.properties

className=java.util.ArrayList

java代码:

public static void test() throws Exception{
	InputStream is = new FileInputStream("config.properties");
	Properties pro = new Properties();
	pro.load(is);
	is.close();  // 关闭流
		
	String className = pro.getProperty("className");
	Collection c = (Collection) Class.forName(className).newInstance();
	c.add("abc1");
	c.add("abc2");
	c.add("abc3");
	System.out.println(c.toString());
}

5.2 JavaBean的内省操作——PropertyDescriptor

JavaBean是一种特殊的java类,主要用于传递数据信息,该类中的方法主要通过get与set方法访问私有的字段,且方法名符合某种命名规则。在JavaEE开发中,JavaBean的使用非常广泛,所以JDK提供了一套对其进行操作的API,这套API就叫做内省。

5.2.1 创建PropertyDescriptor对象

创建PropertyDescriptor对象,其有两个参数,第一个参数为JavaBean的属性名,第二个参数为JavaBean的字节码。

例如:

pulicstatic void test3() throws Exception{
	Point p = new Point(3,5);
	String propertyName = "x";
		
	PropertyDescriptor pd = new PropertyDescriptor(propertyName, Point.class);
}

5.2.2 得到get方法并获取值——getReadMethod

通过PropertyDescriptor对象来获取getReadMethod方法

例如:

/**
  * @param obj			 javaBean对象
  * @param propertyName   JavaBean的属性名称
  * @return				 返回属性值
  * @throws IntrospectionException
  * @throws IllegalAccessException
  * @throws InvocationTargetException
  */
 private static Object getProperties(Object obj, String propertyName)
	 throws IntrospectionException, IllegalAccessException, InvocationTargetException {
	 PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());
	 Method readMethod = pd.getReadMethod();
	 Object invoke = readMethod.invoke(obj);
	 return invoke;
 }

注意:get方法没有参数,所以invoke方法调用的时候,只需要传入被执行的对象即可

5.2.3 得到set方法并设置值——getWriteMethod

通过PropertyDescriptor对象来获取getWriteMethod方法

/**
  * @param obj					javaBean对象
  * @param propertyName			JavaBean属性名
  * @param value					需要给属性设置的值
  * @throws IntrospectionException
  * @throws IllegalAccessException
  * @throws InvocationTargetException
  */
 private static void setProperties(Object obj, String propertyName,Object value)
	 throws IntrospectionException, IllegalAccessException, InvocationTargetException {
	 PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());
	 Method writeMethod = pd.getWriteMethod();
	 writeMethod.invoke(obj, value);
 }

注意:在JavaBean中set方法通常会传递一个参数,所以在调用invoke方法的时候,需要有参数,第一个参数表示被执行的对象,第二个参数开始为传递的形参。

5.3 javaBean的内省操作——Introspector

对于javaBean还有一个相对比较复杂的操作,就是采用遍历BeanInfo的所有属性方式来查找和设置某个JavaBean对象的属性。

在程序中,将一个java类当做JavaBean来看,就是调用Introspector.getBeanInfo方法,得到的BeanInfo对象封装了把这个类当做JavaBean看的结果信息。

private static Object getProperties1(Object obj, String propertyName)
		throws IntrospectionException, IllegalAccessException, InvocationTargetException {
		
	Object retVal = null; 
	// 获取BeanInfo对象
	BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
	// 得到BeanInfo中的所有的属性描述
	PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
	for(PropertyDescriptor p : propertyDescriptors){
		// 如果传递过来的属性名与得到的属性名相等
		if(propertyName.equals(p.getName())){
			Method readMethod = p.getReadMethod();
			retVal = readMethod.invoke(obj);
			break;
		}
	}
	return retVal;
}



版权声明:本文为weixin_43318134原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。