Java反射机制

一、反射的概念

1.反射出现的背景:

Java程序中,所有的对象都有两种类型:编译时类型和运行时类型 ,而很多时候对象的编译时类型和运行时类型不一致。例如某些变量或形参的声明类型是Object类型,但是程序却需要调用该对象运行时类型的方法,该方法不是Object中的方法,可以依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。

2.反射概述:

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。加载完类之后在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息,我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为反射。

3.Java反射机制研究及应用

Java反射机制提供的功能:

(1)在运行时判断任意一个对象所属的类

(2)在运行时构造任意一个类的对象

(3)在运行时判断任意一个类所具有的成员变量和方法

(4)在运行时获取泛型信息

(5)在运行时调用任意一个对象的成员变量和方法

(6)在运行时处理注解

(7)生成动态代理

4.反射相关的主要API

(1)java.lang.Class:代表一个类

(2)java.lang.reflect.Method:代表类的方法

(3)java.lang.reflect.Field:代表类的成员变量;

(4)java.lang.reflect.Constructor:代表类的构造器;

5.反射的优缺点:

(1)优点:

a.提高了Java程序的灵活性和扩展性,降低了耦合性,提高自适应能力

b.允许程序创建和控制任何类的对象,无需提前硬编码目标类

(2)缺点:

a.反射的性能较低,反射机制主要应用在对灵活性和扩展性要求很高的系统框架上

b.反射会模糊程序内部逻辑,可读性较差;

二、Class类:

1.Class类的理解:

针对于编写好的.java源文件进行编译(使用javac.exe),会生成一个或多个.class字节码文件。接着我们使用java.exe命令对指定的.class文件进行解释运行。这个解释运行的过程中,我们需要将.class字节码文件加载到内存中的方法区。加载到内存中的.class文件对应的结构即为Class的一个实例;

Class clazz1 = Person.class;运行时类
Class clazz2 = User.class;运行时类
Class clazz3 = Comparable.class;

2.获取Class实例的方法:

1.调用运行时类的静态属性:class
Class clazz1 = 运行时类名.class;
2.调用运行时类的对象的getClass()方法
Class clazz2 = 运行时类的的对象名.getClass();
3.调用Class的静态方法forName(String className)
Class clazz3 = Class.forName(某个类的全类名);
4.使用类的加载器
Class clazz4 = ClassLoader.getSystemClassLoader().loadClass(某个类的全类名);
clazz1 == clazz2 == clazz3 == clazz4

3.Class的实例可以指向的结构:

(1)class:外部类、成员内部类、成员静态内部类、局部内部类、匿名内部类;

(2)interface:接口;

(3)数组;

(4)enum:枚举;

(5)annotation:注解@interface;

(6)primitive type:基本数据类型;

(7)void;

4.类的加载过程:

(1)类的装载(loading):将类的class文件读入内存,并为之创建一个java.lang.Class对象,此过程由类加载器完成;

(2)链接(linking):

a.验证(Verify):确保加载的类信息符合JVM规范

b.准备(Prepare):正式为类变量(static)分配内存并设置类变量默认初始化值的阶段,这些内存都将在方法区中进行分配;

c.解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程;

(3)初始化(initializing):

执行类构造器<clinit>()方法的过程,该方法是由编译器自动收集类中所有的类变量的复制动作和静态代码块中的语句合并产生的

5.类的加载器:

(1)作用:负责类的加载,并对应于一个Class的实例对象;

(2)分类:

a.BootstrapClassLoader:引导类加载器、启动类加载器;

a1.使用C/C++语言编写,不能通过Java代码获取其实例;

a2.负责加载Java核心类库;

b.继承于ClassLoader的类加载器:

ExtensionClassLoader(扩展类加载器)

SystemClassLoader/ApplicationClassLoader(系统类加载器、应用程序类加载器)

用户自定义的类加载器

(3)获取加载器:

获取系统类加载器
ClassLoader classLoader1 = ClassLoader.getSystemClassLoader();
获取扩展类加载器
ClassLoader classLoader2 = classLoader1.getParent();
获取引导类加载器:失败
ClassLoader classLoader3 = classLoader2.getParent();
获取用户自定义类的加载器:用户自定义类的加载器是由系统类加载器加载的
Class clazz = User.class;
ClassLoader classLoader = clazz.getClassLoader();
对于Java的核心API使用引导类加载器加载

三、反射的应用

1.创建运行时类的对象

(1)实现方式:通过Class实例调用newInstance()方法即可

Class clazz = Person.class;
Person p  = (Person) clazz.newInstance();

(2)成功创建对象需要满足的条件:

a.要求运行时类中必须有一个空参构造器

b.要求提供的空参构造器权限足够

(3)在JDK9中替换后的结构:通过Constructor类调用newInstance(...)

2.获取运行时类的内部结构

(1)获取运行时类的内部结构:所有属性、方法、构造器

Class clazz = Person.class;
获取运行时类本身及其所有父类中声明为public的属性
Field[] fields = clazz.getFields();
获取当前运行时类声明的所有属性
Field[] fields = clazz.getDeclaredFields();
获取权限修饰符:
PUBLIC = 0x00000001;
PRIVATE = 0x00000002;
PROTECTED = 0x00000004;
STATIC = 0x00000008;
FINAL = 0x00000010;
for(Field f:fields){
int modifier =f.getModifiers();
System.out.print(modifier + ":"+ Modifier.toString(modifier)+ "\t");
}
获取数据类型:
for(Field f:fields){
Class type =f.getType();
System.out.print(type.getName());
}
获取变量名:
for(Field f:fields){
String name =f.getName();
System.out.print(name);
}
获取运行时类本身及其所有父类中声明为public的方法
Method[] methods = clazz.getMethods();
获取当前运行时类声明的所有方法
Method[] methods = clazz.getDeclaredMethods();
获取方法声明的注解
Annotation[] annos = m.getAnnotations();
获取方法权限修饰符
System.out.print(Modifier.toString(m.getModifiers())+ "\t");
获取方法返回值类型
System.out.print(m.getReturnType().getName()+"\t");
获取方法名
System.out.print(m.getName());
获取形参列表
Class[] parameterTypes = m.getParmeterTypes();
if(!(parameterTypes == null && parameterTypes.length == 0)){
    for(int i=0;i<parameterTypes.length;i++){
        if(i ==parameterTypes.length-1){
            System.out.print(parameterTypes[i].getName()+ " args_" + i);
            break;
    }
    System.out.print(parameterTypes[i].getName()+ " args_" + i + ",")
    }
}
获取抛出的异常:
Class[] exceptionTypes = m.getExceptionTypes();
if(exceptionTypes.length>0){
    System.out.print("throws ");
    for(int i=0;i<exceptionTypes.length;i++){
        if(i==exceptionTypes.length-1){
            System.out.print(exceptionTypes[i].getName());
            break;
        }
    System.out.print(exceptionTypes[i].getName()+ ",");
    }
}

(2)获取运行时类的内部结构:父类、接口、包、带泛型的父类、父类的泛型 

获取运行时类的父类:
Class clazz = Class.forName(全类名)
Class superClass = clazz.getSuperclass();
获取运行时类的带泛型的父类:
Type superClass = clazz.getGenerticSuperclass();
获取运行时类实现的接口:
Class[] interfaces = clazz.getInterfaces();
获取运行时类所在的包
Package pack = clazz.getPackage();
获取运行时类的父类的泛型:
//获取带泛型的父类(Type是一个接口,Class实现了此接口)
Type superclass = clazz.getSuperclass();
//如果父类是带泛型的,则可以强转为ParameterizedType
ParamterizedType paramType = (ParameterizedType) superclass;
//调用getActualTypeArguments()获取泛型的参数,结果是一个数组,因为可能有多个泛型参数
Type[] arguments =paramType.getActualTypeArguments();
//获取泛型参数的名称
System.out.println(((class)arguments[0]).getName());

(3)调用指定的结构:属性、方法、构造器

Class clazz = Person.class;
Person per = (Person)clazz.newInstance();
Field ageField = clazz.getField("age");
int age = ageField.get(per);
ageField.set(per,2);
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(per,"Tom");
Method showNameMethod = clazz.getDeclaredMethod("showName",String.class,int.class);
showNameMethod.setAccessible(true);
Obejct returnValue = showNameMethod.invoke(per,"CHN",10);
Constructor con = clazz.getDeclaredConstructor(String.class,int.class);
con.setAccessible(true);
Person per = (Person)con.newInstance("Tome",123);
1.获取运行时类指定名称的属性
a.通过Class实例调用getDeclaredField(String fieldName)获取运行时类指定名的属性
b.setAccessible(true):确保此属性是可以访问的
c.通过Filed类的实例调用get(Object obj)(获取的操作);或set(Object obj,Object valve)(设置的操作)进行操作。
2.调用指定的方法
a.通过Class的实例调用getDeclaredMethod(String methodName,Class...args),获取指定的方法
b.setAccessible(true):确保此方法是可以访问的
c.通过Method类的实例调用invoke(Object obj,Object ... objs)即为对Method对应的方法的调用
invoke()的返回值即为Method对应方法的返回值;
如果Method对应的返回值为void,则invoke()的返回值为null;
3.调用指定的构造器
a.通过Class的实例调用getDeclaredConstructor(Class ... args)获取指定参数类型的构造器;
b.setAccessible(true):确保此构造器是可以访问的;
c.通过Constructor实例调用newInstance(Object... objs)返回一个运行时类的实例;

(4)调用指定的结构:获取注解信息

Class clazz = Customer.class;
Table annotation = (Table)clazz.getDeclaredAnnotation(Table.class);
String value = annotation.value();
Field nameField = clazz.getDeclaredField("name");
Column nameColumn = nameField.getDeclaredAnnotation(Column.class);
nameColumn.getColumnName();
nameColumn.getColumnType();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值