【Java入门提高篇】Day13 Java中的反射机制
前一段时间一直忙,所以没什么时间写博客,拖了这么久,也该更新更新了。最近看到各种知识付费的推出,感觉是好事,也是坏事,好事是对知识沉淀的认可与推动,坏事是感觉很多人忙于把自己的知识变现,相对的在沉淀上做的实际还不够,我对此暂时还没有什么想法,总觉得,慢慢来,会更快一点,自己掌握好节奏就好。
好了,言归正传。
反射机制是Java中的一个很强大的特性,可以在运行时获取类的信息,比如说类的父类,接口,全部方法名及参数,全部常量和变量,可以说类在反射面前已经衣不遮体了(咳咳,这是正规车)。先举一个小栗子,大家随意感受一下:
public void testA(){ String name = "java.lang.String"; try{ Class cl = Class.forName(name); Class supercl = cl.getSuperclass(); String modifiers = Modifier.toString(cl.getModifiers()); if (modifiers.length() > 0){ System.out.print(modifiers + " "); } System.out.print(name); if (supercl != null && supercl != Object.class){ System.out.print(" extents " + supercl.getName()); } System.out.print("{\n"); printFields(cl); System.out.println(); printConstructors(cl); System.out.println(); printMethods(cl); System.out.println("}"); }catch (ClassNotFoundException e){ e.printStackTrace(); } System.exit(0); }
private static void printConstructors(Class cl){ Constructor[] constructors = cl.getDeclaredConstructors(); for (Constructor c : constructors){ String name = c.getName(); System.out.print(" "); String modifiers = Modifier.toString(c.getModifiers()); if (modifiers.length() > 0){ System.out.print(modifiers + " "); } System.out.print(name + "("); Class[] paraTypes = c.getParameterTypes(); for (int j = 0; j < paraTypes.length; j++){ if (j > 0){ System.out.print(", "); } System.out.print(paraTypes[j].getSimpleName()); } System.out.println(");"); } } private static void printMethods(Class cl){ Method[] methods = cl.getDeclaredMethods(); for (Method m : methods){ Class retType = m.getReturnType(); String name = m.getName(); System.out.print(" "); String modifiers = Modifier.toString(m.getModifiers()); if (modifiers.length() > 0){ System.out.print(modifiers + " "); } System.out.print(retType.getSimpleName() + " " + name +"("); Class[] paramTypes = m.getParameterTypes(); for(int j = 0; j < paramTypes.length; j++){ if (j > 0){ System.out.print(", "); } System.out.print(paramTypes[j].getName()); } System.out.println(");"); } } private static void printFields(Class cl){ Field[] fields = cl.getFields(); for (Field f : fields){ Class type = f.getType(); String name = f.getName(); System.out.print(" "); String modifiers = Modifier.toString(f.getModifiers()); if (modifiers.length() > 0){ System.out.print(modifiers + " "); } System.out.println(type.getSimpleName() + " " + name +";"); } }
调用testA方法输出如下:
public final java.lang.String{ public static final Comparator CASE_INSENSITIVE_ORDER; public java.lang.String(byte[], int, int); public java.lang.String(byte[], Charset); public java.lang.String(byte[], String); public java.lang.String(byte[], int, int, Charset); public java.lang.String(byte[], int, int, String); java.lang.String(char[], boolean); public java.lang.String(StringBuilder); public java.lang.String(StringBuffer); public java.lang.String(byte[]); public java.lang.String(int[], int, int); public java.lang.String(); public java.lang.String(char[]); public java.lang.String(String); public java.lang.String(char[], int, int); public java.lang.String(byte[], int); public java.lang.String(byte[], int, int, int); public boolean equals(java.lang.Object); public String toString(); public int hashCode(); public int compareTo(java.lang.String); public volatile int compareTo(java.lang.Object); public int indexOf(java.lang.String, int); public int indexOf(java.lang.String); public int indexOf(int, int); public int indexOf(int); static int indexOf([C, int, int, [C, int, int, int); static int indexOf([C, int, int, java.lang.String, int); public static String valueOf(int); public static String valueOf(long); public static String valueOf(float); public static String valueOf(boolean); public static String valueOf([C); public static String valueOf([C, int, int); public static String valueOf(java.lang.Object); public static String valueOf(char); public static String valueOf(double); public char charAt(int); private static void checkBounds([B, int, int); public int codePointAt(int); public int codePointBefore(int); public int codePointCount(int, int); public int compareToIgnoreCase(java.lang.String); public String concat(java.lang.String); public boolean contains(java.lang.CharSequence); public boolean contentEquals(java.lang.CharSequence); public boolean contentEquals(java.lang.StringBuffer); public static String copyValueOf([C); public static String copyValueOf([C, int, int); public boolean endsWith(java.lang.String); public boolean equalsIgnoreCase(java.lang.String); public static transient String format(java.util.Locale, java.lang.String, [Ljava.lang.Object;); public static transient String format(java.lang.String, [Ljava.lang.Object;); public void getBytes(int, int, [B, int); public byte[] getBytes(java.nio.charset.Charset); public byte[] getBytes(java.lang.String); public byte[] getBytes(); public void getChars(int, int, [C, int); void getChars([C, int); private int indexOfSupplementary(int, int); public native String intern(); public boolean isEmpty(); public static transient String join(java.lang.CharSequence, [Ljava.lang.CharSequence;); public static String join(java.lang.CharSequence, java.lang.Iterable); public int lastIndexOf(int); public int lastIndexOf(java.lang.String); static int lastIndexOf([C, int, int, java.lang.String, int); public int lastIndexOf(java.lang.String, int); public int lastIndexOf(int, int); static int lastIndexOf([C, int, int, [C, int, int, int); private int lastIndexOfSupplementary(int, int); public int length(); public boolean matches(java.lang.String); private boolean nonSyncContentEquals(java.lang.AbstractStringBuilder); public int offsetByCodePoints(int, int); public boolean regionMatches(int, java.lang.String, int, int); public boolean regionMatches(boolean, int, java.lang.String, int, int); public String replace(char, char); public String replace(java.lang.CharSequence, java.lang.CharSequence); public String replaceAll(java.lang.String, java.lang.String); public String replaceFirst(java.lang.String, java.lang.String); public String[] split(java.lang.String); public String[] split(java.lang.String, int); public boolean startsWith(java.lang.String, int); public boolean startsWith(java.lang.String); public CharSequence subSequence(int, int); public String substring(int); public String substring(int, int); public char[] toCharArray(); public String toLowerCase(java.util.Locale); public String toLowerCase(); public String toUpperCase(); public String toUpperCase(java.util.Locale); public String trim(); }
这里把String类型的所有方法和变量都获取到了,使用的仅仅是String类型的全名。当然,反射的功能不仅仅是获取类的信息,还可以在运行时动态创建对象,回想一下,我们正常的对象使用,都是需要在代码中先声明,然后才能使用它,但是使用反射后,就能在运行期间动态创建对象并调用其中的方法,甚至还能直接查看类的私有成员变量,还能获取类的注解信息,在泛型中类型判断时也经常会用到。反射可以说完全打破了类的封装性,把类的信息全部暴露了出来。
上面的代码看不太明白也没关系,只要稍微感受一下反射的能力就好了。介绍完了反射能做的事情,本篇教程就不再写一些玩具代码了,这次以一个实用型的代码为媒介来介绍反射。
在开发中,经常会遇到两个不同类对象之间的复制,把一个类中的字段信息get取出来,然后set到另一个类中,大部分情况下,两个类对应的字段是一样,每次这样使用是很麻烦的,那么利用反射就可以实现一个封装,只需要调用一个方法即可实现简单的类字段复制。
那么,先来想想,要复制一个类对象的所有字段信息到另一个类对象中,首先,怎么获取一个类的某个字段的值呢?我们先来编写一个方法:
/** * 获取对象的指定字段的值 * @param obj 目标对象 * @param fieldName 目标字段 * @return 返回字段值 * @throws Exception 可能抛出异常 */ private static Object getFieldValue(Object obj, String fieldName) throws Exception{ //获取类型信息 Class clazz = obj.getClass(); //取对应的字段信息 Field field = clazz.getDeclaredField(fieldName); //设置可访问权限 field.setAccessible(true); //取字段值 Object value = field.get(obj); return value; }
这里使用了两个之前没有说过的类,一个是Class,是不是很眼熟,想一想,我们每次定义一个类的时候是不是都要用到它,哈哈,那你就想错了,那是class关键词,java是大小写的敏感的,这里的Class是一个类名,那这个类是干嘛用的呢?
虚拟机在加载每一个类的时候,会自动生成一个对应的Class类来保存该类的信息,可以理解为Class类是那个类的代理类,是连接实际类与类加载器的桥梁,可以通过它来获取虚拟机的类加载器引用,从而实现更多的骚操作。Class类是一个泛型类,每个类都有对应的一个Class类,比如String对应的Class类就是Class<String>。
Class有很多方法来获取更多关于类的信息,这里使用getDeclaredField方法来获取指定字段信息,返回的是Field类型对象,这个对象里存储着关于字段的一些信息,如字段名称,字段类型,字段修饰符,字段可访问性等,setAccessible方法可以设置字段的可访问性质,这样就能直接访问private修饰的字段了,然后使用get方法来获取指定对象的对应字段的值。
我们来测试一下:
public void testB(){ try{ Employee employee = new Employee(); employee.setName("Frank"); employee.setSalary(6666.66); System.out.println((String)getFieldValue(employee,"name")); System.out.println((double)getFieldValue(employee,"salary")); }catch (Exception e){ e.printStackTrace(); } }
输出如下:
Frank
6666.66
接下来,我们需要获取类中所有字段,然后在另一个类中查找是否有对应字段,如果有的话就设置字段的值到相应的字段中。
/** * 复制一个类对象属性到另一个类对象中 * @param objA 需要复制的对象 * @param objB 复制到的目标对象类型 * @return 返回复制后的目标对象 */ private static void parseObj(Object objA,Object objB) throws Exception{ if (objA == null){ return; } //获取objA的类信息 Class classA = objA.getClass(); Class classB = objB.getClass(); try { //获取objA的所有字段 Field[] fieldsA = classA.getDeclaredFields(); //获取objB的所有字段 Field[] fieldsB = classB.getDeclaredFields(); if (fieldsA == null || fieldsA.length <= 0 || fieldsB == null || fieldsB.length <= 0){ return; } //生成查询map Map<String,Field> fieldMap = new HashMap<>(); for (Field field:fieldsA){ fieldMap.put(field.getName(),field); } //开始复制字段信息 for (Field fieldB : fieldsB){ //查找是否在objB的字段中存在该字段 Field fielaA = fieldMap.get(fieldB.getName()); if (fielaA != null){ fieldB.setAccessible(true); fieldB.set(objB,getFieldValue(objA,fielaA.getName())); } } } catch (IllegalStateException e) { throw new IllegalStateException("instace fail: " ,e); } }
这里获取到classA和classB的所有字段之后,先生成了一个map用于查找,可以减少遍历次数,然后之后只需要遍历一次就可以判断相应字段是否存在,如果存在则取出对应值设置到相应的字段里去。
接下来测试一下:
public void testB(){ try{ //生成Employee对象 Employee employee = new Employee("Frank",6666.66); //生成一个Manager对象 Manager manager = new Manager(); //复制对象 parseObj(employee,manager); System.out.println(manager.getName()); System.out.println(manager.getSalary()); }catch (Exception e){ e.printStackTrace(); } }
public class Employee { private String name; private Double salary; public Employee(String name, Double salary) { this.name = name; this.salary = salary; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } }
public class Manager { private String name; private Double salary; private Double bonus; public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Double getBonus() { return bonus; } public void setBonus(Double bonus) { this.bonus = bonus; } }
输出如下:
Frank
6666.66
完美,这样我们就利用了反射机制完美的把相同的字段在不同类的对象之间进行了复制,这里仅仅是两个字段,所以可能好处不明显,但事实上,实际开发中,经常会有将BO转换为VO的操作,这时候,这个操作就很有必要了,简单的一行命令就可以代替一大堆的get和set操作。
当然,使用反射机制固然高端大气上档次,但是也是一把双刃剑,使用不当很可能会带来严重后果,而且使用反射的话,会占用更多资源,运行效率也会降低,上述工具类是用运行效率换开发效率。开发中不建议大量使用,还是那句话,技术只是手段,需要使用的时候再使用,不要为了使用而使用。
至于反射中的其他方法和姿势,大家尽可以慢慢去摸索,这里仅仅是抛砖引玉。
至此,本篇讲解完毕,欢迎大家继续关注。