反射

反射是动态性的
反射机制允许,在程序运行期间,使用反射来获取任何类的内部信息,并能操作任何类的属性和方法,框架中常有用到

在java中,加载完之后,会在堆内存中的方法区中会生成一个 Class类型对象(一个类只有一个Class对象)这个对象包含了这个类完整的结构信息

一般情况下
我们都是引入需要的包类名称 -> 然后通过 new 实例化->取得实例化对象

而在反射情况下
实例化对象-> getClass -> 得到完整的包类名称

关于java中 java.lang.Class类的理解

从类的加载过程来看:

程序在经过javac 命令以后会生成一个或多个 class(字节码)文件
接着我们使用java 这个命令对字节码文件进行解释运行,将其加载到内存中,(加载到内存中的过程,就称为类的加载)
加载到内存中的类,我们称之为运行时类,此运行时类就作为 Class 的一个实例

换句话说,Class对象对应着一个运行时类

获取 Class 对象的方式

前三个要求掌握,其中,第三个使用频率更多

  1. //方式一
  2. Class clazz = Person.class;
  3. //Class<Person> clazz = Person.class;
  4. System.out.println(clazz);
  5. //方式二
  6. Person p1 = new Person();
  7. Class clazz1 = p1.getClass();
  8. System.out.println(clazz1);
  9. //方式三
  10. //调用Class 的静态方法 forName ( String classPath)
  11. Class<?> clazz2 = Class.forName("reflection.Person");
  12. System.out.println(clazz2);
  13. //方式四,作了解,不要求掌握
  14. ClassLoader classLoader = ReflectionTest.class.getClassLoader();
  15. Class clazz4 = classLoader.loadClass("reflection.Person");
  16. System.out.println(clazz4);

通过反射创建运行时类的对象

使用反射创建运行时对象,可以使用 Class.forName(String classPath)
这个方法来获取运行时对象 然后通过 newInstance() 方法获取类的对象。

  1. Class<Person> clazz = (Class<Person>) Class.forName("reflection.Person");
  2. Person o = clazz.newInstance();
  3. System.out.println(o);

体现反射的动态性

  1. public void main() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
  2. //体现反射的 动态性
  3. int random = new Random().nextInt(3);//0,1,2
  4. String classPath = "";
  5. switch (random){
  6. case 0:
  7. classPath = "java.lang.String";
  8. break;
  9. case 1:
  10. classPath = "reflection.Person";
  11. break;
  12. case 2:
  13. classPath = "java.lang.Object";
  14. break;
  15. }
  16. Object instance = getInstance(classPath);
  17. System.out.println(instance.getClass());
  18. }
  19. public Object getInstance(String classPath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
  20. Class clazz = Class.forName(classPath);
  21. return clazz.newInstance();
  22. }

反射机制与对象的封装行矛盾吗?

不矛盾
封装里private 更想告诉你,这个方法你是用不到的,就不要用了,一般是我自己内部使用的。
我给你公开提供的方法,更好
尽管我把权限私有起来了,但是你非要调用的话,也不是不可以,但一般你要通过反射之后自己做的操作,我在公开方法里都给你做好了。那又何必呢

通过反射获取运行时类的完整结构

获取运行时类的属性

  1. public void TestField() throws ClassNotFoundException {
  2. Class clazz = Class.forName("reflection.Test.Person");
  3. Field[] fields = clazz.getFields();
  4. //会发现都是 public 标识的成员变量,且包含父类属性
  5. for (Field f : fields){
  6. System.out.println(f);
  7. }
  8. System.out.println("------------------");
  9. //获取当前类自己定义的所有(不看权限修饰符)属性,但不包括父类属性
  10. Field[] declaredFields = clazz.getDeclaredFields();
  11. for (Field f : declaredFields){
  12. System.out.println(f);
  13. }
  14. System.out.println("------------------");
  15. for (Field f : declaredFields){
  16. //权限修饰符,但是返回的是 int 类型,这个时候我们要转为string类型 Modifier.toString()
  17. System.out.print(Modifier.toString(f.getModifiers()) + "\t");
  18. System.out.print(f.getType().getName() + "\t");
  19. System.out.println(f.getName());
  20. }
  21. }

其他的可以不加演示,以获取属性为例:你可以从代码里发现
直接getXXX() 获取的是包含父类的,但是权限修饰符 必须是 public
如果 getDeclaredXXX()方法的话, 获取的是自己所定义的所有的,不包含父类,不受访问权限约束
获取运行时类的方法结构同理

获取运行时类的方法

  1. public void TestMethod() throws ClassNotFoundException {
  2. Class clazz = Class.forName("reflection.Test.Person");
  3. //获取 当前类和父类 public权限的方法
  4. Method[] methods = clazz.getMethods();
  5. for (Method method :methods){
  6. System.out.println(method);
  7. }
  8. System.out.println("---------------");
  9. //获取当前运行时类 当中的所有方法, 不包含父类,不在乎权限
  10. Method[] declaredMethods = clazz.getDeclaredMethods();
  11. for (Method method:declaredMethods){
  12. System.out.println(method);
  13. }
  14. System.out.println("---------------");
  15. //获取方法具体结构, 权限符 返回值 方法名字 (参数类型 参数名 )
  16. for (Method method:declaredMethods){
  17. //获取注解
  18. Annotation[] annotations = method.getAnnotations();
  19. for (Annotation annotation :annotations) {
  20. System.out.print(annotation + "\t");
  21. }
  22. //获取权限修饰符
  23. int modifiers = method.getModifiers();
  24. System.out.print(Modifier.toString(modifiers) + "\t");
  25. //返回值类型
  26. System.out.print(method.getReturnType().getName() + "\t");
  27. //方法名
  28. System.out.print(method.getName() + "\t(");
  29. //形参列表
  30. Class<?>[] parameterTypes = method.getParameterTypes();
  31. if (!(parameterTypes.length == 0 || parameterTypes == null )) {
  32. for(int i = 0; i < parameterTypes.length; i++){
  33. if (i == parameterTypes.length - 1){
  34. System.out.print(parameterTypes[i].getName() + " args_" + i);
  35. }else {
  36. System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
  37. }
  38. }
  39. }
  40. System.out.print(")");
  41. System.out.println();
  42. }
  43. }

获得运行时类的指定结构

        Class<Person> clazz = Person.class;
        //通过反射创建对象
        //获取构造器
        Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
        Object tom = constructor.newInstance("tom", 12);
        System.out.println(tom);

        //通过反射。调用对象指定的属性和方法,这个时候,还不能直接获取private 修饰的属性
        Field age = clazz.getDeclaredField("age");
        age.set(tom,10);
        System.out.println(tom);

        //通过反射调用方法
        Method show = clazz.getDeclaredMethod("show");
        show.invoke(tom);

        //通过反射调用私有结构
        Constructor constructor1 = clazz.getDeclaredConstructor(String.class);
        constructor1.setAccessible(true);
        Person jerry = (Person) constructor1.newInstance("jerry");
        System.out.println(jerry);

        //调用私有的属性
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(tom,"jerry");
        System.out.println(tom);

        //调用私有方法,有参数的话,要指定参数类型
        Method hello = clazz.getDeclaredMethod("hello", String.class);
        hello.setAccessible(true);
        String returnValue = (String) hello.invoke(jerry, "tom");
        System.out.println(returnValue);

这个与获取完整结构不同之处就是参数不同,指定出你要的方法名。
但是注意,如果你要获取的是 private 要设置 XXX.setAccessible(true);来保证当前方法可以被访问,否则会报错。

当你获取方法时,如果需要参数,那么就要指定参数类型,如果你想调用这个方法,那么就需要使用 invoke() 这个方法去执行,如果被调用的这个方法有返回值,那么 invoke方法会帮你返回这个值

invoke() 第一个参数是 调用类,也就是你需要调用哪个对象的该方法
举例来讲: hello.invoke(jerry, “tom”); 要调用的是 jerry.hello();
如果该类里面没有 hello 这个方法,则报错。