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