注解与反射(三)

反射

类的加载与ClassLoader的理解

加载

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。

链接

将java类的二进制代码合并到JVM的运行状态之中的过程
① 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
② 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
③ 解析:虚拟机常量池的符号引用(常量名)替换为直接引用(地址)的过程

初始化

执行类构造器<clinit>()方法的过程,类构造器<clinit>()方法是由编译期自动收集类中的所有类变量的复制动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器。)
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

分析类的初始化

什么时候会发生类的初始化

类的主动引用(一定会发生类的初始化)
① 当虚拟机启动,先初始化main方法所在的类
② new 一个类的对象
③ 调用类的静态成员(除了final常量)和静态方法
④ 使用java.lang.reflect包的方法对类进行反射调用
⑤ 当初始化一个类,如果其父类没有被初始化,则会初始化它的父类
类的被动引用(不会发生类的初始化)
① 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类的初始化
② 通过数组定义类引用,不会触发该类的初始化
③ 引用常量不会触发该类的初始化(常量在链接阶段就存入调用类的常量池中了)

代码示例

public class ReflectDemo {
static {
    System.out.println("Main 类被加载...");
}

public static void main(String[] args) throws ClassNotFoundException {
    // 1、主动引用
    Son son = new Son();

    // 2、反射也会产生主动引用
    Class.forName("test.Son");

    // 不会产生类的引用的方法

    // 1、通过子类引用父类的静态变量,不会导致子类的初始化
    System.out.println(Son.b);

    // 2、通过数组定义类引用,不会触发该类的初始化
    Son[] array = new Son[10];
  }
}

class Father {
  static {
      System.out.println("父类被加载...");
  }
  static int b = 8;
}

class Son extends Father {
  static {
      System.out.println("子类被加载...");
      m = 300;
  }

  static int m = 100;
  static final int M = 10;
}

类加载器

类加载器的作用

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表该类的java.lang.Class对象,作为方法区中类数据的访问入口。

类缓存

标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

类加载器分类

在这里插入图片描述

引导类加载器
用C++编写的,是JVM自带的类加载器,负责JAVA平台核心库(rt.jar),用来装载核心类库。该加载器无法直接获取
扩展类加载器
负责jre/lib/ext目录下的jar包或 -Djava.ext.dirs指定目录下的jar包装入工作库
系统类加载器
负责java -classpath 或 -Djava.class.path所指的目录下的类与jar包装入工作库,是最常用的类加载器
双亲委派机制
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器(Bootstap Classloader),只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
代码示例
public class ClassloaderDemo {
  public static void main(String[] args) throws ClassNotFoundException {
    // 获取系统类的加载器
    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); // AppClassLoader
    System.out.println(systemClassLoader);

    // 获取系统类的加载器的父类加载器 -> 扩展类加载器
    ClassLoader parent = systemClassLoader.getParent();
    System.out.println(parent); // ExtClassLoader

    // 获取扩展类加载器的父类加载器 -> 根加载器(由C++编写)
    ClassLoader parent1 = parent.getParent();
    System.out.println(parent1); // null  java无法获取到

    // 测试当前类是由那个加载器加载的
    ClassLoader classLoader = Class.forName("test.ClassDemo").getClassLoader();
    System.out.println(classLoader); // AppClassLoader

    // 测试JDK内置类的加载器
    classLoader = Class.forName("java.lang.Object").getClassLoader();
    System.out.println(classLoader); // 根加载器加载的 null

    // 如何获取系统类加载器可以加载那些jar包
    System.out.println(System.getProperty("java.class.path"));
  }
}

创建运行时类的对象

获取运行时类的完整结构

通过反射获取运行时类的完整结构:Field、Method、Constructor、Superclass、Interface、Annotation
① 实现的全部接口
② 所继承的父类
③ 全部的构造器
④ 全部的方法
⑤ 全部的Field
⑥ 注解
......

代码示例

public class ClassInformationDemo {
  public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
    Class clazz = Class.forName("test.User");

    // 获取包名+类名
    System.out.println(clazz.getName());
    // 获取类名
    System.out.println(clazz.getSimpleName());
    System.out.println("===========获取全部public的属性==============");
    // 获取类的属性
    // 获取全部public的属性
    Field[] fields = clazz.getFields();
    for (Field field : fields) {
        System.out.println(field);
    }
    System.out.println("==========获取全部的属性===============");
    // 获取全部的属性
    fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        System.out.println(field);
    }
    System.out.println("============获取指定属性的值=============");
    // 获取指定属性的值
    Field name = clazz.getDeclaredField("name");
    System.out.println(name);
    // 获取全部的方法
    System.out.println("============获取本类及其父类的全部public方法=============");
    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
        System.out.println(method);
    }
    System.out.println("===========获取本类的全部的方法==============");
    methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println(method);
    }
    System.out.println("===========获取指定的方法============");
    Method method = clazz.getMethod("getName", null);
    System.out.println(method);
    System.out.println("===========获取类的全部public构造器=============");
    Constructor[] constructors = clazz.getConstructors();
    for (Constructor constructor : constructors) {
        System.out.println(constructor);
    }
    System.out.println("===========获取类的全部构造器=============");
    Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
    for (Constructor declaredConstructor : declaredConstructors) {
        System.out.println(declaredConstructor);
    }

  }
}

class User {
  String name;

  int age;

  User(){
  }

  public User(String name, int age) {
    this.name = name;
    this.age = age;
  }

  private void test() {
  }

  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }

  public void setAge(int age) {
    this.age = age;
  }
  public int getAge() {
    return age;
  }
}

动态创建对象

创建类的对象:调用Class对象的newInstance()方法
  ① 类必须有一个无参构造器
  ② 类的构造器的访问权限需要足够
难道没有无参的构造器就不能创建对象了吗?
  只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。
  步骤如下:
    ① 通过Class类的getDeclaredConstructor(Class ... parameterTypes)取得本类的制度形参类型的构造器
    ② 向构造器的形参中传递一个对象数组进去,里面包含了构造器所需的各个参数
    ③ 通过Constructor实例化对象

调用指定的方法

通过反射,调用类中的方法,通过Method类完成。
① 通过Class类的getMethod(String name, Class...parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型
② 之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。

Object invoke(Object obj, Object… args)

Object 对应原方法的返回值,若原方法无返回值,此时返回null
若原方法为静态方法,此时形参Object obj可为null
若原方法的形参列表为空,则Object[] args为null
若原方法声明为private,则需要在调用此invoke()方法前,显示调用方法对象的setAccessible(true)方法,将可访问private的方法

setAccessible

Method和Field、Contructor对象都有setAccessible()方法
setAccessible作用是启动和禁用访问安全检查的开关
参数值为true则指示反射的对象在使用时应取消JAVA语言访问检查
   1、提高反射的效率。如果代码中必须使用反射,而该句代码需要频繁的被调用,那么请设置为true
   2、使得原本无法访问的私有成员也可以访问
参数值为false则指示反射的对象应实施JAVA语言访问检查

代码示例

public class ClassNewInstance {
  public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
    // 获取class对象
    Class clazz = Class.forName("test.User");
    // 构造一个对象
    User user = (User) clazz.newInstance(); // 本质上是调用无参构造器
    System.out.println(user);

    // 通过构造器创建对象
    Constructor constructor = clazz.getDeclaredConstructor(String.class, Integer.class);
    User user1 = (User) constructor.newInstance("Lucy", 10);
    System.out.println(user1);

    // 通过反射调用普通方法
    User user2 = (User) clazz.newInstance();
    // 通过反射获取一个方法
    // invoke(对象, "方法的值") 激活
    Method setName = clazz.getDeclaredMethod("setName", String.class);
    setName.invoke(user2, "betty");
    System.out.println(user2.getName());
    // 通过反射操作属性
    User user3 = (User) clazz.newInstance();
    Field field = clazz.getDeclaredField("name");
    // 不能直接操作私有属性,需要先关闭安全检查
    field.setAccessible(true);
    field.set(user3, "zz");
    System.out.println(user3.getName());
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值