反射
1.什么是反射?
反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。
正常情况下,如果我们要调用一个对象的方法,或者访问一个对象的字段,通常会传入对象实例。但是,如果不能获得该对象的类,这个时候就要用到反射机制了。
所以,反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。
反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)
2.认识反射
反射之中包含了一个“反”的概念,所以要想解释反射就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化对象,但是“反”指的是通过对象找到类。
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person per = new Person() ; // 正着操作
System.out.println(per.getClass().getName()); // 反着来
}
}
以上的代码使用了一个getClass()
方法,而后就可以得到对象所在的“包.类”名称,这就属于“反”了,但是在这个“反”的操作之中有一个getClass()
就作为发起一切反射操作的开端。
Person的父类是Object类,而上面所使用getClass()方法就是Object类之中所定义的方法。
3.获取Class对象
取得Class对象:public final Class<?> getClass()
,反射之中的所有泛型都定义为?,返回值都是Object。
而这个getClass()
方法返回的对象是Class类的对象,所以这个Class就是所有反射操作的源头。但是在讲解其真正使用之前还有一个需要先解释的问题,既然Class是所有反射操作的源头,那么这个类肯定是最为重要的,而如果要想取得这个类的实例化对象,Java中定义了三种方式。
3.1 三种获取方式
1. 通过Object类的getClass()
方法取得,基本不用
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person per = new Person() ; // 正着操作
Class<?> cls = per.getClass() ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}
2. 使用“类.class”取得
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Person.class ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}
3. 使用Class类内部定义的一个static方法, Class.forName("全限定类名")
public static Class<?> forName(String className) throws ClassNotFoundException;
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class> cls = Class.forName("com.github.krislinzhao.demo.Person") ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}
3.2 Class类常用方法
String getSimpleName()
: 获得类名字符串:类名String getName()
: 获得类全名:包名+类名T newInstance()
: 创建Class对象关联类的对象
示例代码:
public class ReflectDemo02 {
public static void main(String[] args) throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得类名字符串:类名
System.out.println(c.getSimpleName());
// 获得类全名:包名+类名
System.out.println(c.getName());
// 创建对象
Student stu = (Student) c.newInstance();
System.out.println(stu);
}
}
4.反射机制的应用
1.通过反射实例化对象
那么现在一个新的问题又来了,取得了Class类的对象有什么用处呢?对于对象的实例化操作之前一直依靠构造方法和关键字new完成,可是有了Class类对象之后,现在又提供了另外一种对象的实例化方法:
public T newInstance() throws InstantiationException, IllegalAccessException;
class Person {
@Override
public String toString() {
return "Person Class Instance .";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class> cls = Class.forName("com.github.krislinzhao.demo.Person") ; // 取得Class对象
Object obj = cls.newInstance() ; // 实例化对象,和使用关键字new一样
Person per = (Person) obj ; // 向下转型
System.out.println(per);
}
}
那么现在可以发现,对于对象的实例化操作,除了使用关键字new之外又多了一个反射机制操作,而且这个操作要比之前使用的new复杂一些,可是有什么用?
对于程序的开发模式之前一直强调:尽量减少耦合,而减少耦合的最好做法是使用接口,但是就算使用了接口也逃不出关键字new
,所以实际上new
是造成耦合的关键元凶。
范例:回顾一下之前所编写的工厂设计模式
interface Fruit {
public void eat() ;
}
class Apple implements Fruit {
public void eat() {
System.out.println("吃苹果。");
};
}
class Factory {
public static Fruit getInstance(String className) {
if ("apple".equals(className)){
return new Apple() ;
}
return null ;
}
}
public class FactoryDemo {
public static void main(String[] args) {
Fruit f = Factory.getInstance("apple") ;
f.eat() ;
}
}
以上为之前所编写最简单的工厂设计模式,但是在这个工厂设计模式之中有一个最大的问题:如果现在接口的子类增加了,那么工厂类肯定需要修改,这是它所面临的最大问题,而这个最大问题造成的关键性的病因是new
,那么如果说现在不使用关键字new
了,变为了反射机制呢?
反射机制实例化对象的时候实际上只需要“包.类”就可以,于是根据此操作,修改工厂设计模式。
interface Fruit {
public void eat() ;
}
class Apple implements Fruit {
public void eat() {
System.out.println("吃苹果。");
};
}
class Orange implements Fruit {
public void eat() {
System.out.println("吃橘子。");
};
}
class Factory {
public static Fruit getInstance(String className) {
Fruit f = null ;
try {
f = (Fruit) Class.forName(className).newInstance() ;
} catch (Exception e) {
e.printStackTrace();
}
return f ;
}
}
public class FactoryDemo {
public static void main(String[] args) {
Fruit f = Factory.getInstance("com.github.krislinzhao.demo.Orange") ;
f.eat() ;
}
}
发现,这个时候即使增加了接口的子类,工厂类照样可以完成对象的实例化操作,这个才是真正的工厂类,可以应对于所有的变化。如果单独从开发角度而言,与开发者关系不大,但是对于日后学习的一些框架技术这个就是它实现的命脉,在日后的程序开发上,如果发现操作的过程之中需要传递了一个完整的“包.类”名称的时候几乎都是反射机制作用。
2.获取指定构造器方法
//获取字节码文件
Class clazz1=Class.forName("Reflect.User");
//先获取有参构造器,parameterTypes:表示参数列表,有多少写多少,也可以不写,不写就是调用无参构造器Constructor constructor=clazzl.getConstructor(int.class,String.class);
//通过构造器来实例化对象,将实际的参数传进去。
User user=(User)constructor.newInstance(12,"小明");
总结上面创建实例对象:Class类的newInstance()
方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用Class类的getConstructor(String.class,int.class)
方法获取一个指定的构造函数然后再调用Constructor类的newInstance("张三",20)
方法创建对象
获取全部构造方法
Class clazz1=Class.forName("Reflect.User");
//获得所有构造方法
Constructor[] constructors = clazz1.getConstructors();
//遍历所有构造方法
for(int i=0;i< constructors.length;i++)
//获取每个构造函数中得参数类型字节码对象。
Class[] parameterTypes = constructors[i].getParameterTypes();
System.out.println("第"+i+"个构造函数");
for(int j=0; j< parameterTypes.length; j++){
//获取构造函数中参数类型
System.out.print(parameterTypes[j].getName()+",")
}
}
3.获取成员变量并使用Field对象
获取指定成员变量
//获取字节码文件
Class clazz1=Class.forName=("Reflect.User");
//获得其实例对象
User user=(User)clazz1.newInstance();
//获取成员变量clazz1.getField(name);通过name来获取指定成员变量,
//如果该成员变量是私有的,则应该使用getDeclaredField(name);
Field field=clazz1.getDeclaredField("id");
//因为属性是私有的,获得其属性对象后,还要让其打开可见权限
field.setAccessible(true);
//对其成员变量进行操作
//赋值操作
field.setInt(user,5);
//获取成员变量的值,field.get(obj);obj为所表示字段的值的对象。也就是该属性对应类的实例对象
System.out.printin(field.getInt(user));
Class.getField(String)
方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField("name")
方法获取,通过set(obj, "李四")
方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)
设置访问权限,用获取的指定的字段调用get(obj)
可以获取指定对象中该字段的值
获取全部成员变量
// 获取全部成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field:fields){
// 将权限打开
field.setAccessible(true);
System.out.println(field.toString());
}
4.获得方法并使用Method
获取指定方法
//获取字节码文件对象
Class clazz1=Class.forName("Reflect.User");
//实例化
User user=(User)clazz1.newInstance();
//不带参数的方法,eat为不带参数的public方法
/*
*clazz1.getMethod(name,parameterTypes)
*name:方法的名字
*parameterTypes:方法的参数类型的Class类型,没有则什么都不填,比如参数为String,则填String.class
*/
Method method =clazz1.getMethod("eat");
//调用方法
/*
*method.invoke(obj,args)
* obj:方法的对象
*args:实际的参数值,没有则不填
*/
method.invoke(user);
//带参数的方法,sing为带一个String类型参数的方法
Method method1=clazz1.getMethod("sing",String.class);
method1.invoke(user,"小明");
//获取私有的方法,和获取私有属性一样,say为私有方法
Method method2=clazz1.getDeclaredMethod("say");
method2.setAccessible(true)
method2.invoke(user);
Class.getMethod(String, Class...)
和 Class.getDeclaredMethod(String, Class...)
方法可以获取类中的指定方法, 如果为私有方法,则需要打开一个权限。setAccessible(true);
用invoke(Object, Object...)
可以调用该方法,
获取全部方法
// 获取全部方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method:methods){
// 把权限打开
method.setAccessible(true);
System.out.println(method.getName());
// 获得方法的参数
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> parameterType:parameterTypes){
// 获取构造函数中的参数类型
System.out.print(parameterType.getName()+',');
}
}
5.获得该类的所有接口
Class[] getInterfaces():
确定此对象所表示的类或接口实现的接口
返回值:接口的字节码文件对象的数组
6.获取指定资源的输入流
InputStream getResourceAsStream(String name)
返回值:一个 InputStream 对象;如果找不到带有该名称的资源,则返回 null
参数:所需资源的名称,如果以"/"开始,则绝对资源名为"/"后面的一部分。