反射
Java 反射是Java语言的一个很重要的特征,它使得Java具体了“动态性”。
反射机制的概述:
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取信息以及动态调用对象的方法的功能称为 Java语言的反射机制。
总之: 反射也称为对类的解剖。把类的各个组成部分映射成一个个相应的Java类。
要想解剖一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
什么叫字节码?
当源程序中用到类时,首先要从硬盘把这个类的那些二进制代码,一个类编译成class放在硬盘上以后,就是一些二进制代码,要把这些二进制代码加载到内存中里面来,再用这些字节码去复制出一个一个对象来。
所以说,每一个字节码就是一个类的实例对象。
下面就来讲一下反射的基石——Class类
谈到Class,大家估计会把把它和class联系到一块儿,下面就先来说说它们的区别吧:
Class和class的区别:
1)class:Java中的类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则由此类的实例对象确定,不同的实例对象有不同的属性值。
2)Class:指的是Java程序中的各个Java类是属于同一类事物,都是Java程序的类,这些类称为Class。
例如:人对应的是Person类,Java类对应的就是Class。
Class是Java程序中各个Java类的总称;它是反射的基石,通过Class类来使用反射
反射是在运行状态下,通过class文件对象(Class的对象),去使用构造方法,成员变量,成员方法。
Class的概述:
所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就叫Class(描述字节码文件的对象)。
Class类中就包含属性有field(字段)、method(方法)、construction(构造函数)。
而field中有修饰符、类型、变量名等复杂的描述内容,因此也可以将字段封装成为一个对象。
用来获取类中field的内容,这个对象的描述叫Field。
同理,方法和构造函数也被封装成 对象Method、Constructor。
要想对一个类进行内容的获取,必须要先获取该字节码文件的对象。该对象是Class类型。
那么,我们是如何获取到class文件对象的呢? (字节码文件对象)
A:Object类的getClass()方法
B:数据类型的静态的class属性
C:通过Class类的静态方法forName(String className)
三种方式练习如下:
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 方式1
Person p = new Person(); //Person是一个类名
Class c = p.getClass();
Person p2 = new Person();
Class c2 = p2.getClass();
System.out.println(p == p2);// false
System.out.println(c == c2);// true
// 方式2
Class c3 = Person.class;
System.out.println(c == c3);// true
// 方式3
Class c4 = Class.forName("cn.itcast01.Person");// true
System.out.println(c == c4);
}
}
这三种方法,到底使用哪一种好呢?
第一种方法:麻烦之处在于每次都需要具体的类和该类的对象,以及调用getClass方法。
第二种方法:任何数据类型都具备着一个静态的属性class,这个属性直接获取到该类型的对应Class对象。比第一个简单,不用创建对象,不用调用getclass()方法,但是还是要使用具体的类,和该类的中的一个静态属性class完成;
第三种方法:只要知道类的名字即可,不需要使用该类,也不需要调用具体的属性和行为,就可以获取到Class对象了。
开发中建议用第三种,自己写例子测试可以使用前两种。 因为第三种方式可以结合配置文件使用。
九个预定义的Class:
1)包括八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class。 参看Class.isPrimitive();方法的帮助。
2)Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,所以和int.class是相等的。
基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示。
总之 ,只要是在源程序中出现的类型都有各自的Class实例对象,如int[].class。
数组类型的Class实例对象,可以用 Class.isArray() 方法判断是否为数组类型的。
返回与给定字符串名的类或接口的相关联的Class对象。
Class getClass()
返回的是Object运行时的类,即返回Class对象即字节码对象
Constructor getConstructor()
返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。
Field getField(String name)
返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段。
Field[] getFields()
返回包含某些Field对象的数组,表示所代表类中的成员字段。
Method getMethod(String name,Class… parameterTypes)
返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。
Method[] getMehtods()
返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。
String getName()
以String形式返回此Class对象所表示的实体名称。
String getSuperclass()
返回此Class所表示的类的超类的名称
boolean isArray()
判定此Class对象是否表示一个数组
boolean isPrimitive()
判断指定的Class对象是否是一个基本类型。
T newInstance()
创建此Class对象所表示的类的一个新实例。
1、Constructor类
概述:
如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化。这时怎么办呢?
这时就不能使用Class类中的newInstance方法了。既然要通过指定的构造函数进行对象的初始化。
就必须先获取这个构造函数——Constructor
Constructor代表某个类的构造方法。
1)通过反射获取构造方法并使用
Constructor类代表某个类中的一个构造方法
得到某个类所有的构造方法:
例子:Constructor[] constructors=Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
例子:Constructor constructors=Class.forName("java.lang.String").getConstructors(String.class);
需要注意的是:
1、创建实例时newInstance方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致。
2、newInstance():构造出一个实例对象,每调用一次就构造一个对象。
3、利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/*
* 反射获取构造方法并使用
* 构造方法 Constructor
* 成员变量 Field
* 成员方法 Method
*/
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException,
NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
//获取字节码文件对象
Class c=Class.forName("cn.itcast09.Person");
//获取构造器对象
Constructor con=c.getConstructor(); //表示我使用的是无参数的构造方法
//通过构造器对象创建对象
Object obj=con.newInstance();
System.out.println(obj);
//使用带参构造方法
Constructor con1=c.getConstructor(String.class,int.class);
//通过构造器对象创建对象
Object obj1=con1.newInstance("孙俪",32);
System.out.println(obj1);
}
}
运行后结果:
2、Field类
Field类代表某个类中一个成员变量
类中的方法有:
Field getField(String s); //只能获取公有和父类中公有
Field getDeclaredField(String s); //获取该类中任意成员变量,包括私有 setAccessible(ture);
如果是私有字段,要先将该私有字段进行取消权限检查的能力。也称暴力访问。
set(Object obj, Object value); //将指定对象变量上此Field对象表示的字段设置为指定的新值。
Object get(Object obj); //返回指定对象上Field表示的字段的值。
用一个案例帮助理解:import java.lang.reflect.Field;
/*步骤
* 1.得到所有成员变量的数组
* 2.遍历数组
* 3.判断数组内的元素是否是String类型的,用==判断,可以用getType方法获得Field对象的类型的class
* 然后使用==来判断getType返回的class是否与String.class相同,从而判断数组中元素属否是String类型的
* 4.得到变量的旧的字符串"hello"
* 5.将就字符串中的"hello"替换成“黑马,我正在为你而努力”,获取新的字符串
* 6.将新的字符串赋给成员变量。
*/
//创建一个演示的类:
public class StringDemo {
//定义成员变量
public String str1="hello,hurry up!";
public String str2="hello";
public String str3="有志者,事竞成!";
//输出方法:
@Override
public String toString() {
return str1 +" : "+ str2 + " : "+str3;
}
}
public class ReflectDemo1 {
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
//创建StringDemo对象
StringDemo sd=new StringDemo();
//输出没有修改之前的sd对象中的成员变量
System.out.println(sd);
//调用修改成员变量值的方法
ChangeStringvalue(sd);
//输出修改之后的sd对象中的成员变量。
System.out.println(sd);
}
//修改成员变量值的方法
private static void ChangeStringvalue(Object obj) throws IllegalArgumentException, IllegalAccessException {
//先获取类中所有的成员变量
Field[] fields=obj.getClass().getFields();
//遍历数组
for(Field field:fields){
//判断数组中的成员是不是String类型
if(field.getType()==String.class){ //这里应该用“==” 因为是同一个字节码
//如果是,就得到变量对应的字符串
String oldValue=(String)field.get(obj);
//将字符串中的旧串替换成新串,并返回一个新的字符串
String newValue=oldValue.replace("hello", "黑马,我正在为你而努力!");
//将新的字符串赋给obj对象中对应的成员变量
field.set(obj, newValue);
}
}
}
}
运行结果:
3、Method类
概述:Method类代表某个类中的一个成员方法。调用某个对象身上的方法,要先得到方法,再针对某个对象调用。
专家模式:谁调用这个数据,就是谁在调用它的专家。
如人关门:
调用者:是门调用关的动作,对象是门,因为门知道如何执行关的动作,通过门轴之类的细节实现。
指挥者:是人在指挥门做关的动作,只是给门发出了关的信号,让门执行。
总结:变量使用方法,是方法本身知道如何实现执行的过程,也就是“方法对象”调用方法,才执行了方法的每个细节的。
Method类中的方法:
Method[] getMethods(); //只获取公共和父类中的方法。
Method[] getDeclaredMethods(); //获取本类中包含私有。
Method getMethod("方法名",参数.class(如果是空参可以写null));
Object invoke(Object obj ,参数); //调用方法
如果方法是静态,invoke方法中的对象参数可以为null。
如:
获取某个类中的某个方法:(如String str =”abc”)
1)通常方式:str.charAt(1)
2)反射方式:
Method charAtMethod =Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);
charAtMethod.invoke(str,1);
说明:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法
用反射方式执行某个main方法:
首先要明确为何要用反射:在写源程序时,并不知道使用者传入的类名是什么,但是虽然传入的类名不知道,而知道的是这个类中的方法有main这个方法。所以可以通过反射的方式,通过使用者传入的类名(可定义字符串型变量作为传入类名的入口,通过这个变量代表类名),内部通过传入的类名获取其main方法,然后执行相应的内容。
此时会出现下面的问题:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?
按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢? jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。
所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke( null , new Object[]{new String[]{"xxx"}});
mainMethod.invoke( null , (Object)new String[]{"xxx"});
这两种方式编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。
小练习:import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/*
* 获取成员方法并使用。
*/
public class ReflectDemo2 {
public static void main(String[] args)throws Exception {
//获取字节码文件对象
Class c=Class.forName("cn.itcast09.Person");
//创建对象
Constructor con=c.getConstructor();
Object obj=con.newInstance();
//第一种: 无参数无返回值
Method m1=c.getMethod("show",null);
m1.invoke(obj, null);
System.out.println("--------------");
//第二种:带参数无返回值
Method m2=c.getMethod("function",String.class);
m2.invoke(obj, "张曦之");
System.out.println("----------------");
//第三种:带多个参数有返回值
Method m3=c.getMethod("reutrnValue",String.class,int.class);
Object ooo=m3.invoke(obj,"张曦之",26);
System.out.println(ooo);
System.out.println("-----------------");
//第四种:私有方法的调用
Method m4=c.getDeclaredMethod("hello",null);
m4.setAccessible(true);
m4.invoke(obj, null);
}
}
打印结果:
public static void main(String[] args) throws Exception {
// Student s = new Student();
// s.love();
// Teacher t = new Teacher();
// t.love();
// 如果有需求上的改动,这里的代码一直在发生改动。
// 通过反射如何来实现呢
// 反射+配置文件
//作为配置文件来说
/*
* 键是固定的,是已经知道的。
* 值是变化的。
*
* className,methodName
*/
Properties prop = new Properties();
FileReader fr = new FileReader("test.properties");
prop.load(fr);
fr.close();
//获取类名
String className = prop.getProperty("className");
//获取方法名
String methodName = prop.getProperty("methodName");
//获取字节码文件对象
Class c = Class.forName(className);
Constructor con = c.getConstructor();
Object obj = con.newInstance();
Method m = c.getMethod(methodName, null);
m.invoke(obj, null);
}
}
将来如果想调用哪个类,只要改一下配置文件就行了,很方便。
import java.lang.reflect.Method;
import java.util.ArrayList;
/*
*需求:给你一个ArrayList<Integer>的一个对象, 如果要求往这个集合中添加一个字符串数据,该怎么实现呢?
* 通过反射实现。
* 反射可以越过泛型检查
*/
public class ReflectTest {
public static void main(String[] args)throws Exception {
ArrayList<Integer> array=new ArrayList<Integer>();
//获取字节码文件对象
Class c=array.getClass();
Method m=c.getMethod("add", Object.class);
//往集合中添加数据
m.invoke(array, "hello");
m.invoke(array, "world");
m.invoke(array, "java");
//打印输出集合数据
System.out.println(array);
}
}
打印结果为:
这道题如果不用反射做的话,根本不能实现;