反射(二)

静态加载和动态加载

静态加载:编译时加载相关的类,如果没有找到则报错。
动态加载:运行时加载需要的类。

反射是动态加载,只有被执行到的时候,才会去加载类。

public class Main{
    public static void main(String args[]) throws ClassNotFoundException{
    
    // 静态加载, 编译的时候就要进行加载, 如果没找到就会报错, 依赖性很强
        Cat cat = new Cat();    
        
    // 动态加载, 编译的时候不去管它, 也不会去检查 被加载的类是否存在
    // 真正运行且执行到这段代码的时候才会去加载
        Class.forName("com.itheima.Cat");   
        
    }
}

类加载

当程序要使用某个类时,如果该类还未被加载到内存,则系统会通过类的加载类的连接类的初始化这三个步骤来对类进行初始化。
一般情况下,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载。

类的加载:JVM将字节码从不同的数据源(class文件,jar包,网络等等)转化为二进制字节流加载到内存中,并生成Class实例对象。

类的连接
验证阶段:确保 .class 文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。
准备阶段:为static修饰的字段变量分配内存并且设置默认初始值,不包含用final修饰的static,因为final在编译的时候就会分配了。
解析阶段:主要将常量池中的符号引用替换为直接引用的过程。

类的初始化:类加载最后阶段,真正开始执行类中定义的Java程序代码,此阶段是执行 < clinit >()方法的过程。
< clinit >()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
虚拟机会保证一个类的 < clinit >()方法在多线程环境中被正确地加锁,同步,如果多个线程同时去初始化一个类,则只会有一个线程去执行这个类的 < clinit >()方法,其他线程都需要阻塞等待,直到活动线程执行 < clinit >() 方法完毕。
若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。
在这里插入图片描述
在这里插入图片描述

@SuppressWarnings({"all"})
public class lambdademo {
    public static void main(String[] args) {
        System.out.println(fun.num);
    }
}

/**
 *  在 Linking 的 Preparation 阶段, 对 num 分配内存,初始化为 0
 *  在 Initialization 阶段, 收集 静态变量,静态代码块 至 <clinit> 方法中, 并执行 <clinit>方法
 */
class fun {
    static{
        System.out.println("静态代码块被执行");
        num = 300;
    }
    static int num = 100;
    public fun(){
        System.out.println("构造器被执行");
    }
}
// Output
静态代码块被执行
100

// 分析结果, 可以明显地看到, 类加载中做了什么, 当然我们并没有去创建对象, 所以构造方法也没有被调用
// 大致的过程应该是这样

1. 加载 fun 类, 并生成对应的一份 Class实例, 以及在方法区中的 字节码二进制数据
2. Linking阶段, 为 num 分配内存,初始化为 0
3. Initialization阶段, 按在源文件中出现的顺序, 依次收集所有静态变量的赋值动作和静态代码块中的语句,合并至<clinit>方法中,并执行。

clinit() {
///静态代码块中的语句/
	System.out.println("静态代码块被执行");
	num = 300;
///静态变量赋值语句//
    num = 100;
}

Field类的一些其他API

getModifiers(): default/0 , public/1 , private/2 , protected/4 , static/8 , final/16
// 可以使用 Modifier.toString(cls.getModifiers()) 将数字转为字符串
getType(): 返回一个 Class对象,表示属性的类型
getName(): 返回属性名 
        Class<fun> funClass = fun.class;
        Field[] fields = funClass.getDeclaredFields();
        for(Field f : fields){
            System.out.println(f.getModifiers());   // 返回数值代表其访问权限修饰符
            System.out.println(f.getType());        // int/double/java.lang.String ...
            System.out.println(f.getName());        // 属性名
        }

Mehod类

getModifiers()			// 访问权限修饰符
getReturnType()			// 返回Class对象,表示返回类型
getName()				// 方法名
getParameterTypes()		// 返回Class[], 表示参数类型数组

通过反射创建对象

        Class<?> cls = Class.forName("com.itheima.fun");

        // 1.使用无参构造器
        Object o1 = cls.newInstance();

        // 2.通过有参构造器(指定参数列表)
        Constructor<?> cons1 = cls.getDeclaredConstructor(String.class);
        Object o2 = cons1.newInstance("xxx");

        // 3.也可以使用private的构造器
        Constructor<?> cons2 = cls.getDeclaredConstructor(int.class, String.class);
        cons2.setAccessible(true);
        Object o3 = cons2.newInstance(100, "fang_shou_ba..");

Class对象的生成方式如下:
1、类名.class
JVM将使用类装载器, 将类装入内存,不做类的初始化工作,返回Class的对象。
2、Class.forName(“类的全限定名”)
装入类,并做类的初始化(会执行静态代码块和初始化静态变量),返回Class的对象。

平时使用 new 创建对象时,会先进行类加载,之后执行 非静态的代码块,之后执行构造函数。
几种生成Class实例方法的区别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值