Java基础 -------反射机制

经常听到java 反射,自己也看过一些文章和视频,但总是感觉没有很好地理解,所以结合了自己觉得比较好的文章(主要是我能理解的),自己又总结了一下,作为记录。

前言

在这里插入图片描述

    我们都知道,JVM加载的是.class文件。其实,类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
    同时,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器会在程序首次主动使用该类时会生成错误报告(LinkageError错误),如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

举例Object o=new Object();

例如要运行一段代码 Object o=new Object();
其运行过程:

首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码:new Object()。
上面的流程就是你自己写好的代码扔给jvm去跑,跑完就over了,jvm关闭,你的程序也停止了。

理解了上面的过程,再理解反射就比较容易了。

一、反射的概述

    Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。

反射提供的主要功能:

  1. 在运行时判断任意一个对象所属的类;
  2. 在运行时构造任意一个类的对象;
  3. 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  4. 在运行时调用任意一个对象的方法;
  5. 生成动态代理.(下篇再讲)
    重点:是运行时而不是编译时

二、获取class文件对象的方式

反射是对一个类进行解剖,要想解剖一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
再强调一下, 咱们写的代码是存储在后缀名.java文件中的,它会被编译成.class文件,jvm真正执行的是.class文件。Java是面向对象的语言,一切皆为对象,所以java认为编译后的.class文件也是对象,给抽象成了一个类,这个类就是Class。在这里插入图片描述
通过文档了解到,使用forName(String className)方法就可以得到想要反射的类。
在这里插入图片描述
获取class文件对象的有三种方式,具体如下:
(1)使用Class类的forName()静态方法

 Class perClass = Class.forName("com.diligentkong.bean.Person");
System.out.println(perClass);
输出:class com.diligentkong.bean.Person

(2)直接获取某一个对象的class

Person person = new Person();      // new一个Person对象
Class perClass = person.getClass();// 获取Class对象
System.out.println(perClass);
//结果输出:class com.diligentkong.bean.Person

(3)调用某个对象的getClass()方法


Class perClass = Person.class;
System.out.println(perClass);
输出:class com.diligentkong.bean.Person

三、反射的基本运用

1.获取构造器信息

通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:

Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//获取有参构造函数

可以根据传入的参数来调用对应的构造函数。

2.通过反射来生成对象

1 .使用Class对象的newInstance()方法来创建Class对象对应类的实例。

Class perClass = Class.forName("com.diligentkong.bean.Person");
//调用 Person 的无参数构造方法,创建此Class对象所表示的类的一个新实例
Person person = (Person) perClass.newInstance(); 
System.out.println(person);
输出:Person{name='null', age=0}

2.先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。

Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//获取有参构造函数
Person p = (Person) constructor.newInstance("kong",22);
System.out.println(p); //Person{name='kong', age=22}

3.获取方法

获取某个Class对象的方法集合,主要有以下几个方法:
在这里插入图片描述
在这里插入图片描述

1.获取所有的:

  •  public Method[] getMethods():获取所有"公有(public)方法";(包含了父类的方法也包含Object类)
    
  •  public Method[] getDeclaredMethods():方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
    

2.获取单个的: 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。

  •  public Method getMethod(String name,Class<?>... parameterTypes):
    
  • public Method getDeclaredMethod(String name,Class<?>… parameterTypes)
          参数:
               name : 方法名;
                Class … : 形参的Class类型对象

调用方法:
public Object invoke(Object obj,Object… args):
      参数说明:
           obj : 要调用方法的对象;
           args:调用方式时所传递的实参;
实例代码:

public class Person {

    private String name;
    private int age;
    public void  run(){
        System.out.println("I can  run ....");
    }
    protected void eat(int num){
        System.out.println("我今天吃了"+num+"顿大餐,哈哈哈!");
    }

     public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }



}

  public static void main(String[] args) throws Exception {

            //获取Class对象
           Class perClass = Class.forName("com.diligentkong.bean.Person");

            Constructor constructor = perClass.getConstructor(String.class,int.class);//获取有参构造函数
            Person p = (Person) constructor.newInstance("kong",22);
            System.out.println(p); //Person{name='kong', age=22}

            // 获取所有公有方法
            System.out.println("------------------获取所有“公有”方法-------------");
            Method[] methodArray = perClass.getMethods();
            for (Method m: methodArray){
                System.out.println(m);

            }


            System.out.println("----------------获取所有的方法,包括私有的---------");
            Method[] declaredMethods = perClass.getDeclaredMethods();
            for (Method m : declaredMethods){
                m.setAccessible(true); //解除私有限定
                System.out.println(m);
            }

            System.out.println("----------------获取公有的run()方法-------------------");
            Method m = perClass.getMethod("run");
            m.invoke(p);
            System.out.println(m);


            System.out.println("-------------------获取私有的eat()方法----------------------------");
            Method promethod = perClass.getDeclaredMethod("eat",int.class);
            promethod.setAccessible(true);
            promethod.invoke(p,3);

        }

结果:
在这里插入图片描述

4.获取类的成员变量信息

在这里插入图片描述
getFiled:访问公有的成员变量
getDeclaredField:所有已声明的成员变量,但不能得到其父类的成员变量
示例代码:

在Person中加入如下字段:

public int i;
public int j;

 Class perClass = Class.forName("com.diligentkong.bean.Person");

            Constructor constructor = perClass.getConstructor(String.class,int.class);//获取有参构造函数

            Field field =  perClass.getDeclaredField("age");


            System.out.println("-------------访问所有的属性,包括public属性-------");
            Field[] fields = perClass.getDeclaredFields();
            for (Field f: fields){
                System.out.println(f.getName());
            }

            System.out.println("--------------获取public属性--------------");
            Field[] privateFields = perClass.getFields();
            for (Field f: privateFields){
                System.out.println(f.getName());
            }

            System.out.println("--------------获取获取特定的属性:age--------------");
            Field ff = perClass.getDeclaredField("age");
            System.out.println(ff);

            System.out.println("----------------获取特定的属性: i------------");
            Field ff2= perClass.getField("i");
            System.out.println(ff2);
            System.out.println("---------------------");

//实例化这个类,赋值给p1
            Person p1 = (Person) perClass.newInstance();
            //解除私有限定
            field.setAccessible(true);
            // 给p1对象的age属性赋值20
            field.set(p1,20);
            //获取属性age
            System.out.println(""+field.get(p1));
            
        }

结果:
在这里插入图片描述

5.通过反射越过泛型检查

例如:ArrayList的一个对象,在这个集合中添加一个字符串数据,

// 泛型只在编译期有效,在运行期会被擦出掉
ArrayList<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
Class clazz = Class.forName("java.util.ArrayList");
Method m = clazz.getMethod("add",Object.class);
m.invoke(list,"aaa");
System.out.println(list); //[100, 200, aaa]

好了,暂时总结这些,有新的认识再去补充。

参考的博文:
https://www.zhihu.com/question/24304289
https://www.sczyh30.com/posts/Java/java-reflection-1/

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