什么是Java反射机制
JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
Java的反射机制允许编程人员在对类未知的情况下,获取类相关信息的方式变得更加多样灵活,调用类中相应方法,是Java增加其灵活性与动态性的一种机制。----百度百科
反射机制有什么好处?
1.使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法。
2.反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
3.减少硬编码,所以框架中的代码无需写死,因此反射被形象的成为框架的灵魂
缺点
1.性能问题。 Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。
2.安全限制。 使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。(因为在反射面前,可以无视访问权限修饰符)
3.程序健壮性。 反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。
下面是在B站学习的反射的具体的一些用法和功能。其实也就是把视频里的东西自己写了一遍,加深映像(前方高能预警)
首先来看看我新建的包的最终的目录结构:
第一个类是一个辅助类Person,方便演示反射的用法
package reflect.demo;
/**
* 一个辅助类,用于测试反射机制
* @author TinySpot
*/
public class Person {
private String name;
private int age;
public String gender;
public int height;
//修饰符的访问权限从大到小
public String a;
protected String b;
String c;
private String d;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
// TODO Auto-generated constructor stub
}
private Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", gender=" + gender + ", height=" + height + ", d=" + d + "]";
}
public void eat() {
System.out.println("eat...");
}
public void eat(String foodType) {
System.out.println("eat " + foodType);
}
private void saySomething(String something) {
System.out.println("say " + something);
}
}
获取Class实例的三种方法
package reflect.demo;
/**
* <span>反射机制学习第一步</span><br>
* 作用如类名所示,得到Class类的对象
* 得到一个类的对应的class类的对象的方法有3种<br>
* 1.源代码阶段:Class.forName("全类名")<br>
* 2.Class对象阶段:类名.class <br>
* 3.Runtime运行时阶段:实例.getClass() <br>
* @author TinySpot
*/
public class GetClassObject {
public static void main(String args[]) throws Exception {
//方法一,通过Class类的静态方法获取Class对象
Class clazz1 = Class.forName("reflect.demo.Person");
System.out.println(clazz1);
//方法二,通过所想要得到的那个类的静态属性得到Class对象
Class clazz2 = Person.class;
System.out.println(clazz2);
//方法三,通过类实例的getClass()方法获得Class对象
Person p = new Person();
Class clazz3 = p.getClass();
System.out.println(clazz3);
//看看返回的Class对象是否相等
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
//得到两个true,说明三个实例都是同一个实例,且类只加载了一次
}
}
掌握Class对象常用的13个方法
package reflect.demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 反射机制学习第二步<br>
* Class类的功能--->获取功能<br>
* 1.获取成员变量们<br>
* Field[] getFields() 获取一个类或接口的所有public修饰的字段<br>
* Field getField(String name) 获取一个类或接口的指定名字的public修饰的字段<br>
* <br>
* Field[] getDeclaredFields() 获取所有字段,不考虑修饰符<br>
* Field getDeclaredField(String name) 获取类或接口的指定名字的字段 <br>
* 2.获取构造方法们<br>
* Constructor<?>[] getConstructors() 获取所有的公有构造方法<br>
* Constructor<T> getConstructor(Class<?>... parameterTypes) 获取指定参数的公有构造方法 ,不考虑修饰符 || Class<?>...表示变长参数<br>
* <br>
* Constructor<?>[] getDeclaredConstructors() 获取所有的构造方法<br>
* Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 获取指定参数的构造方法,不考虑修饰符<br>
* 3.获取成员方法们<br>
* Method[] getMethods() 获取所有公有的方法,包括从父类,超类那里继承过来的公有方法<br>
* Method getMethod(String name, Class<?>... parameterTypes) 获取指定参数的公有方法<br>
* <br>
* Method[] getDeclaredMethods() 获取所有方法,但不包括继承的方法<br>
* Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取指定参数的方法<br>
* 4.获取类名<br>
* String getName()
* @author TinySpot
*/
public class ClassObjectFunction {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("reflect.demo.Person");//获取person类对应的字节码对象
/*
1.获取成员变量们<br>
* Field[] getFields() 获取一个类或接口的所有public修饰的字段<br>
* Field getField(String name) 获取一个类或接口的指定名字的public修饰的字段<br>
* <br>
* Field[] getDeclaredFields() 获取所有字段<br>
* Field getDeclaredField(String name) 获取类或接口的指定名字的字段 <br>
*/
//Field[] getFields() 获取一个类或接口的所有public修饰的字段<br>
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("---------------------");
//Field getField(String name) 获取一个类或接口的指定名字的public修饰的字段<br>
Field field = clazz.getField("gender");
System.out.println(field);
System.out.println("=====================");
//Field[] getDeclaredFields() 获取所有字段,不考虑修饰符<br>
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field2 : declaredFields) {
System.out.println(field2);
}
System.out.println("---------------------");
//Field getDeclaredField(String name) 获取类或接口的指定名字的字段 <br>
Field field2 = clazz.getDeclaredField("d");
System.out.println(field2);
System.out.println("*********************");
/*
* 2.获取构造方法们<br>
* Constructor<?>[] getConstructors() 获取所有的公有构造方法<br>
* Constructor<T> getConstructor(Class<?>... parameterTypes) 获取指定参数的公有构造方法 || Class<?>...表示变长参数<br>
* <br>
* Constructor<?>[] getDeclaredConstructors() 获取所有的构造方法<br>
* Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 获取指定参数的构造方法<br>
*/
//Constructor<?>[] getConstructors() 获取所有的公有构造方法<br>
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("---------------------");
//Constructor<T> getConstructor(Class<?>... parameterTypes) 获取指定参数的公有构造方法 || Class<?>...表示变长参数<br>
Constructor constructor = clazz.getConstructor(String.class, int.class);//参数为对应构造方法形参的Class对象
System.out.println(constructor);
System.out.println("=====================");
//Constructor<?>[] getDeclaredConstructors() 获取所有的构造方法,不考虑修饰符<br>
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor constructor2 : declaredConstructors) {
System.out.println(constructor2);
}
System.out.println("---------------------");
//Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 获取指定参数的构造方法,不考虑修饰符<br>
Constructor constructor2 = clazz.getDeclaredConstructor(int.class, String.class);
System.out.println(constructor2);
System.out.println("*********************");
/*
* 3.获取成员方法们<br>
* Method[] getMethods() 获取所有公有的方法,包括从父类,超类那里继承过来的公有方法<br>
* Method getMethod(String name, Class<?>... parameterTypes) 获取指定参数和名字的公有方法<br>
* <br>
* Method[] getDeclaredMethods() 获取所有方法,但不包括继承的方法<br>
* Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取指定参数的方法<br>
*/
//Method[] getMethods() 获取所有公有的方法,包括从父类,超类那里继承过来的公有方法<br>
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("---------------------");
//Method getMethod(String name, Class<?>... parameterTypes) 获取指定参数和名字的公有方法<br>
Method method = clazz.getDeclaredMethod("eat", String.class);//方法名||该方法对应的参数对应的Class对象
System.out.println(method);
System.out.println("=====================");
// Method[] getDeclaredMethods() 获取所有方法,但不包括继承的方法<br>
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method2 : declaredMethods) {
System.out.println(method2);
}
System.out.println("---------------------");
//Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取指定参数的方法<br>
Method method2 = clazz.getDeclaredMethod("saySomething", String.class);
System.out.println(method2);
System.out.println("*********************");
/*
* 4.获取类名<br>
* String getName()
*/
System.out.println(clazz.getName());
}
}
通过Class对象的那个几个方法获得的Field对象有什么作用
可以设置和访问成员变量的值,不考虑修饰符
package reflect.demo;
import java.lang.reflect.Field;
/**
* 反射机制学习第3.1步<br>
* 通过Class对象获取的Field对象数组或单个对象有什么作用<br>
* Filed:成员变量<br>
* 操作:<br>
* 1.设置变量的值<br>
* void set(Object obj, Object value) 设置该字段的值<br>
* 2.得到变量的值<br>
* Object get(Object obj) 获取指定对象中该Field对应的值 <br>
* @author TinySpot
*/
public class FieldObjectFunction {
public static void main(String[] args) throws Exception{
Class clazz = Person.class;
Person p = new Person();//创建一个Person对象,方便后续使用
//Object get(Object obj) 获取指定对象中该Field对应的值 <br>
Field field_a = clazz.getDeclaredField("a"); //获得Person类中成员变量d对应的Filed对象
//获取公有成员变量a的值
Object object = field_a.get(p); //参数为想要得到的那个实例对象,即该字段就是该对象里的字段
System.out.println(object);
//获取私有成员变量d的值
Field fiel_d = clazz.getDeclaredField("d");
fiel_d.setAccessible(true); //忽略访问权限符的安全检查,否则会报无法访问异常。设置值的时候也是同理
Object object2 = fiel_d.get(p);
System.out.println(object2);
//设置私有成员变量d的值,暴力反射 void set(Object obj, Object value) 设置该字段的值
//fiel_d.setAccessible(true);//设置之前需要忽略访问权限符的安全检查
fiel_d.set(p, "张三"); //字段所在的对象,要设置的值
System.out.println(p);
}
}
Constructor对象可以创建类的实例
package reflect.demo;
import java.lang.reflect.Constructor;
/**
* 反射机制学习第3.2步<br>
* 通过Class实例获取的Constructor对象们有什么作用呢?<br>
* Constructor:构造方法:
* 作用-->创建对象:T newInstance(Object... initargs) 使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。<br>
*
* 为了简化无参构造方法创建实例的步骤,Class对象提供了一个方法:T newInstance()
* @author TinySpot
*
*/
public class ConstructObjectFunction {
public static void main(String args[]) throws Exception{
Person p = new Person();
Class clazz = p.getClass();
//得到私有构造方法
Constructor constructor = clazz.getDeclaredConstructor(int.class, String.class);
System.out.println(constructor);
//通过私有构造方法来创建实例对象,创建对象前需要忽略访问权限修饰符的安全检查,公有构造方法不需要这一步
constructor.setAccessible(true); //暴力反射
Object person = constructor.newInstance(20, "李四");//可变长参数,里面的参数为具体的构造方法里面需要传递进去的参数
System.out.println(person);
System.out.println("---------------");
//通过无参构造方法实例一个对象
Constructor constructor2 = clazz.getConstructor(); //可変长参数
Object person2 = constructor2.newInstance();
System.out.println(person2);
//用Class对象创建一个实例,用于简化上述操作
Object o = clazz.newInstance();
System.out.println(o);
}
}
Method对象可以执行方法
package reflect.demo;
import java.lang.reflect.Method;
/**
* 反射机制学习第3.3步<br>
* 通过Class对象得到的Method对象们有什么作用呢?<br>
* Method:可以用来执行对应的方法:Object invoke(Object obj, Object... args) 在具有指定参数的 方法对象上调用此 方法<br>
*
* 获取方法的名称:String getName()
* @author TinySpot
*
*/
public class MethodObjectFunction {
public static void main(String[] args) throws Exception{
Class clazz = Person.class;
Person p = new Person();
//通过Class对象获取私有方法saySomething
Method method = clazz.getDeclaredMethod("saySomething", String.class);
System.out.println(method);
System.out.println(method.getName());
//执行该方法,因为该方法为私有方法,所以执行之前需要忽略访问权限符的安全检查
method.setAccessible(true);//暴力反射
method.invoke(p, "hello"); //实际的那个类,该方法的参数(可变长)
}
}
下面是一个利用反射机制写的小案例
案例需求:写一个“框架”,在不改变任何代码的前提下,可以创建任意类的对象,并执行其中的任意方法
-
实现依赖: *1.配置文件 *2.反射机制
-
步骤: *1.将需要创建的对象的全类名和需要执行的方法放入配置文件中 *2.在程序中加载读取配置文件 *3.利用反射机制将类加载 *4.创建类的实例 *5.执行方法
配置文件pro.properties如下:
className=reflect.demo.Student
methodName=sleep
另外还需一个辅助类Student
package reflect.demo;
/**
* 辅助类,方便反射机制的测试
* @author TinySpot
*/
public class Student {
private String name;
private int sNumber;
public void sleep() {
System.out.println("sleeping...");
}
}
“框架”类如下:
package reflect.demo;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Properties;
/**
* 反射机制学习第4步<br>
* 案例需求:写一个“框架”,在不改变任何代码的前提下,可以创建任意类的对象,并执行其中的任意方法
* @author TinySpot
*/
public class ReflectCase {
public static void main(String[] args) throws Exception{
//1.加载配置文件
//1.1创建Properties对象,该对象可以将一个以.properties为后缀名的文件转换为一个集合
Properties properties = new Properties();
//1.2加载配置文件,转换为一个集合
//1.2.1获取class目录下的配置文件
/*
* Class.getResourceAsStream(String name)方法
* 1.如果name不是以/开头,那么会在该类所在包查找相应资源
* 2.若name前面有/ ,那么会从会从classpath的根路径下查找。
*/
InputStream is = ReflectCase.class.getResourceAsStream("pro.properties");
properties.load(is);
/*
* 也可以是使用ClassLoader.getResourceAsStream(String name)方法获取相应文件的输入流
* 该方法也是会从classpath的根路径下查找。
*/
// ClassLoader classLoader = ReflectCase.class.getClassLoader();
// InputStream is1 = classLoader.getResourceAsStream("pro.properties");
// properties.load(is1);
//2.获取配置文件中的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//3.加载配置文件中定义的类
Class clazz = Class.forName(className);
//4.创建对象
Object instance = clazz.newInstance();
//5.获取方法对象
Method method = clazz.getDeclaredMethod(methodName);
//6.执行方法
method.invoke(instance);
}
}
运行上述程序结果如下:
sleeping...
将配置文件修改如下:
className=reflect.demo.Person
methodName=eat
然后再运行,得到结果如下:
eat...
经过后面这两步的操作应该可以更加深刻的领会到反射机制的妙处。它可以降低耦合度,防止硬编码,不愧是框架的灵魂。