java反射
- 简介
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
java不是一种动态语言(程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言),但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
- 应用
java反射引用最多的场合就是在java的第三方框架中,一般类都是在配置文件中配置,然后在项目启动的时候进行加载,这其中就是反射的功劳。下面就从反射的基础一点点的深入到其核心来研究一下。
Person代码:
1 public interface Chinese { 2 public static final String name="abc"; 3 public static int age=20; 4 public void sayChina(); 5 public void sayHello(String name, int age); 6 } 7 public class Person implements Chinese{ 8 private String sex; 9 private String address; 10 public Person() { 11 12 } 13 public Person(String sex) { 14 this.sex = sex; 15 } 16 public String getAddress() { 17 return address; 18 } 19 public void setAddress(String address) { 20 this.address = address; 21 } 22 public Person(String sex, String address) { 23 this.sex = sex; 24 this.address = address; 25 } 26 public String getSex() { 27 return sex; 28 } 29 public void setSex(String sex) { 30 this.sex = sex; 31 } 32 public void sayChina() { 33 System.out.println("say,chinese"); 34 } 35 public void sayHello(String name, int age) { 36 System.out.println(name+" "+age); 37 } 38 @Override 39 public String toString() { 40 return this.name+" "+this.age+" "+this.sex; 41 } 42 }
1、返回一个类的class的几种方式:
1 Class<?> class1 = Person.class; 2 Class<?> class2 = new Person().getClass(); 3 Class<?> class3 = Person.class; 4 try { 5 class1 = Class.forName("reflecttest.Person"); 6 } catch (ClassNotFoundException e) { 7 // TODO Auto-generated catch block 8 e.printStackTrace(); 9 } 10 System.out.println(class1.getName()); 11 System.out.println(class2.getName()); 12 System.out.println(class3.getName());
所有类的对象其实都是Class的实例。
2、通过无参构造实例化对象
1 Person person = null; 2 try { 3 person = (Person)class1.newInstance(); 4 } catch (InstantiationException e) { 5 // TODO Auto-generated catch block 6 e.printStackTrace(); 7 } catch (IllegalAccessException e) { 8 // TODO Auto-generated catch block 9 e.printStackTrace(); 10 } 11 person.setSex("男"); 12 System.out.println(person);
当我们把Person中的默认的无参构造函数取消的时候,比如自己定义只定义一个有参数的构造函数之后。错误如下:
1 java.lang.InstantiationException: reflecttest.Person
所以在自己定于类的时候一定要定于无参构造函数,尤其在使用其他第三方框架的时候,这也就是好多书上反复强调如果定义有参构造函数一定要实现无参构造函数的原因。
3、通过Class调用其他类中的构造函数 (也可以通过这种方式通过Class创建其他类的对象)
1 Person person1 = null; 2 Person person2 = null; 3 Constructor<?> [] constructors = class1.getConstructors(); 4 try { 5 person1 = (Person)constructors[0].newInstance(); 6 person2 = (Person)constructors[1].newInstance("男"); 7 } catch (IllegalArgumentException e) { 8 // TODO Auto-generated catch block 9 e.printStackTrace(); 10 } catch (InstantiationException e) { 11 // TODO Auto-generated catch block 12 e.printStackTrace(); 13 } catch (IllegalAccessException e) { 14 // TODO Auto-generated catch block 15 e.printStackTrace(); 16 } catch (InvocationTargetException e) { 17 // TODO Auto-generated catch block 18 e.printStackTrace(); 19 } 20 System.out.println(person1); 21 System.out.println(person2);
一个类中可能有多个构造函数,我们可以通过反射获取其全部的构造函数,然后通过构造函数来实例化对象,在这里是否想到了spring的构造器注入的方式。
4、返回一个类实现的接口:
1 Class<?>[] interfaces = class1.getInterfaces(); 2 for (int i = 0; i < interfaces.length; i++) { 3 System.out.println(interfaces[i].getName()); 4 }
5、返回父类
1 Class<?> supClasss = class1.getSuperclass(); 2 System.out.println(supClasss.getName());
6、返回类的全部构造方法
1 Constructor<?> [] constructors = class1.getConstructors(); 2 for (int i = 0; i < constructors.length; i++) { 3 System.out.println(constructors[i]); 4 }
7、返回完整的构造方法实现
1 Constructor<?> [] constructors = class1.getConstructors(); 2 for (int i = 0; i < constructors.length; i++) { 3 Constructor<?> constructor = constructors[i]; 4 Class<?>[] parameter = constructor.getParameterTypes(); 5 System.out.print("构造方法 "); 6 int j = constructor.getModifiers();//获取修饰符编号 7 System.out.print(Modifier.toString(j)+" "); 8 System.out.print(constructor.getName()+" "); 9 System.out.print("("); 10 for (int k = 0; k < parameter.length; k++) { 11 if(parameter.length>0){ 12 System.out.print(parameter[k].getName() +" arg"+k); 13 if(k<parameter.length-1){ 14 System.out.print(","); 15 } 16 } 17 18 } 19 System.out.println(")"); 20 }
这个返回的构造函数更加完善,与我们平时所看到的基本相同,包括修饰符、名称、参数类型、参数名称等。
8、返回方法包括异常
1 Method[] methods = class1.getMethods(); 2 for (int i = 0; i < methods.length; i++) { 3 //System.out.println(methods[i]); 4 int m = methods[i].getModifiers(); 5 Class<?> returnTypes = methods[i].getReturnType(); 6 Class<?>[] parameterTypes = methods[i].getParameterTypes(); 7 System.out.print(Modifier.toString(m)+" "); 8 System.out.print(returnTypes.getName()+" "); 9 System.out.print(methods[i].getName()); 10 System.out.print("("); 11 for (int j = 0; j < parameterTypes.length; j++) { 12 System.out.print(parameterTypes[j].getName() +" arg"+j); 13 if(j<parameterTypes.length-1){ 14 System.out.print(","); 15 } 16 } 17 System.out.print(")"); 18 Class<?>[] exes = methods[i].getExceptionTypes(); 19 if(exes.length>0){ 20 System.out.print("throws "); 21 for (int j = 0; j < exes.length; j++) { 22 System.out.print(exes[j]); 23 if(j<exes.length){ 24 System.out.print(","); 25 } 26 } 27 } 28 System.out.println("{}"); 29 }
9、获取类属性
1 Field [] fields = class1.getDeclaredFields(); 2 for (int i = 0; i < fields.length; i++) { 3 //System.out.println(fields[i]); 4 int m = fields[i].getModifiers(); 5 String modifier = Modifier.toString(m); 6 Class<?> type = fields[i].getType(); 7 String fieldName = fields[i].getName(); 8 System.out.println(modifier +" "+type.getName()+" " +fieldName); 9 }
10、通过反射调用其他的方法
1 try { 2 Person person = (Person)class1.newInstance(); 3 Method method = class1.getMethod("sayChina"); 4 method.invoke(person); 5 Method method1 = class1.getMethod("sayHello",String.class,int.class); 6 method1.invoke(person, "lc",22); 7 } catch (SecurityException e) { 8 // TODO Auto-generated catch block 9 e.printStackTrace(); 10 } catch (NoSuchMethodException e) { 11 // TODO Auto-generated catch block 12 e.printStackTrace(); 13 } catch (Exception e) { 14 // TODO Auto-generated catch block 15 e.printStackTrace(); 16 }
这个也很好理解,先获取对象和方法名称,然后更具参数类型来获取具体的方法(因为有重载的原因),最后在利用反射传入参数来调用方法。
11、通过反射调用get\set的方法
1 try { 2 Person person = (Person)class1.newInstance(); 3 setMethod(person, "Sex", String.class, "女"); 4 System.out.println(getMethod(person, "Sex")); 5 System.out.println(person); 6 } catch (InstantiationException e) { 7 // TODO Auto-generated catch block 8 e.printStackTrace(); 9 } catch (IllegalAccessException e) { 10 // TODO Auto-generated catch block 11 e.printStackTrace(); 12 } catch (Exception e) { 13 // TODO Auto-generated catch block 14 e.printStackTrace(); 15 } 16 public static Object getMethod(Object obj,String MethodName) throws Exception{ 17 Method method = obj.getClass().getMethod("get"+MethodName); 18 return method.invoke(obj); 19 } 20 public static void setMethod(Object obj,String MethodName,Class<?> type,Object value) throws Exception{ 21 Method method = obj.getClass().getMethod("set"+MethodName, type); 22 method.invoke(obj, value); 23 }
这个和上面的做法差不多,只是将普通方法改为了set/get方法,在开源框架中也有许多利用这种方式来实例化对象。如:spring的“set注入”;struts的form表单;MyBatis的参数类型到SQL参数的映射和结果集到对象的映射等等。
12、通过反射操作属性
1 try { 2 Person person = (Person)class1.newInstance(); 3 //java.lang.NoSuchFieldException: sex 4 //Field field = class1.getField("sex"); 5 //java.lang.IllegalAccessException: Class reflecttest.ReflectTest can not access a member of class reflecttest.Person with modifiers "private" 6 //Field field = class1.getDeclaredField("sex"); 7 Field field = class1.getDeclaredField("sex"); 8 field.setAccessible(true); 9 field.set(person, "未知"); 10 System.out.println(field.get(person)); 11 } catch (SecurityException e) { 12 // TODO Auto-generated catch block 13 e.printStackTrace(); 14 } catch (NoSuchFieldException e) { 15 // TODO Auto-generated catch block 16 e.printStackTrace(); 17 } catch (Exception e) { 18 // TODO Auto-generated catch block 19 e.printStackTrace(); 20 }
因为sex属性的修饰符是private,一般情况下是不允许外部类直接操作,除非调用其get/set方法,但是通过反射就可以直接操作。
class1.getDeclaredField("sex")这个是Class
对象所表示的类或接口的指定已声明字段,可以获取其全部的属性,不管其访问修饰符。class1.getField("sex")这个只能获取其对外公开的属性即public修饰的属性。
field.setAccessible(true);这个设置就是反射的对象在使用时应该取消 Java 语言访问检查,也就是说将属性暴力访问。
13、通过反射取得并修改数组的信息
1 int [] temp = {1,2,3,4}; 2 Class<?> class2 = temp.getClass().getComponentType(); 3 System.out.println("数组类型:"+class2.getName()); 4 System.out.println("数组长度:"+Array.getLength(temp)); 5 System.out.println("数组第一个元素:"+Array.get(temp, 0)); 6 Array.set(temp, 0, 10); 7 System.out.println("修改数组第一个元素:"+Array.get(temp, 0)); 8 Object newArr = arryinc(temp, 10); 9 print(newArr); 10 System.out.println("--------------------"); 11 String [] str = {"a","b","c","d"}; 12 Object newStr = arryinc(str, 8); 13 print(newStr); 14 /** 15 * 修改数组大小 16 * */ 17 public static Object arryinc(Object object,int length){ 18 Class<?> class1 = object.getClass().getComponentType(); 19 Object newArr = Array.newInstance(class1, length); 20 int ol = Array.getLength(object); 21 System.arraycopy(object, 0, newArr, 0, ol); 22 return newArr; 23 } 24 /** 25 * 打印数组大小 26 */ 27 public static void print(Object object){ 28 Class<?> class1 = object.getClass(); 29 if(!class1.isArray()){ 30 return; 31 } 32 System.out.println("数组大小"+Array.getLength(object)); 33 for (int i = 0; i < Array.getLength(object); i++) { 34 System.out.println("数组元素:"+Array.get(object, i)); 35 } 36 }
可以看出,反射也可以直接操作数组修改数组大小、修改数组内容等。
14、动态代理
被代理人授予代理人权限。然后由代理人实行被代理人的权限。
代理接口
1 public interface Target { 2 public String say(String name,String age); 3 }
代理接口实现(被代理人)
1 public class TargetImpl implements Target { 2 private String name; 3 private String age; 4 public TargetImpl() { 5 super(); 6 // TODO Auto-generated constructor stub 7 } 8 public TargetImpl(String name, String age) { 9 super(); 10 this.name = name; 11 this.age = age; 12 } 13 public String say(String name, String age) { 14 return name+" "+age; 15 } 16 }
动态代理
1 public class DProxy implements InvocationHandler{ 2 3 public Object target; 4 5 public Object bing(Object object){ 6 this.target = object; 7 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); 8 } 9 10 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 11 Object object = method.invoke(this.target, args); 12 return object; 13 } 14 }
测试代码:
1 DProxy proxy = new DProxy(); 2 Target target = (Target)proxy.bing(new TargetImpl()); 3 System.out.println(target.say("name", "age"));
可以看出,动态代理实现和前面的通过反射来执行对象方法十分类似,先将被代理对象注册,也就是实例化,然后通过反射调用其方法。
动态代理在框架中也是很有用,回想下我们使用过的hibernate,在其中有一个懒加载的概念,当通过懒加载获取的对象并不是真正的对象,而是代理对象,这个就是动态代理的一个典型应用。
15、工厂模式的应用
在以前的工厂模式中都是通过硬编码来实现其功能,在学习了反射以后就可以在配置文件中设置类,然后通过反射来实现工厂模式,这样就减少了对代码的修改了,增强了其可扩展性。
接口:
1 public interface Fruit { 2 public void eat(); 3 }
具体实现
1 public class Apple implements Fruit{ 2 public void eat() { 3 System.out.println("eat apple"); 4 } 5 } 6 public class Orange implements Fruit{ 7 public void eat() { 8 System.out.println("eat orange"); 9 } 10 }
工厂类:
1 public class FruitFactory { 2 /** 3 * 这样,当我们在添加一个子类的时候,就需要修改工厂类了。如果我们添加太多的子类的时候,改的就会很多。 4 * @param fruitName 5 * @return 6 */ 7 public static Fruit getInstance(String fruitName){ 8 Fruit fruit = null; 9 if("apple".equalsIgnoreCase(fruitName)){ 10 fruit = new Apple(); 11 }else if("orange".equals(fruitName)){ 12 fruit = new Orange(); 13 } 14 return fruit; 15 } 16 /** 17 * 利用反射来实例化对象,通过反射取得接口的实例,但是需要传入完整的包和类名。而且用户也无法知道一个接口有多少个可以使用的子类 18 * 所以我们通过属性文件的形式配置所需要的子类。 19 * @param className 20 * @return 21 */ 22 public static Fruit getInstance1(String className){ 23 Fruit fruit = null; 24 try { 25 fruit = (Fruit)Class.forName(className).newInstance(); 26 } catch (InstantiationException e) { 27 // TODO Auto-generated catch block 28 e.printStackTrace(); 29 } catch (IllegalAccessException e) { 30 // TODO Auto-generated catch block 31 e.printStackTrace(); 32 } catch (ClassNotFoundException e) { 33 // TODO Auto-generated catch block 34 e.printStackTrace(); 35 } 36 return fruit; 37 } 38 /** 39 *加载配置文件 40 */ 41 public static Properties loadPro(){ 42 Properties properties = new Properties(); 43 File file = new File("fruit.properties"); 44 if(file.exists()){ 45 try { 46 properties.load(new FileInputStream(file)); 47 } catch (FileNotFoundException e) { 48 // TODO Auto-generated catch block 49 e.printStackTrace(); 50 } catch (IOException e) { 51 // TODO Auto-generated catch block 52 e.printStackTrace(); 53 } 54 }else{ 55 properties.setProperty("apple", "reflecttest.Apple"); 56 properties.setProperty("orange", "reflecttest.Orange"); 57 try { 58 properties.store(new FileOutputStream(file), "fruit"); 59 } catch (FileNotFoundException e) { 60 // TODO Auto-generated catch block 61 e.printStackTrace(); 62 } catch (IOException e) { 63 // TODO Auto-generated catch block 64 e.printStackTrace(); 65 } 66 } 67 return properties; 68 } 69 }
配置文件信息:
1 apple=reflecttest.Apple 2 orange=reflecttest.Orange
测试代码:
1 Fruit fruit = FruitFactory.getInstance("apple");//普通方式实例化 2 if(fruit != null){ 3 fruit.eat(); 4 } 5 Fruit fruit1 = FruitFactory.getInstance1("reflecttest.Orange");//通过反射实例化,但是需要传入类的全路径 6 if(fruit1 != null){ 7 fruit1.eat(); 8 } 9 Fruit fruit2 = FruitFactory.getInstance1(FruitFactory.loadPro().getProperty("apple"));//通过配置文件实例化 10 if(fruit2 != null){ 11 fruit2.eat(); 12 }
可以看出通过反射来实例化,当产品结构修改以后,工厂类是不需要修改的,只是在使用时硬编码传入产品类的全路径,而使用配置文件来实现只需要在配置文件中配置相关信息,比较清晰明了,易于扩展信息。
从上面对反射的介绍,其一般是在框架中使用,就是将固定的代码封装然后将动态的内容在配置文件中配置,方便使用,灵活扩展性强。另外,通过反射实例化对象的性能比直接实例化对象要差,所以一般在自己的代码中不用使用反射来实现,除非在对象性能和其他因素折中考虑,使用反射更利于实现才使用。