java之反射(脑图+详解)

在这里插入图片描述

反射

定义

允许程序在执行时借助反射获取类的内部信息,并能直接操作任意对象的内部属性和方法。
我们可以这样理解:一个包,在包下创建一个类,在类中创建对象。
通过一个对象,找到创建该对象的类,通过所在类的声明找到对应的包名。

被视为动态语言的关键

因为反射,java才是准动态语言,不然java属于静态语言。

功能

前提:Person类中定义私有属性age,私有方法showNation。

Class clazz = Person.class;
Field age = clazz.getDeclaredField("age"); // 调用私有属性
clazz.getDeclaredMethod("showNatoin", String.class); // 调用私有方法
showNatoin.setAccessible(true);
showNatoin.invoke(p1,"中国");

运行时的7个功能

  1. 判断任意一个对象所属的类
  2. 构造任意一个类的对象
  3. 判断任意一个类所具有的成员变量和方法、
  4. 调用任意一个对象的成员变量和方法
  5. 获取泛型信息
  6. 处理注解
  7. 生成动态代理

初学反射2个疑问

1. 通过直接new的方式或反射的方式都可以调用公共的结构,开发时用哪个?

编译时,我们还不确定要创建哪些类的对象,此处理解为一个模板,让之后众多对象去套用。
先让后端服务器代码运行启动,此时前端发送给后端数据,后端满足前端发送的需求再去创建对象。

2. 反射机制和面向对象的封装性是否矛盾,如何看待两个技术?

当然不矛盾,这里理解为,封装性是有可能将私有的方法在公共的方法中调用,建议我们去调用公共的方法。比如单例模式,构造器是私有的,我们直接通过封装创建一个对象就可,没必要在调用私有的构造方法。强调建议怎么调用。
反射是可以强制的去调用私有的属性法法,再去做封装性已经封装好的事情。强调能不能调用

java.lang.Class类的理解

类的加载过程:
程序经过javac.exe命令后编译生成一个或多个字节码文件(.class)
接着使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存,这个第二个过程就成为类的加载。*加载在内存中的类,就称为运行时类(作为Class的一个实例),对应着一个运行时类。

万事万物皆对象

以往通过类造对象,现在类也作为大Class的对象
任何一个类都是对象
对于类中的静态结构,直接通过类去调用,而不用创建类的对象去调用。类本身也是一个对象,还是通过对象去调用的。

Class的对象有哪些?

类,接口,数组,枚举,注解,基本数据类型int…,void

@Test
    public void test4(){
        Class<Object> c1 = Object.class;
        Class<Comparable> c2 = Comparable.class;
        Class<String[]> c3 = String[].class;
        Class<int[][]> c4 = int[][].class;
        Class<ElementType> c5 = ElementType.class;
        Class<Override> c6 = Override.class;
        Class<Integer> c7 = int.class;
        Class<Void> c8 = void.class;
        int[] a = new int[10];
        int[] b = new int[100];
        Class<? extends int[]> c10 = a.getClass();
        Class<? extends int[]> c11 = b.getClass();
        System.out.println(c10 == c11);
        System.out.println(c1+""+c2+""+c3+""+c4+""+c5+""+c6+""+c7+""+c8);
    }

获取Class的实例

  1. 类名.class
    Class clazz1 = Person.class;
  2. 类创建的对象名.getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();// 获取这个对象是哪个类创建的
  1. 调用Class的静态方法Class.forName()
    Class clazz3 = Class.forName("com.reflect.Person");
  2. 获取类的加载器对象去加载类getClassLoader().loadClass()
Class clazz4 = Person.class.getClassLoader().loadClass("com.reflect.Person"); // 合并写法

类的加载与类的加载器

此处知识仍需深入理解,仍需深入理解
// TO DO

读取配置文件(两种方法)

配置文件jdbc.properties如下:

username=彤
password=123456
  1. 通过字节码输入流读取配置文件.properties,调用load()方法读取,配置文件默认识别为当前module下,代码如下:
@Test
    /**
     * 法1:读取配置文件,配置文件默认识别为当前module下
     */
    public void test5() throws Exception {
        Properties properties = new Properties();
        FileInputStream fis = new FileInputStream("jdbc.properties");// 此时的文件在当前的模块下module
        properties.load(fis);
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
        System.out.println("用户:"+username+"\t密码:"+password);
    }
  1. 通过获取类的加载器获取源流,调用load()方法读取,配置文件默认识别为当前module的src目录下,而不是当前module下,代码如下:
    /**
     * 读取配置文件的方式2:ClassLoader(),配置文件默认识别为当前module的src目录下,而不是当前module下
     * @throws IOException
     */
    @Test
    public void test6() throws IOException {
        Properties properties = new Properties();
        ClassLoader classLoader = ClassLoader.class.getClassLoader(); // 获取类的加载器
        InputStream inputStream = classLoader.getResourceAsStream("src\\jdbc.properties");
        properties.load(inputStream);
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
        System.out.println(username+"\t"+password);
    }

创建运行时类的对象

调用newInstance()方法正常创建运行时类的对象,须满足

  1. 运行时类必须提供空参的构造器
  2. 空参的构造器的访问权限得够。通常设为public (通过反射创建运行时类的对象,javaBean通常要去提供空参的构造器)
/**
     * 创建运行时类的对象
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    @Test
    public void test1() throws IllegalAccessException, InstantiationException {
        Class<Person> clazz = Person.class;
        // Person类中必须声明公共的空参构造器
        Person obj = clazz.newInstance(); // 调用newInstance()方法创建对应的运行时类的对象,本质上只有构造器才能创建对象
        System.out.println(obj);
    }

反射的动态性

实现:封装一个指定全类名就能创建该类的对象的方法,这里使用随机值switch-case语句来指定可能是哪一个全类名(目的:体现反射的动态性)

@Test
    public void test2() throws Exception {
        int num = new Random().nextInt(3); // 这里随机生成3个数来生成随机的对象
        String classPath = "";
        switch (num){
            case 0:
                classPath = "java.util.Date";
                break;
            case 1:
                classPath = "java.lang.Object";
                break;
            case 2:
                classPath = "com.reflect.Person";
                break;
        }
        System.out.println(num);
        Object obj = getInstance(classPath); // 通过指定类classPath路径名创建类的对象
        System.out.println(obj);
    }
    /**
     * 功能:创建一个指定类的对象
     * @param classPath 指定类的全类名
     * @return
     * @throws Exception
     */
    public Object getInstance(String classPath) throws Exception {
        Class clazz = Class.forName(classPath);
        return clazz.newInstance();
    }

获取运行时类的完整结构

创建一个package,包中创建父类Creature,Person子类,,myInterface接口,MyAnnotatio注解。
Person子类中:声明私有的name、默认类型age,公共的类型id,公共的空参构造器,私有的带参构造器(加注解),默认权限的全参构造器,私有的show()方法(加注解),公共的返回String类型的display()方法,重写MyInterface中的info()方法,重写实现比较器接口的compareTo()方法.
myInterface接口中:定义info()抽象方法
MyAnnotaton注解中:String value() default “hello”;
创建另一个package,包中创建测试类FieldTest,测试如下方法:

  1. getFields():获取当前运行时类及其父类中声明为public访问权限的属性,包含父类中的属性
  2. getDeclaredFields():获取当前运行时类中声明的所有属性(与权限无关,但不包含父类中的属性
  3. getModifiers():权限修饰符
  4. getType():数据类型
  5. getName():变量名
  6. getMethons():获取当前运行时类及其所有父类中声明为public权限的方法
  7. getDeclaredMethods():获取当前运行时类中的所有权限的方法(不包含父类中声明的),Method[] declaredMethods = clazz.getDeclaredMethods(); 遍历declaredMethon,在循环中获取方法声明的注解Annotation[] annotations = m.getAnnotations();,遍历annotations
  8. getModifiers():获取权限修饰符
  9. getReturnType().getName():获取返回值类型
  10. getParameterTypes()得到数组,遍历数组:获取方法名
  11. getExceptionTypes():获取异常类型
  12. getConstructors():获取房钱运行时类中声明为public的构造器
  13. getDeclaredConstructors():获取当前运行时类中声明的所有属性构造器
  14. getSuperclass():获取运行时类的父类
  15. getGenericSuperclass():获取的父类带泛型
  16. getGenericSuperclass强转为ParameterizedType接口,再调用getActualTypeArguments()得到数组,遍历数组,代码如下:
@Test
    public void test3(){
        Class<Person> clazz = Person.class;
        // 获取带泛型的父类
        Type superclass = clazz.getGenericSuperclass();
        System.out.println(superclass);
        System.out.println();
        // 获取带泛型的父类 的泛型
//        Type genericSuperclass = clazz.getGenericSuperclass();
//        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        ParameterizedType paramType = (ParameterizedType) clazz.getGenericSuperclass();
        Type[] actualTypeArguments = paramType.getActualTypeArguments(); // 获取泛型类型
        for(Type t : actualTypeArguments){
            System.out.println(t.getTypeName());
        }
    }
  1. getInterfaces():获取运行时类的接口,应用场景:动态代理,代码如下:
@Test
    public void test4(){
        Class<Person> clazz = Person.class;
        // 获取运行时类的接口
        Class<?>[] interfaces = clazz.getInterfaces();
        for (Class c : interfaces){
            System.out.println(c);
        }
        System.out.println();
        // 获取运行时类 的父类实现的接口
        Class<?>[] interfaces1 = clazz.getSuperclass().getInterfaces();
        for (Class cc : interfaces1){
            System.out.println(cc);
        }
    }
  1. getSuperclass().getInterfaces():获取运行时类的父类实现的接口
  2. getPackage():获取运行时类Person所在的包
  3. getAnnotations():获取运行时类声明的注解

调用运行时类中的指定结构(重点:方法)

操作运行时类中指定的属性

通常调用访问权限更大的getDeclaredField()方法,但不能忘记调用对象名.setAccessible(true)方法,否则会报非法访问异常IllegalAccessException。

/**
     * 如何操作运行时类中指定的属性
     * @throws Exception
     */
    @Test
    public void testField() throws Exception {
        Class<Person> clazz = Person.class;
        // 1.创建运行时类的对象
        Person p = clazz.newInstance();
        // 2.获取指定的属性
//        Field id = clazz.getField("age"); // 通常不采用
        Field name = clazz.getDeclaredField("name");
        // 3.保证当前属性可访问不会报IllegalAccessException异常
        name.setAccessible(true);
        // 4.设置当前属性的值,(哪个对象的属性,属性值)
        name.set(p,"lwt");
        // 5.获取哪个对象当前的属性值
        String pname = (String) name.get(p);
        System.out.println(pname);
    }

操作运行时类中指定的方法(*)

通常调用访问权限更大的getDeclaredMethon()方法,勿忘设置权限setAccessible(true)。
调用invoke()时,参数列表中写运行时类.class,返回值为null

/**
     * 调用静态方法
     * @throws Exception
     */
    @Test
    public void testStaticMethon() throws Exception {
        Class<Person> clazz = Person.class;
        Person p = clazz.newInstance();

        Method regist = clazz.getDeclaredMethod("regist");
        regist.setAccessible(true);
        // 如果调用的运行时类中的方法没有返回值,则此invoke()返回null
        Object returnVal = regist.invoke(Person.class);
        System.out.println(returnVal); // null
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值