反射就是把java类中的各种成分映射成为相应的java类,这句话是在某视频中看到的,感觉颇有道理。
说的再明白一点,就是通过class文件去使用该文件中的成员变量,构造方法和成员方法。
反射技术可以动态的获取类以及类中的成员,并且可以调用该类的成员,提高了程序的扩展性。但是反射技术有一个缺点就是降低了程序执行的效率。
一、使用反射技术去解决一些问题,首先要做的即使如何获取该类的字节码文件对象。有以下三种方式:
1、通过对象获取,调用对象的getClass()方法,返回字节码文件对象。
Student student = new Student();
Class class1 = student.getClass();
2、通过类名获取,类名.class,返回字节码文件对象。
Class class2 = Student.class;
3、通过类名的全路径方式获取,Class.forName("类的全路径")。
Class class3 = Class.forName("reflect_test.Student");
必须要知道,在内存中,我们所要的类的字节码文件只有一份,所以不管我们通过什么样的方式获取,也不管我们获取的字节码文件对象的对象名是否一样,他们都是相等的。
public class ReflectTest {
public static void main(String[] args) throws Exception {
//方式一,通过对象获取
Student student = new Student();
Class class1 = student.getClass();
//方式二,通过类获取
Class class2 = Student.class;
//方式三,通过全路径的方式获取
Class class3 = Class.forName("reflect_test.Student");
System.out.println(class1 == class2);
System.out.println(class2 == class3);
}
}
执行结果:
在这必须说明的是,在项目中我们通常使用的是第三种方式,即通过全路径的方式获取字节码文件对象,这样我们就可以通过传递一个字符串的方式将类的全路径传递过来,提高了程序的扩展性和可维护性。
二、有了字节码文件对象,我们就可以获取该类所有的构造方法:
public class ReflectTest {
public static void main(String[] args) throws Exception {
//全路径获取字节码文件对象
Class classX = Class.forName("reflect_test.Student");
//获取该类的所有构造方法
Constructor[] constructors = classX.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
}
}
运行结果:
public reflect_test.Student()
public reflect_test.Student(java.lang.String,int)
当然,我们也可以获取单独的构造:
public class ReflectTest {
public static void main(String[] args) throws Exception {
//全路径获取字节码文件对象
Class classX = Class.forName("reflect_test.Student");
//获取该类的无参构造方法
Constructor constructor = classX.getConstructor();
System.out.println(constructor);
}
}
注意和上边方法的区别。
带参构造也可以单独获取,方法是getConstructor(Class<?>... parameterTypes),返回一个 Constructor
对象,它反映此 Class
对象所表示的类的指定公共构造方法。调用这个方法的前提是必须知道所要获取的构造的参数列表是什么。
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 全路径获取字节码文件对象
Class classX = Class.forName("reflect_test.Student");
// 获取该类的无参构造方法
Constructor constructor = classX.getConstructor(String.class, int.class);
System.out.println(constructor);
}
}
三、获取构造的目的就是利用构造来构造类的对象了。
通过构造获取实例的方法:newInstance(Object... initargs),使用此 Constructor
对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 全路径获取字节码文件对象
Class classX = Class.forName("reflect_test.Student");
// 获取该类的带参构造方法
Constructor constructor = classX.getConstructor(String.class, int.class);
Student student = (Student) constructor.newInstance("张无忌", 25);
System.out.println(student);
}
}
注意:因为newInstance()方法返回值的类型是Object,所以要进行向下转型。
这里我们重写了toString()方法,将这个获取的实例打印出来:
Student [name=张无忌, age=25]
有了类的实例,我们就可以使用该实例获取该类的成员变量以及使用该类的成员方法了。
四、我们也可以通过反射获取该类的成员变量字段。
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 全路径获取字节码文件对象
Class classX = Class.forName("reflect_test.Student");
// 获取该类的带参构造方法
Constructor constructor = classX.getConstructor(String.class, int.class);
Student student = (Student) constructor.newInstance("张无忌", 25);
Field field1 = classX.getField("name");
String name = (String) field1.get(student);
System.out.println(name);
}
}
注意:通过getField()获取的字段对象不是成员变量,而是一个Field类型的对象,该对象中保存了name的实际变量值。
对于该类中私有的Field字段,如果我们想要访问,就必须进行暴力反射setAccessible(true),同时获取字段的方法要使用getDeclaredField()获取所有已声明的字段,包括private。
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 全路径获取字节码文件对象
Class classX = Class.forName("reflect_test.Student");
// 获取该类的带参构造方法
Constructor constructor = classX.getConstructor(String.class, int.class);
Student student = (Student) constructor.newInstance("张无忌", 25);
Field field1 = classX.getField("name");
Field field2 = classX.getDeclaredField("age");
field2.setAccessible(true);
String name = (String) field1.get(student);
int age = (int) field2.get(student);
System.out.println(name+": "+age);
}
}
执行结果:
五、通过反射获取类中的成员方法,举一反三,依次类推。
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 全路径获取字节码文件对象
Class classX = Class.forName("reflect_test.Student");
// 获取该类的带参构造方法
Constructor constructor = classX.getConstructor(String.class, int.class);
// 创建实例对象
Student student = (Student) constructor.newInstance("张无忌", 25);
// 获取成员变量字段
Field field1 = classX.getField("name");
Field field2 = classX.getDeclaredField("age");
// 获取成员方法
Method method1 = classX.getMethod("show", String.class);
Method method2 = classX.getDeclaredMethod("study");
Method method3 = classX.getMethod("love");
// 暴力反射
field2.setAccessible(true);
method2.setAccessible(true);
method1.invoke(student, "倚天屠龙记");
method2.invoke(student);
method3.invoke(null);
// 获取字段值
String name = (String) field1.get(student);
int age = (int) field2.get(student);
System.out.println("我叫" + name + ", 年龄" + age + "岁。");
}
}
代码运行结果:
我是倚天屠龙记中的主角!
我练乾坤大挪移!
我爱赵敏!
我叫张无忌, 年龄25岁。
需要注意的是,字段的反射在获取字段值的时候需要传递字段值的对象,方法同样如此,用来表明是哪个对象的方法或者字段。只有一种情况例外,即方法或者字段值被static修饰。这样就不需要通过对象,可以直接访问。
六、Student类代码
public class Student {
public String name;
private int age;
public Student() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
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 void show(String str) {
System.out.println("我是" + str + "中的主角!");
}
private void study() {
System.out.println("我练乾坤大挪移!");
}
public static void love() {
System.out.println("我爱赵敏!");
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}