1、反射的根本 -- Class类
1.1 Class类的概念理解
在我们的生活中,很多很多的人,我们可以用一个java类Person来表示,那么我们的java程序有很多很多的类,又该怎样表示呢?就是Class!!
java程序中的各个java类属于同一类事物,描述这一类事物的java类就是Class。
1.2 Class类的内容
我们知道,在Person这个类中,每一个实例对象就是一个具体的人,如张三李四等,那Class的实例对象又代表什么呢?它代表的就是java类的二进制字节码,一个java类的所有内容,如类名、成员变量、成员方法、构造函数、继承的抽象类或实现的接口等等,这些信息都存储在二进制字节码中,而Class对象就是表示着这些东西。
1.3 三种方法获取Class对象(字节码)
(1)类名.class(),如System.class();
(2)对象.getClass(),如Person.getClass();
(3)Class.forName("类名"),如Class.forName("java.util.Date")。
1.4 Class.forName()的作用
获取类的字节码,而获取类的字节码有两种情况,一是类已经被java虚拟机缓存,这时是直接从虚拟机缓存中取;二是类还没有被虚拟机缓存,就通过类加载器,把类的字节码从磁盘加载到java虚拟机内存,然后获取,同时虚拟机将该字节码缓存,以便以后获取。
2、认识反射
2.1 反射的概念
反射主要是指程序可以访问、检测和修改它本身的状态或行为的一种能力。
换一种更容易理解的说法,反射就是把java类中的各种成分(如类的成员变量、方法、构造方法等)映射成相应的java类
(Method、Constructor等)。
映射成相应的java类该怎么理解呢?汽车是一个类,而汽车的发动机、刹车装置等也都是一个个类,那么对于java类也可以这
样理解,在jdk中,java类的每一种成分都有相应的类来表示,这些类就是Method、Constructor等。
2.2 反射与Class类的关系
上文说到,java类的所有信息都存储于二进制字节码中,而Class的对象就是指这些二进制字节码,当我们需要用反射获取一个
java类的一些信息时,我们就得通过字节码来获取。所以我们才说Class是反射的根本,是反射的基石。
2.3 反射的一些基本操作实例
(以下代码的输出结果笔者已写在System.out.println()语句旁边,以便读者阅读),以下程序用到了一个属性配置文件,笔者
想运行可以自己建立
public class ReflectTest {
public static void main(String[] args) throws Exception{
Person p2 = new Person();
Person p3 = new Person();
//从输出结果可知,同属一个类的所有成员,对象,获取出来的字节码是一样的,也就是说,类的字节码只有一份
System.out.println(p2.getClass() == p3.getClass());//true
//一、反射与构造方法
//1.普通方式创建String对象
String str1 = new String(new StringBuffer("abc"));
System.out.println("str1:" + str1);//str1:abc
//反射的方式创建:思路是参考正常的方式,创建对象需要到相应的构造方法,那么可以通过反射来得到相应的构造方法,再创建对象
Constructor construct = String.class.getConstructor(StringBuffer.class);
String str2 = (String) construct.newInstance(new StringBuffer("abc"));
System.out.println("str2:" + str2);//str1:abc
//二、反射与成员变量
//对Person类进行测试,以下会告诉你,反射是会破坏封装的
Person p1 = new Person("xian", 22);
//1、访问public的成员的变量
Field fieldName = p1.getClass().getField("name");
//注意,此时fieldName的值并不是创建对象时赋的"xian",而是Person这个类的字节码所对应的成员变量名,见下行输出
System.out.println(fieldName);//public java.lang.String com.xiaoxian.app.Person.name
//要获取它的值,可以用下行方法,针对public的成员
String name = (String)fieldName.get(p1);
System.out.println(name);//xian
//2、访问private成员
//下行会报错,是因为getField方法不能获取到private的成员变量
//Field fieldAge = p1.getClass().getField("age");
//用此方法,获取已声明的的成员变量
Field fieldAge = p1.getClass().getDeclaredField("age");
System.out.println(fieldAge);//private java.lang.Integer com.xiaoxian.app.Person.age
//对于private的成员变量,也不能用get方法来获取值,因为没有权限
//int age = (int) fieldAge.get(p1);
//用暴力反射来获取,先将访问权限设为可访问,见下行,从此可以看出,反射可以访问private的成员,破坏了封装性
//设置访问权限为可访问
fieldAge.setAccessible(true);
int age = (int) fieldAge.get(p1);
System.out.println(age);//22
//应用:将Person类的成员变量类型为String的含有b的全部换成a
Field fields[] = Person.class.getFields();
for (Field field : fields) {
//这里用 == 进行比较,因为字节码一个类只有一个
if (field.getType() == String.class) {
String oldStr = (String) field.get(p1);
String newStr = oldStr.replace('b', 'a');
field.set(p1, newStr);
}
}
System.out.println(p1);//Person [name=xian, age=22, str1=aallaase, str2=aasketaall, str3=xiaoxian]
//三、反射与成员方法
//如果m1方法是private的,访问形式与访问private成员变量一样
//Method m1 = p1.getClass().getMethod("printTest", null);
Method m1 = p1.getClass().getDeclaredMethod("printTest", null);
m1.setAccessible(true);
//正常情况下,方法调用通常是对象名.方法名(参数列表),而反射恰恰是反着过来的
//取得方法m1,通过invoke()方法执行,invoke()的参数即为调用该方法的对象和相应的实参
m1.invoke(p1, null);//输出:i am printTest()!
Method m2 = p1.getClass().getMethod("printTest", int.class);
//传入的第一个参数值为null,表示该方法是static的,不需要用实例对象来调用
m2.invoke(null, 18);//输出:you give me a number :18
//四、反射与数组
int[] a1 = new int[]{1, 2, 3};
if (a1.getClass().isArray()) {
System.out.println(Array.get(a1, 1));//2
System.out.println(Array.getLength(a1));//3
}
//java.lang.Reflect.Array提供了动态创建和访问 Java 数组的方法。
String[] ss = (String[]) Array.newInstance(String.class, 3);
System.out.println(ss.getClass().isArray());//true
//利用反射读取配置文件,模拟框架思想动态调用java类
FileInputStream in = new FileInputStream(new File("conf.properties"));
Properties ps = new Properties();
ps.load(in);
in.close();
String className = ps.getProperty("className");
Constructor con = Class.forName(className).getConstructor(className.getClass());
String a = (String)con.newInstance("aaaa");
System.out.println(a);//aaaa
//应用:ArrayList与HashSet的hashcode比较分析,并根据此举例java的内存泄漏
}
}
class Person{
public String name;
private Integer age;
public String str1 = "ballbase";
public String str2 = "basketball";
public String str3 = "xiaoxian";
public char cc = 'a';
public Person() {}
public Person(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", str1=" + str1
+ ", str2=" + str2 + ", str3=" + str3 + "]";
}
private void printTest(){
System.out.println("i am printTest()!");
}
public static void printTest(int para){
System.out.println("you give me a number :" + para);
}
}
2.4 反射与框架技术
做过开发的读者应该或多或少都接触过框架,如spring、Struts2等,其实框架技术的基石也是反射,我们平时开发基本都是通
过配置文件的方式来为java类的各种成员设置属性值,我们最常见的也是最容易看到的(意思是指不用看源代码就能知道)就
是在配置文件中指定某个类的全路径名,那个就是利用了反射机制来动态获取那个类的信息(字节码),当获取到这个类的信
息之后,框架技术就可以在后台动态的操作这些类了,上文的实例代码中也有一个小例子模拟框架用反射来加载类,可见,框
架技术是与反射离不开,当然,要真正领会到一个框架技术的奥妙所在,还得仔细的去研读源代码才行哦!
2.5 反射的强大用处
尽管反射会破坏了封装性,但是它的强大用处是毋庸置疑的:
(1)在运行时判断任意一个对象所属的类;
(2)在运行时构造任意一个类的对象;
(3)在运行时判断任意一个类所具有的成员变量和方法;
(4)在运行时调用任意一个对象的方法;
(5)动态的创建一个类,这个类的继承关系可以动态指定(动态代理设计模式)
(6)用于自定义注解,(所以说自定义注解要会写反射)
(7)……(反射的用处还有很多,就需要笔者自己慢慢摸索了)
3.总结
总之记住一句话,反射就是把java类中的各种成分(如类的成员变量、方法、构造方法等)映射成相应的java类(Method、
Constructor等)。而类的全部信息都存储在.class的二进制字节码文件中,想要获取到类的有关信息,就要先取得它的字节
码。并且,反射的思维基本上跟我们平时写程序的思维是逆过来的,只要理解了反射的本质,就能很灵活的应用反射!!!
希望对笔者有帮助!转载请注明出处!