1. 反射的概念
反射的概念是由 Smith 在 1982 年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力, 并能根据自身行为的状态和结果, 调整或修改应用所描述行为的状态和相关的语义。Java 中,反射是一种强大的工具。它使您能够创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码链接。反射允许我们在编写与执行时,使我们的程序代码能够接入装载到 JVM 中的类的内部信息,而不是源代码中选定的类协作的代码。这使反射成为构建灵活的应用的主要工具。 但需注意的是:如果使用不当,反射的成本很高。
2.Class反射机制
指的是可以于运行时加载,探知和使用编译期间完全未知的类.
程序在运行状态中, 可以动态加载一个只有名称的类, 对于任意一个已经加载的类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能调用他的任意一个方法和属性;
加载完类之后, 在堆内存中会产生一个Class类型的对象(一个类只有一个Class对象), 这个对象包含了完整的类的结构信息,而且这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以被称之为:反射。
每个类被加载进入内存之后,系统就会为该类生成一个对应的java.lang.Class对象,通过该Class对象就可以访问到JVM中的这个类.
3.Class对象的获取方式
2.1对象的getClass()方法;
2.2类的.class(最安全/性能最好)属性;
2.3运用Class.forName(String className)动态加载类,className需要是类的全限定名(最常用).
4.反射中常用的类
java.lang.Reflect;
java.lang.reflect.Field;
java.lang.reflect.Modifier;
java.lang.reflect.Constructor;
java.lang.reflect.Method;
5.从Class中获取信息
Class
类提供了大量的实例方法来获取该Class对象所对应的详细信息,Class类大致包含如下方法,其中每个方法都包含多个重载版本,这里只是做简单的介绍,详细请参考JDK文档
获取类内信息
| 获取内容 | 方法签名 |
|---|---|
| 构造器 | Constructor<T> getConstructor(Class<?>... parameterTypes) |
| 包含的方法 | Method getMethod(String name, Class<?>... parameterTypes) |
| 包含的属性 | Field getField(String name) |
包含的Annotation | <A extends Annotation> A getAnnotation(Class<A> annotationClass) |
| 内部类 | Class<?>[] getDeclaredClasses() |
| 外部类 | Class<?> getDeclaringClass() |
| 所实现的接口 | Class<?>[] getInterfaces() |
| 修饰符 | int getModifiers() |
| 所在包 | Package getPackage() |
| 类名 | String getName() |
| 简称 | String getSimpleName() |
一些判断类本身信息的方法
| 判断内容 | 方法签名 |
|---|---|
| 注解类型? | boolean isAnnotation() |
使用了该Annotation修饰? | boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) |
| 匿名类? | boolean isAnonymousClass() |
| 数组? | boolean isArray() |
| 枚举? | boolean isEnum() |
| 原始类型? | boolean isPrimitive() |
| 接口? | boolean isInterface() |
obj是否是该 Class的实例 | boolean isInstance(Object obj) |
属性:
getField() | 得到类中指定的public修饰的属性 |
getFields() | 得到类中的所有public修饰的属性 |
getDeclaredField() | 得到类中的指定属性 |
getDeclaredField() | 得到类中的所有属性 |
构造方法:
getConstructor() | 得到类中指定的public修饰的构造方法 |
getConstructors() | 得到类中所有public修饰的构造方法 |
getDeclaredConstructor() | 得到类中指定的构造方法 |
getDeclaredConstructors() | 得到类中所有的构造方法 |
方法:
getMethod() | 得到类中的指定的public修饰的方法(包含继承父类的public方法) |
getMethods() | 得到类中的所有的public修饰的方法(包含继承父类的public方法) |
getDeclaredMethod() | 得到当前类中指定的方法 |
getDeclaredMethods() | 得到当前类中的所有方法 |
父类:
getSuperclass() | 得到当前类继承的父类 |
父接口:
getInterfaces() | 得到当前类的所有父接口 |
6.反射的基本使用
示例:反编译的简单示例
package com.kawa.reflect;
import java.lang.reflect.*;
public class AntiCompilation {
public static void main(String[] args) throws Exception {
// 加载类
Class c = Class.forName("com.kawa.reflect.User");
Field[] fields = c.getDeclaredFields(); // 获取类中所有属性
// 创建StringBuffer对象
StringBuffer sb = new StringBuffer();
//拼接类名
sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() + " {\n");
sb.append("\n\t// Fields\n");
//1.拼接属性
for (Field field : fields) {
String mod = Modifier.toString(field.getModifiers());
if ((mod != null) && (!"".equals(mod))) {
sb.append("\t" + Modifier.toString(field.getModifiers()) + " "); //得到属性访问修饰符
sb.append( field.getType().getSimpleName() + " " ); //得到属性数据类型
sb.append( field.getName() + ";\n"); //得到属性名
} else {
sb.append("\t" + field.getType().getSimpleName() + " "
+ field.getName() + ";\n");
}
}
sb.append("\n\t// Constructors\n");
//2.拼接构造方法
Constructor[] ctors = c.getDeclaredConstructors();
for (Constructor ctor : ctors) {
sb.append("\t"+Modifier.toString(ctor.getModifiers()) + " "); //得到构造方法访问修饰符
sb.append(c.getSimpleName() + "("); // 得到构造方法名
Class[] params = ctor.getParameterTypes(); // 得到构造方法参数类型列表
for (int i = 0; i < params.length; i++) {
if (i == params.length - 1) {
sb.append(params[i].getSimpleName() + " ");
} else {
sb.append(params[i].getSimpleName() + " ,");
}
}
sb.append("){}\n");
}
sb.append("\n\t// Methods\n");
//3.拼接所有方法
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
sb.append("\t"+Modifier.toString(method.getModifiers()) + " "); // 得到方法访问修饰符
Class returnType = method.getReturnType();
sb.append(returnType.getSimpleName() + " "); // 得到方法返回值类型
sb.append(method.getName() + "("); // 得到方法名
Class[] params = method.getParameterTypes(); // 得到方法参数类型列表
for (int i = 0; i < params.length; i++) {
if (i == params.length - 1) {
sb.append(params[i].getSimpleName()+" ");
} else {
sb.append(params[i].getSimpleName()+" ,");
}
}
sb.append("){}\n");
}
//come over
sb.append("}");
// 打印类
System.out.println(sb);
}
}
User类示例:
class User {
// Field
public String name;
private String id;
public int age;
protected String addr;
boolean sex;
// Constructor
public User(){}
public User(String id,String name){
this.id = id;
this.name = name;
}
//Method
public String toString(){
return "User["+id+","+name+"]";
}
// 登录
public boolean login(String name, String pwd) {
if ("admin".equals(name) && "123456".equals(pwd)) {
System.out.println("登录成功!");
return true;
}else{
System.out.println("登录失败!");
return false;
}
}
// 退出
public void logout() {
System.out.println("系统已安全退出!");
}
//私有方法 显示
private void show(){
System.out.println("这是私有显示方法!");
}
protected void show2(){
}
}
7.通过反射创建对象
7.1使用Class
对象的newInstance()
方法来创建该Class
对象对应类的实例(这种方式要求该Class对象的对应类有默认构造器(要求类中要有无参构造方法)).
eg:Object user= c1.newInstance();
7.2先使用Class
对象获取指定的Constructor
对象, 再调用Constructor对象的newInstance()
方法来创建该Class对象对应类的实例(通过这种方式可以选择指定的构造器来创建实例).
eg:
Class c = Class.forName("com.kawa.reflect.User");
//得到指定构造方法
Constructor ctor = c.getDeclaredConstructor(String.class,String.class);
//通过构造方法创建对象
Object obj = ctor.newInstance("1001","张三");
8.通过反射给对象属性赋值
//8.1私有属性
/ /创建对象
Object o = c.newInstance();
//id 是private修饰的 当属性是私有的时,此时我们可以进行打破封装
//获取指定属性 可以进行赋值和读取
Field idF = c.getDeclaredField("id");
//打破封装
idF.setAccessible(true); //使用反射机制可以打破封装性,导致了java对象的属性不安全。
//赋值
idF.set(o, "101");
//8.2公共属性
Field ageF = c.getField("age"); //得到公共属性
ageF.set(o, 18);
9.通过反射调用方法
//9.1:调用公共方法
//获取某个特定的方法 通过:方法名+形参列表
Method login = c.getDeclaredMethod("login", String.class,String.class);
//通过反射机制执行login方法 调用o对象的login方法,传递"admin""123456"参数,方法的执行结果是flag
Object flag = login.invoke(o, "admin","123456");
//9.2:调用私有方法
//获取特定私有方法
Method show = c.getDeclaredMethod("show");
//打破封装
show.setAccessible(true);
//通过反射执行方法
show.invoke(o);
10.反射的小结
10.1: 安全性和反射
在处理反射时安全性是一个较复杂的问题。反射经常由框架型代码使用,由于这一点,我们可能希望框架能够 全面介入代码,无需考虑常规的介入限制。但是,在其它情况下,不受控制的介入会带来严重的安全性风险,例如当代码在不值得信任的代码共享的环境中运行时。
10.2:反射的两个缺点
反射是一种强大的工具,但也存在一些不足。
1.性能问题。使用反射基本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们的要求。 用于字段和方法接入时反射要慢于直接代码。 性能问题的程度取决于程序中是如何使用反射的。 如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。
2. 使用反射会模糊程序内部实际要发生的事情。 程序人员希望在源代码中看到程序的逻辑, 反射等绕过了源代码的技术会带来维护问题。 反射代码比相应的直接代码更复杂。 解决这些问题的最佳方案是保守地使用反射 ——仅在它可以真正增加灵活性的地方——记录其在目标类中的使用。





