当前位置: 首页 > 科技观察

Java高级思维笔记中反思的基本原则之一

时间:2023-03-17 21:16:14 科技观察

之前一直用java作为C++不删,但是最近在工作中遇到了一些问题,发现这种做法行不通,因为工作中遇到的java代码使用许多框架,例如springboot。当我想深入了解这些框架的设计原理时,却发现如果不了解java的高级语法特性,根本无法理解这些框架的设计思维或运行模式。稍微查了一下,发现从java8开始,java的语法发生了翻天覆地的变化,代码的设计模式也不再像以前的“c++不删”。参考原来的c++思维去解读java代码已经行不通了,所以重新学习,顺便在学习和思考中做一些总结输出。在java的新语法特性中,有一个比较抽象的部分叫做反射。其实说白了就是用程序来控制程序。Java把所有的概念都包含在类中,所以对应于每一个具体的类,语言系统也会为具体的具体类生成描述其特性的抽象类。例如,我们定义如下类:);}protectedvoidsayHelloWorld3(){System.out.println("sayhelloworld3");}privatevoidsayHelloworld4(){System.out.println("sayhelloworld4");}publicStringfield1="hello";protectedintfield2=1;privateintfield3=2;}这个类中有public、protected和private方法。这个类在生成字节码的时候,会构建另外一个类来描述这个类的类,可以称之为它的源类。后者在编程语言中用来描述前者的特性,比如它包含了哪些方法,这些方法对应的参数,返回值,public和private等等。如果把一个类比作一个人,那么反射实际上是X光扫描,可以暴露您内部和外部的细节。下面看看如何使用反射来解释上面的定义class:publicstaticvoidmanipulateHelloWorldClass(Objectobj){Classcls=obj.getClass();//打印出类对象对应的类名System.out.println("objectclassname:"+cls.getName());//返回实例对应类Method[]methods=cls.getDeclaredMethods();for(Methodmethod:methods){System.out.println("delcaredmethodname:"+方法.getName());}}publicstaticvoidmain(String[]args){HelloWorldhelloWorld=newHelloWorld();manipulateHelloWorldClass(helloWorld);}上面代码运行后,输出如下:可以看到,代码打印出了程序helloword实例对象的语言信息,例如上面的代码打印出它对应的类的名称,有什么方法等。在Java语言中,Class类也称为原始类,用于分析所有实例对应类的编程语法信息。每个实例对应的类都可以得到Class类对应的一个实例。就像代码中一样,这个实例可以查询对应的类有哪些方法,定义了哪些字段。代码中会调用getDeclaredMethods,在定义时获取实例对应的类的声明。对于所有的方法,Class类还有一个方法叫getMethods,返回类本身对应的实例声明的公共方法。它被定义,所有继承的公共方法反射机制的一个很重要的功能就是能够查询给定的实例是否有特定的接口,然后调用相关的接口。代码如下:]{"helloworld"});method=cls.getMethod("sayHelloWorld2",newClass[]{int.class});method.invoke(obj,newObject[]{newInteger(123)});}catch(NoSuchMethodExceptione){e.printStackTrace();;}catch(IllegalAccessExceptione){thrownewIllegalArgumentException("Insufficientaccesspermissionstocall"+"setColor(:Color)inclass"+cls.getName());}catch(InvocationTargetExceptionex){thrownewRuntimeException(ex);}}publicstaticvoidmain(String[]args){HelloWorldhelloWorld=newHelloWorld();//manipulateHelloWorldClass(helloWorld);callMethod(helloWorld);}上面代码执行后的结果如下图:我们可以体会到c++没有这个特性。Java有这种反射机制,这使得它可以用来开发很多框架。java世界里有很多种框架。跟语法上支持反射不无关系。类似于springboot,java程序员绝对必须掌握的一个框架,它的设计使用了大量的反射机制。使用反射功能也可以很好的实现类实例的序列化。当我们要将一个类实例的信息从内存存储到硬盘时,我们需要将类实例内部各个字段的信息存储在一个文件中。以后需要的时候从文件中读取,然后用读取到的数据来更新类的实例。因此,实现序列化的第一步就是获取类实例所有字段的数据。对应代码如下:importjava.lang.reflect.*;importjava.util.*;publicclassSerializer{publicstaticField[]getInstanceVariable(Objectobj){Classcls=obj.getClass();ListaccumFields=newLinkedList();while(cls!=null){//获取对应类声明的实例AllfieldsField[]fields=cls.getDeclaredFields();for(Fieldfield:fields){accumFields.add(field);//存储该字段对应的元类对象}cls=cls.getSuperclass();//获取父类对象中的}}}代码,需要注意的是,在实例化类实例时,还需要考虑该类的继承关系。如果它有父类,我们还需要获取其父类的字段对应的信息,所以代码中使用了getSupperclass方法获取实例对应的类的父类的源类对象。在前面的代码中,我们看到Field类对应的getModifier可以返回字段的修改属性,即字段是public、private、protected、static、native等,它返回的值为a2的幂,实际上对应一个16位的值,当该字段属于哪种情况时,将对应的位设置为1,因为该字段一共有16个属性,所以getModifier返回一个2字节的整数。拿到这个值之后,我们再调用一系列的方法来获取它的属性,比如isPublic返回字段是否为公有类型,isPrivate返回字段是否为私有类型等。由于我们在序列化实例的时候不需要关注那些静态变量,因为静态变量的值是硬编码的,所以我们需要修改上面的代码,忽略static:importjava.lang修改的那些字段。reflect.*;importjava.util.*;publicclassSerializer{publicstaticField[]getInstanceVariable(Objectobj){Classcls=obj.getClass();ListaccumFields=newLinkedList();while(cls!=null){//获取声明的所有字段实例对应的类Field[]fields=cls.getDeclaredFields();for(Fieldfield:fields){//确保字段不是静态类型if(!Modifier.isStatic(field.getModifiers())){accumFields.add(field);}}cls=cls.getSuperclass();//获取父类对象}Field[]retvalue=newField[accumFields.size()];return(Field[])accumFields.toArray(retvalue);}}还有很多问题需要考虑,比如在序列化一个实例的时候,我们需要获取类实例中字段的值,但是如果字段属性是private或者protected,我们不能直接从类实例中读取字段内容,如果字段对应的是数组类型,那么我们还是要采用特定的处理方式。为了不让文章篇幅过长看不懂,我们会在下一篇文章中总结这些内容。