---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------
加载Person类就是将硬盘上的Person类的字节码文件内容加载到内存里,并封装成Class对象。
--什么是反射?
想要知道反射的概念,要先明白java类的作用是什么?java类就是用于描述一类事物的共性,这些事物包括现实中存在的事物,也包括虚拟的一些事物。把具有相同属性和行为的一类事物抽象化,就形成了类。那么,java中的所有类是不是也有一些共性的内容呢?我们知道,一个类是由多个部分组成的:成员变量、成员方法和构造方法等。那么我们是不是也可以把这些共性的内容抽取成一个类呢?在java中,使用Class这个类来描述java中所有的类。
在java中,所有的类都是Class的一个实例对象,它们的内容不同,但是,它们的特征相同,譬如,都有方法,有字段,有父类,有包。那么,它们的实例对象又是什么呢?学java基础的时候我们知道,每个类在被加载进内存时,都会先把它们对应的.class字节码加载进内存,那么,这份字节码就是Class的实例对象。那么,反射就是将类的字节码加载进内存,而后剖析出这个类的各个组成部分,包括构造函数、成员变量和方法等。这些信息用相应类的实例对象来表示,它们是Constructor、Field和Method等。
--加载类
了解了反射的概念后,我们就先创建一个类:Person。加载该类的字节码,然后通过反射技术剖析这个类的组成部分。
package cn.itcast.reflect;
import java.io.InputStream;
import java.util.List;
/**
* 人类
* @author 中关村阿旺
*
*/
public class Person {
public int age=44; //年龄(public修饰)
private static String address; //住址(private static修饰)
public Person(){
System.out.println("person");
}
//构造方法,private修饰
private Person(List list){
System.out.println("list");
}
public void aa1(){
System.out.println("aa1");
}
private void aa1(InputStream in){
System.out.println(in);
}
//方法,static修饰
public static void aa1(int num){
System.out.println(num);
}
//main方法,static修饰,有点特殊
public static void main(String[] args){
System.out.println("main");
}
}
加载Person类就是将硬盘上的Person类的字节码文件内容加载到内存里,并封装成Class对象。
加载类有三种方式:
第一种方式:通过Class类的静态方法forName()加载类,该方法接收一个参数,字符串类型的完整类名称。
例如:Class clazz=Class.forName("cn.itcast.reflect.Person");
第二种方式:通过实例化类的对象加载该类。因为实例化对象的过程是先要加载该类到内存中,然后才会初始化对象。
通过对象的getClass()方法获得Class对象,也就是该类的字节码。
例如:Class clazz1=new Person().getClass();
第三种方式:直接将该类的字节码加载进内存中。
例如:Class clazz2=Person.class;
例如:Class clazz=Class.forName("cn.itcast.reflect.Person");
第二种方式:通过实例化类的对象加载该类。因为实例化对象的过程是先要加载该类到内存中,然后才会初始化对象。
通过对象的getClass()方法获得Class对象,也就是该类的字节码。
例如:Class clazz1=new Person().getClass();
第三种方式:直接将该类的字节码加载进内存中。
例如:Class clazz2=Person.class;
以上三种方式,我们使用哪种方式都可以,类已经进内存了,就可以用反射技术剖析类的组成部分了。
--反射类的构造函数
在java中Constructor类提供关于类的单个构造方法的信息以及对它的访问权限。
例如:反射构造函数:public Person(){}
通过Class类的getConstructor()方法可以得到一个Constructor对象。该方法接收一个参数,就是你反射的那个构造方法的参数的类型(字节码)。
因为我们反射的是无参的构造函数,所以传入null值,得到构造函数对象。
代码:Constructor c=clazz.getConstructor(null);
此构造函数对象中有一个方法:newInstance(),可以返回我们反射的类的实例对象。该方法接收的参数是实际传入构造方法的参数。
我们反射的是Person类,所以用Person对象接收。由于newInstance()方法返回类型是Object,所以需要强转。
代码:Person p=(Person)c.newInstance(null);
因为我们反射的是无参的构造函数,所以传入null值,得到构造函数对象。
代码:Constructor c=clazz.getConstructor(null);
此构造函数对象中有一个方法:newInstance(),可以返回我们反射的类的实例对象。该方法接收的参数是实际传入构造方法的参数。
我们反射的是Person类,所以用Person对象接收。由于newInstance()方法返回类型是Object,所以需要强转。
代码:Person p=(Person)c.newInstance(null);
用得到的这个对象,我们可以获取该对象中封装的信息。
System.out.println(p.age); //返回44
System.out.println(p.age); //返回44
上面反射的是public修饰符修饰的构造函数,我们也可以反射非public修饰符修饰的构造函数。
例如:反射构造函数:private Person(List list){}
由于此构造函数反射的是private修饰的,所以可以通过Class类的getDeclaredConstructor()方法获取构造函数对象。
getDeclaredConstructor()方法可以反
射在Person类中所有声明(定义)过的构造方法,包括private修饰的。
由于此构造函数有参数,我们可以传入此构造函数参数的字节码即可。
代码:Constructor c=clazz.getDeclaredConstructor(List.class);
由于private修饰的成员只能在本类中被访问,但是,暴力反射可以在让其在本类外被访问。
所谓暴力反射,就是将那些在反射的类中不是public修饰的成员方法、属性、构造方法等,强行打开它们的访问权限,使其可以在本类外访问。
代码:c.setAccessible(true);这就是暴力反射
代码:c.setAccessible(true);这就是暴力反射
代码:Person p=(Person) c.newInstance(new ArrayList());
System.out.println(p.age); //返回44
System.out.println(p.age); //返回44
注:另一种使用反射创建对象的方法,不是反射构造函数,而是直接使用Class类的newInstance()方法。这是因为java为了方便通过反射技术创建类的实例而添加的方法。
例如:Person p=(Person) clazz.newInstance();
System.out.println(p.age); //返回44
System.out.println(p.age); //返回44
--反射类的方法
在java中Method类提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。
1.反射类中的普通方法
通过Class类的getMethod()方法得到Method对象,该方法接收两个参数,第一个指明需要反射的方法名称,第二个指明需要反射的方法的参数类型。
例如:反射类的方法:public void aa1(){}
代码:Method m=clazz.getMethod("aa1", null);
使用Method对象的invoke()方法可以调用反射的方法,需要传入对象名(指明反射的是哪个对象的方法)和参数类型。
代码:m.invoke(p, null); //返回aa1
代码:Method m=clazz.getMethod("aa1", null);
使用Method对象的invoke()方法可以调用反射的方法,需要传入对象名(指明反射的是哪个对象的方法)和参数类型。
代码:m.invoke(p, null); //返回aa1
上面的代码反射的是public修饰的方法,想要访问非public修饰的方法可以通过Class类的getDeclaredMethod()方法访问在Person类中定义(声明)的所有方法。
例如:反射类的方法:private void aa1(InputStream in){}
由于要反射的此方法有参数,那么在第二个参数中传入该参数的字节码即可。
代码:Method m=clazz.getDeclaredMethod("aa1", InputStream.class);
m.setAccessible(true); //暴力反射
代码:m.invoke(p, new FileInputStream("e:\\黑马\\黑马视频笔记 \\033java 基础加强\\课堂笔记1.txt")); //返回java.io.FileInputStream@150bd4d
代码:Method m=clazz.getDeclaredMethod("aa1", InputStream.class);
m.setAccessible(true); //暴力反射
代码:m.invoke(p, new FileInputStream("e:\\黑马\\黑马视频笔记 \\033java 基础加强\\课堂笔记1.txt")); //返回java.io.FileInputStream@150bd4d
注:如果反射的方法是一个静态方法,比如:public static void aa1(int num){}
那么在用反射技术得到Method对象后,调用invoke()方法传入对象时,可以传入null。
代码:m.invoke(null, 56); //返回56
2.反射类中的main方法
反射类中的main方法:public static void main(String[] args)
有两种调用方式:
第一种方式:
代码:Method m=clazz.getMethod("main", String[].class);
由于main方法也是静态方法,所以可以不用传入对象。
由于main方法也是静态方法,所以可以不用传入对象。
代码:m.invoke(null, new Object[]{new String[]{"aaa","bbb"}}); //返回main
那么之所以这样写,是因为JDK1.4当中没有可变参数这一特性,它使用的是Object类型的数组作为参数,调用invoke()方法时,会把数组中的每一个元素当做一个参数来看待,也就是会把数组拆开。JDK1.5虽然有了可变参数这一新特性,但是它兼容JDK1.4,所以,它也会把可变参数当做是一个数组进行拆封。这样一拆,就成了两个字符串对象了,就与main方法的参数类型不一致了,所以会出现参数类型不匹配异常。为了解决这一异常,需要把数组再一次封装成数组,这样在进行拆封时,得到的仍然是一个数组,刚好符合main方法的参数类型。
第二种方式:
强制让java虚拟机把参数类型当做是一个Object类型的参数,而不是一个Object类型的数组。这样就不会进行拆封,而里面实际存放的是字符串数组,所以仍然与main方法的参数类型一致。
代码:m.invoke(null, (Object)new String[]{"rrrr","dddd"}); //返回main
那么之所以这样写,是因为JDK1.4当中没有可变参数这一特性,它使用的是Object类型的数组作为参数,调用invoke()方法时,会把数组中的每一个元素当做一个参数来看待,也就是会把数组拆开。JDK1.5虽然有了可变参数这一新特性,但是它兼容JDK1.4,所以,它也会把可变参数当做是一个数组进行拆封。这样一拆,就成了两个字符串对象了,就与main方法的参数类型不一致了,所以会出现参数类型不匹配异常。为了解决这一异常,需要把数组再一次封装成数组,这样在进行拆封时,得到的仍然是一个数组,刚好符合main方法的参数类型。
第二种方式:
强制让java虚拟机把参数类型当做是一个Object类型的参数,而不是一个Object类型的数组。这样就不会进行拆封,而里面实际存放的是字符串数组,所以仍然与main方法的参数类型一致。
代码:m.invoke(null, (Object)new String[]{"rrrr","dddd"}); //返回main
--反射类的字段
在java中Field类提供有关类或接口的单个字段的信息,以及对它的动态访问权限。
通过Class类的getField()方法可以得到Field对象,该方法接收一个参数,即需要反射的字段的名称。
例如:反射类的字段:public int age=44;
代码:Field f=clazz.getField("age");
可以根据Field类对象的get()方法得到该字段的值,该方法接收一个参数,指明需要反射的那个对象的字段。
可以根据Field类对象的get()方法得到该字段的值,该方法接收一个参数,指明需要反射的那个对象的字段。
代码:int num=(Integer) f.get(p); //由于该方法返回值为Object类型,而age字段为int,所以需要强转。
代码:System.out.println(num); //返回44
注:由于Person类文件可能不是我们自己编写的,所以不知道字段的类型,我们可以通过Field类对象的getType()方法得到该字段所属类型的字节码,根据字节码判断该字段的类型。
代码:Object value=f.get(p);
代码:Class type=f.getType(); //获得指定字段的类型,返回字节码对象
由于每种类型的字节码只存在一份,所以用“==”判断比较高效,当然equals()方法也可以。
if(type == int.class){
int newValue=(Integer) value; //自动拆箱
System.out.println(newValue); //自动装箱
}
代码:System.out.println(num); //返回44
注:由于Person类文件可能不是我们自己编写的,所以不知道字段的类型,我们可以通过Field类对象的getType()方法得到该字段所属类型的字节码,根据字节码判断该字段的类型。
代码:Object value=f.get(p);
代码:Class type=f.getType(); //获得指定字段的类型,返回字节码对象
由于每种类型的字节码只存在一份,所以用“==”判断比较高效,当然equals()方法也可以。
if(type == int.class){
int newValue=(Integer) value; //自动拆箱
System.out.println(newValue); //自动装箱
}
上面的代码反射的是public修饰的字段,那么反射非public修饰的字段需要使用Class类的getDeclaredField()方法,该方法可以反射在Person类中定义(声明)过的所有字段。
例如:反射类的字段:private static String address;
代码:Field f=clazz.getDeclaredField("address");
代码:f.setAccessible(true); //由于反射的是private修饰的字段,所以需要暴力反射。
该字段没有手动赋予值,可以通过Field类对象的set()方法写入指定的值。该方法接收两个参数,一个是需要反射的类的对象,一个是赋予该字段的实际值。由于所要获取的字段是静态的,不必创建该类的对象,所以传入null。
代码:f.set(null, "北京市");
代码:f.setAccessible(true); //由于反射的是private修饰的字段,所以需要暴力反射。
该字段没有手动赋予值,可以通过Field类对象的set()方法写入指定的值。该方法接收两个参数,一个是需要反射的类的对象,一个是赋予该字段的实际值。由于所要获取的字段是静态的,不必创建该类的对象,所以传入null。
代码:f.set(null, "北京市");
如果我们不知道该字段的类型,同样可以通过getType()方法得到。
代码:Object value=f.get(null);
代码:Class type=f.getType();
if(type == String.class){
String newValue=(String)value;
System.out.println(newValue); //返回北京市
}
代码:Object value=f.get(null);
代码:Class type=f.getType();
if(type == String.class){
String newValue=(String)value;
System.out.println(newValue); //返回北京市
}
---------------------- <a href="
http://edu.csdn.net"target="blank">ASP.Net+Android+IOS
开发</a>、<a href="
http://edu.csdn.net"target="blank">.Net
培训</a>、期待与您交流! ----------------------