Java 基础进阶篇(十七):反射概述及获取对象的方式
一、反射概述
反射是指对于任何一个Class类,在 “运行的时候”,不用创建对象,就可以直接得到这个类全部成分。
- 在运行时,可以直接得到这个类的构造器对象:Constructor
- 在运行时,可以直接得到这个类的成员变量对象:Field
- 在运行时,可以直接得到这个类的成员方法对象:Method
这种运行时动态获取类信息以及动态调用类中成分的能力称为 Java 语言的反射机制。
反射的作用:反射是在运行时获取类的字节码文件对象,然后可以解析类中的全部成分。
反射的关键:反射的第一步都是先得到编译后的 Class 类对象,然后就可以得到 Class 的全部成分。
注意:“类对象”和“类的对象”之间的区别,“类的对象”是 new 出来对象,而“类对象”是其本身,即 class 文件,是“类”类型。
二、反射获取类对象
反射的第一步:获取 Class 类的对象。
三种方式:
Class c1 = Class.forName(“全类名");
Class c2 = 类名.class;
Class c3 = 对象.getClass();
举例:
public class Test {
public static void main(String[] args) throws Exception {
// 1、Class类中的一个静态方法:forName(全限名:包名 + 类名)
Class c = Class.forName("com.itheima.d2_reflect_class.Student");
System.out.println(c); // Student.class
// 2、类名.class
Class c1 = Student.class;
System.out.println(c1);
// 3、对象.getClass() 获取对象对应类的Class对象。
Student s = new Student();
Class c2 = s.getClass();
System.out.println(c2);
}
}
三、反射获取构造器对象
步骤:
获取构造器的方法:
Constructor类中用于创建对象的方法:
注:反射可以破坏封装性,私有的也可以执行了。
举例:
public class Student {
private String name;
private int age;
// 构造器私有化
private Student(){
System.out.println("无参数构造器执行!");
}
public Student(String name, int age) {
System.out.println("有参数构造器执行!");
this.name = name;
this.age = age;
}
...
}
@Test
public void getDeclaredConstructor() throws NoSuchMethodException {
// 1. 第一步:获取类对象
Class c = Student.class;
// 2. 定位单个构造器对象 (按照参数定位到无参构造器)
Constructor con = c.getDeclaredConstructor();
System.out.println(con.getName() + " ==> " + con.getParameterCount()); // Student.class ==> 0
// 3. 定位单个构造器对象 (按照参数定位到有参构造器)
Constructor con1 = c.getDeclaredConstructor(String.class, int.class);
System.out.println(con1.getName() + " ==> " + con1.getParameterCount()); // Student.class ==> 2
}
@Test
public void getDeclaredConstructors(){
// 1. 第一步:获取类对象
Class c = Student.class;
// 2. 提取类中的全部构造方法
Constructor[] constructors = c.getDeclaredConstructors();
// 3. 遍历构造器
// 拿到所有的构造器
for (Constructor con : constructors) {
System.out.println(con.getName() + " ==> " + con.getParameterCount());
}
}
暴力反射举例:
@Test
public void getDeclaredConstructor() throws Exception {
// 1. 第一步:获取类对象
Class c = Student.class;
// 2. 定位单个构造器对象 (按照参数定位到无参构造器)
Constructor con = c.getDeclaredConstructor();
// 遇到私有的构造器对象,可以暴力反射
con.setAccessible(true);
Student student = (Student) con.newInstance();
System.out.println(student); // Student{name='null', age=0}
}
四、反射获取成员变量对象
步骤:
反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
Class类中用于获取成员变量的方法:
Field类中用于取值、赋值的方法:
注:某成员变量是非public的,需要打开权限(暴力反射),然后再取值、赋值。
举例:
public class Student {
private String name;
private int age;
public static String schoolName;
public static final String COUNTTRY = "中国";
....
}
@Test
public void getDeclaredField() throws Exception {
// 1. 第一步:获取类对象
Class c = Student.class;
// 2. 第二步:根据名称定位某个成员变量
Field f = c.getDeclaredField("age");
System.out.println(f.getName() +" ===> " + f.getType()); // age ===> int
}
@Test
public void getDeclaredFields(){
// 1. 第一步:获取类对象
Class c = Student.class;
// 2. 第二步:根据名称定位某个成员变量
Field[] fields = c.getDeclaredFields();
// 3. 第三步:遍历
for (Field field : fields) {
System.out.println(field.getName() + " ==> " + field.getType());
// name ==> class java.lang.String ...
}
}
暴力反射举例:
@Test
public void setField() throws Exception {
// 1. 第一步:反射第一步,获取类对象
Class c = Student.class;
// 2. 第二步:提取某个成员变量
Field ageF = c.getDeclaredField("age");
ageF.setAccessible(true); // 暴力打开权限
// 3. 第三步:赋值
Student s = new Student();
ageF.set(s, 18); // 相当于 s.setAge(18);
System.out.println(s);
// 4. 第四步:取值
int age = (int) ageF.get(s);
System.out.println(age);
}
五、反射获取方法对象
步骤:
获取成员方法的方法:
Method类中用于触发执行的方法:
注:某成员方法是非 public 的,需要打开权限(暴力反射),然后再取值、赋值。
举例:
public class Dog {
public void run(){
System.out.println("狗跑的贼快~~");
}
private void eat(){
System.out.println("狗吃骨头");
}
private String eat(String name){
System.out.println("狗吃" + name);
return "吃的很开心!";
}
public static void sleep(){
System.out.println("狗在睡觉!");
}
}
@Test
public void getDeclaredMethods(){
// 1. 第一步:获取类对象
Class c = Dog.class;
// 2. 第二步:提取全部方法;包括私有的
Method[] methods = c.getDeclaredMethods();
// 3. 第三步:遍历全部方法
for (Method method : methods) {
System.out.println(method.getName() +" 返回值类型:"
+ method.getReturnType() + " 参数个数:" + method.getParameterCount());
}
// getName 返回值类型:String 参数个数:0 ...
}
暴力反射举例:
@Test
public void getDeclaredMethod() throws Exception {
// 1. 第一步:获取类对象
Class c = Dog.class;
// 2. 第二步:提取单个方法对象
Method eat = c.getDeclaredMethod("eat");
Method eat2 = c.getDeclaredMethod("eat", String.class);
// 暴力打开权限了
eat.setAccessible(true);
eat2.setAccessible(true);
// 3. 第三步: 触发方法的执行
Dog d = new Dog();
// 注意:方法如果是没有返回值,那么返回的是null。
Object result = eat.invoke(d); // 狗吃骨头
System.out.println(result); // null
Object result2 = eat2.invoke(d, "骨头"); // 狗吃骨头
System.out.println(result2); // 吃的很开心!
}
六、 反射的作用
6.1 绕过编译阶段为集合添加数据
绕过泛型约束:泛型只是在编译阶段可以约束集合只能操作某种数据类型,编译成Class文件后,进入运行阶段的时候,泛型会自动擦除。
反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素。
举例:
public class Test {
public static void main(String[] args) throws Exception {
// 需求:反射实现泛型擦除后,加入其他类型的元素
ArrayList<String> lists1 = new ArrayList<>();
ArrayList<Integer> lists2 = new ArrayList<>();
System.out.println(lists1.getClass() == lists2.getClass()); // true ArrayList.class
ArrayList<Integer> lists3 = new ArrayList<>();
lists3.add(23);
lists3.add(22);
// lists3.add("黑马"); // 报错
Class c = lists3.getClass(); // ArrayList.class ===> public boolean add(E e)
// 定位类对象 c 中的 add 方法
Method add = c.getDeclaredMethod("add", Object.class); // 第二个参数代表此时 add方法中参数是任意类型
boolean rs = (boolean) add.invoke(lists3, "黑马");
System.out.println(rs); // true
System.out.println(lists3); // [23, 22, 黑马]
}
}
举例2:变量被赋值后,泛型也会被擦除
public class Test2 {
public static void main(String[] args) {
// 需求:反射实现泛型擦除后,加入其他类型的元素
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(23);
list1.add(22);
// list.add("黑马"); // 报错
ArrayList list2 = list1;
list2.add("白马");
list2.add(false);
System.out.println(list2); // [23, 22, 白马, false]
}
}
6.2 通用框架的底层原理
需求:给定任意一个对象,在不清楚对象字段的情况可以,可以把对象的字段名称和对应值存储到文件中去。
public class MyBatisUtil {
/**
* 保存任意类型的对象
*/
public static void save(Object obj){
try (
PrintStream ps = new PrintStream(new FileOutputStream("day11-oop/src/data.txt", true));
){
// 提取到这个对象的全部成员变量,只有反射可以解决
Class c = obj.getClass(); // c.getSimpleName() 获取当前类名 c.getName() 获取全限名:包名+类名
ps.println("================" + c.getSimpleName() + "================");
// 提取到它全部成员变量
Field[] fields = c.getDeclaredFields();
// 获取成员变量的信息
for (Field field : fields) {
field.setAccessible(true);
String name = field.getName();
String value = field.get(obj) + "";
ps.println(name + " ==> " + value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
Student s = new Student();
s.setName("猪八戒");
s.setClassName("西天跑路1班");
s.setAge(1000);
s.setHobby("吃,睡");
s.setSex('男');
MyBatisUtil.save(s);
Teacher t = new Teacher();
t.setName("波仔");
t.setSex('男');
t.setSalary(6000);
MyBatisUtil.save(t);
}
}
测试结果: