zoukankan      html  css  js  c++  java
  • 云笔记项目-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编程的逻辑》

  • 相关阅读:
    hdu6354 杭电第五场 Everything Has Changed 计算几何
    hdu6351 Beautiful Now 杭电第五场 暴力枚举
    牛客多校第六场 J Heritage of skywalkert 随即互质概率 nth_element(求最大多少项模板)
    百度之星资格赛 调查问卷 bitset模板(直接将字符串转化成二进制数组并可以计算出十进制值)
    百度之星资格赛 子串查询 线段树
    牛客多校第五场 F take 期望转化成单独事件概率(模板) 树状数组
    牛客多校第五场 E room 二分图匹配 KM算法模板
    牛客第五场多校 J plan 思维
    idhttp提交post
    centos6.2 shutdown now关机进入单用户模式
  • 原文地址:https://www.cnblogs.com/youngchaolin/p/10515406.html
Copyright © 2011-2022 走看看