1、反射
Java 反射机制是在 运行时 动态的获取信息和调用对象的方法,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性
程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的
1.1 反射的作用
-
可以实现简单的反编译,获取类中的属性和方法等基本信息
-
获取类的属性、方法等,甚至可以调用 private 方法
-
在使用 IDE 时,当输入一个对象或类并想调用它的属性或方法时,输入点号,编译器就会自动列出它的属性或方法,这里就会用到反射
-
最重要的用途就是开发各种通用框架。很多框架都是配置化的,比如通过 XML 文件配置 Bean,为了保证框架的通用性,可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态的加载需要加载的对象
1.2 反射的优点
- 提高程序的灵活性和扩展性,降低耦合性
- 允许程序创建和控制任何类的对象,无需提前硬编码目标类
1.3 反射的缺点
-
影响性能:使用反射时,代码量更多,并且反射包括了一些动态类型,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多,应该避免在经常被执行的代码或对性能要求很高的程序中使用反射
-
安全限制:使用反射技术要求程序必须在一个没有安全限制的环境中运行
-
破坏封装性:由于反射允许代码执行一些在正常情况下不被允许的操作,如访问私有的属性和方法,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化
1.4 Class 对象
在类加载器将 .class
文件读取到内存中的时候,JVM 会创建这个 .class
文件的对象,并且只创建一个存放到 JVM 的方法区内存中,即 在运行期间,一个类,只有一个 Class 对象产生。在 java.lang
包下有个 Class 类,这个类就是 .class
文件的对象类型,任何类在被使用时,都会创建这个类的 Class 对象。反射相关的类一般都在 java.lang.relfect
包里
Class 和 java.lang.reflect
一起对反射提供了支持,java.lang.reflect
类库主要包含了以下三个类
- Field :可以使用 get 和 set 方法读取和修改 Field 对象关联的字段
- Method :可以使用 invoke 方法调用与 Method 对象关联的方法
- Constructor :可以用 Constructor 创建新的对象
1.4.1 获取 Class 对象的三种方式
// 1.调用某个对象的getClass方法,不常用,已经有对象了,需要反射的意义不大
Person person = new Person();
Class one = person.getClass();
// 2.使用Class类的forName静态方法,常用,字符串可以传入也可写在配置文件中等多种方法
Class two = Class.forName("com.test.Person");
// 3.直接获取某一个对象的class,不常用,需要导入类的包,依赖太强,不导包就抛编译错误
Class three = Person.class;
// 因为Person这个类在JVM中只有一个,所以内存地址应该都是相同的,指向堆中唯一的Class对象
System.out.println(one == two); // true
System.out.println(two == three); // true
// 也可以使用isInstance方法来判断是否为某个类的实例
System.out.println(one.isInstance(person)); // true
System.out.println(two.isInstance(person)); // true
System.out.println(three.isInstance(person)); // true
1.4.2 使用反射创建对象
// 1.通过Class对象的newInstance()方法
Test test = (Test) Class.forName("com.test.Test").newInstance();
// 或
Test test = Test.class.newInstance();
// 2.通过Constructor对象的newInstance()方法
Constructor<Test> constructor = Test.class.getConstructor();
Test test = constructor.newInstance();
1.4.3 获取方法
public class Person {
public String name;
public Person() {}
public Person(String name) {this.name = name;}
public void show() {System.out.println(this.name);}
public void say(String name){System.out.println(name);}
}
// 获取类或接口的所有方法,但不包括继承的方法
Method[] methods = person.getDeclaredMethods();
// 输出:访问修饰符 返回类型 包名.类名.方法名
// public java.lang.String com.test.Test.Show()
for (Method method : methods) {
System.out.println(method);
}
// 获取类或接口的所有公共方法,包括继承的方法
Method[] methods = person.getMethods();
// 返回一个特定的方法,第一个参数为方法名称,后面的参数为方法的参数对应Class的对象
Method method = person.getDeclaredMethod("show");
Method method = person.getDeclaredMethod("say", String.class);
// 返回一个特定的方法,第一个参数为方法名称,后面的参数为方法的参数对应Class的对象
// 只能获取共有的方法,否则回会报NoSuchMethodException
Method method = person.getMethod("say", String.class);
// 获取类中所有的构造方法
Constructor[] constructors = person.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
// 获取类中所有的公共的构造方法
Constructor[] constructors = person.getConstructors();
// 返回一个特定的构造方法,参数为方法的参数对应Class的对象
Constructor constructor = person.getDeclaredConstructor(String.class);
// 返回一个特定的公共的构造方法,参数为方法的参数对应Class的对象
Constructor constructor = person.getConstructor(String.class);
// 使用反射调用类中的方法
Method method = person.getDeclaredMethod("say", String.class);
Object obj = person.newInstance();
method.invoke(obj, "特朗普");
1.4.4 获取成员变量
// 获取类或接口的所有的成员变量,但不包括继承的成员变量
Field[] fields = person.getDeclaredFields();
// 输出:访问修饰符 数据类型 包名.类名.变量名
// public java.lang.String first.Person.name
for (Field field : fields) {
System.out.println(field);
}
// 获取类或接口的所有的公共成员变量,包括继承的成员变量
Field[] fields = person.getFields();
// 返回一个特定的成员变量,参数为成员变量名
Field field = person.getDeclaredField("name");
// 返回一个特定的共有的成员变量,参数为成员变量名
Field field = person.getField("name");
// 使用反射获取类中指定的属性并赋值
Field field = person.getDeclaredField("name");
Object obj = person.newInstance();
// 从外部打破封装性
field.setAccessible(true);
field.set(obj, "特朗普");
System.out.println(field.get(obj));
- 可以使用
void setAccessible(true)
方法,之后调用私有方法或成员变量,
1.4.5 获取父类或父接口
// 获取父类
Class sup = person.getSuperclass();
// 获取父接口
Class[] inter = person.getInterfaces();