java入门学习提升第十三篇: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("{");

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 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操作。

当然,使用反射机制固然高端大气上档次,但是也是一把双刃剑,使用不当很可能会带来严重后果,而且使用反射的话,会占用更多资源,运行效率也会降低,上述工具类是用运行效率换开发效率。开发中不建议大量使用,还是那句话,技术只是手段,需要使用的时候再使用,不要为了使用而使用。

至于反射中的其他方法和姿势,大家尽可以慢慢去摸索,这里仅仅是抛砖引玉。

我做开发十多年的时间,如果大家对于学习java的学习方法,学习路线以及你不知道自己应该是自学还是培训的疑问,都可以随时来问我,大家可以加我的java交流学习qun:四九四,八零一,九三一,qun内有学习教程以及开发工具。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容