Java中的反射

反射

今天我来分享下, 我关于Java中反射的理解。如果做过iOS开发的同学应该很清楚iOS里Runtime的黑魔法, 而Java中的反射其实就是iOS中的Runtime.

什么是反射

反射是一种专门为静态语言提供的技术,用于在程序运行时(Runtime)动态的修改程序的结构,改变程序的行为.

Java为什么要引入反射

Java也是静态语言.为了让Java语言也能够在运行时修改类或者对象状态和改变类或对象行为因此引入了反射机制.

静态语言中,使用一个变量时,必须知道它的类型.在Java中,变量的类型信息在编译时都保存到了class文件中,这样在运行时才能保证准确无误.换句话说,程序在运行时的行为都是固定的.如果想在运行时改变,就需要反射这东西了.举个例子:

在Spring中,有这样的java bean配置:

<bean id="someID" class="com.sweetcs.AppleBean">
    <property name="someField" value="someValue" />
</bean>

Spring在处理这个bean标签时,发现class属性指定的是com.sweetcs.AppleBean这个类,就会调用Class.forName(String)来实例化这个类,再通过反射,可以取到someField属性的值了.
如果我们想改变这个程序运行时的信息,我们这里直接修改bean,property的属性即可,无需重新编译.

动态语言中,使用变量不需要声明类型,因而不需要这反射这种机制
比如在javascript中,我们知道有个变量applebean,不管applebean有没有sell()属性,我们都可以这么写:applebean.sell()
因为没有类型检查,这里这么写是允许的。至于在运行时报不报错,就要看运行时applebean的真正值了。

一点思考

一、反射是可在运行期间确定对象的类型, 多态也是在运行期间才确定类型, 那么多态的实现是否和反射有关?

  • 多态的技术上的实现是方法后期的动态绑定, 即在运行时才决定方法应该绑定到那块内存中(该内存即对应相应的对象)。
  • 反射技术上的实现是基础是Java中的方法区的class,有的语言把它们称为类对象.Java中的类都是有类对象创建的,我们可以通过类对象来管理我们创建的所有对象.
  • 说一说多态的实现本质上和反射并没有关系.

二、为什么Java也能够实现反射,加入我们在编译期间就确定了真实对象类型还能使用反射吗?

  • Java在自建立以来就有反射技术, 其反射技术和多态主要基于JVM在运行时才动态加载类或调用方法/访问属性的机制.该机制不需要事先(写代码的时候或编译期)知道真实的运行对象是谁.
  • 而反射是基于Class对象,我们每编写的一个类,都会被编译成字节码文件,将类信息存储在这个字节码文件中,该文件即Class对象,在我们使用到这个Class的静态成员变量或者成员时它就会加载进JVM.
  • 多态主要是基于方法的动态绑定,前提是JVM在运行时才确定对象的真实类型,这时候才去绑定方法到真实的对象上.

反射机制的作用

我们先了解下反射究竟能做哪些事情, 可以分为以下四类.

  • 在运行期间可以动态的创建任意类型的对象
  • 在运行期间可以获取任意的类型的任意信息
  • 在运行期间可以获取某个对象某个属性的值或设置某个属性的值
  • 在运行期间可以调用某个对象的任意方法

Class对象

要学习反射, 首先要学习Class对象,Class对象表示的是 我们类的类信息(我们编写的类,).
比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息.
实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值.

获取Class对象

类名.class

这种类型表明编译期间就能确定类型。仅限于编译期间这个类型是已知的。

@Test
public void test1(){
    Class c1 = int[].class;
    Class c2 = int[].class;
    System.out.println(c1 == c2);

    Class c3 = int[][].class;
    System.out.println(c1 == c3);

    Class c4 = byte[].class;
    System.out.println(c1 == c4);
}

对象.getClass()

获取某个对象的运行时类型

@Test
public void test2(){
    Object obj = new TestClass();
    Class class1 = obj.getClass();
    System.out.println(class1);
}

Class.forName(全限定类名)

使用properties配置文件, 动态配置运行时要创建的类.

@Test
public void test3() throws Exception{
    Properties pro = new Properties();
    //文件在src下,最终在bin等类路径(.class文件)下,可以用ClassLoader加载这个文件
    //文件在src外面,只能使用FileInputStream来加载
    pro.load(new FileInputStream("type.properties"));
    String name = pro.getProperty("typename");

    //这句代码可能会发生:ClassNotFoundException,类型找不到异常
    Class clazz = Class.forName(name);
    System.out.println(clazz);
}

类加载器.loadClass(全限定类名)

@Test
public void test4()throws Exception{
    Properties pro = new Properties();
    //文件在src下,最终在bin等类路径(.class文件)下,可以用ClassLoader加载这个文件
    //文件在src外面,只能使用FileInputStream来加载
    pro.load(new FileInputStream("type.properties"));
    String name = pro.getProperty("typename");

    //获取系统类加载器对象
    ClassLoader c = ClassLoader.getSystemClassLoader();
    Class loadClass = c.loadClass(name);
    System.out.println(loadClass);
}
类加载器应用场景
  • 主要应用于.class文件加密, 需要自身的类加载器来解密
  • .class文件是特殊路径,系统不知道,只能用类加载器

注意事项

  • 提供的类名是全限定类名(Class.forName或者类加载器获取类对象)
  • Class.forName可能抛出ClassNotFoundException

使用场景

(一) 创建任意类型对象

方式一 使用Class对象.newInstance(parameters)

步骤
  • 获取这个类型的类对象
  • 通过这个类对象.newInstance()创建出对应对象.
    • 这个类型必须有一个公共的无参构造器.
示例代码
@Test
public void testNewInstanceMethod1_2() {
    FileInputStream fis = null;
    try {
        fis = new FileInputStream("properties.properties");
        Properties properties = new Properties();
        properties.load(fis);

        String className =  properties.getProperty("student");
        Class classObject = Class.forName(className);

        Object stu = classObject.newInstance();
        System.out.println(stu);

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } finally {

        if (null != fis) {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

@Test
public void testNewInstanceMethod1_1() {

    FileInputStream fis = null;
    try {
        fis = new FileInputStream("properties.properties");
        Properties properties = new Properties();
        properties.load(fis);

        String className =  properties.getProperty("typename");
        Class classObject = Class.forName(className);
        String str = (String) classObject.newInstance();

        System.out.println(classObject);

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } finally {

        if (null != fis) {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
特点
  • 这个类型必须有一个公共的无参构造
    • 非公共的:IllegalAccessException
      没有无参构造:NoSuchMethodException 类型<init>()
  • 这个类型在运行期间必须存在
    • 不存在 ClassNotFoundException

方式二 使用构造器对象.newInstance(parameters)

步骤
  • 得到这个类型的class对象

  • 通过class对象, 获取构造器对象。

    • 可能有,可能没有

      • 如果构造器的访问权限不允许,那么可以使用如下方法,使得它可以被访问

      构造器对象.setAccessible(true)

  • 使用构造器对象创建对应的类型。

    • 构造器对象.newInstance(...)
示例代码
    @Test
    public void testNewInstanceMethod2_1() {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("properties.properties");
            Properties properties = new Properties();
            properties.load(fis);

            String className =  properties.getProperty("student");
            Class classObject = Class.forName(className);

            Constructor constructor = classObject.getDeclaredConstructor(String.class, int.class);

            if (!constructor.isAccessible())
                constructor.setAccessible(true);
            Object o = constructor.newInstance("SweetCS", 26);
            System.out.println(o);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } finally {

            if (null != fis) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
特点
  • 如果够照的访问权限是不允许的, 可以通过调用构造器对象的setAccessible解决
    • 如果没有会抛出 IllegalAccessException

(二)获取类的任意信息

通过这个Class对象可以获取这个类型的所有的信息
包、类名、修饰符、父类、接口们、成员们:属性、构造器、方法、注解信息(必须是RetentionPolicy.RUNTIME

  • 关于属性:
    修饰符、数据类型、属性名、注解信息
  • 关于构造器
    属性能获取+ 2(形参列表、抛出的异常列表)
  • 关于方法
    属性能获取 + 3(返回值类型、形参列表、抛出的异常列表)
public class TestClassInfo {

    @Test
    public void testClassInfo() throws ClassNotFoundException {

        Class managerClass = Class.forName("bean.Manager");

        // 获取属性信息 (访问控制符(Modifilers) 类型 属性名称)
        System.out.println("fileds: ");
        Field[] fields = managerClass.getDeclaredFields();
        System.out.println("modifiler\ttype\tpropertyName");
        for (int i= 0; i <fields.length; i++) {
            Field field = fields[i];
            System.out.print(
                "|-"+ Modifier.toString(  + field.getModifiers()) + "-|" + "\t" +
                "|-"+  field.getType() + "-|" + "\t" +
                "|-"+ field.getName() + "-|"+ "\t");

            System.out.println();

            // 获取注解
            String annotations =  StringUtils.join(field.getDeclaredAnnotations(), ",");
            if (StringUtils.trimToNull(annotations) != null)
                System.out.println("方法上的注解们:" + annotations);
        }

        // 构造器比属性少了一个类型
        System.out.println("construtors: ");
        Constructor[] constructors = managerClass.getConstructors();
        System.out.println("访问控制符\t构造器名称");
        for (Constructor c :
             constructors) {
            System.out.println("|-" + Modifier.toString(c.getModifiers()) + "\t" + "-|" + "|-" +  c.getName()  + "-|" );

            String exceptions = StringUtils.join(c.getExceptionTypes(), ",");
            if (StringUtils.trimToNull(exceptions) != null)
                System.out.println("构造器上的异常:" + exceptions);

        }


        // Method 就比属性多了一个返回类型
        System.out.println("methods: ");
        Method[] methods =  managerClass.getDeclaredMethods();
        for (Method m :
             methods) {

            System.out.println("|-" + Modifier.toString(m.getModifiers()) + "-|"+ "\t"  +
                               "|-" + m.getReturnType() +  "-|"+"\t" +
                               "|-" +  m.getName() +"-|"+
                               "("+ StringUtils.join(m.getParameterTypes(), ",") + ")");


            // 获取注解
            String annotations =  StringUtils.join(m.getDeclaredAnnotations(), ",");
            if (StringUtils.trimToNull(annotations) != null)
                System.out.println("方法上的注解们:" + annotations);
            String exceptions = StringUtils.join(m.getExceptionTypes(), ",");
            if (StringUtils.trimToNull(exceptions) != null)
                System.out.println("构造器上的异常:" + exceptions);
        }

        // 其他
        // 获取包名
        Package p = managerClass.getPackage();
        System.out.println("包名: " + p.getName());

        // 类访问修饰符
        String modifierName = Modifier.toString(managerClass.getModifiers());
        System.out.println("类访问修饰符:" + modifierName);


        // 父类
        Class superClass = managerClass.getSuperclass();
        System.out.println("父类:" + superClass);

        // 获取实现的接口
        Class[] interfaces = managerClass.getInterfaces();
        for (Class i:
             interfaces) {
            System.out.println("接口:" + i);
        }
    }
}

输出

fileds:
modifiler type propertyName
|-private static final-| |-long-| |-serialVersionUID-|
|-private-| |-double-| |-bonus-|
construtors:
访问控制符 构造器名称
|-public -||-day23.bean.Manager-|
|-public -||-day23.bean.Manager-|
methods:
|-public-| |-class java.lang.String-| |-toString-|()
|-public-| |-int-| |-compareTo-|(class day23.bean.Manager)
|-public volatile-| |-int-| |-compareTo-|(class java.lang.Object)
|-public-| |-double-| |-getBonus-|()
|-public-| |-void-| |-setBonus-|(double)
方法上的注解们:@day23.bean.MyAnnotation(value=SweetCS)
包名: day23.bean
类访问修饰符:public final
父类:class day23.bean.Employee
接口:interface java.io.Serializable
接口:interface java.lang.Comparabl

(三) 动态设置和获取属性值

@Test
public void testFiled() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
    //假设Manager对象是在运行期间创建的
    Class mangerClass = Class.forName("day23.bean.Manager");

    //obj代表一个经理对象
    Object obj = mangerClass.newInstance();

    //2、设置obj这个经理的奖金值
    //(1)先得到奖金属性对象
    Field bounsField = mangerClass.getDeclaredField("bonus");                                    //"bonus"配置文件中配置

    //(2)设置属性可操作,private属性既不能使用反射直接setter也不能直接getter
    if (!bounsField.isAccessible()) bounsField.setAccessible(true);
    System.out.println("bonus :" + bounsField.get(obj));
    //(3)设置奖金值
    bounsField.set(obj, 1000.0);

    //3、获取obj这个经理的奖金值
    System.out.println("bonus :" + bounsField.get(obj));
}

反射进行属性操作终结

  • 通过类对象获取属性对象(Field)
    • 最好用getDeclaredField来获取属性对象, 该方法私有属性也能获取
  • 如果是读属性, 调用 属性对象.get(实例对象)
  • 如果是写属性, 调用 属性对象.set(实例对象, 参数)

注意: 如果属性不可访问, 设置属性为accessible, 即可访问

(四) 方法调用

反射技术还可以通过类对象来获取这个对象所有的方法对象。java中方法对象使用Method表示。所以我们可以通过类对象拿到Method对象, 来进行方法的调用。

@Test
public void testMethodInvoke() throws Exception {
    // 假设Manager对象是在运行期间创建的
    Class clazz = Class.forName("day23.bean.Manager");// 这个字符串可以从配置文件中获取

    // obj代表一个经理对象
    Object obj = clazz.newInstance();

    //调用obj经理对象的setEId,setEName,setSalary等
    //(1)得到setName方法对象
    //参数一:方法名
    //参数二:方法的形参类型列表
    //因为方法可能重载,所以需要用“方法名 + 形参列表”
    //"setEname", String.class通常也在xml文件中配置
    //getMethod可以得到这个类型的公共的方法包括从父类继承的
    Method method = clazz.getMethod("setEname", String.class);//

    //(2)调用这个方法
    //参数一:哪个对象的method方法
    //参数二:该method方法需要的实参列表
    //invoke方法的返回值,就是method方法调用后的返回值,如果method方法没有返回值,那么为null
    Object returnValue = method.invoke(obj, "张三");
    System.out.println(returnValue);

    //      (1)得到getName方法对象
    Method method2 = clazz.getMethod("getEname");
    //(2)调用
    Object value = method2.invoke(obj);
    System.out.println(value);
}

反射调用方法总结

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