Java高级技术第二章——Java的反射机制,
Java高级技术第二章——Java的反射机制,
前言
Java高级技术系列前言点击此处
JAVA 反射
Java让我们在运行时识别对象和类的信息,主要有2种方式:
一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型信息;另一种是反射机制,它允许我们在运行时发现和使用类的信息。
RTTI
RTTI (Run-Time Type Information),通过运行时类型信息程序能够使用引用来检查这些引用所指的对象的实际派生类型,并且获取一些诸如类名,包名等信息。
RTTI实现,如下例子:
class Human
{
private int height;
public int getHeight()
{
return this.height;
}
public void setHeight(int height){
this.height = height;
}
}
class Woman extends Human
{
Woman(int height){
this.setHeight(height);
System.out.println("I am Woman");
}
public Human giveBirth()
{
System.out.println("Give birth");
return (new Human());
}
}
//使用类
public class Main {
public static void main(String[] args) throws Exception{
Human aPerson = new Human();
Class c1 = aPerson.getClass();
System.out.println(c1.getName());//Human
Human anotherPerson = new Woman(158);//I am Woman
Class c2 = anotherPerson.getClass();
System.out.println(c2.getName());//Woman
Class c3 = Class.forName("Woman");
System.out.println(c3.getName());//Woman
Woman woman = (Woman) c3.newInstance();//InstantiationException
//这里会报异常,如果Woman的构造函数是无参数的,则不会报错!
System.out.println(woman.getHeight());//空构造函数,则返回int默认值0
}
}
其返回结果为:
Human
I am Woman
Woman
Woman
InstanceException异常
从上面的例子中,可以看出来,RTTI主要用到的是:
Object的方法getClass()
该方法返回一个Class类的实例,在这个对象中可以调用方法来获取类名等信息。
Class的静态方法forName()
该方法会返回一个Class实例,该实例同上。
可以通过RTTI来获取该对象所属的类,由于Class对象的存在,Java不会因为类型的向上转换而迷失,这就是多态的原理。
.####JAVA反射机制:
通过反射的方式可以获取class对象中的属性、方法、构造函数等。
首先定义一个类,名称为Person,使用反射机制动态地获取该类的信息:
public class Person {
private Long id = 0L;
private String name = "";
public Person(Long id,String name){
//这里的Long一定要大写,不能写成long
// 因为反射是要反射的对象,不能是基本数据类型
this.id = id;
this.name = name;
}
private Person(String name){
this.name = name;
}
public Person(Long id){
this.id = id;
}
//两个构造函数,一个是私有的,另一个是default的
Person(){}//一个空参数的构造函数
long getId() {
return id;
}
String getName() {
return name;
}
private String getSomeThing(){
return “hello”;
}
}
在下面来完成反射的功能:
Class mclass1 = Class.forName(“Person”);//第一种实现方法,使用Class的静态方法
Class mclass2 = Person.class;//第二种方法获取Class对象
调用刚刚获取到的Class对象的方法newInstance即可,例如:
mclass.newInstance();
这里需要注意的是:
创建一个class文件表示的真实对象,底层会调用空参数的构造方法。要注意是空参数的构造函数,如果没有空参数的构造函数,则会报错。
如果要实例化有参数的构造函数,可以使用Constructor对象的newInstance方法来实现。这个Constructor对象是通过Class对象的getConstructor()方法来实现的。
具体的过程见下面的获取构造函数部分的例程。
非私有构造函数,参见下面的例程:
public void getPublicConstructor() throws Exception {
Constructor constructor = personClass.getConstructor(Long.class,String.class);
Person person = (Person)constructor.newInstance(100L,"zhangsan");
//实例化有参数构造函数
System.out.println(person.getId());
System.out.println(person.getName());
}
私有构造函数,参见下面的例程:
public void getPrivateConstructor() throws Exception {
Constructor con = personClass.getDeclaredConstructor(String.class);
con.setAccessible(true);//强制取消Java的权限检测
Person person2 = (Person)con.newInstance("zhangsan");
System.out.println(person2.getName());
}
说明:
上面的Class对象的getConstructor()方法的参数,一定都是对象,不能够使用基本的数据类型。所以,在定义函数的构造函数的时候,例如long可以写成Long,这样代表了Long对象,防止出现找不到构造函数的异常。
getConstructor()方法只能获取到public的构造函数,private和default的不可以获取到。
如果想要获取到非公开的构造函数的话,需要使用方法:getDeclaredConstructor()
非私有成员变量,参见下面的例程:
public void getNotPrivateField() throws Exception {
Constructor constructor = personClass.getConstructor(Long.class,String.class);
Object obj = constructor.newInstance(100L,"zhangsan");
Field field = personClass.getField("name");
field.set(obj, "lisi");
System.out.println(field.get(obj));
}
私有成员变量,参见下面的例程:
public void getPrivateField() throws Exception {
Constructor constructor = personClass.getConstructor(Long.class);
Object obj = constructor.newInstance(100L);
Field field2 = personClass.getDeclaredField("id");
field2.setAccessible(true);//强制取消Java的权限检测
field2.set(obj,10000L);
System.out.println(field2.get(obj));
}
说明:
获取成员变量的方法和获取构造函数的方法类似,只是变量在赋值和获取的时候,要使用Field对象的set()和get()方法来完成。
获取非私有的成员函数:
public void getNotPrivateMethod() throws Exception {
System.out.println(personClass.getMethod("toString"));
Object obj = personClass.newInstance();//获取空参的构造函数
Object object = personClass.getMethod("toString").invoke(obj);
System.out.println(object);
}
获取私有的成员函数:
public void getPrivateMethod() throws Exception {
Object obj = personClass.newInstance();//获取空参的构造函数
Method method = personClass.getDeclaredMethod("getSomeThing");
method.setAccessible(true);
Object value = method.invoke(obj);
System.out.println(value);
}
说明:
可以看出,这里面使用了一个invoke()方法,该方法的参数是一个Object对象,也就是一个具有该方法的类的实例化。调用invoke()方法后,就是执行了反射到的该方法,其返回值也为一个Object对象。
但是,上述方法getSomeThing()方法是空参数的,如果想要反射的该方法是有参数的如何操作呢?
例如修改getSomeThing()方法为:
private String getSomeThing(String string){
return string;
}
则对应的反射代码改为:
public void getPrivateMethod() throws Exception {
Object obj = personClass.newInstance();//获取空参的构造函数
Method method = personClass.getDeclaredMethod("getSomeThing",String.class);
method.setAccessible(true);
Object value = method.invoke(obj,"I am an arg");
System.out.println(value);
}
可以看出,在getDeclaredMethod()方法中多写入一项参数,对应地在invoke()方法中也多写一个对应的参数即可。这个多写的参数就是需要传递的参数。
具体的获取方法,见下面的例程:
//首先,获取一个Class对象
Class personClass = Person.class;
//获取当前加载这个class文件的那个类加载器对象
System.out.println(personClass.getClassLoader());
//获取某个类实现的所有接口
Class[] interfaces = personClass.getInterfaces();
for (Class class1 : interfaces) {
System.out.println(class1);
}
//反射当前这个类的直接父类
System.out.println(personClass.getGenericSuperclass());
//获取输入流
/**
* getResourceAsStream这个方法可以获取到一个输入流,这个输入流会关联到name所表示的那个文件上。
* path 不以’/'开头时默认是从此类所在的包下取资源,以’/'开头则是从ClassPath根下获取。其只是通过path构造一个绝对路径,最终还是由ClassLoader获取资源。
* 默认则是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。
*/
System.out.println(personClass.getResourceAsStream("/log4j.properties"));
//判断当前的Class对象表示是否是数组
System.out.println(personClass.isArray());
System.out.println(new String[3].getClass().isArray());
//判断当前的Class对象表示是否是枚举类
System.out.println(personClass.isEnum());
//判断当前的Class对象表示是否是接口
System.out.println(personClass.isInterface());
相关文章
- 暂无相关文章
用户点评