高级 Java 思考笔记之反射的基本原理之一

开发 后端
原来我一直将java当做不用delete的c++来用,但是最近在工作上遇到一些问题,发现这样的做法不管用,因为工作上遇到的java代码采用了很多框架,例如spring boot之类。当我想深入了解这些框架的设计原理时发现,如果不了解java的高级语法特性,这些框架的设计思维或运行方式根本就无法理解。

[[432232]]

原来我一直将java当做不用delete的c++来用,但是最近在工作上遇到一些问题,发现这样的做法不管用,因为工作上遇到的java代码采用了很多框架,例如spring boot之类。当我想深入了解这些框架的设计原理时发现,如果不了解java的高级语法特性,这些框架的设计思维或运行方式根本就无法理解。

后来稍微调查一下发现,自从java8之后,java的语法发生了巨大的变化,代码的设计模式也不再像以前“不用delete的c++”,参照原来c++的思维去解读java代码已经行不通了,于是重新学习,顺便在学习和思考中做一些总结输出。

java新增语法特征中,有较为抽象的一部分叫反射,其实说白了就是用程序来控制程序。java将一切概念都包含在类中,于是对应每一个具体类,语言体系还为特定的具体类生成了描述其特性的抽象类,例如我们定义了如下一个类:

  1. static public class HelloWorld { 
  2.     public void sayHelloWorld1(String s) { 
  3.            System.out.println("say hello world 1!: " + s); 
  4.        } 
  5.  
  6.        public void sayHelloWorld2(int i) { 
  7.            System.out.println("say hello world 2: " + i); 
  8.        } 
  9.  
  10.        protected  void sayHelloWorld3() { 
  11.            System.out.println("say hello world3"); 
  12.        } 
  13.  
  14.        private void sayHelloworld4() { 
  15.            System.out.println("say hello world4"); 
  16.        } 
  17.  
  18.        public String field1 = "hello"
  19.        protected int field2 = 1; 
  20.        private int field3 = 2; 
  21.  
  22.    } 

这个类里面有公有,保护,私有等几个方法,虚拟机在为这个类生成字节码时,还构建了另一个类,用于描述这个类的类,可以称为它的源类,后者用来描述前者在编程语言上的特性,例如它包含了哪些方法,这些方法对应的参数,返回值,公有私有等,如果把一个类比作人,那么反射其实就是x光扫描,它把你内外的具体细节都暴露出来,我们看看怎么用反射来解读上面定义的类:

  1. public static void manipulateHelloWorldClass(Object obj) { 
  2.         Class cls = obj.getClass(); 
  3.         //打印出类对象对应的类名 
  4.         System.out.println("object class name: " + cls.getName()); 
  5.         //返回实例对应的类所声明的所有函数 
  6.         Method[] methods = cls.getDeclaredMethods(); 
  7.         for (Method method : methods) { 
  8.             System.out.println("delcared method name: " +  method.getName()); 
  9.         } 
  10.     } 
  11.  
  12.     public static void main(String[] args) { 
  13.         HelloWorld helloWorld = new HelloWorld(); 
  14.         manipulateHelloWorldClass(helloWorld); 
  15.     } 

上面代码运行后输出结果如下:

可以看到,代码打印出helloword实例对象在编程语言上的信息,例如上面代码就打印出它对应类的名字,都有哪些方法等等,在Java语言中Class类也叫原类,它用来解析所有实例对应类在编程语法上的信息,每个实例对应的类都能得到一个Class类对应的实例,就像代码中那样,这个实例能够查询对应类有哪些方法,定义了哪些字段.

代码中调用了getDeclaredMethods来获得实例所对应类在定义时声明的所有方法,Class类还有一个方法叫getMethods,它返回实例对应类自己在定义时所声明的公有方法,以及继承过来的所有公有方法。

反射机制一个很重要的作用就是能够查询给定实例是否有特定接口,然后调用相关接口,代码如下:

  1. public static void callMethod(Object obj) { 
  2.         Class cls = obj.getClass(); 
  3.         try { 
  4.             Method method = cls.getMethod("sayHelloWorld1", new Class[] {String.class}); 
  5.             method.invoke(obj, new Object[]{"hello world"}); 
  6.             method = cls.getMethod("sayHelloWorld2", new Class[] {int.class}); 
  7.             method.invoke(obj, new Object[]{new Integer(123)}); 
  8.         } catch(NoSuchMethodException e) { 
  9.             e.printStackTrace();; 
  10.         } catch (IllegalAccessException e) { 
  11.             throw new IllegalArgumentException("Insufficient access permissions to call" 
  12.                     + "setColor(:Color) in class " + cls.getName()); 
  13.         } catch (InvocationTargetException ex) { 
  14.             throw new RuntimeException(ex); 
  15.         } 
  16.  
  17.     } 
  18.  
  19.     public static void main(String[] args) { 
  20.         HelloWorld helloWorld = new HelloWorld(); 
  21.       //  manipulateHelloWorldClass(helloWorld); 
  22.         callMethod(helloWorld); 
  23.     } 

上面代码执行后结果如下所示:

我们可以体会到,c++是不会有这种特性的,java由于具备了这种反射机制,使得它能够用来开发很多框架,在java世界里形形色色的框架特别多,这跟它在语法上支持反射不无关系,类似spring boot这些java程序员绝对必须要掌握的框架,它的设计就大量使用了反射机制。

使用反射功能还可以很好的实现类实例的序列化,当我们想要将一个类实例的信息从内存存储到硬盘时,我们就需要将类实例当前内部各个字段的信息存储到文件里,以后需要的时候再从文件中读出,然后利用读到的数据重新把类的实例new出来,因此实现序列化第一步就是要获得类实例所有字段的数据,相应代码如下:

  1. import java.lang.reflect.*; 
  2. import java.util.*; 
  3.  
  4. public class Serializer { 
  5.     public static Field[] getInstanceVariable(Object obj) { 
  6.         Class cls = obj.getClass(); 
  7.         List accumFields = new LinkedList(); 
  8.         while (cls != null) { 
  9.             //获得实例对应类所声明的全部字段 
  10.             Field[] fields = cls.getDeclaredFields(); 
  11.             for (Field field : fields) { 
  12.                 accumFields.add(field); //将字段对应的元类对象存储起来 
  13.             } 
  14.  
  15.             cls = cls.getSuperclass(); //获得父类对象 
  16.         } 
  17.     } 

代码中要注意到,实例化一个类实例时,还需要考虑这个类的继承关系,如果它有父类的话,我们还需要取得其父类的字段对应的信息,所以代码中使用getSupperclass方法获得实例对应类的父类的源类对象。

前面代码中我们看到,Field类对应的getModifier能返回字段的修饰属性,也就是字段是public, private, protected , static, native等等,它返回的是一个2的指数幂数值,实际上它对应一个16比特位数值,当字段属于哪种情况,就在相应的比特位上设置为1,因为字段的属性总共有16种,因此getModifier返回一个2字节的整数。

得到这个数值后,我们再调用一系列方法获得其属性,例如isPublic返回字段是否是public类型,isPrivate返回字段是否为private类型等。由于我们在序列化一个实例时,不用关注那些静态变量,因为静态变量的值是写死的,因此需要对上面的代码进行修改,忽略掉那些被static修饰的字段:

  1. import java.lang.reflect.*; 
  2. import java.util.*; 
  3.  
  4. public class Serializer { 
  5.     public static Field[] getInstanceVariable(Object obj) { 
  6.         Class cls = obj.getClass(); 
  7.         List accumFields = new LinkedList(); 
  8.         while (cls != null) { 
  9.             //获得实例对应类所声明的全部字段 
  10.             Field[] fields = cls.getDeclaredFields(); 
  11.             for (Field field : fields) { 
  12.                 //确保字段不是static类型 
  13.                 if(!Modifier.isStatic(field.getModifiers())) { 
  14.                     accumFields.add(field); 
  15.                 } 
  16.             } 
  17.  
  18.             cls = cls.getSuperclass(); //获得父类对象 
  19.         } 
  20.  
  21.         Field[] retvalue = new Field[accumFields.size()]; 
  22.         return (Field[])accumFields.toArray(retvalue); 
  23.     } 

这里还有很多问题需要考虑,例如序列化实例时,我们需要获取类实例里面字段的值,但是如果字段属性是private或者protected时,我们就不能直接从类实例中读取字段内容,同时如果字段对应的是数组类型,那么我们还得采取特定的处理方法,为了防止文章长度过长令人看不下去,我们把这些内容总结再下一篇。

 

责任编辑:武晓燕 来源: Coding迪斯尼
相关推荐

2012-01-12 14:37:34

jQuery

2016-08-17 23:53:29

网络爬虫抓取系统

2011-11-29 12:17:00

2013-04-07 14:09:55

Android应用基本

2010-08-20 13:29:33

OFDM

2020-03-21 14:57:14

手机定位智能手机APP

2010-03-18 20:13:03

Java socket

2009-02-24 09:43:00

IP电话原理

2021-04-27 19:21:48

HBase原理开源

2010-03-17 13:35:02

2021-02-08 21:40:04

SockmapBPF存储

2016-08-18 00:04:09

网络爬虫抓取系统服务器

2019-11-28 10:45:28

ZooKeeper源码分布式

2011-08-10 19:33:09

Cocoa对象

2020-10-14 06:23:54

SpringBean实例化

2020-11-26 13:54:03

容器LinuxDocker

2011-07-07 14:46:10

Cocoa Xcode

2010-06-18 17:28:37

Linux Anacr

2011-07-07 14:10:21

Cocoa 内省 hash

2020-12-29 16:55:44

ZooKeeper运维数据结构
点赞
收藏

51CTO技术栈公众号