每日一篇JavaJava 杂谈java基础

Java中的反射

2018-08-06  本文已影响76人  sixleaves

反射

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

Java为什么要引入反射

说java是强类型是因为java的每种数据都需要声明类型。并且java会在编译期间进行引用类型检查。

实际开发中,不是所有对象都可以在编译期间确定类型。Java希望学习动态语言的特点,在运行期间去确定对象的类型,获取对象的信息,Java引入反射机制就是为了实现这个目标,所以Java又被称为准动态语言。

一点思考

一、静态语言和反射概念冲突吗

可能你看到这里会有疑惑, 竟然说java是静态语言, 那么它在编译期间就确定了变量的类型, 那么不是和反射的概念相互冲突?

其实这并不冲突, java中有编译期间类型和运行时类型的机制, 也就说java是静态语言说的是其在编译期间的数据类型。反射可以再运行期间获得对象在运行时的真实类型。因此,Java中所有的方法都是通过动态绑定来实现多态的,但如果直接访问某个域,则这个访问就会在编译其进行解析;

二、反射是可在编译期间确定类的类型, 多态也是在编译期间才确定类型, 那么多态的实现是否和反射有关?

多态的技术上的实现是方法后期的动态绑定。而反射技术上的实现是因为Java中的类都是有类对象创建的,我们可以通过类对象来管理我们创建的所有对象。多态的实现本质上和反射并没有关系。他们之前的实现的技术细节我也不是很清楚,留作后续研究

反射机制的作用

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

反射机制的根源

所谓的反射的根源, 就是反射机制实现的依赖和原理。反射主要依赖于java的类对象实现。Java在使用任何类型的时候都会使用ClassLoader将这个类型加载到内存。并且在内存中创建一个唯一一个Class对象来代表该类型。

Class对象

要学习反射, 首先要学习Class对象, 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对象.newInstance(parameters)

步骤
示例代码
@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();
            }
        }
    }
}
特点

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

步骤
示例代码
    @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();
                }
            }
        }
    }
特点

(二)获取类的任意信息

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

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));
}

反射进行属性操作终结

注意: 如果属性不可访问, 设置属性为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);
}

反射调用方法总结

上一篇 下一篇

猜你喜欢

热点阅读