Java反射知识总结

前情提要

本文内容来着老杜的Java零基础教程视频(适合Java 0基础,Java初学入门)_哔哩哔哩_bilibili

作者只是将其知识进行提炼和二次整合

基础知识

  • 作用:反射机制可以操作字节码文件,可以操作代码片段(class文件)

  • 相关类所在包:java.lang.reflect.*

  • 反射机制相关且重要的类

    • java.lang.Class:代表字节码文件(代表整个字节码、一个类型、整个类)

    • java .lang .reflect .Method:代表字节码中的方法字节码(类中的方法)

    • java.lang.reflect.Constructor:代表字节码中的构造字节码(类中的构造方法)

    • java.lang.reflect.Field:代表字节码中的属性字节码(类中的属性)

获取class的三种方式

  • 要操作一个类的字节码,首先得获得这个类的字节码,以下有获取class的三种方式

    • Class.forname("字符串")

      • 静态方法

      • 方法的参数是一个完整类名的字符串

        • 必须带有包名(java.lang包也不能省略)

        • 在类名中右键可以复制完整类名

          image-20230905204043881

      • 该方法的执行会导致类加载,可以通过该方法调用类的静态代码块(类加载是静态代码块执行)

        • 只是希望该类的静态代码块执行,且其它代码不执行,就可以使用该方法

    • 对象.getClass()

      • 任何对象都有getClass()方法,是Object类实现好的

    • 类型.class

      • java语言中任何一种类型,包括基本数据类型,都有class属性

  • 同一个类型的Class对象指向同一内存单元

Class c1=null;
try {
    c1=Class.forName("java.lang.String");//代表String.class文件或者是String类型
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
String word="abbc";
Class<? extends String> x = word.getClass();//代表的也是String.class文件或者是String类型
System.out.println(c1==x);//结果为true

image-20230905202426531

.

!!!通过反射实例化对象

  • 反射对象.newInstance():会调用反射对象对应类型无参数构造方法,完成对象的创建

    • 如果定义了有参构造而没有定义无参,会报实例化异常

  • 反射实例化对象的优点:可以通过外部配置文件设置的类型(属性名=完整的类名),动态的创建指定类型的对象

    • 在不改变java源代码的基础上,可以实现不同对象的实例化(符合ocp开闭原则:对扩展开放,对修改关闭)

FileReader fileReader = new FileReader("reflect/src/type.properties");
Properties properties = new Properties();//获取配置信息对象map, k v都是String
properties.load(fileReader);//加载
fileReader.close(); 
//通过反射机制获取对象
String classType = properties.getProperty("className");//通过key获取value
System.out.println(classType.getClass());

读取配置文件相关

获取文件的绝对路径

  • 通用方式获取文件绝对路径(前提是该文件放在类路径下【src是类的根路径】)

    • 可以跨系统,跨平台,不会收到环境移植的影响

    String path = Thread.currentThread().getContextClassLoader().
        getResource("以类的根路径作为起点的文件相对路径").getPath()
    • Thread.currentThread() 当前线程对象

    • getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象

    • getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源

    • getPath():获取加载资源的绝对路径

以流的形式返回

InputStream is = Thread.currentThread().getContextClassLoader().
    getResourceAsStream("以类的根路径作为起点的文件相对路径")

资源绑定器读取属性配置文件

  • 使用资源绑定器,属性配置文件必须放在类路径下,且必须以properties为后缀

ResourceBundle bundle=ResourceBundle.getBundle("type");
String className = bundle.getString("className");//根据属性名获取属性值
System.out.println(className);

获取属性

获取所有属性相关信息

  • field对象包含属性修饰符、属性类型和属性名

    image-20230906133425977

  • 获取所有属性对象

    • Class对象.getFields():获取所有公共属性组成的数组

    • Class对象.getDeclaredFields():获取所有属性组成的数组

  • 获取属性名

    • 属性对象.getName()

  • 获取属性类型

    • 属性对象.getType():返回属性类型的Class对象

    • 获取类名

      • Class对象.getName():获取完整类名

      • Class对象.getSimpleName():获取简类名(不带包名)

  • 获取属性或类的修饰符

    1. field/Class对象.getModifiers():返回修饰符的代号

    2. Modifier.toString(代号):将代号转成字符串

反编译(了解)

  • 通过java文件获取源码

  • 代码

    • 使用StringBuilder是为了拼接字符串

StringBuilder builder = new StringBuilder();
Class<?> aClass = Class.forName("java.lang.String");
builder.append(Modifier.toString(aClass.getModifiers())+" class "+ aClass.getSimpleName()+"{\n");
Field[] fields = aClass.getDeclaredFields();//获取该类对象的属性
for (Field field : fields) {
    builder.append(Modifier.toString(field.getModifiers())+" "+field.getName()+"\n");
}
builder.append("}");
System.out.println(builder);
  • 运行结果

image-20230906152858922

.

!!!反射机制访问属性

非私有属性访问

  • Class对象.getDeclaredField("属性名"):通过属性名获取属性对象

  • 属性对象.set(对象,属性值):给对象的该属性赋值

  • 属性对象.get(对象):获取对象的该属性的值

Class<?> student = Class.forName("Student");
Object stu = student.newInstance();//实例化对象
Field age = student.getDeclaredField("age");
age.set(stu,5555);//给对象的age属性赋值
System.out.println(age.get(stu));

私有属性访问

  • 私有属性无法直接赋值,根据上述步骤会报无效访问异常

image-20230906155733842

.

  • 属性对象.setAccessible():打破封装,可以访问私有属性

Class<?> student = Class.forName("Student");
Object stu = student.newInstance();//属性实例化
Field name = student.getDeclaredField("name");
name.setAccessible(true);
name.set(stu,"打破成功");
System.out.println(name.get(stu));

image-20230906155705716

.

获取方法

获取所有方法相关信息

  • Class对象.getDeclaredMethods():获取所有方法

  • 方法对象.getReturnType():获取方法返回值类型

  • 方法对象.getParameterTypes():获取方法所有的参数类型

  • 获取方法的修饰符、方法名与获取属性的基本一致

反编译(了解)

  • 方法体(业务逻辑)无法反编译

  • 代码

    • builder.deleteCharAt("下标"):删除指定下标位置的字符

StringBuilder builder = new StringBuilder();
Class<?> aClass = Class.forName("java.lang.String");
Method[] methods = aClass.getDeclaredMethods();//获取该类对象的所有方法
for (Method method : methods) {
    builder.append(Modifier.toString(method.getModifiers())+" "
            +method.getReturnType()+" "+method.getName()+ "("
    );
    for(Class type:method.getParameterTypes()){
        //获取参数类型
        builder.append(type.getSimpleName()+",");
    }
    if(builder.substring(builder.length()-1).equals(",")){
        //判断最后一个字符是否为多出来的“,”,如果是就删除
        builder.deleteCharAt(builder.length()-1);
    }
    builder.append("){}\n");
}
System.out.println(builder);
  • 运行结果

    image-20230906165616194

    .

!!!反射机制调用方法

  • getDeclaredMethod(方法名,接收参数类型的可变参数):获取指定方法对象

  • !!!方法对象.invoke(对象,方法名,参数列表):调用对象的指定方法,返回结果

Class<?> student = Class.forName("Student");
Object stu = student.newInstance();//对象实例化
Method check = student.getDeclaredMethod("check", String.class, String.class);//根据方法名和参数列表定位具体方法
Object result = check.invoke(stu,  "liao", "123");//调用对象的check方法,并传入参数
System.out.println(result);//输出返回结果

/**check方法内容**/
public Boolean check(String username,String password){
	return username.equals("liao")&&password.equals("123");
}

image-20230906171411657

.

获取构造器(了解)

反编译

  • 除了没有方法返回值,反编译构造方法和反编译方法基本一致

StringBuilder builder = new StringBuilder();
Class<?> aClass = Class.forName("Student");
Constructor[] methods = aClass.getConstructors();//获取该类对象的所有方法
for (Constructor method : methods) {
    builder.append(Modifier.toString(method.getModifiers())+" "
            +method.getName()+ "("
    );
    Class[] types=method.getParameterTypes();
    for(Class type:types){
        //获取参数类型
        builder.append(type.getSimpleName()+",");
    }
    if(types.length>0){
        //参数列表大于0的时候删除多余的逗号
        builder.deleteCharAt(builder.length()-1);
    }
    builder.append("){}\n");
}
System.out.println(builder);

反射机制调用构造器方法

  • 调用无参构造方法

    • Class对象.newInstance()

    • Class对象.getDeclaredConstructor()获取无参构造方法对象之后,调用newInstance()

  • 调用有参构造方法

    1. Class对象.getDeclaredConstructor(参数类型列表):获取有参构造方法对象

    2. 构造方法对象.newInstance(参数列表)

!!!获取父类和接口

  • Class对象.getSuperClass():获取父类

  • Class对象.getInterfaces():获取所有实现的接口

扩展(与反射无关)

类加载器(了解)

  • 定义:专门负责加载类的命令/工具

  • jdk自带的三个类加载器

    • 启动类(父加载器)

    • 扩展类(母加载器)

    • 应用类

  • 执行案例String s ="abc"

    1. 代码在开始执行之前,会将所需要类全部加载到JVM当中

    2. 通过类加载器加载,看到以上代码类加载器会找string.class文件,找到就加载

      1. 首先通过“启动类加载器"”加载

        • 启动类加载器专门加载:jdk1.8.0 101\jre\lib\rt.jar(rt.jar中都是JDK最核心的类库)

      2. 如果通过“启动类加载器"加载不到的时候,会通过"扩展类加载器"加载

        • 扩展类加载器专门加载:jdk1.8.0 101\jre\lib\ext\*.jar

      3. 如果“扩展类加载器"没有加载到,那么会通过“应用类加载器”加载

        • 应用类加载器专门加载: classpath中的类

  • 双亲委派机制

    • java中为了保证类加载的安全,使用了双亲委派机制

      1. 优先从启动类加载器中加载,这个称为“父"

      2. "父"无法加载到,再从扩展类加载器中加载这个称为“母”

      3. 如果都加载不到才会考虑从应用类加载器中加载

可变长度参数

  • 可变长参数

    • 接收参数个数是0~N个

    • 在参数列表中必须放在最后一个位置只能有一个

    • 可以看作是数组,也可以接收一个数组

  • 语法:类型...

image-20230906174436144

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值