反射的常见用法有三类, 第一类是 "查看", 比如输入某个类的属性方法等信息, 第二类是 "装载", 比如装载指定的类到内存里, 第三类是 "调用", 比如通过传入参数, 调用指定的方法.
1 查看属性的修饰符, 类型和名字
通过反射机制, 我们能从. class 文件里看到指定类的属性, 比如属性的修饰符, 属性和类型和属性的变量名. 通过下面的 ReflectionReadVar.java, 我们看演示下具体的做法.
- import java.lang.reflect.Field;
- import java.lang.reflect.Modifier;
- class MyValClass{
- private int val1;
- public String val2;
- final protected String val3 = "Java";
我们在第 3 行定义了一个 MyValCalss 的类, 并在第 4 到第 6 行里, 定义了三个属性变量.
- public class ReflectionReadVar {
- public static void main(String[] args) {
- Class<MyValClass> clazz = MyValClass.class;
- // 获取这个类的所有属性
- Field[] fields = clazz.getDeclaredFields();
- for(Field field : fields) {
- // 输出修饰符 System.out.print(Modifier.toString(field.getModifiers()) + "\t");
- // 输出属性的类型
- System.out.print(field.getGenericType().toString() + "\t");
- // 输出属性的名字
- System.out.println(field.getName());
- }
- }
- }
在 main 函数的第 10 行里, 通过 MyValClass.class, 得到了 Class<MyValClass> 类型的变量 clazz, 在这个变量中, 存储了 MyValClass 这个类的一些信息.
在第 12 行里, 通过了 clazz.getDeclaredFields() 方法得到了 MyValClass 类里的所有属性的信息, 并把这些属性的信息存入到 Field 数组类型的 fields 变量里.
通过了第 13 行的 for 循环依次输出了这些属性信息. 具体来讲, 通过第 14 行的代码输出了该属性的修饰符, 通过第 16 行的代码输出了该属性的类型, 通过第 18 行的代码输出了该属性的变量名. 这段代码的输出如下, 从中我们能看到各属性的信息.
- private int val1
- public class java.lang.String val2
- protected final class java.lang.String val3
2 查看方法的返回类型, 参数和名字
通过 ReflectionReadFunc.java, 我们能通过反射机制看到指定类的方法.
- import java.lang.reflect.Constructor;
- import java.lang.reflect.Method;
- class MyFuncClass{
- public MyFuncClass(){}
- public MyFuncClass(int i){}
- private void f1(){}
- protected int f2(int i){return 0;}
- public String f2(String s) {return "Java";}
在第 3 行定义的 MyFuncClass 这个类里, 我们定义了 2 个构造函数和 3 个方法.
- public class ReflectionReadFunc {
- public static void main(String[] args) {
- Class<MyFuncClass> clazz = MyFuncClass.class;
- Method[] methods = clazz.getDeclaredMethods();
- for (Method method : methods)
- { System.out.println(method); }
- // 得到所有的构造函数
- Constructor[] c1 = clazz.getDeclaredConstructors();
- // 输出所有的构造函数
- for(Constructor ct : c1)
- { System.out.println(ct); }
- }
- }
在 main 函数的第 12 行, 我们同样是通过了类名. class 的方式 (也就是 MyFuncClass.class 的方式) 得到了 Class<MyFuncClass> 类型的 clazz 对象.
在第 13 行里, 是通过了 getDeclaredMethods 方法得到了 MyFuncClass 类的所有方法, 并在第 14 行的 for 循环里输出了各方法. 在第 17 行里, 是通过了 getDeclaredConstructors 方法得到了所有的构造函数, 并通过第 19 行的循环输出.
本代码的输出结果如下所示, 其中第 1 到第 3 行输出的是类的方法, 第 4 和第 5 行输出的是类的构造函数.
- private void MyFuncClass.f1()
- protected int MyFuncClass.f2(int)
- public java.lang.String MyFuncClass.f2(java.lang.String)
- public MyFuncClass()
- public MyFuncClass(int)
不过在实际的项目里, 我们一般不会仅仅 "查看" 类的属性和方法, 在更多的情况里, 我们是通过反射装载和调用类里的方法.
3 通过 forName 和 newInstance 方法加载类
在前文 JDBC 操作数据库的代码里, 我们看到在创建数据库连接对象 (Connection) 之前, 需要通过 Class.forName("com.mysql.jdbc.Driver"); 的代码来装载数据库 (这里是 MySQL) 的驱动.
可以说, Class 类的 forName 方法最常见的用法就是装载数据库的驱动, 以至于不少人会错误地认为这个方法的作用是 "装载类".
其实 forName 方法的作用仅仅是返回一个 Class 类型的对象, 它一般会和 newInstance 方法配套使用, 而 newInstance 方法的作用才是加载类.
通过下面的 ForClassDemo.java 这段代码, 我们来看下综合使用 forName 和 newInstance 这两个方法加载对象的方式.
- class MyClass{
- public void print()
- { System.out.println("Java"); }
- }
- public class ForClassDemo {
- public static void main(String[] args) {
- // 通过 new 创建类和使用类的方式
- MyClass myClassObj = new MyClass();
- myClassObj.print();// 输出是 Java
- // 通过 forName 和 newInstance 加载类的方式
- try {
- Class clazz = Class.forName("MyClass");
- MyClass myClass = (MyClass)clazz.newInstance();
- myClass.print();// 输出是 Java
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (InstantiationException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
- }
在第 1 行定义的 MyClass 这个类里, 我们在其中的第 2 行定义了一个 print 方法.
Main 函数的第 8 和第 9 行里, 我们演示了通过常规 new 的方式创建和使用类的方式, 通过第 9 行, 我们能输出 "Java" 这个字符串.
在第 12 行, 我们通过 Class.forName("MyClass") 方法返回了一个 Class 类型的对象, 请注意, forName 方法的作用不是 "加载 MyClass 类", 而是返回一个包含 MyClass 信息的 Class 类型的对象. 这里我们是通过第 13 行的 newInstance 方法, 加载了一个 MyClass 类型的对象, 并在第 14 行调用了其中的 print 方法.
既然 forName 方法的作用仅仅是 "返回 Class 类型的对象", 那么在 JDBC 部分的代码里, 为什么我们能通过 Class.forName("com.mysql.jdbc.Driver"); 代码来装载 MySQL 的驱动呢? 在 MySQL 的 com.MySQL.jdbc.Driver 驱动类中有如下的一段静态初始化代码.
- static {
- try {
- java.sql.DriverManager.registerDriver(new Driver());
- } catch (SQLException e) {
- throw new RuntimeException("Can't register driver!");
- }
- }
也就是说, 当我们调用 Class.forName 方法后, 会通过执行这段代码会新建一个 Driver 的对象, 并调用第 3 行的 DriverManager.registerDriver 把刚创建的 Driver 对象注册到 DriverManager 里.
在上述的代码里, 我们看到了除了 new 之外, 我们还能通过 newInstance 来创建对象.
其实这里说 "创建" 并不准确, 虽然说通过 new 和 newInstance 我们都能得到一个可用的对象, 但 newInstance 的作用其实是通过 Java 虚拟机的类加载机制把指定的类加载到内存里.
我们在工厂模式中, 经常会通过 newInstance 方法来加载类, 但这个方法只能是通过调用类的无参构造函数来加载类, 如果我们在创建对象时需要传入参数, 那么就得使用 new 来调用对应的带参的构造函数了.
4 通过反射机制调用类的方法
如果我们通过反射机制来调用类的方式, 那么就得解决三个问题, 第一, 通过什么方式来调? 第二, 如何传入参数, 第三, 如何得到返回结果?
通过下面的 CallFuncDemo.java 代码, 我们将通过反射来调用类里的方法, 在其中我们能看下上述三个问题的解决方法.
- import java.lang.reflect.Constructor;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- class Person {
- private String name;
- public Person(String name)
- {this.name = name;}
- public void saySkill(String skill) {
- System.out.println("Name is:"+name+",skill is:" + skill);
- }
- public int addSalary(int current)
- { return current + 100;}
- }
在第 4 行里, 我们定义了一个 Person 类, 在其中的第 6 行里, 我们定义了一个带参的构造函数, 在第 8 行里, 我们定义了一个带参但无返回值得 saySkill 方法, 在第 11 行里, 我们定义了一个带参而且返回 int 类型的 addSalary 方法.
- public class CallFuncDemo {
- public static void main(String[] args) {
- Class c1azz = null;
- Constructor c = null;
- try {
- c1azz = Class.forName("Person");
- c = c1azz.getDeclaredConstructor(String.class);
- Person p = (Person)c.newInstance("Peter");
- //output: Name is:Peter, skill is:java
- p.saySkill("Java");
- // 调用方法, 必须传递对象实例, 同时传递参数值
- Method method1 = c1azz.getMethod("saySkill", String.class);
- // 因为没返回值, 所以能直接调
- // 输出结果是 Name is:Peter, skill is:C#
- method1.invoke(p, "C#");
- Method method2 = c1azz.getMethod("addSalary", int.class);
- Object invoke = method2.invoke(p, 100);
- // 输出 200
- System.out.println(invoke);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (NoSuchMethodException e1) {
- e1.printStackTrace();
- } catch (InstantiationException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- }
- }
- }
在第 19 行里, 我们通过 Class.forName 得到了一个 Class 类型的对象, 其中包含了 Person 类的信息. 在第 20 行里, 通过传入 String.class 参数, 得到了 Person 类的带参的构造函数, 并通过了第 21 行的 newInstance 方法, 通过这个带参的构造函数创建了一个 Person 类型的对象. 随后在第 23 行里调用了 saySkill 方法. 这里我们演示通过反射调用类的构造函数来创建对象的方式.
在第 25 行里, 我们通过了 getMethod 方法, 得到了带参的 saySkill 方法的 Method 类型的对象, 随后通过第 28 行的 invoke 方法调用了这个 saySkill 方法, 这里第一个参数是由哪个对象来调用, 通过第二个参数, 我们传入了 saySkill 方法的 String 类型的参数.
用同样的方式, 我们在第 29 和 30 行通过反射调用了 Person 类的 addSalary 方法, 由于这个方法有返回值, 所以我们在 30 行用了一个 Object 类型的 invoke 对象来接收返回值, 通过第 32 行的打印语句, 我们能看到 200 这个执行结果.
来源: http://www.bubuko.com/infodetail-3400493.html