java反射

版权声明:本文为博主原创文章,若需转载请注明出处。 https://blog.csdn.net/ju_362204801/article/details/90578678
思考:在讲反射之前,先思考一个问题,java中如何创建一个对象,有哪几种方式?

Java中创建对象大概有这几种方式:

1、使用new关键字:这是我们最常见的也是最简单的创建对象的方式

2、使用Clone的方法:无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去

3、使用反序列化:当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象

上边是Java中常见的创建对象的三种方式,其实除了上边的三种还有另外一种方式,就是接下来我们要讨论的 “反射”

 

1、反射概述

1.1什么是反射

反射就是把Java类中的各种成分,映射成一个个的java对象,拿到这些对象后可以做一些事情。

例如,一个类有:成员变量,方法,构造方法,等信息,利用反射技术可以对一个类进行解剖,把各个组成部分映射成一个个对象。

1.2、反射能干什么

说完反射的概念后,要说一说反射能干什么?

一般来说反射是用来做框架的,或者说可以做一些抽象度比较高的底层代码,所以说有一句很经典的话:反射乃框架设计之灵魂。现在说完这个可能还不太能理解,不急,等下说完一个快速入门的例子后,应该会稍微有点感觉

1.3、怎么得到想反射的类

刚才已经说过,反射是对一个类进行解剖,想解剖一个东西,前提是首先你得拿到这个东西,那么怎么得到咱们想解剖的类呢?

首先大家要明白一点,咱们写的代码是存储在后缀名是 .java的文件里的,但是它会被编译,最终真正去执行的是编译后的 .class文件。Java是面向对象的语言,一切皆对象,所以java认为 这些编译后的 class文件,这种事物也是一种对象,它也给抽象成了一种类,这个类就是Class,大家可以去AIP里看一下这个类

所以拿到这个类后,就相当于拿到了咱们想解剖的类,那怎么拿到这个类?

看API文档后,有一个方法forName(String className); 而且是一个静态的方法,这样咱们就可以得到想反射的类了

到这里,看Class clazz = Class.forName("com.cj.test.Person");这个应该有点感觉了吧

Class.forName("com.cj.test.Person");这个方法里接收的是个字符串啊,字符串的话,我们就可以在配置文件里进行配置,利用反射让它根据我们配置文件里的配置去反射生成我们需要的对象,这才是我们想要的。

2、解剖类

我们知道一个类里一般有构造函数、方法、成员变量(字段/属性)这三部分组成

翻阅API文档,可以看到

Class对象提供了如下常用方法:

public Constructor getConstructor(Class<?>…parameterTypes)

public Method getMethod(String name,Class<?>… parameterTypes)

public Field getField(String name)

public Constructor getDeclaredConstructor(Class<?>…parameterTypes)

public Method getDeclaredMethod(String name,Class<?>… parameterTypes)

public Field getDeclaredField(String name)

这些方法分别用于从类中解剖出构造函数、方法和成员变量(属性)。

解剖出的成员分别使用Constructor、Method、Field对象表示。

2.1反射构造方法

2.1.1反射无参的构造函数

可以看到 默认的无参构造方法执行了

从上边的例子看出,要想反射,首先第一步就是得到类的字节码

所以简单说一下得到类的字节码的几种方式

(1)、Class.forName("com.cj.test.Person"); 这就是上边我们用的方式

(2)、对象.getClass();

(3)、类名.class;

2.1.2反射“一个参数”的构造函数

2.1.3反射“多个参数”的构造函数

2.1.4反射“私有”的构造函数

注意:在反射私有的构造函数时,用普通的clazz.getConstructor()会报错,因为它是私有的,所以提供了专门反射私有构造函数的方法 clazz.getDeclaredConstructor(int.class);//读取私有的构造函数,用这个方法读取完还需要设置一下暴力反射才可以

c.setAccessible(true);//暴力反射

2.1.5反射得到类中所有的构造函数

 

2.2反射类中的方法


 
 
  1. package com.cj.test;
  2. import java.util.Date;
  3. public class Person {
  4. public Person(){
  5. System.out.println( "默认的无参构造方法执行了");
  6. }
  7. public Person(String name){
  8. System.out.println( "姓名:"+name);
  9. }
  10. public Person(String name,int age){
  11. System.out.println(name+ "="+age);
  12. }
  13. private Person(int age){
  14. System.out.println( "年龄:"+age);
  15. }
  16. public void m1() {
  17. System.out.println( "m1");
  18. }
  19. public void m2(String name) {
  20. System.out.println(name);
  21. }
  22. public String m3(String name,int age) {
  23. System.out.println(name+ ":"+age);
  24. return "aaa";
  25. }
  26. private void m4(Date d) {
  27. System.out.println(d);
  28. }
  29. public static void m5() {
  30. System.out.println( "m5");
  31. }
  32. public static void m6(String[] strs) {
  33. System.out.println(strs.length);
  34. }
  35. public static void main(String[] args) {
  36. System.out.println( "main");
  37. }
  38. }

 
 
  1. package com.cj.test;
  2. import java.lang.reflect.Method;
  3. import java.util.Date;
  4. import org.junit.Test;
  5. public class Demo2 {
  6. @Test //public void m1()
  7. public void test1() throws Exception{
  8. Class clazz = Class.forName( "com.cj.test.Person");
  9. Person p = (Person)clazz.newInstance();
  10. Method m = clazz.getMethod( "m1", null);
  11. m.invoke(p, null);
  12. }
  13. @Test //public void m2(String name)
  14. public void test2() throws Exception{
  15. Class clazz = Person.class;
  16. Person p = (Person) clazz.newInstance();
  17. Method m = clazz.getMethod( "m2", String.class);
  18. m.invoke(p, "张三");
  19. }
  20. @Test //public String m3(String name,int age)
  21. public void test3() throws Exception{
  22. Class clazz = Person.class;
  23. Person p = (Person) clazz.newInstance();
  24. Method m = clazz.getMethod( "m3", String.class, int.class);
  25. String returnValue = (String)m.invoke(p, "张三", 23);
  26. System.out.println(returnValue);
  27. }
  28. @Test //private void m4(Date d)
  29. public void test4() throws Exception{
  30. Class clazz = Person.class;
  31. Person p = (Person) clazz.newInstance();
  32. Method m = clazz.getDeclaredMethod( "m4", Date.class);
  33. m.setAccessible( true);
  34. m.invoke(p, new Date());
  35. }
  36. @Test //public static void m5()
  37. public void test5() throws Exception{
  38. Class clazz = Person.class;
  39. Method m = clazz.getMethod( "m5", null);
  40. m.invoke( null, null);
  41. }
  42. @Test //private static void m6(String[] strs)
  43. public void test6() throws Exception{
  44. Class clazz = Person.class;
  45. Method m = clazz.getDeclaredMethod( "m6",String[].class);
  46. m.setAccessible( true);
  47. m.invoke( null,(Object) new String[]{ "a", "b"});
  48. }
  49. @Test
  50. public void test7() throws Exception{
  51. Class clazz = Person.class;
  52. Method m = clazz.getMethod( "main",String[].class);
  53. m.invoke( null, new Object[]{ new String[]{ "a", "b"}});
  54. }
  55. }

*****注意:看下上边代码里test6和test7的invoke方法里传的参数和其他的有点不一样

这是因为 jdk1.4和jdk1.5处理invoke方法有区别

1.5:public Object invoke(Object obj,Object…args)

1.4:public Object invoke(Object obj,Object[] args)

由于JDK1.4和1.5对invoke方法的处理有区别, 所以在反射类似于main(String[] args) 这种参数是数组的方法时需要特殊处理

启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数个数不对的问题。

上述问题的解决方法:

(1)mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});

这种方式,由于你传的是一个数组的参数,所以为了向下兼容1.4的语法,javac遇到数组会给你拆开成多个参数,但是由于咱们这个Object[ ] 数组里只有一个元素值,所以就算它拆也没关系

(2)mainMethod.invoke(null,(Object)new String[]{"xxx"});

这种方式相当于你传的参数是一个对象,而不是数组,所以就算是按照1.4的语法它也不会拆,所以问题搞定

编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了

对上边的描述进行一下总结:在反射方法时,如果方法的参数是一个数组,考虑到向下兼容问题,会按照JDK1.4的语法来对待(JVM会把传递的数组参数拆开,拆开就会报参数的个数不匹配的错误)
解决办法:防止JVM拆开你的数组
    方式一:把数组看做是一个Object对象
    方式二:重新构建一个Object数组,那个参数数组作为唯一的元素存在。

2.3反射类中的属性字段


 
 
  1. package com.cj.test;
  2. import java.util.Date;
  3. public class Person {
  4. public String name= "李四";
  5. private int age = 18;
  6. public static Date time;
  7. public int getAge() {
  8. return age;
  9. }
  10. public Person(){
  11. System.out.println( "默认的无参构造方法执行了");
  12. }
  13. public Person(String name){
  14. System.out.println( "姓名:"+name);
  15. }
  16. public Person(String name,int age){
  17. System.out.println(name+ "="+age);
  18. }
  19. private Person(int age){
  20. System.out.println( "年龄:"+age);
  21. }
  22. public void m1() {
  23. System.out.println( "m1");
  24. }
  25. public void m2(String name) {
  26. System.out.println(name);
  27. }
  28. public String m3(String name,int age) {
  29. System.out.println(name+ ":"+age);
  30. return "aaa";
  31. }
  32. private void m4(Date d) {
  33. System.out.println(d);
  34. }
  35. public static void m5() {
  36. System.out.println( "m5");
  37. }
  38. public static void m6(String[] strs) {
  39. System.out.println(strs.length);
  40. }
  41. public static void main(String[] args) {
  42. System.out.println( "main");
  43. }
  44. }

 
 
  1. package com.cj.test;
  2. import java.lang.reflect.Field;
  3. import java.util.Date;
  4. import org.junit.Test;
  5. public class Demo3 {
  6. //public String name="李四";
  7. @Test
  8. public void test1() throws Exception{
  9. Class clazz = Person.class;
  10. Person p = (Person)clazz.newInstance();
  11. Field f = clazz.getField( "name");
  12. String s = (String)f.get(p);
  13. System.out.println(s);
  14. //更改name的值
  15. f.set(p, "王六");
  16. System.out.println(p.name);
  17. }
  18. @Test //private int age = 18;
  19. public void test2() throws Exception{
  20. Class clazz = Person.class;
  21. Person p = (Person)clazz.newInstance();
  22. Field f = clazz.getDeclaredField( "age");
  23. f.setAccessible( true);
  24. int age = (Integer)f.get(p);
  25. System.out.println(age);
  26. f.set(p, 28);
  27. age = (Integer)f.get(p);
  28. System.out.println(age);
  29. }
  30. @Test //public static Date time;
  31. public void test3() throws Exception{
  32. Class clazz = Person.class;
  33. Field f = clazz.getField( "time");
  34. f.set( null, new Date());
  35. System.out.println(Person.time);
  36. }
  37. }

以上就是自己对Java中反射的一些学习总结,欢迎大家留言一起学习、讨论

看完上边有关反射的东西, 对常用框架里的配置文件是不是有点思路了

上边是Spring配置文件里的常见的bean配置,这看起来是不是可以用反射很轻易的就可以实现:解析xml然后把xml里的内容作为参数,利用反射创建对象。

除了这个,常用的框架里还有很多地方都用到了反射,反射是框架的灵魂,具备反射知识和思想,是看懂框架的基础

平常用到的框架,除了配置文件的形式,现在很多都使用了注解的形式,其实注解也和反射息息相关,使用反射也能轻而易举的拿到类、字段、方法上的注解,然后编写注解解析器对这些注解进行解析,做一些相关的处理,所以说不管是配置文件还是注解的形式,它们都和反射有关

感兴趣的话可以看下这篇,这篇里边包含了XML解析、反射的东西,模拟了一个Struts2的核心代码

利用Java反射模拟一个Struts2框架 Struts2主要核心设计 手动实现Struts2核心代码

跟反射相关的还有内省、自定义注解,最近也抽时间大概整理了一下

Java反射——内省(Introspector)以及BeanUtils内省框架

Java中的注解以及自定义注解

感兴趣的小可爱可以了解一下~

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值