什么是反射
Oracle 官方对反射的解释是: Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.
简而言之,通过反射我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。其本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
反射的优缺点
1、优点:在运行时获得类的各种内容进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。 简而言之,反射可以动态地创建和使用对象,通过外部文件配置,在不修改源码的情况下,来控制程序。反射机制是 Java 框架的底层核心,其使用灵活,没有反射机制,底层框架就失去支撑。
2、缺点: (1)反射会消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射; (2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
反射的主要组成部分
Class:任何运行在内存中的所有类都是该Class类的实例对象,每个Class类对象内部都包含了本来的所有信息。
Field:描述一个类的属性,内部包含了该属性的所有信息,例如数据类型,属性名,访问修饰符 Constructor:描述一个类的构造方法,内部包含了构造方法的所有信息,例如参数类型,参数名字,访问修饰符
Method:描述一个类的所有方法(包括抽象方法),内部包含了该方法的所有信息,与Constructor类似,不同之处是Method拥有返回值类型信息,因为构造方法是没有返回值的。
1.getField、getMethod和getCostructor方法可以获得指定名字的域、方法和构造器。
2.getFields、getMethods和getCostructors方法可以获得类提供的public域、方法和构造器数组,其中包括超类的共有成员。
3.getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。
反射常用的Api
实体类User
@UserAnnotation
public class User {
private static String name;
private Integer age;
public String address;
public User() {
}
public User(String name, String address) {
this.name = name;
this.address = address;
}
public User(String name, Integer age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
@UserAnnotation
private String privateTest(Integer i ,Integer j){
return "privateTest:"+(i+j);
}
}
java反射的三种创建方式
第一种、使用 .class的方法
Class<User> userClass1 = User.class;
第二种、使用Class.forName(类的完整路径地址)的方式
Class<?> userClass2 = Class.forName("com.reflex.User");
第三种、使用类对象.getClass()的方法
User user = new User();
Class userClass3 = user.getClass();
验证在运行期间,一个类,只有一个Class对象产生
//反射的三种方法
//1.使用 .class的方法
Class<User> userClass1 = User.class;
//2.使用Class.forName(类的完整路径地址)的方式
Class<?> userClass2 = Class.forName("com.reflex.User");
//3.使用类对象.getClass()的方法
User user = new User();
Class userClass3 = user.getClass();
System.out.println("userClass1 == userClass2 --> "+(userClass1 == userClass2));
System.out.println("userClass1 == userClass3 --> "+(userClass1 == userClass3));
打印结果
userClass1 == userClass2 --> true
userClass1 == userClass3 --> true
反射调用构造函数
//1.默认执行无参构造函数
User instance = (User) userClass2.newInstance();
//2.执行有参构造函数
Constructor<?> constructor = userClass2.getConstructor(String.class, Integer.class, String.class);
User instance1 = (User) constructor.newInstance("张三", 22, "广州");
System.out.println(instance1);
Constructor<?> declaredConstructor = userClass2.getDeclaredConstructor(String.class, String.class);
User instance2 = (User) declaredConstructor.newInstance("李四", "深圳");
System.out.println(instance2);
访问类中公有私有属性
//访问类中公有私有属性
Class<?> userC = Class.forName("com.reflex.User");
User user1 = (User) userC.newInstance();
Field nameField = userC.getDeclaredField("name");
// name字段为私有属性
// 设置允许访问私有属性
nameField.setAccessible(true);
nameField.set(user1,"李四");
System.out.println(user1.getName());
Field ageField = userC.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(user1,11);
System.out.println(user1);
//Field.get()方法的使用:
//1. 如果字段不是静态字段的话,要传入反射类的对象.如果传null是会报java.lang.NullPointerException
//2. 如果字段是静态字段的话,传入任何对象都是可以的,包括null
System.out.println(nameField.get(null));
Field name1 = userC.getField("address");
name1.set(user1,"深圳");
System.out.println(user1.getAddress());
反射拿到实体类中的属性和方法
Class<?> userC = Class.forName("com.reflex.User");
//拿到类中的属性 不包括私有和受保护的成员
Field[] fieldPublicArr = userC.getFields();
for (Field field :
fieldPublicArr) {
System.out.println(field);
}
//拿到类中所有属性 包括私有和受保护的成员
Field[] fields = userC.getDeclaredFields();
for (Field field :
fields) {
System.out.println(field);
}
//拿到实体类中的方法
//Method[] methods = userC.getMethods();
Method[] methods = userC.getDeclaredMethods();
for (Method method :
methods) {
System.out.println(method);
}
调用类中公有和私有的方法
//调用类中公有和私有的方法
Class<?> aClass = Class.forName("com.reflex.User");
User user = (User) aClass.newInstance();
Field name = aClass.getDeclaredField("name");
// 设置允许访问私有属性
name.setAccessible(true);
name.set(user,"zs");
Method getName = aClass.getDeclaredMethod("getName");
System.out.println(getName.invoke(user));
Method privateTest = aClass.getDeclaredMethod("privateTest",Integer.class,Integer.class);
// 设置允许访问私有属性
privateTest.setAccessible(true);
System.out.println(privateTest.invoke(user,9,1));
通过反射越过泛型检查
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
Class listClass = list.getClass();
Method add = listClass.getDeclaredMethod("add",Object.class);
System.out.println(add.invoke(list, 1));
System.out.println(list.toString());
//如果使用lambda遍历java.lang.Integer cannot be cast to java.lang.String
list.forEach(a ->{
System.out.println(a);
});
反射拿到方法上的注解
注解
/**
* 元注解:
* * @Target 指定新注解标注的位置,比如类、字段、方法等,取值有ElementType.Method等
* @Retention 指定新注解的信息保留到什么时候,取值有RetentionPolicy.RUNTIME等
* @Inherited 指定新注解标注在父类上时可被子类继承
* 如果@Retention没有RetentionPolicy.RUNTIME,反射会在jvm中无法拿到自定义注解
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UserAnnotation {
}
//获取当前方法上的注解
Class<?> aClass = Class.forName("com.qbh.ant1.reflex.User");
Method method = aClass.getDeclaredMethod("privateTest",Integer.class,Integer.class);
System.out.println(method.getDeclaredAnnotation(UserAnnotation.class));
//注意:如果没有@Retention(RetentionPolicy.RUNTIME),反射会在jvm中无法拿到UserAnnotation注解
//获取当前类上的注解
UserAnnotation declaredAnnotation = aClass.getDeclaredAnnotation(UserAnnotation.class);
System.out.println(declaredAnnotation);
总结
Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,就不需要提前在编译期知道运行的对象是谁了。