反射

类加载过程

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

类加载

  • 类加载就是指将class文件从硬盘读入内存,并为之创建一个Class对象
  • 任何类被使用时系统都会建立一个Class对象(Class对象就是类的字节码,字节码中包括类的属性、构造、方法等)

连接

  • 验证:是否有正确的内部结构,并和其他类协调一致
  • 准备:负责为类的静态成员分配内存,并设置默认初始化值
  • 解析:将类的二进制数据中的符号引用替换为直接引用

初始化

  • 对象初始化


类加载的时机

    1. 创建类的实例
    1. 使用类的静态变量,或者为静态变量赋值
    1. 使用类的静态方法
    1. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    1. 初始化某个类的子类
    1. 直接使用java.exe命令来运行某个主类

类加载器

  • java.lang.ClassLoader类是类的加载器。

  • 类加载器负责将.class文件加载到内在中,并为之生成对应的Class对象。

类加载后,在JVM的元空间中就有了类的Class对象(类的字节码),Class对象是供我们来使用的。最常用的使用方式是new对象,还可以通过反射来使用Class对象。

反射

AVA反射机制是在<font color='red'>运行时</font>,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象。

Class类

Class类是java.lang包中的类,Class对象表示类的字节码,每个加载到元空间中的.class文件都是一个Class对象。
获得Class对象的三种方法:

  • 通过Object类的getClass()获得对象
    语法:obj.getClass()
User user = new User();
Class clazz = user.getClass();//通过对象,获取User类的Class对象
  • 通过类名获得Class对象
    语法: 类名.class
Class clazz = User.class;//通过类名,获取User类的Class对象
  • 通过Class.forName()获得Class对象
    语法:Class.forName(类路径)
Class clazz = Class.forName("com.sunmer.User");

通过反射,获取构造方法

在反射机制中,把类中的成员(构造方法、成员方法、成员变量)都封装成了对应的类进行表示。其中,构造方法使用类Constructor类表示。可通过Class类中提供的方法获取构造方法:

返回一个构造方法
Constructor<T> getConstructor(类<?>... parameterTypes) 获取public修饰, 指定参数类型所对应的构造方法
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) 获取所有修饰, 指定参数类型所对应的构造方法
  • getConstructor:根据参数匹配,获取public的构造函数
  • getDeclaredConstructor:根据参数匹配,获取构造函数,包括private的
返回多个构造方法
Constructor<T>[] getConstructors() 获取public修饰的所有构造方法
Constructor<T>[] getDeclaredConstructors() 获取所有修饰的构造方法
  • getConstructors:获取public的所有构造函数
  • getDeclaredConstructors:获取所有的构造函数,包括private的

如下:

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //获取Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        System.out.println("---获取所有的构造方法,包含private的---");
        Constructor[] cons1 = c.getDeclaredConstructors();
        for (Constructor con : cons1) {
            System.out.println(con);
        }
        System.out.println();

        System.out.println("---获取所有的构造方法,不包含private的---");
        Constructor[] cons2 = c.getConstructors();
        for (Constructor con : cons2) {
            System.out.println(con);
        }
        System.out.println();

        System.out.println("---获取一个无参构造方法---");
        Constructor con3 = c.getConstructor(null);
        System.out.println(con3);
        System.out.println();

        System.out.println("---获取一个String参数的构造方法---");
        Constructor con4 = c.getConstructor(String.class);
        System.out.println(con4);
        System.out.println();

        System.out.println("---获取private的,有String和int参数的构造方法---");
        Constructor con5 = c.getDeclaredConstructor(String.class, int.class);
        System.out.println(con5);
        System.out.println();

        System.out.println("---获取private的,有String、int、String参数的构造方法---");
        Constructor con6 = c.getDeclaredConstructor(String.class, int.class, String.class);
        System.out.println(con6);
    }
}

通过反射,获取构造方法,创建对象

步骤如下:

  1. 获取到Class对象
  2. 获取指定的构造方法
  3. 通过构造方法类Constructor中的方法,创建对象
    public T newInstance(Object... initargs)
/*
 * 通过反射,获取构造方法,创建对象
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        //2:获取构造方法
        Constructor con = c.getConstructor(String.class, int.class, String.class);

        //3,通过构造方法,创建对象
        Object obj = con.newInstance("扈三娘", 22, "扈家庄");

        //4:显示对象
        System.out.println(obj);
    }
}

运行结果

带有String, int, String的构造方法
Person [name=扈三娘, age=22, address=扈家庄]

通过反射,获取私有构造方法,创建对象

步骤如下:

  1. 获取到Class对象

  2. 获取指定的构造方法

  3. 暴力访问, 通过setAccessible(boolean flag)方法

  4. 通过构造方法类Constructor中的方法,创建对象

    public T newInstance(Object... initargs)

/*
 * 通过反射,获取私有构造方法,创建对象
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        //2:获取私有构造方法
        Constructor con = c.getDeclaredConstructor(String.class, int.class);

        //3:暴力访问, 取消 Java 语言访问检查,因为默认private不允许类外访问
        con.setAccessible(true);

        //4:通过构造方法,创建对象
        Object obj = con.newInstance("扈三娘", 22);

        //5:显示对象
        System.out.println(obj);
    }
}

运行结果

带有String,int的构造方法
Person [name=扈三娘, age=22, address=null]

通过反射,获取成员变量

在反射机制中,把类中的成员变量使用类Field表示。可通过Class类中提供的方法获取成员变量:

返回一个成员变量
Field getField(String name) 获取指定的 public修饰的成员变量
Field getDeclaredField(String name) 获取指定的任意成员变量
返回多个成员变量
Field[] getFields() 获取所有public 修饰的成员变量
Field[] getDeclaredFields() 获取所有的成员变量 (包含私有)

代码演示:

/*
 * 通过反射,获取成员变量
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        System.out.println("---获取多个成员变量,包含private的---");
        Field[] fields1 =  c.getDeclaredFields();
        for (Field field : fields1) {
            System.out.println(field);
        }
        System.out.println();

        System.out.println("---获取多个public的成员变量---");
        Field[] fields2 = c.getFields();
        for (Field field : fields2) {
            System.out.println(field);
        }
        System.out.println();

        System.out.println("---获取一个public的成员变量---");
        Field ageField = c.getField("age");
        System.out.println(ageField);
        System.out.println();

        System.out.println("---获取private的成员变量---");
        Field addressField = c.getDeclaredField("address");
        System.out.println(addressField);

    }
}

通过反射,对成员变量进行赋值与取值操作

步骤如下:

  1. 获取Class对象
  2. 获取构造方法
  3. 通过构造方法,创建对象
  4. 获取指定的成员变量(私有成员变量,通过setAccessible(boolean flag)方法暴力访问)
  5. 通过方法,给指定对象的指定成员变量赋值或者获取值
    • public void set(Object obj, Object value)
      在指定对象obj中,将此 Field 对象表示的成员变量设置为指定的新值
    • public Object get(Object obj)
      返回指定对象obj中,此 Field 对象表示的成员变量的值

代码演示:

/*
 * 通过反射,对成员变量进行赋值与取值操作
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        //2:获取构造方法
        Constructor con = c.getConstructor(String.class);

        //3:通过构造方法,创建对象
        Object obj = con.newInstance("孙二娘");

        //4:获取指定的成员变量
        Field nameField = c.getField("name");
        Field ageField = c.getField("age");
        Field addressField = c.getDeclaredField("address");
        addressField.setAccessible(true);

        //5:通过方法,从指定对象的指定成员变量取值
        System.out.println("-------------赋值前-----------");
        System.out.println("name = "+ nameField.get(obj));
        System.out.println("age = "+ ageField.get(obj));
        System.out.println("address = "+ addressField.get(obj));

        //5:通过方法,为指定对象的指定成员变量赋值
        ageField.set(obj, 23);
        addressField.set(obj, "十字坡");

        System.out.println("-------------赋值后-----------");
        System.out.println("name = "+ nameField.get(obj));
        System.out.println("age = "+ ageField.get(obj));
        System.out.println("address = "+ addressField.get(obj));
    }
}

通过反射,获取成员方法

在反射机制中,把类中的成员方法使用类Method表示。可通过Class类中提供的方法获取成员方法。

返回一个成员方法
Method getMethod(String name, Class<?>... parameterTypes) 获取public 修饰的方法
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取任意的方法,包含私有的
返回多个成员方法
Method[] getMethods() 获取本类与父类中所有public 修饰的方法
Method[] getDeclaredMethods() 获取本类中所有的方法(包含私有的)

如下:

/*
 * 通过反射,获取成员方法
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        System.out.println("---获取本类定义的所有的方法,包括private的---");
        Method[] methods1 = c.getDeclaredMethods();
        for (Method method : methods1) {
            System.out.println(method);
        }
        System.out.println();

        System.out.println("---获取本类及父类所有public的方法---");
        Method[] methods2 = c.getMethods();
        for (Method method : methods2) {
            System.out.println(method);
        }
        System.out.println();

        System.out.println("---获取无参方法---");
        Method method1 = c.getMethod("method1", null);
        System.out.println(method1);
        System.out.println();


        System.out.println("---获取有一个String参数的方法---");
        Method method2 = c.getMethod("method4", String.class);
        System.out.println(method2);
        System.out.println();

        System.out.println("---获取私有方法---");
        Method method3 = c.getDeclaredMethod("method5", null);
        System.out.println(method3);
    }
}

通过反射,创建对象,调用指定的方法

步骤如下:

  1. 获取Class对象

  2. 获取构造方法

  3. 通过构造方法,创建对象

    1. 获取指定的方法
    2. 执行找到的方法
      • public Object invoke(Object obj, Object... args)

    执行指定对象obj中,当前Method对象所代表的方法,方法要传入的参数通过args指定。

代码演示:

/*
 * 通过反射,创建对象,调用指定的方法
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        //2:获取构造方法
        Constructor con = c.getConstructor(String.class, int.class, String.class);

        //3:通过构造方法,创建对象
        Object obj = con.newInstance("扈三娘", 22, "扈家庄");

        //4:获取指定的方法
        Method method = c.getMethod("method4", String.class);

        //5:执行找到的方法
        //method.invoke(obj, null);//若方法无参,则传入null

        Object result = method.invoke(obj, "张清");
        System.out.println("result = " + result);
    }
}

运行结果

带有String, int, String的构造方法
有返回值,有参数的方法
result = 返回值:张清

通过反射,创建对象,调用指定的private 方法

步骤如下:

  1. 获取Class对象
  2. 获取构造方法
  3. 通过构造方法,创建对象
  4. 获取指定的方法
  5. 开启暴力访问
  6. 执行找到的方法
    • public Object invoke(Object obj, Object... args)
      执行指定对象obj中,当前Method对象所代表的方法,方法要传入的参数通过args指定。
/*
 * 通过反射,创建对象,调用指定的 private 方法
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        //2:获取构造方法
        Constructor con = c.getConstructor(String.class, int.class, String.class);

        //3:通过构造方法,创建对象
        Object obj = con.newInstance("扈三娘", 22, "扈家庄");

        //4:获取指定的方法
        Method method = c.getDeclaredMethod("method5", null);

        //5:开启暴力访问
        method.setAccessible(true);

        //6,执行找到的方法
        method.invoke(obj, null);
    }
}

泛型擦除

分析:泛型集合在添加元素时,是有类型检查的,字符串是不能添加到 Integer泛型的集合中的。

泛型集合的类型检查是在源码中进行的,在.class中已经没有泛型约束了,这种现象我们称为泛型的擦除。那么,我们可以通过反射技术,来完成向有泛型约束的集合中,添加任意类型的元素。

**如下:**
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        ArrayList<Integer> list = new ArrayList<Integer>();
        //添加元素到集合
        list.add(new Integer(110));
        list.add(new Integer("120"));
        list.add(119);
        //list.add("时迁");//报错,因为有泛型类型的约束
        System.out.println(list);

        //通过反射技术,实现添加任意类型的元素
        //1:获取字节码文件的Class对象
        Class c = list.getClass();

        //2:找到add()方法
        Method addMethod = c.getMethod("add", Object.class);

        //3:执行add()方法
        addMethod.invoke(list, "时迁");// list.add("时迁");
        
        //4:显示集合内容
        System.out.println(list);
        System.out.println(list.get(3));
    }
}

[110, 120, 119]
[110, 120, 119, 时迁]
时迁

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容