什么是反射

(1)Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。

(2)Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

  1. Apple apple = new Apple(); //直接初始化,「正射」
  2. apple.setPrice(4);

上面这样子进行类对象的初始化,我们可以理解为「正」。
而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。

这时候,我们使用 JDK 提供的反射 API 进行反射调用:

  1. Class clz = Class.forName("com.chenshuyi.reflect.Apple");
  2. Method method = clz.getMethod("setPrice", int.class);
  3. Constructor constructor = clz.getConstructor();
  4. Object object = constructor.newInstance();
  5. method.invoke(object, 4);

上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。

反射的基本使用

1.获取类

主要有三种方法

(1)任意对象的getClass()方法

通过任意对象的getClass方法,就能得到他的类是什么,得到Class对象

  1. //第一种方式获取Class对象
  2. Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
  3. Class stuClass = stu1.getClass();//获取Class对象
  4. System.out.println(stuClass.getName());

(2)任何数据类型(包括基本的数据类型)都有一个“静态”的class属性

  1. //第二种方式获取Class对象
  2. Class stuClass2 = Student.class;
  3. System.out.println(stuClass2);

(3)通过Class类的静态方法:forName(String className)(最常用)

  1. //第三种方式获取Class对象
  2. try {
  3. Class stuClass3 = Class.forName("com.test1.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
  4. System.out.println(stuClass3);
  5. } catch (ClassNotFoundException e) {
  6. e.printStackTrace();
  7. }

三种方式中,常用第三种,第一种对象都有了还要反射干什么,第二种需要导入类包,依赖太强,不导包就抛编译错误。一般都使用第三种,一个字符串可以传入也可以写在配置文件中等多种方法。

2.判断是否为某个类的实例

  1. try {
  2. Student s = new Student();
  3. Class stuClass3 = Class.forName("com.test1.Student");
  4. //判断对象s是否是stuClass3类的实例
  5. boolean b = stuClass3.isInstance(s);
  6. System.out.println(b);//true
  7. } catch (ClassNotFoundException e) {
  8. e.printStackTrace();
  9. }

3.创建实例

通过反射来生成对象有两种方式

(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例

  1. Class stuClass3 = Class.forName("com.test1.Student");
  2. //通过类反射出对象,这个方法从jdk9之后就被抛弃了
  3. Object str = stuClass3.newInstance();

(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建对象

这种方法可以用指定的构造器构造类的实例

  1. Class stuClass3 = Class.forName("com.test1.Student");
  2. //通过构造器反射对象,getConstructor的参数为构造器要传入的数据类型的类
  3. Constructor constructor = stuClass3.getConstructor(String.class,int.class);
  4. Object obj = constructor.newInstance("李明",32);//传入构造器不同的参数

4.获取构造器

  1. //获取全部公共的构造器
  2. Constructor[] constructors = stuClass3.getConstructors();
  3. //获取所有构造器,不管访问权限是什么
  4. Constructor[] constructors2 = stuClass3.getDeclaredConstructors();
  5. //获取单个公共的构造器
  6. Constructor constructor = stuClass3.getConstructor(String.class,int.class);
  7. //获取单个构造器,不管访问权限
  8. Constructor constructor2 = stuClass3.getDeclaredConstructor();
  9. //调用构造器的方法反射对象
  10. Object obj = constructor.newInstance("李明",32);

5.设置访问权限

不管是属性,方法还是构造器对象。哪怕是私有的,通过反射,可以忽略访问修饰符,直接访问

  1. constructor.setAccessible(true);//暴力访问(忽略掉访问修饰符)

6.获取属性并设置属性

  1. Class stuClass3 = Class.forName("com.test1.Student");
  2. //获取所有公共字段
  3. Field[] fieldArray = stuClass3.getFields();
  4. //获取所有字段,不限于访问修饰符
  5. Field[] fieldArray2 = stuClass3.getDeclaredFields();
  6. //获取age字段,公共的
  7. Field f = stuClass3.getField("age");
  8. //获取score字段,不限于访问符
  9. Field f2 = stuClass3.getDeclaredField("score");
  10. f2.setAccessible(true);//score字段是私有的,设置访问
  11. //设置属性
  12. //通过类的构造器反射一个对象
  13. Object obj = stuClass3.getConstructor().newInstance();
  14. //设置属性,传入这个属性的对象,和值。反射思想反着来
  15. f.set(obj,"23");

7.获取方法并使用

  1. Class stuClass3 = Class.forName("com.test1.Student");
  2. //全部,公共
  3. Method[] m =stuClass3.getMethods();
  4. //全部,所有访问修饰,不包括继承的
  5. Method[] m2 =stuClass3.getDeclaredMethods();
  6. //单个,公共,传入参数为方法名,可变形参为方法参数的类型类
  7. Method m3 =stuClass3.getMethod("run");
  8. //单个,所有修饰符,show方法是私有的
  9. Method m4 = stuClass3.getDeclaredMethod("show",String.class,int.class);
  10. m4.setAccessible(true);
  11. //使用方法
  12. //通过类的构造器反射一个对象
  13. Object obj = stuClass3.getConstructor().newInstance();
  14. //调用方法,传入这个方法的对象,和参数
  15. Object i = m4.invoke(obj,"传入文字参数",123);//方法返回值为i
  16. //如果方法是静态的,第一个对象参数传入null
  17. m3.invoke(null);

8.反射创建数组

  1. //反射一个String[]数组,长度为5
  2. Object objarr = Array.newInstance(String.class,5);
  3. Array.set(objarr,0,"数组的第一个值");
  4. Array.set(objarr,1,"数组的第二个值");
  5. Array.set(objarr,2,"数组的第三个值");
  6. //获取某一项的内容
  7. System.out.println(Array.get(objarr,1));

9.通过反射越过泛型检查

泛型用在编译期,编译过后泛型擦除(消失掉),所以是可以通过反射越过泛型检查的

  1. //创建一个数组列表,这时候它限定的是String类型
  2. ArrayList<String> strList = new ArrayList<>();
  3. strList.add("aaa");
  4. strList.add("bbb");
  5. Class arrlist = strList.getClass();
  6. Method add = arrlist.getMethod("add",Object.class);//获取add方法,添加的类型为object
  7. //向这个strList列表对象中,添加各种类型的数据
  8. add.invoke(strList,100);
  9. add.invoke(strList,true);
  10. for(Object obj:strList){
  11. System.out.println(obj);
  12. }