云笔记项目-Java反射知识学习

在云笔记项目中,补充了部分反射的知识,反射这一部分基础知识非常重要,前面学习的框架Spring和MyBatis读取xml配置文件创建对象,以及JDBC加载驱动等都用了反射,但只知道有这个东西,具体不知道怎么用,大概的原理是怎么样的,现在简单的记录下

 什么是反射

反射(Reflection)是Java提供的动态执行机制,可以动态加载类,动态创建对象,动态获取类信息,比如接口信息,方法信息,属性信息,构造信息等,是JDK1.4开始出现的功能。并可以通过获取到的信息动态创建对象,动态调用方法等。如果想更好的感受反射,可以从静态执行方式和动态执行方式两种去对比。

a.静态执行:Java代码通过编译以后就确定的执行次序,称为静态执行次序。比如新建一个对象Foo foo=new Foo(),然后调用对象的方法foo.test()执行,这就是静态执行,代码在编译期就知道具体的对象是什么,对象要执行的方法是什么。

b.动态执行:在运行期间才确定要创建哪个类,执行类的哪个方法。也就是编译期无法得知具体的类信息,也无法得到类中的方法信息,等具体加载类执行时才能知道。Java反射API,可以实现动态执行加载类和执行方法的功能。

反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

为了更好理解反射,先准备两个写好的类,后面做用素材测试使用:

Foo类:

 1 package Test;
 2 
 3 public class Foo {
 4     //加几个属性,演示通过反射得到所有的属性
 5     public String name;
 6     public int age;
 7     private int salary;
 8     
 9     
10     //加一个构造器,演示通过反射获得构造器
11 //    public Foo(String name) {
12 //        super();
13 //        this.name = name;
14 //    }
15 
16     //加几个方法,演示通过反射动态调用方法
17     private String getPrice() {
18         return "100";
19     }
20 
21     public String hello() {
22         return "hello reflect";
23     }
24 }

Too类:

 1 package Test;
 2 
 3 public class Too {
 4    public String name;
 5    public int age;
 6    private int salary;
 7    
 8    public String Hello() {
 9        System.out.println("Hello Too");
10        return "Success";
11    }
12 }

 反射功能

a.动态加载类,主要有三种方法:

(1)使用Class类的forName()静态方法

作用是将类名对应的类加载到方法区,如果类名错误就抛出异常。

forName()工作原理:写好一个类,比如Foo.java类,通过编译后会生成Foo.class字节码文件,当执行Class cls=Class.forName("Foo")后,Class.forName()这个方法会首先读取Foo.class字节码文件,将其加载到方法区中。Class.forName()执行完后的返回值就是一个具体的Class类型对象,储存在堆中,其通向方法区。而cls是一个指向具体对象的引用,会储存在栈中。通过cls,可以获取Foo类的一切信息,属性和方法等。当forName()方法中的类名更改后,其加载新的类对应的字节码文件到方法区,从而实现了类的动态加载。

 1 package Test;
 2 
 3 import java.lang.reflect.Constructor;
 4 import java.lang.reflect.Field;
 5 import java.lang.reflect.InvocationTargetException;
 6 import java.lang.reflect.Method;
 7 import java.util.Scanner;
 8 
 9 public class Demo {
10 
11     public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
12         //动态加载类
13         Scanner scan=new Scanner(System.in);
14         //得到用户输入的类名
15         System.out.println("请输入类名:");
16         String className=scan.nextLine();
17         //动态加载类
18         Class cls=Class.forName(className);
19         System.out.println("---------------类信息---------------");
20         System.out.println(cls);//输出class 包名.类名
21         System.out.println(cls.getName());//输出包名.类名
64     }
66 }

控制台输入Test.Foo后,输出结果如下,getName()获得的是Java内部使用的真正名称,包含包名和类名,其他还有getSimpleName()和getPackage()方法等,分别代表返回不包含包名和只包含包名的信息:

(2)直接获取某一个对象的class

1 Class<Date> cls = Date.class;

获取Class对象如果提前知道了类名,不一定需要实例对象,可以使用<类名>.class获取Class对象。

(3)调用某个对象的getClass()方法

1 StringBuilder str = new StringBuilder("123");
2 Class<?> cls = str.getClass();

所有类的根父类Object有一个方法 public final native Class<?> getClass() ,可以获取对象的Class对象。Class是一个泛型类,使用getClass()方法时并不知道返回的具体类是什么类型,因此返回Class<?>。问号"?"代表类型的实参,不是类型形参,其代表所有类型的父类,是一种实际的参数。

b.动态创建对象,主要有两种方法

(1)可以使用cls.newInstance()方法创建对象,相当如使用无参构造器创建了对象。

  Object obj=cls.newInstance()

  特点:动态创建对象;可以创建任何对象;cls对应的类必须有无参数构造器,如果没有将抛出异常,(一般符合javabean规范的类都有无参数构造器)

(2)使用cls.getConstructor()方法得到Constructor对象,然后使用Constructor.getInstance()方法获取对象,这个构造器可以传入参数,是跟第一种主要的区别。

在上述main方法中加上如下代码获取对象,注释的部分是通过带参数的构造器创建对象:

1         //动态创建对象
2         //1 使用cls.newInstance()来获取对象,使用无参数构造器
3         Object obj=cls.newInstance();
4         System.out.println("---------------对象信息---------------");
5         System.out.println(obj);
6         //2 使用cons.newInstance()获取对象,使用特定的构造器
7 //        Constructor cons=cls.getConstructor(String.class);
8 //        Object obj=cons.newInstance("clyang");
9 //        System.out.println(obj);

同样控制台使用Test.Foo类进行测试,输出结果为:

c.反射可以查找类中的方法

可以返回类中声明的全部方法信息,eclipse开发工具中,当得到一个对象后,可以通过输入点,就可以列出对象里所有的方法,其实也是用的反射机制。如Foo foo=new Foo(),当输入foo.时,"foo."后面会列出一堆方法和属性信息,其实就是在点了后,java获得了foo,然后获取到了类名,通过java反射得到这个类下的方法和属性,然后将其输出到界面,列出来展示给开发人员。

(1)getDeclaredMethods()

在上述main方法中加上如下代码获取所有声明的方法,使用getDeclaredMethods()方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法:

1         //动态检查类中声明的方法信息
2         Method[] method=cls.getDeclaredMethods();//返回所有private,public,protected等修饰的方法
3         System.out.println("---------------方法信息---------------");
4         for(Method m:method) {
5             System.out.println(m);//输出方法信息
6         }

同样使用Test.Foo类进行测试,控制台输出结果为:

(2)getMethods()

方法返回某个类的所有公用(public)方法,包括其继承类的公用方法.

(3)getMethod(String name,Class<?>... parameterTypes)

方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象

d.动态执行方法

(1) 需要在动态创建对象后再动态执行方法

Object obj=cls.newInstance();//动态创建对象

(2)找到对象对应的类型方法信息,方法信息在类上查找

method m=cls.getDeclaredMethod(name);//找到对象对应类cls中的方法信息

(3)接下来就可以调用方法了

m.invoke(obj);//字面意思是,m方法调用了obj,个人猜测是m方法唤醒了obj,然后底层调用了obj.m()方法。

1         //动态调用方法
2         System.out.println("请输入方法名");
3         String methodName=scan.nextLine();
4         Method m=cls.getDeclaredMethod(methodName);
5         //如果想让private方法也能被调用,需要加上
6         System.out.println("---------------动态执行方法---------------");
7         m.setAccessible(true);
8         Object o=m.invoke(obj);
9         System.out.println(o);

同样使用Test.Foo类进行测试,测试执行hello方法,控制台输出结果为:

e.反射也可以查找类中的属性

(1)getDeclaredFields()

可以返回类所有已声明的成员变量,但不能得到其父类的成员变量,Field[] field=cls.getDeclaredField()。

        //返回类的所有成员变量
        Field[] fields=cls.getDeclaredFields();
        System.out.println("---------------属性信息---------------");
        for(Field f:fields) {
        System.out.println(f);
        }

同样使用Test.Foo类进行测试,控制台输出结果可以看出私有的成员变量也可以得到:

(2)getField

访问共有的成员变量。

f.反射可以获取类中的构造器

可以获取类中所有声明的构造器,Constructor[] constructor=cls.getDeclaredConstructors();

        //返回类中声明的构造器
        Constructor[] constructor=cls.getDeclaredConstructors();
        System.out.println("---------------构造器信息---------------");
        for(Constructor c:constructor) {
            System.out.println(c);
        }

同样控制台使用Test.Foo类进行测试,输出结果为:

发现输出的是默认构造器方法,因为在Foo类中,没有写构造器,因此创建对象时默认调用了无参数构造器。

g.反射的用途

(1)Eclipse快捷菜单使用了反射,利用反射发现类的属性和方法

(2)Spring利用了反射:动态加载类,动态创建bean,动态注入属性,包括私有属性注入,动态解析注解

(3)Mybatis利用了反射,查询时候,动态将查询结果利用反射注入到bean并返回

(4)Junit使用了反射

(5)注解的解析使用了反射

(6)Servlet调用使用了反射

总结

反射内容非常复杂,现在只是学习如何基本的使用,具体底层的实现,invoke方法的原理,还需要后续学习补充。

参考博客:https://www.sczyh30.com/posts/Java/java-reflection-1/#%E4%B8%80%E3%80%81%E5%9B%9E%E9%A1%BE%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF%E5%8F%8D%E5%B0%84%EF%BC%9F

参考博文:https://www.cnblogs.com/coprince/p/8603492.html

参考书籍:《Java编程的逻辑》

原文地址:https://www.cnblogs.com/youngchaolin/p/10515406.html