反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
1. 基本概念
反射把Java类中的各种结构(方法、成员变量、构造器、类名)映射成为一个个Java对象(在运行期)
反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分
Class c = Class.forName("cn.javareflection.Student")
此代码可以将Student类实时、动态地加载到程序中。加载完成后,在堆内存中就产生了一个Class类型的对象,这个对象就包含了完整的类的结构信息。
好处:
-
可以在程序运行过程中操作这些对象
-
可以解耦,提高程序的可扩展性
-
使得java语言具有动态特性
2. Class类
-
java.lang.Class
类十分特殊,用来表示java中的类型(class、interface、enum、annotation、primitive type、void)本身 -
Class类是Reflection的根源。想动态加载运行的类,必须先获得相应的Class对象
3. 获取Class类对象的方式
方式一:对象.getClass()
- Object中方法:
类<?> getClass()
:返回此 Object的运行时类
方式二:类名.class()
方式三:Class.forName("包名.类名")
-
此方式使用最多,推荐
-
Class类中方法:
static 类<?> forName(String className)
:返回与给定字符串名称的类或接口相关联的类对象
注意:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都一样
示例代码
public class ReflectTest {
public static void main(String[] args) throws ClassNotFoundException {
// 三种方式
// 1、 对象.getClass()
Iphone iphone = new Iphone();
Class clz1 = iphone.getClass();
// 2、 类.class()
Class clz2 = Iphone.class;
// 3、 Class.forName("包名.类名")
Class clz3 = Class.forName("cn.sxt_01.Iphone");
// 同一个类只会加载一个Class对象
System.out.println(clz1 == clz2);
System.out.println(clz2 == clz3);
int[] arr01 = new int[10];
int[] arr02 = new int[30];
int[][] arr = new int[10][30];
System.out.println(arr01.getClass().hashCode());
System.out.println(arr02.getClass().hashCode());
System.out.println(arr.getClass().hashCode());
}
}
class Iphone {
}
打印结果
true
true
2129789493
2129789493
668386784
打印结果说明:同一个类(同一维度)只会被加载一次,产生一个Class对象
4. Class类对象的功能
1、获取功能
应用反射的API,获取类的信息(类的名称、属性、方法、构造器)
获取类的名称 | 描述 |
---|---|
Class.getName() | 获取包名+类名 |
Classs.getSimpleName() | 只获取类名 |
获取成员变量 | 描述 |
---|---|
Field[] getFields() | 获取所有public修饰的成员变量,包括继承变量 |
Field getField(String name) | 获取指定名称的 public修饰的成员变量 |
Field[] getDeclaredFields() | 获取所有的成员变量,不考虑修饰符,可以获得私有 |
Field getDeclaredField(String name) | 获取指定的成员变量,不考虑修饰符,可以获得私有 |
获取构造方法 | 描述 |
---|---|
Constructor<?>[] getConstructors() | 获得所有的公共构造方法 |
Constructor<T> getConstructor(类名,参数列表) | 获得指定的公共构造方法 |
获取成员方法 | 描述 |
---|---|
方法[] getMethods() | 获取所有公共的方法,包括继承的方法 |
方法 getMethod(String name, 类<?>... parameterTypes) | 获得指定的公共成员方法 |
示例代码
先自定义公共类User
public class User {
private int id;
private int age;
private String uname;
//必须要有无参的构造方法!
public User() {
}
public User(int id, int age, String uname) {
super();
this.id = id;
this.age = age;
this.uname = uname;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public void setUname() {
this.uname = "张三";
}
}
测试类
/**
* 应用反射的API,获取类的信息(类的名称、属性、方法、构造器)
*/
public class ReflectionDemo {
public static void main(String[] args) {
try {
Class clazz = Class.forName("cn.sxt_01.Reflection.User");
// 1、获取类的名称
System.out.println(clazz.getName()); // 获得包名 + 类名
System.out.println(clazz.getSimpleName()); // 获得类名
// 2、获取属性信息
Field[] fields = clazz.getDeclaredFields(); // 获得所有的field
System.out.println(fields.length);
for (Field temp : fields) {
System.out.println("属性:" + temp);
}
// 3、获取方法信息
Method[] methods = clazz.getDeclaredMethods();
Method method1 = clazz.getMethod("getUname", null);
Method method2 = clazz.getMethod("setUname", String.class);
// 若方法有参,则必须传入参数类型对应的class对象。以便于区分重载的方法。
for (Method method : methods) {
System.out.println("方法:" + method);
}
// 4、获得构造器信息
Constructor[] constructors = clazz.getDeclaredConstructors(); // 获得所有构造器
for (Constructor constructor : constructors) {
System.out.println("构造器:" + constructor);
}
Constructor constructor = clazz.getConstructor(int.class,
int.class, String.class);
System.out.println("指定构造器:" + constructor);
} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
}
}
}
打印结果
cn.sxt_01.Reflection.User
User
3
属性:private int cn.sxt_01.Reflection.User.id
属性:private int cn.sxt_01.Reflection.User.age
属性:private java.lang.String cn.sxt_01.Reflection.User.uname
方法:public int cn.sxt_01.Reflection.User.getId()
方法:public void cn.sxt_01.Reflection.User.setAge(int)
方法:public void cn.sxt_01.Reflection.User.setUname()
方法:public void cn.sxt_01.Reflection.User.setUname(java.lang.String)
方法:public java.lang.String cn.sxt_01.Reflection.User.getUname()
方法:public void cn.sxt_01.Reflection.User.setId(int)
方法:public int cn.sxt_01.Reflection.User.getAge()
构造器:public cn.sxt_01.Reflection.User()
构造器:public cn.sxt_01.Reflection.User(int,int,java.lang.String)
指定构造器:public cn.sxt_01.Reflection.User(int,int,java.lang.String)
2、反射调用
Field:提供有关类或接口的单个字段的信息和动态访问, 可以理解成 成员变量
反射新建实例 | 描述 |
---|---|
Class.newInstance() | 创建由此类对象表示的类的新实例 |
反射调用成员变量 | 描述 |
---|---|
Field.get(Object obj) | 返回指定对象上由此Field表示的字段的值 |
Field.set(Object obj, Object value) | 将指定对象变量上此 Field 对象表示的字段设置为指定的新值 |
Field.setAccessible(true) | 忽略访问权限修饰符的安全检查,暴力反射,私有成员允许被访问 |
反射调用成员方法 | 描述 |
---|---|
Field.setAccessible(true) | 私有方法允许被访问 |
Object invoke(Object obj, Object... args) | 在具有指定参数的方法对象上调用此方法对象表示的底层方法 |
示例代码
public class ReflectionDemo02 {
public static void main(String[] args) {
try {
Class clazz = Class.forName("cn.sxt_01.Reflection.User");
// 1、通过反射API动态调用构造方法,构造对象
User user = (User) clazz.getDeclaredConstructor().newInstance();
// 这里是调用了User的无参构造
System.out.println(user);
// 有参构造
Constructor<User> c = clazz.getDeclaredConstructor(int.class, int.class, String.class);
User user1 = c.newInstance(1001, 18, "张三");
System.out.println(user1.getUname());
// 2、通过反射API调用普通方法
Method method = clazz.getDeclaredMethod("setUname", String.class);
method.invoke(user1, "李四");
System.out.println(user1.getUname());
// 3、通过反射API操作属性
Field f = clazz.getDeclaredField("uname");
f.setAccessible(true); // 暴力反射,使得可以操作私有属性
f.set(user1, "王五");
System.out.println(user1.getUname());
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
e.printStackTrace();
}
}
}
打印结果
cn.sxt_01.Reflection.User@7ef20235
张三
李四
王五
5. 反射机制的性能问题
setAccessible
-
启用和禁用访问安全检查的开关,值为true,则表示反射的对象在使用时应该取消Java语言访问检查。值为false,则指示反射的对象应该实施Java语言访问检查。
-
禁止安全检查,可以提高反射的运行速度
示例代码
@SuppressWarnings("all")
public class Demo {
public static void main(String[] args) throws Exception {
test1();
test2();
test3();
}
public static void test1() {
User user = new User();
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000L; i++) {
user.getUname();
}
long end = System.currentTimeMillis();
System.out.println("普通调用,执行10亿次,耗时:" + (end - start) + "ms");
}
public static void test2() throws Exception {
User user = new User();
Class clazz = user.getClass();
Method method = clazz.getDeclaredMethod("getUname");
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000L; i++) {
method.invoke(user, null);
}
long end = System.currentTimeMillis();
System.out.println("反射动态方法调用,通过安全检查,执行10亿次,耗时:" + (end - start) + "ms");
}
public static void test3() throws Exception {
User user = new User();
Class clazz = user.getClass();
Method method = clazz.getDeclaredMethod("getUname");
method.setAccessible(true); // 跳过安全检查
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000L; i++) {
method.invoke(user, null);
}
long end = System.currentTimeMillis();
System.out.println("反射动态方法调用,不通过安全检查,执行10亿次,耗时:" + (end - start) + "ms");
}
}
打印结果
普通调用,执行10亿次,耗时:920ms
反射动态方法调用,通过安全检查,执行10亿次,耗时:5332ms
反射动态方法调用,不通过安全检查,执行10亿次,耗时:2648ms
6. 反射操作泛型
Java采用泛型擦除机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有的和泛型有关的类型全部擦除。
和反射+泛型有关的接口类型
- java.lang.reflect.Type:java语言中所有类型的公共父接口
- ParameterizedType:表示一种参数化的类型,比如Collection< String >
- GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
- TypeVariable:是各种类型变量的公共父接口
- WildcardType:代表一种通配符类型表达式,比如?、? extends Number、? super Integer。(wildcard是一个单词:就是”通配符“)
此部分内容用到情况较少,用到时可自行百度
7. 反射操作注解
可通过反射API获得相关注解信息
常用的相关方法 | 说明 |
---|---|
Class.getAnnotations() | 获得类的所有有效注解 |
Class.getAnnotation(类<A> annotationClass) | 获得指定注解信息 |
示例代码
public class AnnotationDemo {
public static void main(String[] args) {
try {
Class clazz = Class.forName("cn.sxt.annotation_03.Student");
// 获得类的所有有效注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a);
}
// 获得指定注解
myTable st = (myTable) clazz.getAnnotation(myTable.class);
System.out.println(st.value());
// 获得类的所有属性的注解
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
myField myfield = field.getAnnotation(myField.class);
System.out.println(myfield.columnName()+" - "+myfield.type()+" - "+myfield.length());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}