Java 11:反射

来源:互联网 发布:博客平台推荐 知乎 编辑:程序博客网 时间:2024/05/22 05:27

1、

首先先放上知乎的一段关于虚拟机的解释,


JVM是一个进程,用来跑我们写的Java代码,上面的图是一个JVM内存模型。

代码Object o=new Object();首先将代码编译成class文件,然后被类加载器加载到JVM中,类Object加载到方法区,创建了Object类的class对象(对应的是是Object类而不是对象o,每个类只有一个class对象,作为方法区类的数据结构的接口),Jvm对象创建对象前会检查类是否加载,寻找对应的class对象,若加载好再为你的对象分配内存。

反射是程序运行过程中,需要动态加载的一些类,这些类之前可能用不到,假设程序可能用到Oracle和Mysql两个数据库,需要动态地根据情况加载驱动类,这时候就可以根据类的全名让jvm在服务器中找到并加载这个类,不同的数据库传入的类名不同。


2、

以下一段来自《李兴华的Java se实战》,也是一位知乎大神总结的

理解反射之前首先要理解反这个字的含义,那“正”是什么,当用户使用一个类的时候必须预先知道这个类,然后通过这个类产生实例化对象,“反”指的是通过对象找到类

Person p=new Person();//正着来

per.getClass.getName();//反着来

取得class对象的方法,public final Class<?> getClass(),反射中所有的泛型都定义为?,返回值是Object。getClass返回的是Class类的对象,所以这个Class就是所有反射操作的源头。

获得Class对象有三种方法:

1、Object的getClass方法,就是前面说到的

2、类名.class

3、使用Class内部定义的一个static方法,(主要使用),Class.forName()

取得了Class类对象以后,可以不依赖构造函数+new ,而使用public T newInstance(),

使用接口可以减少耦合,但是即使使用接口还是要使用关键字new,new成为耦合的元凶(意思是不管使用什么接口最后还是要new成具体的子类对象来赋给接口?)

比如一个工厂模式的产出,用string的类名来判别具体是哪个子类并new,返回,问题在于接口的子类增加了,工厂肯定要修改添加,如果不使用new关键字而使用反射?

工厂中不需要为每个类单独写new的操作,可以应用于所有的变化,几乎所有后面框架技术中心,程序传递一个“包.类”名称的时候几乎都是反射机制。

为了实例化一个对象,还需要调用类的构造函数、普通方法、属性,也可以通过反射完成


3、

程序中一般的对象都是在编译期就确定的,而Java反射机制可以动态地创建对象,这样的对象的类型在编译期间是未知的

反射框架主要提供一下几个功能:1判断任意一个对象所属的类,2、在运行时任意构造一个类的对象,3、在运行时判断任意类的成员变量和方法(甚至是private)4、运行时调用任意对象的方法·

反射在Java开发中其实应用十分广泛,在使用IDE时,输入一个对象自动列出的属性和方法,就用到了反射。

反射最重要的方法是开发各种通用框架,许多框架都是配置化的,为了保证框架的通用性,他们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候必须使用反射的作用——运行时加载需要的对象

比如Struts,在XML中配置一个Action,里面包含一个类的信息,当View层发出请求时,请求会被StrutsPrepareAndExecuteFilter拦截,然后动态创建Action实例,比如我们请求login.action,那么StrutsPrepareAndExecuteFilter去解析struts.xml,检索其中name为login的Action,并根据该Action里class属性创建实例,并用invoke方法来调用相应的方法。


4、基本使用

反射会额外消耗系统资源,因此不需要动态创建时就老老实实地new,另外反射可以忽略权限检查,会破坏封装性导致安全问题

下面就回到《java核心技术》里的具体使用了,

Java运行的时候始终为所有对象维护一个被称为运行时的类型标识,这个信息跟踪每个对象所属的类,可以通过Java类Class访问这些信息

Employee e=new Employee();Class c=e.getClass();System.out.println(c.getName());
String name="ppp.Employee";Class cc=Class.forName(name);System.out.println(cc.getName());
System.out.println(Employee.class.getName());
三种方式获取class,输出“包名.类名”


注意,一个Class类型表示一个类型,不一定是一个类,比如int.class也是可以的,class的name就是int,每个类型都有一个class,可以用==判断两者是否属于同一类型

可以用class的newInstance()方法构造一个新对象,但是好像只能调用默认的构造函数。(得使用Constructor类的newInstance语句才能加入参数)


java.lang.reflect包中有三个类Field,Method,Constructor分别描述类的域,方法和构造器

getFields、getMethods、getConstructors方法将返回对应的public结果,包括超类中的public成员,但是其他就不行了

getDeclareFields、getDeclareMethods、getDeclareConstructors可以返回全部域、方法和构造器,包括私有成员和protected成员,但是不包括超类的成员(不管是什么类型的),只显示在自己的Class里声明的那些

获取构造器的两个函数也只能获取自己的,不会在向上获取父类的构造器

获取的Field、Method、Constructor可以用getModifiers()获取描述static、public、final这些修饰符

Employee e=new Employee(100, "ninglu");Field[] fields=e.getClass().getFields();for (Field field : fields) {System.out.println(field.get(e));}//直接获取了对象e的两个public域的实际值
而private方法要用getDeclareFields才能获取,但由于是private,直接get它的值会报错,只能查看有那些域不能读取

这是加上一句field.setAccessible(true);

System.out.println(field.get(e).getClass());本来这是一个int变量,但是自动打包到相应的包装器Integer中了
fields[1].setAccessible(true);fields[1].set(e,"a");System.out.println(fields[1].get(e));//改变了对象e的变量值,而且可以是私有的,厉害了


想到一个关于继承的例子,反射对动态绑定怎么看

Object e=rst();//返回一个Manager对象System.out.println(e.getClass().getName());Employee ee=new Manager();System.out.println(ee.getClass().getName());
结果是都能识别出实际的Class,Manager


java.lang.reflect包中的Arrays可以动态地拷贝数组,用一般的方法,如果想将任意类型的数组拷贝到新的数组,在不知道数组类型的情况下,会new  Object[newLength],但初始化就声明成Object无法再强转为具体的对象。

这时候就需要用到Array里的newInstance,运用反射动态获取的class信息来得到具体的类型,也因为使用了反射,除了基本的类对象,还可以动态拷贝基本类型的数组

public static Object[] goodCopy(int newLength,Object[] array){int oldLength=array.length;Class c=array.getClass().getComponentType();Object newArray=Array.newInstance(c, newLength);Object[] a=(Object[])newArray;for(int i=0;i<oldLength;i++){a[i]=array[i];}return a;}

这是我自己一开始的写法,可以对任意类对象的数组进行复制,但是问题是不能传基本类型的数组,虽然Array.newInstance能对基本类型作用,但是函数调用的时候,int[]这些不能转化为Object[]数组。

这里就要说一下数组、基本类型、Object之间的关系了

1、基本类型有对应的class类型,可以通过反射操作,但是不是类对象类型,不继承于Object

2、数组作为整体,也是一个类对象,继承于Object,但每一个元素不一定,如果是int[],不能转换为Object[],但可以转换为一个Object

Object o=new int[10];System.out.println(o.getClass().getComponentType().getName());
然后可以查询出具体数组里的元素是什么,如果这里的Object o不是数组,只是一个普通的对象,调用getComponentType会报空指针,可以用Class类的isArray()方法判断是不是一个数组类型

还是回到书上,得到了下面这样的优化结果

public static Object goodCopy(int newLength,Object array){Class c=array.getClass();if(c.isArray()){int oldLength=Array.getLength(array);Object arrayObject=Array.newInstance(c.getComponentType(), newLength);System.arraycopy(array, 0, arrayObject, 0, Math.min(newLength, oldLength));return arrayObject;}elseSystem.out.println("不是数组元素!");return null;}}
原来Array和Arrays这两个类都是反射的package里面的,数组的Object没法用数组的函数操作,但是可以用Array类获取它的长度,很好!


5、invoke

C++可以从函数指针执行任意函数,即将一个方法的存储地址传给另一个方法,以便另一个方法能够调用他。Java认为函数指针很危险,认为Java 接口是更好的解决方案,然而反射机制也允许你调用任意方法。

在Method类中有一个invoke方法,允许调用包装在当前Method对象中的方法,签名是QObject invoke(Object obj,Object args),第一个obj是隐式参数,后面是显示参数,返回值如果是基本类型会返回相应的包装器类型

getDeclareMethod()、getMethod()可以获取方法,由于可能有同名方法,必须提供方法的参数类型。


















0 0
原创粉丝点击