一、反射的基石–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、byte、char、short、int、long、float、double和void。
int.class == Integer.TYPE
6、数组类型的Class实例对象用的方法是:
Class.isArray()
7、总之,只要是在源程序中出现的类型,都有各自的Class实例对象,
例如int[],void。
例:获取String类的字节码的三种方法
class Demo1{
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 Demo2{
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 D19_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
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 D20_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 D21_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 D22_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
*/
七、 对接收数组参数的成员方法进行反射
例:对接收数组参数的成员方法进行反射
package zxx.enhance;
import java.lang.reflect.Method;
public class D23_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的关系及其反射类型
package zxx.enhance;
public class D24_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
*/
八、数组与Object的关系及其反射类型
package zxx.enhance;
public class D24_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工具类用于完成对数组的反射操作。
例:数组的反射
package zxx.enhance;
import java.lang.reflect.Array;
public class D25_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)打印集合
例:
package zxx.enhance;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
public class D27_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 v = new Vector();
那么通过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
*/
十二、反射接口中的成员变量
package zxx.enhance;
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 D28_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
*/