反射

 

反射
Reflection
JDK1.2

一、反射的基石--Class类

1、Java程序中的各个java类属于同一事物,描述这些类事物的java类名就是Class。
2、对比提问:从多的人用一个什么类表示?从多的java类用一个什么类表示?
    人类---Person
    java类--Class
    注意这里Class是大写的,不是关键字class。

3、对比提问:
    1)Person类代表人,它的实例对象就是张三,李四这样一个个具体的人,
    2)Class类代表java类,它的各个实现对象又分别对应什么呢?

对应各个类在内存中的字节码,例如Person类的字节码,ArrayList类的字节码,等待。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,
不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?

4、得到字节码对象的三种方法
    1)类名.class,例如System.class
    2)对象.getClass(),例如new Date().getClass()
    3)Class.forName("完整类名"),例如 Class.forName("java.util.Data");
 反射时主要用第三种。它是静态方法。

 面试题:Class.forName()的的作用是什么?
 获取一个类的字节码对象,如果该类的字节码已经在内存中存在,就可以直接调用,
 如果还没有存在,就调用类加载器进行加载,然后获取该类的字节码对象。

5、九个预定义的Class对象:
    参看 Class.isPrimitive方法的帮助文档
    八个基本类型和void,分别是:boolean、bytecharshortintlongfloat、double和void。
    int.class == Integer.TYPE

6、数组类型的Class实例对象用的方法是:
 Class.isArray()

7、总之,只要是在源程序中出现的类型,都有各自的Class实例对象,
    例如int[],void。

实例:获取String类的字节码的三种方法

class Demo{
	public static void main(String[] args) throws Exception{
		String str1 = "abc";
		Class cls1 = str1.getClass();
		Class cls2 = String.class;
		Class cls3 = Class.forName("java.lang.String");

		System.out.println(cls1 == cls2);               //true
		System.out.println(cls1 == cls3);               //true
        
		//是否是原始类型
		System.out.println(cls1.isPrimitive());         //false
		System.out.println(int.class.isPrimitive());    //true
		System.out.println(int.class == Integer.class); //false
		System.out.println(int.class == Integer.TYPE);  //true
	}   
}


二、反射的概念

反射就是把java类中的各种成分映射成相应的java类。
也可以理解成反射就是程序自己能够检测自身信息,就像人会通过镜子来查看自己的身体。

例如,一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也是用一个个java类来表示。

就像骑车是一个类,骑车中的发动机,变速箱等等也是一个个类,表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,他们是Field、Method、Contructor、Package等等。

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象胡有什么用?怎么使用?这这是学习和应用反射的要点

实例

class Demo{
    public static void sop(Object obj){System.out.println(obj);}
	public static void main(String[] args) throws Exception {
		String s1 = "1234";
		Class c1 = s1.getClass();
		Class c2 = String.class;
		Class c3 = Class.forName("java.lang.String");
		sop(c1==c2);                    //c1与c2是否是同一个对象true
		sop(c1==c3);                    //c1与c3是否是同一个对象true
		sop(String.class.isPrimitive());//String是否是基本类型false
		sop(int.class.isPrimitive());   //int是否是基本类型true
		sop(int.class==Integer.class);  //int与Integer的字节码是否是同一个对象false
		sop(int.class==Integer.TYPE);   //int与Integer.TYPE的字节码是否是同一个对象true
		sop(int[].class.isPrimitive()); //int[]是否是基本类型false
		sop(int[].class.isArray());     //int[]是否是数组类型true
	}
}

三、构造方法的反射

Constructor 类

1、Constructor类代表某个类中的一个构造方法。
2、得到某个类所有的构造方法:
例子:
Constructor constructor[] =
    Class.forName("java.lang.String").getConstructor(StringBuffer.class);
3、创建实例对象:
通常方式:String str = new String(new StringBuffer("abc"));
反射方式:String str = (String)constructor.newInstance(new StringBuffer("abc"));
调用获得的方法时要用到上面相同类型的实例对象。

4、Class.newInstance()方法:
例子:String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎么样写的呢?用到了缓冲机制来保存默认构造方法的实例对象。

一个类有多个构造方法,用什么方式可以区分清楚想要得到其中的哪个方法呢?根据参数的个数和类型,
例如,Class.getMethod(name.Class...args)中的args参数就代表索要获取的那个方法的各个参数的类型的列表。
重点:参数类型用什么方式表示?用Class实例对象。
例如:
int.class,(int []).class
int [] ints = new int[0];
ints.getClass();

需求:反射String类的 String(StringBuffer buffer) 这个构造方法
思路:
1、通过String类的字节码对象调用getConstructor方法获取这个类的构造方法。
2、具体要获得哪个构造方法,就给这个方法传递一个参数类型。这个参数类型是Class对象的一个数组。
3、用第一步返回的一个Constructor对象调用newInstance方法,创建StringBuffer的实例对象。
实例:反射构造方法

import java.lang.reflect.Constructor;
public class ReflectConstructor {
	public static void main(String[] args) throws Exception {
    	Constructor<String> c = String.class.getConstructor(StringBuffer.class);
        String s = (String)c.newInstance(new StringBuffer("abc"));
        System.out.println(s);
    }
}
/*
结果
abc
*/

四、成员变量的反射

Field类
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。
反射的字段可能是一个类(静态)字段或实例字段。

部分方法:
Object get(Object obj) 返回指定对象上此 Field 表示的字段的值。
void set(Object obj, Object value) 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
String getName() 返回此 Field 对象表示的字段的名称。
Class<?> getType() 返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。 
T newInstance() 创建此 Class 对象所表示的类的一个新实例。
boolean isEnum() 当且仅当该类声明为源代码中的枚举时返回 true。
boolean isArray() 判定此 Class 对象是否表示一个数组类。
boolean isPrimitive() 判定指定的 Class 对象是否表示一个基本类。就是那8个基本数据类
boolean isSynthetic() 判定指定的 Class 对象是否表示一个复合类。 除了那8个基本类,就是复合类
boolean isInterface() 判定指定的 Class 对象是否表示一个接口类。
boolean isMemberClass() 当且仅当底层类是成员类时返回 true。
boolean isEnumConstant() 如果此字段表示枚举类型的元素,则返回true,否则返回false。
boolean isInstance(Object obj) 判定指定的 Object 是否与此 Class 所表示的对象赋值兼容。
 
Field getDeclaredField(String name)
返回此类所有已声明字段的Field对象
该对象映射此Class对象所表示的类或接口的指定已声明字段。
name参数是一个String,用于指定所需字段的简称

Field[] getDeclaredFields()
返回此类所有已声明字段的Field对象数组。
这些对象映射此Class对象所表示的类或接口所声明的所有字段。
包括公共、私有、保护、默认(包)访问字段,但不包括继承的字段。

Field getField(String name)
返回公共字段的Field对象,该对象反映此Class对象所表示的类或接口的指定公共成员字段。
name参数是一个String,用于指定所需字段的简称。

Field[] getFields()
返回公共字段的Field对象数组,这些对象反映此Class对象所表示的类或接口的所有可访问的公共字段。
返回数组中的元素没有排序,也没有任何特定的顺序。如果类或接口没有可访问的公共字段,
或者表示一个数组类、一个基本类型或 void,则此方法返回长度为 0 的数组。

需求:定义一个类ClassPoint,然用另外一个类来反射这个类的成员变量,包括私有成员变量。
实例:反射成员变量-和暴力反射

import java.lang.reflect.Field;
//定义一个用来反射的类
class ClassPoint { 
	public int x;
	private int y;
	public ClassPoint(int x, int y) {
		this.x = x;
		this.y = y;
	}
}
public class ReflectField {
	public static void main(String[] args) throws Exception {
		ReflectField();
	}
	//对ClassPoint继承成员变量反射
	public static void ReflectField() throws Exception {
	//创建该类的实例对象
		ClassPoint cp = new ClassPoint(3,5);
		
		//反射变量 x
		//获取对象的字节码,根据字节码获得x对应的成员字段
		Field fieldX = cp.getClass().getField("x");
		//通过字段fieldX获取它对应的值
		System.out.println("变量x的值:"+fieldX.get(cp));
        
		//反射私有成员变量y--暴力反射
		//getDeclaredField()获取声明的字段,不管是被什么修饰的,但只要不是继承的。
		Field fieldY = cp.getClass().getDeclaredField("y");
		fieldY.setAccessible(true);
		//setAccessible()取消的字段的权限检查,false表示要检查
		fieldY.setAccessible(true);
		System.out.println("变量y类型:"+fieldY.getType());//获取该字段对应的变量类型
		System.out.println("变量y名称:"+fieldY.getName());//获取该字段对应的变量名 
		System.out.println("变量y的值:"+fieldY.get(cp));  //获取该字段对应的变量的值
	}
}
/*
结果
变量x的值:3
变量y类型:int
变量y名称:y
变量y的值:5
*/

五、成员变量反射的综合实例

需求:反射某个类中所有的String类型的成员变量,并将该变量的值中指定的字符替换成新的字符。
分析:其实就是通过反射用用新字符串替换所有String类型的原来的字符串
思想:
1、定义StringDemo类,类里定义多种类型的成员变量,有的被public修饰。
2、另外定义一个类实现对StringDemo类的反射和其他操作,该类首先创建StringDemo的实例对象。
3、用getFields方法返回一个Field数组,用getFields方法是限制了只能反射被public修饰的成员字段。
4、变量该Field数组,取出每个字段,然后用该字段获取它的声明类型的Class对象与String.class比较。
5、如果是同一份字节码,就用set方法把该字段的值用新的值替换掉。

实例:通过反射把String类型的变量的值替换成新值

import java.lang.reflect.Field;
class StringDemo { 
	public int x = 0;
	public String str1 = "wuguangxin";
	public String str2 = "howareyou";
	String str3 = "jiewin";
}
public class ReflectFieldTest {
	public static void main(String[] args) throws Exception {
    	changeStringValue();
    }
    public static void changeStringValue() throws Exception{
		//创建对象
		StringDemo str = new StringDemo();
		//用getFields()方法返回一个所有声明的公共字段数组
		Field[] fields = str.getClass().getFields(); 
		//变量该数组,获取每一个字段进行
		System.out.println("把u替换成*");
		for (Field field : fields){
			//如果该字段的字节码和String的字节码相同,说明是同一份字节码。
			//字节码最适合用==比较,不建议用equals
			if(field.getType() == String.class){
				//获取原来的字段值
				String oldValue = (String)field.get(str);
				//把原来的值的指定字符替换成指定字符
				String newValue = oldValue.replace("u", "*");
				//设置该字段对应的变量的值为新的值
        		field.set(str, newValue);
				System.out.println(oldValue+" --> "+newValue);//测试
			}
		}
	} 
}
/*
结果:
把u替换成*
wuguangxin --> w*g*angxin
howareyou --> howareyo*
*/

六、成员方法的反射

Method 类
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。
所反映的方法可能是类方法或实例方法(包括抽象方法)。

方法:
String getName() 返回此 Method 对象表示的方法名称。
boolean isVarArgs() 如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。
boolean isSynthetic() 如果此方法为复合方法,则返回 true;否则,返回 false。
String toGenericString() 返回描述此 Method 的字符串,包括类型参数。
Object invoke(Object obj, Object... args) 对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。

invoke方法参数:
obj - 从中调用底层方法的对象
args - 用于方法调用的参数
返回:使用参数 args 在 obj 上指派该对象所表示方法的结果

实例:成员方法的反射

import java.lang.reflect.Method;

public class ReflectMethod {
	public static void main(String[] args) throws Exception {
		reflectMethod();
	}
	/**
	 * 利用反射调用String类的charAt方法来获取字符串str的指定值。
	 * Method代表字节码的方法
	 * 从String获取字节码,根据字节码获取方法,参数(要获取的方法名称,参数列表)
	 * @throws Exception
	 */
	public static void reflectMethod() throws Exception {
		String str = "abc";
		Method methodCharAt = String.class.getMethod("charAt", int.class);
    	System.out.println(methodCharAt.invoke(str,1));
    }
}
/*
结果
b
*/


七、 对接收数组参数的成员方法进行反射

实例:对接收数组参数的成员方法进行反射

import java.lang.reflect.Method;

public class ReflectArrayMethod {
	public static void main(String[] args) throws Exception {
		reflectArrayMethod(args);
	}
	public static void reflectArrayMethod(String[] args) throws Exception {
		//普通方式调用main方法
		//TestArguments.main(new String[]{"abcd","123","eeeee"});

		//反射方式调用main方法。
		String srartingClassName = args[0];
		Method mainMethod = Class.forName(srartingClassName).getMethod("main", String[].class);
		//null:因为main方法是静态的,调用静态方法不需要对象
	
		//这样会报错,说参数个数不对,怎么解决这个问题?
		//mainMethod.invoke(null, new String[]{"abcd","123","eeeee"});
		//上面语句报错的原因是:因为接收的参数是1个,而new String[]{"abcd","123","eeeee"}是一个数组,
		//会被拆成单个的元素,于是就出现了3个参数,为了把以上的写法当做一个参数,有2种方法:

		//解决方法1:把这个数组在进行一次包装,把它作为一个元素封装到Object数组中。
		//mainMethod.invoke(null, new Object[]{new String[]{"abcd","123","eeeee"}});

		//解决方法2:类型转换,在前面加上(Object)转换类型,告诉编译器,这是一个Object类型的参数,不要拆包。此效率比较高
		//main方法是静态的,所以invoke方法的参数用null
		mainMethod.invoke(null, (Object)new String[]{"abcd","123","eeeee"});
	}
}
//注意:在运行时首先要获取此类的完整路径“zxx.enhance.TestArguments”,就是包名+类名。
//然后点右键-->Run As-->Run Configurations-->(x)=Arguments,在里面粘贴刚才复制的。保存后在运行。
class TestArguments{
	public static void main(String[] args) throws Exception {
		for(String arg : args){
			System.out.println(arg);
		}
	}
}
/*
结果
abcd
123
eeeee
*/


八、数组与Object的关系及其反射类型

public class ArrayAndObject {
	//反射数组。打印对象中成员方法
	public static void main(String[] args) {
		int[] a1 = new int[3];
		int[] a2 = new int[4];
		int[][] a3 = new int[2][3];
		String[] a4 = new String[3];
		System.out.println(a1.getClass() == a2.getClass());
		//System.out.println(a1.getClass() == a4.getClass());
		//System.out.println(a1.getClass() == a3.getClass());

		System.out.println(a1.getClass().getName());
		//获取他们的父类的字节码名称
		System.out.println(a1.getClass().getSuperclass().getName());
		System.out.println(a2.getClass().getSuperclass().getName());
		System.out.println(a3.getClass().getSuperclass().getName());
		System.out.println(a4.getClass().getSuperclass().getName());

		Object aObj1 = a1;
		Object aObj2 = a4;
		//基本类型的一维数组不能转换为Object类型数组。
		//因为Object这个数组里面装的是int类型的数组,不是Object
		//Object[] aObj3 = a1;
		Object[] aObj4 = a3;
		Object[] aObj5 = a4;
		System.out.println(aObj1);
		System.out.println(aObj2);
		System.out.println(aObj4);
		System.out.println(aObj5);
	}
}
/*
结果
true
[I
java.lang.Object
java.lang.Object
java.lang.Object
java.lang.Object
[I@65690726
[Ljava.lang.String;@525483cd
[[I@2a9931f5
[Ljava.lang.String;@525483cd
*/


 

九、数组的反射

具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用,
非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异。
Array工具类用于完成对数组的反射操作。

实例:数组的反射

import java.lang.reflect.Array;

public class ReflectArray {
	public static void main(String[] args) {
		reflectArray();
	}
	//反射数组。打印对象中成员方法
	private static void reflectArray() {
		String[] obj = {"A","B","C"};
		//String obj ="ABC";

		Class<? extends String[]> cla = obj.getClass();
		//如果是一个数组,就拆成单个元素打印出来。
		if(cla.isArray()){
			int len = Array.getLength(obj);			
			for (int i = 0; i < len; i++) {
				System.out.println(Array.get(obj, i));
			}
		}
		//如果不是数组,就直接打印对象。
		else{
			System.out.println(obj);
		}
	}
}
/*
结果
A
B
C
*/


 

十、集合的反射

*框架的概念及用反射技术开发框架的原理
*用类加载器的方式管理资源和配置文件

反射的作用 -- 实现框架功能

1、框架
我做房子卖给用户,由用户自己安装门窗和空调,我做的房子就是框架,
用户需要使用我的框架,把门窗插入进我的框架中。
框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。

2、框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在读小学,还不会写程序,
那么我写的框架程序怎么调用你以后写的类(门窗)呢?

因为在写程序时无法知道要被调用的类名,所以,
在程序中无法直接new某个类的实例对象,而是要用凡是的方式来做。

3、综合实例:
先直接用new语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成
Reflection类的equals和hashcode方法,比较两个集合的运行结果差异。

然后改为采用配置文件加反射的凡是创建ArrayList和HashSet的实例对象,
比较观察运行结果差异。

需求:利用反射实现框架功能
思路:
1)通过文件输出流,创建一个文件Config.properties,
2)获取”className=java.util.ArrayList“的字节码后存入文件中。文件可以手动创建。
3)创建文件输入流和一个属性集,读取指定文件内容,加载到属性集中。
4)用键”className“在属性集中收索。
5)获得新集合,往集合中添加元素。
6)打印集合

实例

import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;

public class ReflectCollcetion {
	public static void main(String[] args) throws Exception {
		reflectCollcetion();
	}
	//反射集合
	public static void reflectCollcetion() throws Exception {
		//实际开发中,配置文件的路径不是这么写的,必须要写完整的路径。
		//InputStream is = new FileInputStream("config.properties");
		InputStream is = ReflectDemo.class.getResourceAsStream("config.properties");
		Properties props = new Properties();
		props.load(is);
		//关闭的是is对象关联的那个物理资源,对象本身并没有关闭,
		//而是有java的垃圾回收机制处理的。
		is.close();
		
		//反射做法
		String className = props.getProperty("className");
		Collection<ClassPoint> collections = (Collection)Class.forName(className).newInstance();
		
		//原始做法
		//Collection<ClassPointDemo> collections = new HashSet<ClassPointDemo>();
		ClassPoint cp1 = new ClassPoint(3,3);
		ClassPoint cp2 = new ClassPoint(5,5);
		ClassPoint cp3 = new ClassPoint(3,3);
		collections.add(cp1);
		collections.add(cp2);
		collections.add(cp3);
		collections.add(cp1);
		
		//更改config.properties配置文件里的ArrayList为HashSet后,打印结果将不同
		//因为集合的存储方式不同,ArrayList可以有重复元素,是有序的
		//而HashSet集合是无序的,不可以重复存储,因为该集合会判断hashCode和equals方法。
		System.out.println("集合元素个数:"+collections.size());
	}
}
/*
结果
集合元素个数:4
*/

 十一、通过反射获得泛型的实际类型参数

分析:
 比如:Vector<Date> v = new Vector<Date>();
 那么通过v是无法知道定义它的那个泛型类型的,那么可以把这个v交给一个方法当做参数或者返回值类型来使用,
 然后通过Method类的getGenericParameterTypes()方法来获得该方法的参数列表,从而获得参数实际间类型。

实例

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Date;
import java.util.Vector;

/**
 * 通过反射获得泛型的实际类型参数
 * @author Administrator
 */
public class GenericTest {
	public static void main(String[] args) throws Exception {
		//通过v对象是无法得到的,值能通过方法来获取,那么就要定义一个方法如下面applyVector方法。
		//Vector<Date> v = new Vector<Date>();
		
		//获得字节码,通过字节码获得方法,参数是一个方法名,Vector的字节码
		Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);
		
		//通过Method类的getGenericParameterTypes()方法
		//反射applyMethod方法的参数化类型,可能有多个,所以是数组。
		Type[] types = applyMethod.getGenericParameterTypes();
		
		//返回types的第一个参数,返回的是ParameterizedType类型。
		ParameterizedType pType = (ParameterizedType)types[0];
		
		//获得原始类型
		System.out.println(pType.getRawType());
		
		//获得实际类型。
		System.out.println(pType.getActualTypeArguments()[0]);
	}
	
	//需要定义这个方法,通过这个方法的参数列表来反射他的参数类型。
	public static void applyVector(Vector<Date> v) {
	}
}
/*
结果
class java.util.Vector
class java.sql.Date
*/

 

十二、反射接口中的成员变量

实例

import java.lang.reflect.Field;

interface Inter{
	public int z = 8;
}
class ClassPoint1 implements Inter{
	public int x;
	@SuppressWarnings("unused")
	private int y;
	public ClassPoint1(int x, int y){
		this.x = x;
		this.y = y;
	}
}
public class ReflectIntefaceField{
	public static void main(String[] args) throws Exception{
		ClassPoint1 cp = new ClassPoint1(3, 5);
		//先得到接口的Class对象数组
		for(Class<?> ca : cp.getClass().getInterfaces()){
			//得到Field对象数组
			ca.getFields();
			//遍历数组得到所有对象的名字
			for(Field fd : ca.getFields()){
				System.out.println(fd.getType());	//获得类型
				System.out.println(fd.getName());	//获得名称
				System.out.println(fd.get(cp));		//获得值
			}
		}
	}
}
/*
结果
int
z
8
*/

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值