Java反射

 反射是Java的一个特点,也是使原本为静态语言的Java,多了那么一些灵活性,在理解各个框架源码以及组件内容的时候是一个不错的知识点,比如注解,这是一个非常常见,又很好使的玩意,之前也有简单的学习---Java 注解 基础、Java 注解 实践
从主要以下几点开始学习
  • Class类的使用
  • 方法的反射
  • 成员变量的反射
  • 构造器的反射
  • Java类加载机制
  •  
Class类 和 面向对象
在面向对象的环境中,万事万物皆对象,但也总有例外,Java中有两个不属于对象,一个是普通数据类型,一个是静态的成员
普通数据类型有封装类的弥补,静态的属于类,那么类是不是对象呢,类是对象,是java.lang.Class类的实例对象,看文字还比较容易理解,中文说出来就比较绕口, 英文: there is a class named Class
一个普通的类的实例对象表示

 
[AppleScript] 纯文本查看 复制代码
1
2
3
4
5
6
public class Coo {
    //Doo的实例对象 以doo表示
    Doo doo=new Doo();
}
 
class Doo{}
那么一个Class类的实例对象,有三种表示方式,但不能是new Class,因为下面源码中也解释为什么
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
/*
 * Private constructor. Only the Java Virtual Machine creates Class objects.
 * This constructor is not used and prevents the default constructor being
 * generated.
 */
private Class(ClassLoader loader) {
    // Initialize final field for classLoader.  The initialization value of non-null
    // prevents future JIT optimizations from assuming this final field is null.
    classLoader = loader;
}
上面是Class类中的一个构造器,是私有的,而且注释说只有JVM创建Class对象,在以前的Java版本中你可能会看到一个无参的构造器,没关系,那你的构造器也绝对是私有,不能直接创建,在上面中出现了一个JIT编译的关键词,有兴趣的小伙伴可以研究扩展
任何类都是Class的实例对象,下面三种方式:
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Coo {
 
    //Doo的实例对象 以doo表示
    Doo doo=new Doo();
 
    //①可以看出Doo类有一个隐含的静态成员变量class
    Class first=Doo.class;
 
    //②已知类的对象,通过getClass获取
    Class second=doo.getClass();
 
    //重理解一次:doo代表Doo类的实例对象,first、second代表的是Class的实例对象
    //这个Class的实例对象又证明说Doo这个类本身是一个实例对象的存在
    //一本正经的胡说八道,那么给一个官方给出的说法是这样的 :first、second表示了Doo类的类 类型(class type)
    //所有东西都是对象,类也是对象,是Class的实例对象,这个对象称为该类的类类型
    //就可以分析到,Doo的对象是doo,Doo的类类型是Class的对象first、second
    //不管哪种表达方式表示Doo的类类型,一个类只可能是Class类的一个实例对象,所以first == second
 
    //③需要异常处理,参数为类的全称"com.cloud.eureka.Doo"
    Class third=null;
 
    {
        try {
            third = Class.forName("com.cloud.eureka.Doo");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
 
    //依然存在 first == second == third
    //由此可见,我们可以通过类的类类型创建该类的对象,通过first、second、third创建
    //需要异常处理,是谁的类的类类型对象,创建的对象就是谁,需要强转
    //newInstance前提需要无参构造方法
    {
        try {
            Doo dooFirst= (Doo) first.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
 
class Doo{}
Java 动态加载类信息
三种表示Class的实例对象中,第三种具有很好的动态加载类③
  • 可以表示类的类类型,还可以动态加载类
  • 区分编译、运行
  • 编译时加载类属于静态加载类
  • 运行时加载类属于动态加载类
很多时候,大家都是通过工具(IDEA、eclipse等)进行办公或者学习,编译和运行都是由工具来辅助完成的,那么我们需要知道编译、运行的区别
1.2..3...好,我们得到了编译、运行知识的技能
只要是在类里面用到的,都隐含class,对应的类的类类型,如下:
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
public class Coo {
 
    Class c0=int.class;
    Class c1=String.class;
    Class c2=Double.class;
    Class c3=void.class;
     
    // package不是在类里面的,error
    // Class c4=package.class;
}
 
在Doo类中,写个方法
[Java] 纯文本查看 复制代码
1
2
3
4
5
6
class Doo{
    public static void staticVoidMethod(Object o){
        //传递的是什么类型,就是什么类型
        Class co=o.getClass();
    }
}
o传递的是什么对象,co就是该类的类类型,那么底层怎么实现的,可能会比较复杂,贴一份源码
public final native Class<?> getClass();
这是一个native声明的一个方法,称为本地方法,Java中有一项技术JNR,使用Java声明,C语言实现,Java 中调用...一堆,有兴趣的可以了解了解,效果就是上面说的,返回类的类类型
下面是简单的通过Class获取类的信息:
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Doo {
    public static void staticVoidMethod(Object o) {
        //传递的是什么类型,就是什么类型
        Class co = o.getClass();
 
        System.out.println("类的全名称:" + co.getName());
        System.out.println("类的名字:" + co.getSimpleName());
 
        //Method类,方法对象
        //一个成员方法 就是 一个Method对象
        //getMethods 获取所有public的方法,其中包括父类继承的函数
        Method[] allMethods = co.getMethods();
 
        //getDeclaredMethods获取该类自己声明的方法
        Method[] thisMethods = co.getDeclaredMethods();
 
        for (Method method : allMethods) {
            //method.getReturnType()得到的是类的类类型
            //比如返回值是String,那么得到的是String.class的类类型,通过getName获取名称
            System.out.println("返回类型:" + method.getReturnType().getName());
 
            System.out.println("方法名称:" + method.getName());
 
            //获取参数类型
            Class[] parameterTypes = method.getParameterTypes();
            for (Class c : parameterTypes) {
                System.out.println("参数类型:" + c.getName());
            }
            System.out.println("====================================");
        }
 
        //成员变量 =》对象
        //属于java.lang.reflect.Field
        //Field封装了关于成员变量的操作
        //getFields获取所有public的成员变量
        Field[] field=co.getFields();
        //得到自己声明的成员变量
        Field[] declaredFields=co.getDeclaredFields();
        for (Field fields:field) {
            System.out.println("成员变量类型"+fields.getType());
            System.out.println("成员变量名称"+fields.getName());
        }
    }
}
简单来一个main方法,加入一个String类
[Java] 纯文本查看 复制代码
1
2
3
4
5
6
public class Coo {
    public static void main(String[] args) {
        String hello=new String();
        Doo.staticVoidMethod(hello);
    }
}
控制台打印 , 所有String内的方法信息:
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
类的全名称:java.lang.String[/font][/align]类的名字:String
返回类型:boolean
方法名称:equals
参数类型:java.lang.Object
====================================
返回类型:java.lang.String
方法名称:toString
====================================
返回类型:int
方法名称:hashCode
====================================
返回类型:int
方法名称:compareTo
参数类型:java.lang.Object
====================================
//......
 
可以总结出来,getDeclaredXXX()方法都是获取自己声明的内容,包括成员变量,构造器,方法等等,直接的getXXX()方法部分会获取所有内容包括父类的内容,另外数组是一个特殊的存在,打印的是“0]”差不多的样子,在JVM对数组的存储方式也比较VIP,有兴趣的可以理解扩展
方法的反射
上面有获取所有的方法的示例,下面来学习如何获取某一个方法以及方法的反射操作
①方法的名称和方法的参数列表可以唯一定位某一个方法
②method.invoke(对象,参数列表)
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class MethodReflect {
    //获取getMethod方法,获取①号
    public static void main(String[] args) {
        MethodDemo demo = new MethodDemo();
        //1.获取类信息
        Class c0 = demo.getClass();
 
        //2.获取方法
        try {
            //第一种写法
            Method method1 = c0.getDeclaredMethod("getMethod", new Class[]{String.class, String.class});
            //第二种写法
            Method method2 = c0.getDeclaredMethod("getMethod", String.class, String.class);
 
            //平时正常的调用方法: demo.getMethod(str0,str1)
            //现在使用method1来调用--public Object invoke(Object obj, Object... args)
            //第一个参数是调用的类,第二个参数是可用可无,按定义的方法来录入(str0,str1)
            //invoke的方法如果有返回值,则返回Object的值,void的返回值为null
            try {
                Object object1 = method1.invoke(demo, new Object[]{"hello", " world"});
                Object object2 = method2.invoke(demo, "hello", " world");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
 
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
 
    }
 
}
 
class MethodDemo {
    //①
    public void getMethod(String a, String b) {
        System.out.println("concat: " + a + b);
    }
 
    //②
    public void getMethod(int a, int b) {
        System.out.println("sum: " + a + b);
    }
}

反射和泛型
泛型不说了,非常的常用,比如list,map等等,约定类型,不多做解释,直接先来一个操作,比对List和List<String>是否相等
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
public class Coo {
    public static void main(String[] args) {
        //无泛型
        List list0=new ArrayList();
        //String泛型
        List<String> list1=new ArrayList<>();
        list1.add("hello");
 
        Class c0=list0.getClass();
        Class c1=list1.getClass();
        //输出
        System.out.println(c0==c1);
    }
}
输出的结果是true,  编译后的class文件也可以当成字节码,说明反射的操作都是编译之后的操作,而且返回true说明编译之后list的泛型被抹去了,去泛型化的,得到Java的泛型是一种规范,只在编译时有效,跳过编译编译就无效了,为了验证这一点,刚好可以使用反射来做一个验证
[Java] 纯文本查看 复制代码
1
2
3
4
5
//获取list1<String> 中的 add方法 ,向里面加一个int类型的值
Method method=c1.getMethod("add",Object.class);
method.invoke(list1,100);
 
System.out.println(list1.size());
 
list1的大小改变了,说明添加成功了,也验证了Java泛型只在编译期有效,运行时则去泛型化,如果去遍历这个list1是会报类型转化异常的
反射的用处有很多,比如工具类,源码理解,注解解析等等,再例如excel导出导入这样的操作,网上也有非常多的poi操作案例,也可以用反射+注解的方式非常简洁的实现; 例如spring源码中很多的注解@Autowired、@SpringCloudApplication、@Service...等等很多很多
 
 



  •  

1.png (50.37 KB, 下载次数: 0)

 

1.png
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章