定义
大家都知道,要让Java程序能够运行,那么就得让Java类要被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类的已经被加载了。
Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类,这样的特点就是反射。
作用
Java的反射机制可以知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。
大家都用过IDEA和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候,一按点(智能提示),编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。
解释
反射之中包含了一个“反”的概念,所以要想解释反射就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化对象,但是“反”指的是通过对象找到类。
基础Person类:
package com.xm.reflect;
/**
* Created by xuming on 2016/7/21.
*/
public class Person {
private Long id;
private String name;
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
应用示例:
package com.xm.reflect;
public class BaseDemo {
public static void main(String[] args) throws Exception {
Person per = new Person(); // 正着操作
System.out.println(per.getClass().getName()); // 反着来
}
}
以上的代码使用了一个getClass()
方法,而后就可以得到对象所在的“包.类”名称,这就属于“反”了,但是在这个“反”的操作之中有一个getClass()
就作为发起一切反射操作的开端。
Person的父类是Object类,而上面所使用getClass()方法就是Object类之中所定义的方法。
取得Class对象:public final Class<?> getClass()
,反射之中的所有泛型都定义为?,返回值都是Object。
取class对象的三种方法
- 通过Object类的getClass()方法取得,基本不用
- 使用“类.class”取得,在Hibernate开发的时候使用
- 使用Class类内部定义的一个static方法,主要使用
应用示例:
package com.xm.reflect;
/**
* Created by xuming on 2016/7/21.
*/
public class ClassDemo {
public static void main(String[] args) throws Exception {
Person per = new Person(); // 正着操作
Class<?> cls = per.getClass(); // 取得Class对象
System.out.println(cls.getName() + ";toString:" + cls.toString()); // 反着来
Class<?> clss = Person.class; // 取得Class对象
System.out.println(clss.getName()); // 反着来
Class<?> cls1 = Class.forName("com.xm.reflect.Person"); // 取得Class对象
System.out.println(cls1.getName()); // 反着来
Object obj = cls.newInstance(); // 实例化对象,和使用关键字new一样
Person pers = (Person) obj; // 向下转型
System.out.println(pers);
}
}
结果:
上面用到了通过反射实例化对象:public T newInstance() throws InstantiationException, IllegalAccessException;
,可以发现,对于对象的实例化操作,除了使用关键字new之外又多了一个反射机制操作,而且这个操作要比之前使用的new复杂一些,可是有什么用?
对于程序的开发模式之前一直强调:尽量减少耦合,而减少耦合的最好做法是使用接口,但是就算使用了接口也逃不出关键字new,所以实际上new是造成耦合的关键元凶。
工厂模式问题造成的关键性的病因是new,那么如果说现在不使用关键字new了,变为了反射机制呢?
反射机制实例化对象的时候实际上只需要“包.类”就可以,于是根据此操作,修改工厂设计模式。
package com.xm.reflect;
/**
* Created by xuming on 2016/7/21.
*/
interface Fruit {
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.xm.reflect.Orange");
f.eat();
}
}
发现,这个时候即使增加了接口的子类,工厂类照样可以完成对象的实例化操作,这个才是真正的工厂类,可以应对于所有的变化。一些框架技术这个就是它实现的命脉,在日后的程序开发上,如果发现操作的过程之中需要传递了一个完整的“包.类”名称的时候几乎都是反射机制作用。
调用构造及实例化
取得构造方法有两个,实例化对象还是newInstance:
- 取得一个类的全部构造:
public Constructor<?>[] getConstructors() throws SecurityException
- 取得一个类的指定参数构造:
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
- 实例化对象:
public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException
应用示例:
package com.xm.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* Created by xuming on 2016/7/21.
*/
public class ConstructDemo {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> cls = Class.forName("com.xm.reflect.Person"); // 取得Class对象
Constructor<?> cons[] = cls.getConstructors(); // 取得全部构造
for (int x = 0; x < cons.length; x++) {
System.out.println(cons[x]);
}
Object obj = cls.newInstance(); // 实例化对象
System.out.println(obj);
Constructor<?> cons1 = cls.getConstructor(String.class,int.class) ;
Object obj1 = cons1.newInstance("张三", 20); // 为构造方法传递参数
System.out.println(obj1);
}
}
结果:
调用普通方法
- 取得全部方法:
public Method[] getMethods() throws SecurityException;
- 取得指定方法:
public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
发现以上的方法返回的都是java.lang.reflect.Method类的对象。
取得了Method类对象之后还有一个最大的功能,就是可以利用反射调用类中的方法:
- 调用方法:
public Object invoke(Object obj, Object... args)
之前调用类中方法的时候使用的都是“对象.方法”,但是现在有了反射之后,可以直接利用Object类调用指定子类的操作方法。
应用示例:
package com.xm.reflect;
import java.lang.reflect.Method;
/**
* Created by xuming on 2016/7/21.
*/
public class MethodDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.xm.reflect.Person"); // 取得Class对象
Method met[] = cls.getMethods(); // 取得全部方法
for (int x = 0; x < met.length; x++) {
System.out.println(met[x]);
}
Object obj = cls.newInstance(); // 实例化对象,没有向Person转型
String attribute = "name"; // 要调用类之中的属性
Method setMet = cls.getMethod("set"+ initcap(attribute), String.class);// setName()
Method getMet = cls.getMethod("get"+ initcap(attribute));// getName()
setMet.invoke(obj, "张三") ; // 等价于:Person对象.setName("张三")
System.out.println(getMet.invoke(obj));// 等价于:Person对象.getName()
}
public static String initcap(String str) {
return str.substring(0,1).toUpperCase().concat(str.substring(1)) ;
}
}
结果:
调用成员
- 取得本类的全部成员:
public Field[] getDeclaredFields() throws SecurityException
- 取得指定的成员:
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException
- 设置属性内容(类似于:对象.属性= 内容):
public void set(Object obj, Object value)
- 取得属性内容(类似于:对象.属性):
public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException
可是从类的开发要求而言,一直都强调类之中的属性必须封装,所以现在调用之前要想办法解除封装。
- 解除封装:
public void setAccessible(boolean flag) throws SecurityException
应用示例:
Field nameField = cls.getDeclaredField("name") ; // 找到name属性
nameField.setAccessible(true) ; // 解除封装了
nameField.set(obj, "张三") ; // Person对象.name = "张三"
System.out.println(nameField.get(obj)); // Person对象.name
利用反射拷贝对象的应用实例
希望大家通过该例子进一步掌握反射的应用方法:
package com.xm.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Created by xuming on 2016/7/21.
*/
public class ReflectDemo {
/**
* 通过反射拷贝对象
*
* @throws IllegalAccessException
* @throws InstantiationException
*/
public Object copy(Object object) throws Exception {
Class<?> classType = object.getClass();
Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
//获取对象的所有成员变量
Field[] fields = classType.getDeclaredFields();
for (Field field : fields) {
String name = field.getName();//获得成员变量的名称
String firstLetter = name.substring(0, 1).toUpperCase();//将属性的首字母变为大写
String getMethodName = "get" + firstLetter + name.substring(1);
String setMethodeName = "set" + firstLetter + name.substring(1);
Method getMethod = classType.getMethod(getMethodName, new Class[]{});
Method setMethod = classType.getMethod(setMethodeName, new Class[]{field.getType()});
Object value = getMethod.invoke(object, new Object[]{});
setMethod.invoke(objectCopy, new Object[]{value});
}
return objectCopy;
}
public static void main(String[] args) throws Exception {
Person p = new Person("lili", 23);
p.setId(1L);
ReflectDemo test = new ReflectDemo();
Person person = (Person) test.copy(p);
System.out.println(person);
System.out.println("ID:" + person.getId() + ",name:" + person.getName() + ",age:" + person.getAge());
}
}
理解:
1. 要想使用反射,首先需要获得待处理类或对象所对应的Class对象。
2. 获取某个类或某个对象所对应的Class对象的常用的3种方式:
a) 使用Class类的静态方法forName:Class.forName(“java.lang.String”);
b)使用类的.class语法:String.class;
c) 使用对象的getClass()方法:String s = “aa”; Class<?> clazz = s.getClass();
3. 若想通过类的不带参数的构造方法来生成对象,我们有两种方式:
a) 先获得Class对象,然后通过该Class对象的newInstance()方法直接生成即可: Class<?> classType = String.class; Object obj = classType.newInstance();
b) 先获得Class对象,然后通过该对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成: Class<?> classType = Customer.class; Constructor cons = classType.getConstructor(new Class[]{}); Object obj = cons.newInstance(new Object[]{});
4. 若想通过类的带参数的构造方法生成对象,只能使用下面这一种方式: Class<?> classType = Customer.class; Constructor cons = classType.getConstructor(new Class[]{String.class, int.class}); Object obj = cons.newInstance(new Object[]{“hello”, 3});
5. Integer.TYPE返回的是int,而Integer.class返回的是Integer类所对应的Class对象。
结果:
以上具体代码见我的github。
参考文献:
sql驱动的反射机制
知乎中关于反射的讨论:https://www.zhihu.com/question/24304289
利用反射拷贝的方法:http://inotgaoshou.iteye.com/blog/849676