Java知识整理之 反射技术

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三步来实现这个类,从而进行初始化。

目录

 

一、类加载的过程

二、反射概述

三、获取字节码文件

四、Class对象的相关方法

4-1 Constructor类

4-1-2 Constructor类中的常用方法

4-2、Method类

4-2-2 Method类中的方法

五、利用反射来证实泛型的擦除

六、类加载器

总结:


一、类加载的过程

1、加载

       就是指将class文件读入内存,并·为之创建一个Class对象

       任何类被使用时都会建立一个且仅有一个Class对象

2、连接

       验证是否有正确的内部结构,并和其他类协调一致

       准备负责为类的静态成员分配内存,并设置默认初始化值

       解析将类的二进制数据中的符号引用替换为直接引用

3、初始化

       就是初始化,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析,到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。

字节码文件:每个类编译后都是class文件,class文件 包含这个类的所有内容,我们将这种class文件称为字节码文件。

关于java.lang.Class 由于java是面向对象语言,任何事物都可以用类来描述,字节码文件也属于事物,java中有一个类,专门描述了所有的字节码文件。这个类就是java.lang.Class。这个类的对象,不需要我们手动创建,由JVM虚拟机,自动创建 当类一加载到内存中,JVM虚拟机就会创建对应得对象。

二、反射概述

反射是框架设计的灵魂,将JAVA代码的各个组成部分封装为其他对象,可以在程序运行过程中操作这些对象,如图(2-1)。

图(2-1) 

从图中可以看出,使用反射,实际上就是操作类对应的字节码文件,怎么获取对应类的字节码文件呢?

三、获取字节码文件

我们先定义一个User对象(3-1)

package com.qq.heronsbill.bean;

/**
 * @Author: ${user}
 */
public class User {
    private  int id;
    private String username;
    private String password;

    public User() {
    }

    public User(int id,String username, String password) {
        this.id = id ;
        this.username = username;
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

获取字节码文件的常使用方法有三种

获取Class对象的方式作用使用场景
对象.getClass()通过对象的getClass()方法获取多用于对象的获取解码方式
类名.class通过类名的属性获取多用于参数传递
Class.forName("全类名")通过指定路径的字符串获取多用于配置文件,将类名写在配置文件中,读取配置文件来加载类

注:因为一个类只会被加载一次,那么磁盘中就只有一个对应类的字节码文件,所以,以上三种方式获取的字节码文件为同一字节码文件。

四、Class对象的相关方法

String getSimpleName(); 获得简单类名,只是类名,没有包 .

String getName(); 获取完整类名,包含包名+类名

public class App {
    public static void main(String[] args) throws NoSuchMethodException {
        //获取User对象的字节码文件对象
        Class<User> userClass = User.class;

        //方法演示
        String simpleName = userClass.getSimpleName();
        String name = userClass.getName();
        //打印输出
        System.out.println("getSimpleName()方法获取类名:"+ simpleName);
        System.out.println("getSimpleName()方法获取全类名:"+ name);

    }
}

打印结果:

 getSimpleName()方法获取类名:User
getSimpleName()方法获取全类名:com.qq.heronsbilll.User

4-1 Constructor类

Constructor是构造方法类,类中的每一个构造方法都获取一个Constructor对象,通过Constructor对象可以实例化对象

 1、Constructor[] getConstructors()  获取所有的public修饰的构造方法 返回一个Constructor类型的数组,

2、Constructor getConstructor(Class... parameterTypes)  返回一个Constructor对象,参数类型对应的是原对象构造方法里面对应参数的类型的字节码对象,如果没有参数,则指向空参构造。

 

 

public class AppConstructor {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        //获取User对象的字节码文件对象
        Class<User> userClass = User.class;

        //方法演示

        Constructor<User>[] constructors = (Constructor<User>[]) userClass.getConstructors();
        Constructor<User> constructor = userClass.getConstructor(int.class, String.class, String.class);

        System.out.println("getConstructors()方法获取所有的public修饰的构造方法"+Arrays.toString(constructors));
        System.out.println("getConstructor(Class... parameterTypes)方法获取对应参数类型的构造方法"+constructor);

    }
}

打印结果

getConstructors()方法获取所有的public修饰的构造方法[public com.qq.heronsbilll.User(), public com.qq.heronsbilll.User(int,java.lang.String,java.lang.String)]
getConstructor(Class... parameterTypes)方法获取对应参数类型的构造方法public com.qq.heronsbilll.User(int,java.lang.String,java.lang.String)
 

4-1-2 Constructor类中的常用方法

在 获取对应的Constructor对象后,可以再使用newInstanc(Object... initargs)方法来实例化相关对象

1. T newInstance(Object... initargs)  根据指定参数创建对象。这里的参数是实例化对象时传入的参数
2. T newInstance()  空参构造方法创建对象。

注:当一个对象含有空参的public类型的构造方法时,可以直接通过 Class.newInstance()的方法快捷实例化对象,但是这个方法在JDK9以后就过时了。

4-2、Method类

Method是方法类,类中的每一个方法都是Method的对象,通过Method对象可以调用对应方法。和Constructor类相同,Method类有两个方法,一个是获取全部方法的getMethods()方法,另一个是获取指定方法的getMethod("方法名", 方法的参数类型... 类型)方法

1. Method[] getMethods()  获取所有的public修饰的成员方法,包括父类中的方法。

2. Method getMethod("方法名", 方法的参数类型... 类型)   根据方法名和参数类型获得一个方法对象,只能是获取public修饰的方法

public class AppMethod {
    public static void main(String[] args) throws NoSuchMethodException {
        //获取User对象的字节码文件对象
        Class<User> userClass = User.class;

        //方法演示
        Method[] methods = userClass.getMethods();
        Method setId = userClass.getMethod("setId", int.class);


        System.out.println("getMethods()方法获取User类中的所有方法,包括父类");
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("getMethod()方法获取参数为int类型的setId方法:"+setId);
    }
}

打印结果

getMethods()方法获取User类中的所有方法,包括父类
public java.lang.String com.qq.heronsbilll.User.toString()
public int com.qq.heronsbilll.User.getId()
public java.lang.String com.qq.heronsbilll.User.getPassword()
public void com.qq.heronsbilll.User.setId(int)
public void com.qq.heronsbilll.User.setPassword(java.lang.String)
public java.lang.String com.qq.heronsbilll.User.getUsername()
public void com.qq.heronsbilll.User.setUsername(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
-----------------------------------------------------
getMethod()方法获取参数为int类型的setId方法:public void com.qq.heronsbilll.User.setId(int)

4-2-2 Method类中的方法

获取对应大方法对象之后,可以使用Method类中的invoke(Object obj, Object... args)方法,调用相关方法

Object invoke(Object obj, Object... args)  根据参数args调用对象obj的该成员方法,如果obj=null,则表示该方法是静态方法

        Method getId = userClass.getMethod("getId");
        User obj = userClass.newInstance();
        setId.invoke(obj,1);
        System.out.println("打印obj对象:"+obj);
        Object invoke = getId.invoke(obj);
        System.out.println("获取obj对象的id值:"+invoke);

 打印结果:

打印obj对象:User{id=1, username='null', password='null'}
获取obj对象的id值:1

 

 

五、利用反射来证实泛型的擦除

java中的泛型是伪泛型,java为了提高效率,设计泛型仅在编译时有效,而编译后的class文件中是不存在泛型的,如果通过反射技术获取到对应类的字节码文件,将不会受到泛型的限制,下面进行代码演示。

public class GenericityApp {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //创建Integer类型的ArrayList集合
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        //不能添加int以外的数据
        //list.add("aaa");


        //获取ArrayList的字节码文件对象
        Class listClass = list.getClass();
        //获取所有方法,并找到add
        Method[] methods = listClass.getMethods();
        for (Method method : methods) {

            System.out.println(method);
        }
        System.out.println("-----------------------------------------------------");

        //两个add方法参数都是Object类型的(其中一个方法需要加int类型的参数)
        // public void java.util.ArrayList.add(int,java.lang.Object)
        //public boolean java.util.ArrayList.add(java.lang.Object)

        //反射出ArrayList的add方法
        Method add = listClass.getMethod("add", Object.class);

        add.invoke(list,"字符串类型");
        add.invoke(list,true);
        add.invoke(list,'a');
        add.invoke(list,new User(1,"xiaohua","1111"));

        System.out.println("list集合:"+list);


    }
}

打印结果:

public boolean java.util.ArrayList.add(java.lang.Object)
public void java.util.ArrayList.add(int,java.lang.Object)
public boolean java.util.ArrayList.remove(java.lang.Object)
public java.lang.Object java.util.ArrayList.remove(int)
public java.lang.Object java.util.ArrayList.get(int)
public java.lang.Object java.util.ArrayList.clone()
public int java.util.ArrayList.indexOf(java.lang.Object)
public void java.util.ArrayList.clear()
public boolean java.util.ArrayList.isEmpty()
public int java.util.ArrayList.lastIndexOf(java.lang.Object)
public boolean java.util.ArrayList.contains(java.lang.Object)
public void java.util.ArrayList.replaceAll(java.util.function.UnaryOperator)
public int java.util.ArrayList.size()
public java.util.List java.util.ArrayList.subList(int,int)
public java.lang.Object[] java.util.ArrayList.toArray()
public java.lang.Object[] java.util.ArrayList.toArray(java.lang.Object[])
public java.util.Iterator java.util.ArrayList.iterator()
public java.util.Spliterator java.util.ArrayList.spliterator()
public boolean java.util.ArrayList.addAll(int,java.util.Collection)
public boolean java.util.ArrayList.addAll(java.util.Collection)
public java.lang.Object java.util.ArrayList.set(int,java.lang.Object)
public void java.util.ArrayList.forEach(java.util.function.Consumer)
public void java.util.ArrayList.ensureCapacity(int)
public void java.util.ArrayList.trimToSize()
public boolean java.util.ArrayList.retainAll(java.util.Collection)
public boolean java.util.ArrayList.removeAll(java.util.Collection)
public boolean java.util.ArrayList.removeIf(java.util.function.Predicate)
public void java.util.ArrayList.sort(java.util.Comparator)
public java.util.ListIterator java.util.ArrayList.listIterator(int)
public java.util.ListIterator java.util.ArrayList.listIterator()
public boolean java.util.AbstractList.equals(java.lang.Object)
public int java.util.AbstractList.hashCode()
public java.lang.String java.util.AbstractCollection.toString()
public boolean java.util.AbstractCollection.containsAll(java.util.Collection)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
public default java.util.stream.Stream java.util.Collection.stream()
public default java.util.stream.Stream java.util.Collection.parallelStream()
-----------------------------------------------------
list集合:[1, 字符串类型, true, a, User{id=1, username='xiaohua', password='1111'}]

结果中可以看到,加Integer泛型约束的list集合再编译后对数据的添加不再限制,无论是字符串类型,Boolean类型,还是对象,都可以添加。

六、类加载器

类加载器是负责加载类的对象。将class文件从硬盘加载到内存中生成Class对象

类加载器的组成

BootstrapClassLoader  根类加载器 也被称作引导类加载器,负责Java核心类的加载 比如System,String等

ExtClassLoalde  扩展类加载器 JDK中JRE的lib目录下ext目录

AppClassLoader 系统类加载器 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径

 

注意:

  1. 所有的类加载器都是java.lang.ClassLoader的子类
  2. 使用 类.class.getClassLoader()可以获得加载自己的类加载器
  3. 类加载器加载机制:全盘负责委托机制
    1. 全盘负责:A类如果要使用B类(不存在),A类加载器必须负责加载B类
    2. 委托机制:A类加载器如果要加载资源B,必须询问父类加载器是否加载、如果加载,将直接使用,如果没有加载机制,再自己加载
    3. 采用全盘负责委托机制保证一个class文件只会被加载一次,形成一个Class对象

 

总结:

反射是框架设计的灵魂,理解了反射技术,对学习框架有很大的帮助。同时反射技术又可以很好的解耦合,对于优化代码有很大的帮助。

能力尚浅,有待进步,如有不足,不吝赐教!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值