反射的作用
1)在运行时判断任意一个对象所属的类;
2)在运行时构造任意一个类的对象;
3)在运行时判断任意一个类所具有的成员变量和方法;
4)在运行时调用任意一个对象的方法。
5)反射API可以获取程序在运行时刻的内部结构。
6)使用反射的一个最大的弊端是性能比较差。相同的操作,用反射API所需的时间大概比直接的使用要慢一两个数量级。
Class类
可以通过以下三种方式获得Class对象:
1)使用Class类的静态方法forName:Class.forName(“java.lang.String”);
2)使用类的.class语法:String.class;
3)使用对象的getClass()方法:String s = "aa"; Class<?> clazz = s.getClass();
4)对于包装类(8个)可以通过.TYPE语法方式
public class ReflectDemo01 { public static void main(String[] args) throws Exception { // Class<?> clazz = Class.forName(args[0]); Class<?> clazz = Class.forName("java.lang.Object"); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { System.out.println(method); } System.out.println("-------------"); methods = clazz.getMethods(); for (Method method : methods) { System.out.println(method); } } }
注意:
getDeclaredMethods()和getMethods()方法的区别
Integer.TYPE返回的是int;
Integer.class返回的是Integer类所对应的Class对象;
Integer.TYPE返回的是基本类型 int的Class实例;
Integer age = 10;
System.out.println(age.TYPE);
结果是:int
getSuperclass()方法的使用
public class ReflectDemo08 { public static void main(String[] args) { Class<?> classType = Child.class; System.out.println(classType); //class Child classType = classType.getSuperclass(); System.out.println(classType); //class Parent classType = classType.getSuperclass(); System.out.println(classType); //class java.lang.Object classType = classType.getSuperclass(); System.out.println(classType);//null } } class Parent { } class Child extends Parent { }
通过Class的方法来获取到该类中的构造方法、属性和方法。对应的方法分别是getConstructor、getField和getMethod。
Class对象封装了一个java类中定义的成员变量、成员方法、构造方法、类名、包名等。
Constructor类
Constructor类代表某个类中的一个构造方法。
得到某个类所有的构造方法:
Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
创建实例对象:
方法一:通常方式
String str = new String(new StringBuffer("abc"));
方法二:反射方式
String str = (String)constructor.newInstance(new StringBuffer("abc"));
方法三:Class.newInstance()方法
String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
Field类
Field类代表某个类中的一个成员变量;
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?
类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。(注意访问权限的问题)
示例代码:
ReflectPoint point = new ReflectPoint(1,7); Field y = Class.forName("cn.itcast.corejava.ReflectPoint").getField("y"); System.out.println(y.get(point)); //Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getField("x"); Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getDeclaredField("x"); x.setAccessible(true); System.out.println(x.get(point));
Method类
Method类代表某个类中的一个成员方法;
得到类中的某一个方法:
Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
调用方法:
方式一:通常方式
System.out.println(str.charAt(1));
方式二:反射方式
System.out.println(charAt.invoke(str, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
jdk1.4和jdk1.5的invoke方法的区别:
Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke("str", new Object[]{1})形式。
通过Class类实例化对象
方法一:实例化无参构造的类并调用方法
先获得Class对象,然后通过该Class对象的newInstance()方法直接生成即可:
Class<?> classType = String.class; Object obj = classType.newInstance();
先获得Class对象,然后通过该对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成:
Class<?> classType = Customer.class; Constructor cons = classType.getConstructor(new Class[]{}); Object obj = cons.newInstance(new Object[]{});
public class ReflectDemo02 { public int add(int param1, int param2) { return param1 + param2; } public String echo(String message) { return "hello: " + message; } public static void main(String[] args) throws Exception { Class<?> clazz = ReflectDemo02.class; Object obj = clazz.newInstance(); Method addMethod = clazz.getMethod("add", int.class, int.class); Object result = addMethod.invoke(obj, 1, 2); System.out.println(result); System.out.println("----------------华丽的分割线------------------"); Method echoMethod = clazz.getMethod("echo", String.class); result = echoMethod.invoke(obj, "luogankun"); System.out.println(result); } }
getMethod()的定义:
public Method getMethod(String name, Class<?>... parameterTypes)
第二个参数是可变参数。
方法二:实例化有参构造的类并调用方法
若想通过类的带参数的构造方法生成对象,只能使用下面这一种方式:
Class<?> classType = Customer.class; Constructor cons = classType.getConstructor(new Class[]{String.class, int.class}); Object obj = cons.newInstance(new Object[]{"hello", 3});
Class<?> clazz = Dog.class; Constructor<?> constructor = clazz.getConstructor(Integer.class,String.class); Dog dog = (Dog) constructor.newInstance(2, "aaad");
利用反射完成对象的拷贝
public class ReflectDemo03 { public static void main(String[] args) throws Exception { Customer customer = new Customer("Tom", 20); customer.setId(1L); ReflectDemo03 demo = new ReflectDemo03(); Customer customer2 = (Customer) demo.copy(customer); System.out.println(customer2.getId() + "," + customer2.getName() + "," + customer2.getAge()); } // 该方法实现对Customer对象的拷贝操作 @SuppressWarnings("rawtypes") public Object copy(Object object) throws Exception { Class<?> clazz = object.getClass(); System.out.println("clazz " + clazz); Constructor constructor = clazz.getConstructor(); Object objectCopy = constructor.newInstance(); // 以上两行代码等价于clazz.newInstance(); Field[] fields = clazz.getDeclaredFields(); //获得属性 for (Field field : fields) { // System.out.println(field.getName()); String name = field.getName(); String firstLetter = name.substring(0, 1).toUpperCase(); String getMethodName = "get" + firstLetter + name.substring(1); String setMethodName = "set" + firstLetter + name.substring(1); //获得方法 Method getMethod = clazz.getDeclaredMethod(getMethodName); Method setMethod = clazz.getDeclaredMethod(setMethodName,sfield.getType()); Object value = getMethod.invoke(object); setMethod.invoke(objectCopy, value); } return objectCopy; } } public class Customer { private Long id; private String name; private int age; public Customer() { } public Customer(String name, int age) { this.name = name; this.age = age; } setter/getter }
如果要通过反射调用类的静态方法时,只需要将要反射的对象设置成null即可。 setMethod.invoke(null, value);
使用Java反射API的时候可以绕过Java默认的访问控制检查
1)利用反射调用对象的私有方法
public class ReflectDemo06 { public static void main(String[] args) throws Exception { Class<?> clazz = Private.class; Object object = clazz.newInstance(); Method method = clazz.getDeclaredMethod("sayHello", String.class); method.setAccessible(true); Object result = method.invoke(object, "luogankun"); System.out.println(result); } } public class Private { private String name = "zhangsan"; private String sayHello(String name) { return "hello: " + name; } public String getName() { return name; } }
2)利用反射完成对私有成员变量的重新赋值
面试题:私有变量能被外界访问吗? 正常情况下不行,但是可以通过反射来调用
public class ReflectDemo07 { public static void main(String[] args) throws Exception { Class<?> clazz = Private.class; Private object = (Private) clazz.newInstance(); Field field = clazz.getDeclaredField("name"); field.setAccessible(true); field.set(object, "luogankun"); System.err.println(object.getName()); } }
总结:使用Java反射API的时候可以绕过Java默认的访问控制检查,比如可以直接获取到对象的私有域的值或是调用私有方法。
只需要在获取到Constructor、Field和Method类的对象之后,调用setAccessible方法并设为true即可。有了这种机制,就可以很方便的在运行时刻获取到程序的内部状态。
Array类提供了一系列的静态方法用来创建数组和对数组中的元素进行访问和操作
public class ReflectDemo04 { public static void main(String[] args) throws Exception { Class<?> clazz = Class.forName("java.lang.String"); Object array = Array.newInstance(clazz, 10); //等价于 new String[10] Array.set(array, 5, "hello"); //等价于array[5] = "Hello" String string = (String) Array.get(array, 5); //等价于array[5] System.out.println(string); } }