Java反射的常见用法

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: Java反射的常见用法
1 查看属性的修饰符、类型和名字

   通过反射机制,我们能从.class文件里看到指定类的属性,比如属性的修饰符,属性和类型和属性的变量名。通过下面的ReflectionReadVar.java,我们看演示下具体的做法。    

1 import java.lang.reflect.Field;
2 import java.lang.reflect.Modifier;
3 class MyValClass{
4   private int val1;
5   public String val2;
6   final protected String val3 = "Java";

   我们在第3行定义了一个MyValCalss的类,并在第4到第6行里,定义了三个属性变量。    

8 public class ReflectionReadVar {
9   public static void main(String[] args) {
10      Class<MyValClass> clazz = MyValClass.class;
11      //获取这个类的所有属性
12          Field[] fields = clazz.getDeclaredFields();
13        for(Field field : fields) {
14             //输出修饰符           System.out.print(Modifier.toString(field.getModifiers()) + "\t");
15             //输出属性的类型
16           System.out.print(field.getGenericType().toString() + "\t");
17             //输出属性的名字
18             System.out.println(field.getName());
19          }
20    }
21  }

   在main函数的第10行里,通过MyValClass.class,得到了Class<MyValClass>类型的变量clazz,在这个变量中,存储了MyValClass这个类的一些信息。

   在第12行里,通过了clazz.getDeclaredFields()方法得到了MyValClass类里的所有属性的信息,并把这些属性的信息存入到Field数组类型的fields变量里。

   通过了第13行的for循环依次输出了这些属性信息。具体来讲,通过第14行的代码输出了该属性的修饰符,通过第16行的代码输出了该属性的类型,通过第18行的代码输出了该属性的变量名。这段代码的输出如下,从中我们能看到各属性的信息。

         1      private    int val1

         2      public class java.lang.String   val2

         3      protected final   class java.lang.String   val3  

2 查看方法的返回类型,参数和名字

   通过ReflectionReadFunc.java,我们能通过反射机制看到指定类的方法。    

1 import java.lang.reflect.Constructor;
2 import java.lang.reflect.Method;
3 class MyFuncClass{
4   public MyFuncClass(){}
5   public MyFuncClass(int i){}
6   private void f1(){}
7   protected int f2(int i){return 0;}
8   public String f2(String s) {return "Java";}

   在第3行定义的MyFuncClass这个类里,我们定义了2个构造函数和3个方法。 

10  public class ReflectionReadFunc {
11    public static void main(String[] args) {
12      Class<MyFuncClass> clazz = MyFuncClass.class;
13          Method[] methods = clazz.getDeclaredMethods();
14          for (Method method : methods) 
15          { System.out.println(method); }
16          //得到所有的构造函数
17          Constructor[] c1 = clazz.getDeclaredConstructors();
18          //输出所有的构造函数
19          for(Constructor ct : c1)
20          { System.out.println(ct);  }
21    }
22  }

   在main函数的第12行,我们同样是通过了类名.class的方式(也就是MyFuncClass.class的方式)得到了Class<MyFuncClass>类型的clazz对象。

   在第13行里,是通过了getDeclaredMethods方法得到了MyFuncClass类的所有方法,并在第14行的for循环里输出了各方法。在第17行里,是通过了getDeclaredConstructors方法得到了所有的构造函数,并通过第19行的循环输出。

   本代码的输出结果如下所示,其中第1到第3行输出的是类的方法,第4和第5行输出的是类的构造函数。    

1 private void MyFuncClass.f1()
2 protected int MyFuncClass.f2(int)
3 public java.lang.String MyFuncClass.f2(java.lang.String)
4 public MyFuncClass()
5 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这两个方法加载对象的方式。    

1 class MyClass{
2   public void print()
3   { System.out.println("Java"); }
4 }
5 public class ForClassDemo {
6   public static void main(String[] args)  {
7         //通过new创建类和使用类的方式
8     MyClass myClassObj = new MyClass();
9     myClassObj.print();//输出是Java
10      //通过forName和newInstance加载类的方式 
11      try {
12        Class<?> clazz = Class.forName("MyClass");
13        MyClass myClass = (MyClass)clazz.newInstance();
14        myClass.print();//输出是Java
15      } catch (ClassNotFoundException e) {
16        e.printStackTrace();
17      } catch (InstantiationException e) {
18        e.printStackTrace();
19      } catch (IllegalAccessException e) {
20        e.printStackTrace();
21      }   
22    }
23  }

   在第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驱动类中有如下的一段静态初始化代码。  

1 static {
2 try {
3    java.sql.DriverManager.registerDriver(new Driver());
4 } catch (SQLException e) {
5 throw new RuntimeException(“Can’t register driver!”);
6 }
7 }

   也就是说,当我们调用Class.forName方法后,会通过执行这段代码会新建一个Driver的对象,并调用第3行的DriverManager.registerDriver把刚创建的Driver对象注册到DriverManager里。

   在上述的代码里,我们看到了除了new之外,我们还能通过newInstance来创建对象。

   其实这里说“创建”并不准确,虽然说通过new和newInstance我们都能得到一个可用的对象,但newInstance的作用其实是通过Java虚拟机的类加载机制把指定的类加载到内存里。

   我们在工厂模式中,经常会通过newInstance方法来加载类,但这个方法只能是通过调用类的无参构造函数来加载类,如果我们在创建对象时需要传入参数,那么就得使用new来调用对应的带参的构造函数了。

4 通过反射机制调用类的方法

   如果我们通过反射机制来调用类的方式,那么就得解决三个问题,第一,通过什么方式来调?第二,如何传入参数,第三,如何得到返回结果?

   通过下面的CallFuncDemo.java代码,我们将通过反射来调用类里的方法,在其中我们能看下上述三个问题的解决方法。    

1 import java.lang.reflect.Constructor;
2 import java.lang.reflect.InvocationTargetException;
3 import java.lang.reflect.Method;
4 class Person {
5   private String name;
6   public Person(String name) 
7     {this.name = name;}
8   public void saySkill(String skill) {
9     System.out.println("Name is:"+name+",skill is:" + skill);
10    }
11    public int addSalary(int current) 
12      { return current + 100;}
13  }

   在第4行里,我们定义了一个Person类,在其中的第6行里,我们定义了一个带参的构造函数,在第8行里,我们定义了一个带参但无返回值得saySkill方法,在第11行里,我们定义了一个带参而且返回int类型的addSalary方法。    

14  public class CallFuncDemo {
15    public static void main(String[] args) {
16      Class c1azz = null;
17      Constructor c = null;
18      try {
19        c1azz = Class.forName("Person");
20        c = c1azz.getDeclaredConstructor(String.class);
21        Person p = (Person)c.newInstance("Peter");
22        //output: Name is:Peter, skill is:java
23        p.saySkill("Java");
24        // 调用方法,必须传递对象实例,同时传递参数值
25        Method method1 = c1azz.getMethod("saySkill", String.class);
26        //因为没返回值,所以能直接调
27        //输出结果是Name is:Peter, skill is:C#
28              method1.invoke(p, "C#");             
29              Method method2 = c1azz.getMethod("addSalary", int.class);
30              Object invoke = method2.invoke(p, 100);
31              //输出200
32        System.out.println(invoke);
33      } catch (ClassNotFoundException e) {
34        e.printStackTrace();
35      } catch (NoSuchMethodException e1) {
36        e1.printStackTrace();
37      } catch (InstantiationException e) {
38        e.printStackTrace();
39      } catch (IllegalAccessException e) {
40        e.printStackTrace();
41      } catch (InvocationTargetException e) {
42        e.printStackTrace();
43      }
44    }
45  }

   在第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这个执行结果。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
19小时前
|
Java 数据库连接
惊呆了!JAVA反射:你的代码竟然能这样“自我修复”?
【6月更文挑战第30天】Java反射允许运行时访问类和方法,模拟“自我修复”能力。当UserService的getUserById方法抛出异常时,通过反射捕获异常并调用handleException进行处理。此示例展示了如何记录错误,返回默认用户对象,而无需原始代码更改。反射提供了一种动态异常处理机制,增强代码的适应性和弹性。
|
19小时前
|
开发框架 Java
JAVA反射:揭秘!运行时如何窥探类的秘密?
【6月更文挑战第30天】Java反射是运行时检查类信息并动态操作对象的机制。通过`Class`对象,我们可以访问私有成员,如在Person类示例中设置私有变量name和调用方法。反射增加了代码灵活性,常用于动态类型、插件和框架设计。
|
3天前
|
Java API
java之反射机制
java之反射机制
|
4天前
|
自然语言处理 Java
Java IO流进阶教程:掌握字节流和字符流的高级用法!
【6月更文挑战第26天】Java IO流助你高效交换数据,包括字节流(InputStream/OutputStream)和字符流(Reader/Writer)的高级技巧。缓冲流(Buffered*)提升读写性能,对象流(Object*Stream)支持对象序列化。字符流的BufferedReader/BufferedWriter优化文本处理,注意字符集如UTF-8用于编码转换。掌握这些,优化IO操作,提升代码质量。
|
5天前
|
Java
java反射-动态调用方法(无参无返回值、有参无返回值、有参有返回值)
java反射-动态调用方法(无参无返回值、有参无返回值、有参有返回值)
5 0
|
5天前
|
Java
java反射-属性赋值取值
java反射-属性赋值取值
8 0
|
5天前
|
Java 机器人 程序员
Java中的反射编程实用指南
Java中的反射编程实用指南
|
5天前
|
Java
java反射-使用Class对象创建对象
java反射-使用Class对象创建对象
6 0
|
5天前
|
Java
java反射-获取类的属性、构造方法、方法
java反射-获取类的属性、构造方法、方法
5 0
|
10月前
|
存储 SQL Java
Java反射读取注解信息
Java反射读取注解信息
53 0