Java反射机制

来源:互联网 发布:js监听函数是否执行 编辑:程序博客网 时间:2024/06/18 09:31

JVM和类:
运行Java:java 带有mian方法的类名
启动JVM,并加载字节码


当调用Java命令来运行某个程序时,该命令将会启动一个JVM进程。同一个JVM中的所有线程,变量都处于同一个进程中,共享该JVM的内存区域。
当出现以下情况时,JVM会退出:
1、程序正常执行结束;
2、使用System.exit(0)方法;
3、出现异常时,没有捕获异常;
4、平台强制结束JVM进程。


JVM进程一旦结束,该进程中内存的数据将会丢失。



当程序主动使用到某个类时,如果该类还未被加载进内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化操作。
1)类的加载:
类加载是指将类的class文件(字节码文件)载入内存中,并为之创建一个java.lang.Class对象,我们称之为字节码对象。
类的加载过程由类加载器(ClassLoader)完成,类加载器通常由JVM提供,我们称之为系统类加载器,我们也可以继承ClassLoader类来提供自定义类加载器。
不同的类加载器可以实现加载本地字节码文件,jar包中的字节码,通过网络加载字节码等。


2)类的连接:
当类被加载进内存之后,系统为之生产一个对应的Class对象,接着把类的二进制数据合并到JRE中。
1>验证:检测被加载的类是否与正确的内部结构;
2>准备:负责为类的static变量分配内存,并设置默认值;
3>解析:把类的二进制数据中的符号引用替换为直接引用。


3)类的初始化:
在此阶段,JVM负责对类进行初始化,主要就是对static变量进行初始化。
初始化一个包含以下几个步骤:
1>如果该类还未被加载和连接,则程序先加载并连接该类;
2>如果该类的直接父类还未被初始化,则先初始化其父类;
3>如果类中有初始化语句(静态代码块),则系统依次执行这些初始化语句。


符号引用:
符号引用是一个字符串,它给出了被引用的内容的名字并且可能会包含一些其他关于这个被引用项的信息——这些信息必须足以唯一的识别一个类、字段和方法。
这样,对于其他类的符号引用必须给出类的全名。


元数据(metadata):描述数据的描述数据

反射:得到类的元数据的过程。在运行时期,动态地去获取某一个类中的成员信息(构造器,方法,字段,内部类,接口,父类,包等等),并且把类中的每一种成员,都描述成一个新的类。
Class:表示所有的类
Constructor:表示所有的构造器
Method:表示所有的方法
Field:表示所有的字段(很少用)


Class类:用来描述类或者接口的类型,描述类的类
Class类的实例:在JVM中的一份份字节码,Class实例表示在JVM中的类或者接口,枚举是一种特殊的类,注解是一种特殊的接口。


当程序第一次随时用某一个java.util.Date类的时候,就会把该类的字节码对象加载进JVM,并创建出一个Class对象。
此时的Class对象就表示java.util.Date的字节码。
Class类可以表示N个类的字节码对象,问题是到底怎么区分Class类此时表示哪一个类的字节码呢?
为了解决该问题,Class类的设计者提供了泛型-->Class<T>


java.lang.String类的字节码类型:Class<java.lang.String>;
java.util.Date类的字节码类型:Class<java.util.Date>;
java.util.ArrayList类的字节码类型:Class<java.util.ArrayList>;


如何创建Class对象,如何来表示一个字节码对象?
方式1:使用class属性,类名.class;
方式2:通过对象的getClass方法来获取,getClass是Object类中的方法
方式3:通过Class类中的静态方法forName(String className);


以后用得最多的是方式3,在框架中大量使用。

/** * Created by Layne_Yao on 2017-8-2 上午11:07:53. * CSDN:http://blog.csdn.net/Jsagacity */// 获取字节码对象:Class对象public class ClassInstanceDemo {public static void main(String[] args) throws Exception {// 需求:获取java.lang.String类的字节码对象// 方式1:使用class属性Class<java.lang.String> cls1 = java.lang.String.class;// 方式2:通过对象的getClass方法来获取,getClass是Object类中的方法java.lang.String str = new java.lang.String();Class<?> cls2 = str.getClass();// 方式3:通过Class类中的静态方法forName(String className);Class<?> cls3 = Class.forName("java.lang.String");System.out.println(cls1);System.out.println(cls2);System.out.println(cls3);// 说明同一个类在JVM中只存在一份字节码对象System.out.println(cls1 == cls2);// trueSystem.out.println(cls1 == cls3);// trueSystem.out.println(cls3 == cls2);// truedoWork(new Integer(17));}public static void doWork(int num) {System.out.println("int");}public static void doWork(Integer num) {System.out.println("Integer");}}


运行结果:




现在有一个问题:在上述讲了三种获取Class对象的方式,基本数据类型不能表示为对象,也就不能使用getClass,基本数据类型没有类名的概念,
也不能使用Class.forName的方式,如何表示基本数据类型的字节码对象呢?
所有的数据类型都有class属性。
       Class cls = 数据类型.class;


九大内置Class实例:JVM中预先提供好的Class实例,分别:byte,short,int,long,float,double,boolean,char,void。
表示:byte.class,short.class......void.class。


在8达基本数据类型的包装类中,都有一个常量:TYPE,用于返回该包装类对应基本类的字节码对象。
System.out.println(Integer.TYPE == int.class);// true


注意:Integer和int是不同的数据类型
System.out.println(Integer.class == int.class);// false
------------------------------------------------
数组的Class实例:数组是引用数据类型,数组其实是对象。
如何来表示数组的Class实例:
方式1:数组类型.class;
方式2:数组对象.getClass();

/** * Created by Layne_Yao on 2017-8-2 下午1:44:10. * CSDN:http://blog.csdn.net/Jsagacity */public class DataTypeClassInstanceDemo {public static void main(String[] args) {// 8大基本数据类型Class实例Class booleanClass = boolean.class;Class intClass = int.class;System.out.println(booleanClass);System.out.println(intClass);System.out.println("=====================");// Integer.class和int.class相同吗?得出的结果是:Integer和int是不同的数据类型System.out.println(Integer.class == int.class);// falseSystem.out.println(Integer.TYPE == int.class);// trueSystem.out.println("=====================");// 表示数组的Class实例:// 方式1:数组类型.class;// 方式2:数组对象.getClass;int[] arr = { 1, 2, 3 };// 方式1:数组类型.class;Class arr1Class = int[].class;System.out.println(arr1Class);// 方式2:数组对象.getClass;Class arr2Class = arr.getClass();System.out.println(arr2Class);System.out.println(arr1Class == arr2Class);//所有的具有相同的维数和相同元素类型的数组共享同一份字节码对象,和元素没有关系。int[] arr1 = {4,5,6};System.out.println(arr.getClass()==arr1.getClass());}}


运行结果:


注意:所有的具有相同的维数和相同元素类型的数组共享同一份字节码对象,和元素没有关系。

Class:描述所有的类型,所以Class类中应该具有所有类型的相同的方法。
Object:描述所有的对象,所以在Object类中应该具有所有对象的共同方法。






现在有一个需求:通过反射来获取某一个类的构造器:
1、获取该类的字节码对象;
2、从该字节码对象中去寻找需要获取的构造器。

/** * Created by Layne_Yao on 2017-8-2 下午2:19:22. * CSDN:http://blog.csdn.net/Jsagacity */class Person {public Person() {}public Person(String name) {}private Person(String name, int age) {}}public class GetConstruactorDemo {public static void main(String[] args) throws Exception {// getAll();getOne();}// 获取指定的一个构造器private static void getOne() throws Exception {// 1、获取构造器所在的类的字节码对象Class<Person> cls = Person.class;// 2、获取cls对象中的构造器// 需求1:获取public Person()Constructor<Person> cs = cls.getConstructor();System.out.println(cs);// 需求2:获取public Person(String name)cs = cls.getConstructor(String.class);System.out.println(cs);// 需求3:获取private Person(String name, int age)cs = cls.getDeclaredConstructor(String.class,int.class);System.out.println(cs);}// 获取所有的构造器private static void getAll() {// 1、获取构造器所在的类的字节码对象Class<Person> cls = Person.class;// 2、获取cls对象中所有的构造器// public Constructor<?>[] getConstructors():该方法只能获取当前Class所表示类的public修饰的构造器Constructor<?>[] cs = cls.getConstructors();System.out.println(cs.length);for (Constructor<?> c : cs) {System.out.println(c);}System.out.println("=================================");// public Constructor<?>[] getDeclaredConstructors():获取当前Class所表示类的所有的构造器,和访问权限无关。cs = cls.getDeclaredConstructors();System.out.println(cs.length);for (Constructor<?> c : cs) {System.out.println(c);}}}


运行结果:


Class类获取构造器的方法:

Constructor类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器

public Constructor<?>[] getConstructors():该方法只能获取当前Class所表示类的public修饰的构造器
public Constructor<?>[] getDeclaredConstructors():获取当前Class所表示类的所有的构造器,和访问权限无关。


public Constructor<T> getConstructor(Class<?>... parameterTypes):获取当前Class所表示类中指定的一个public的构造器
参数: parameterTypes表示:构造器参数的Class类型
         如:public Person(String name) Constructor<Person> cs = cls.getConstructor(String.class);


public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取当前Class所表示类中指定的一个构造器



构造器最大作用:创建对象。
为什么使用反射创建对象,为什么不直接来new呢?
在框架中,提供给我们的都是字符串。
-----------------------------------------
使用反射创建对象:
步骤:

1、找到构造器所在类的字节码对象
2、获取构造器对象
3、使用反射,创建对象

/** * Created by Layne_Yao on 2017-8-2 下午3:57:20. * CSDN:http://blog.csdn.net/Jsagacity */class User {public User() {System.out.println("构造器");}public User(String name) {System.out.println("构造器" + name);}private User(String name, int age) {System.out.println("构造器" + name + "," + age);}}public class CreateObjectDemo {public static void main(String[] args) throws Exception {// 使用传统方式创建对象// new User();// new User("layne");Class<User> cls = User.class;// 获取cls对象中的构造器// 调用public User()Constructor<User> cs = cls.getConstructor();// 调用构造器的newInstance方法来创建对象,并传入实参cs.newInstance();System.out.println("========================");// 调用public User(String name)cs = cls.getConstructor(String.class);cs.newInstance("Layne");System.out.println("========================");// 调用public User(String name)cs = cls.getDeclaredConstructor(String.class,int.class);//这个构造方法是私有的,需要设置当前构造器可以访问cs.setAccessible(true);cs.newInstance("Layne",18);System.out.println("========================");}}


运行结果:


Constructor<T>类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
常用方法:
public T newInstance(Object... initargs):如调用带参数的构造器,只能使用该方式。
       参数:initargs表示调用构造器的实际参数
       返回:返回创建的实例,T表示Class所表示类的类型


如果:一个类中的构造器是外界可以直接访问,同时没有参数,那么可以直接使用Class类中的newInstance方法创建对象。
       public Object newInstance();相当于new 类名();
利用反射还可以调用私有构造器。





使用反射获取类中的方法:

步骤:

1、获取方法所在类的字节码对象。
2、获取方法。

/** * Created by Layne_Yao on 2017-8-2 下午4:54:04. * CSDN:http://blog.csdn.net/Jsagacity */class User {public void doWork() {}public static void doWork(String name) {}private String goodMornning(String name, int age) {return "早上好," + name + "," + age;}}// 使用反射获取类中的方法public class MethodDemo {public static void main(String[] args) throws Exception {// getAll();getOne();}//获取User类中指定的一个方法private static void getOne() throws Exception {Class cls = User.class;//需求:获取public void dowork()Method m = cls.getMethod("doWork");System.out.println(m);//需求:获取public static void dowork(String name)m = cls.getMethod("doWork",String.class);System.out.println(m);//需求:获取private String goodMornning(String name, int age)m = cls.getDeclaredMethod("goodMornning",String.class,int.class);System.out.println(m);}// 获取User类中所有的方法private static void getAll() {Class cls = User.class;Method[] ms = cls.getMethods();System.out.println(ms.length);for (Method method : ms) {System.out.println(method);}System.out.println("=======================");ms = cls.getDeclaredMethods();System.out.println(ms.length);for (Method method : ms) {System.out.println(method);}}}


运行结果:


Class类中常用的方法:
public Method[] getMethods():获取包括自身和继承过来的所有的public方法
public Method[] getDeclaredMethods():获取自身类中所有的方法(不包括继承的,和访问权限无关)


public Method getMethod(String name, Class<?>... parameterTypes):表示调用指定的一个公共的方法(包括继承的)
name:表示被调用方法的名字
parameterTypes:表示被调用方法的参数的Class类型,如String.class


public Method getDeclaredMethod(String name, Class<?>... parameterTypes):表示调用指定的一个本类中的方法(不包括继承的)
name:表示被调用方法的名字
parameterTypes:表示被调用方法的参数的Class类型,如String.class





使用反射调用方法:
1、获取方法所在类的字节码对象。
2、获取方法对象。
3、使用反射调用方法。


如何使用反射调用一个方法:
在Method类中有方法:

public Object invoke(Object obj, Object... args):表示调用当前Method所表示的方法
参数:
       obj - 表示被调用方法底层所属对象
       args - 表示调用方法时传递的实际参数
返回:
       底层方法的返回结果


调用私有方法:
在调用私有方法之前:应该设置方法为可访问的,又因为Method是AccessibleObject子类,所以Method中具有该方法:method.setAccessible(true);

/** * Created by Layne_Yao on 2017-8-3 上午9:18:51. * CSDN:http://blog.csdn.net/Jsagacity */class Person {public void doWork() {System.out.println("Person.doWork()");}public static void doWork(String name) {System.out.println("Person.doWork()" + name);}private String goodMornning(String name, int age) {System.out.println("Person.goodMornning()" + name + "," + age);return "早上好," + name + "," + age;}}// 使用反射调用方法public class MethodInvokeDemo {public static void main(String[] args) throws Exception {// 需求:调用public void doWork()Class cls = Person.class;Method m = cls.getMethod("doWork");Object obj = m.invoke(cls.newInstance());System.out.println(obj);//null// 需求:调用public static void doWork(String name)m = cls.getMethod("doWork",String.class);obj = m.invoke(cls.newInstance(),"Layne");System.out.println(obj);//null// 需求:调用private String goodMornning(String name, int age)m = cls.getDeclaredMethod("goodMornning",String.class,int.class);//这是私有的方法,需要先设置可访问私有的成员m.setAccessible(true);//调用方法obj = m.invoke(cls.newInstance(),"Layne",18);System.out.println(obj);}}


运行结果:






使用反射调用静态方法:
静态方法不属于任何对象,静态方法属于类本身。
此时把invoke方法的第一个参数设置为null即可。

public static void doWork(String name) {System.out.println("Person.doWork()" + name);   }// 需求:调用public static void doWork(String name)m = cls.getMethod("doWork",String.class);obj = m.invoke(null,"Layne");System.out.println(obj);//null


使用反射调用数组参数(可变参数):
王道:
调用方法的时候把实际参数通通作为Object数组的元素即可。

Method对象.invoke(方法底层所属对象,new Object[]{实参});

/** * Created by Layne_Yao on 2017-8-3 上午10:29:29. * CSDN:http://blog.csdn.net/Jsagacity */class Employee {public static void doWork1(int... arr) {System.out.println("doWork1被调用" + Arrays.toString(arr));}public static void doWork2(String... arr) {System.out.println("doWork2被调用" + Arrays.toString(arr));}}public class MethodInvokeDemo2 {public static void main(String[] args) throws Exception {Class cls = Employee.class;//情况1:数组的元素类型是基本类型Method m = cls.getMethod("doWork1", int[].class);m.invoke(null,new int[]{1,2,3,4});m.invoke(null,new Object[]{new int[]{1,2,3,4}});//情况2:数组的元素类型是引用类型m = cls.getMethod("doWork2", String[].class);System.out.println(m);m.invoke(null,new Object[]{new String[]{"A","B","C","D"}});}}

运行结果: