JavaSE-类加载过程及反射

目录

 

一、类加载过程

1.装载阶段

1.1执行过程

    1.2.类加载器

1.3.双亲委派模型

1.4 类加载时机

2. 链接阶段

2.1验证阶段

2.2准备阶段

2.3解析阶段

3. 初始化阶段

二、反射

1.定义

2.用途

3.步骤

4.代码实现


一、类加载过程

1.装载阶段

1.1执行过程

以Main.java为例

package com.company;

public class Main {

    public static void main(String[] args) {
	    String str1 = "123";
	    int a = 10;
    }
}

其中定义了两个变量,首先利用javac Main.java进行编译生成Main.class文件

然后利用javap -verbose命令查看反汇编

 其中首先由一个4字节大小的魔数来标识文件类型。

上图中的minor version为次版本号,major version表示主版本号,分别占用2字节。

Constant pool为常量池,其大小不固定,由定义的变量来决定。

    1.2.类加载器

   Main类经过javac编译后,生成.class文件保存下来,然后经过类加载器加载类至内存,生成java.lang.Class类的实例,这个实例就是程序访问这个类的入口,通过这个class实例的newInstance方法即可得到这个类的实例对象。

      JVM中的类的加载器主要有三种:启动类加载器拓展类加载器应用类加载器。

  •      启动类加载器(Bootstrap classLoader):又称为引导类(启动类)加载器,由C++编写,无法通过程序得到。主要负责加载JAVA中的一些核心类库,主要是位于<JAVA_HOME>/lib/rt.jar中。
  •      拓展类加载器(Extension classLoader):主要加载JAVA中的一些拓展类,位于<JAVA_HOME>/lib/ext中,是启动类加载器的子类。
  •      应用类加载器(System classLoader):    又称为系统类(应用类)加载器,主要用于加载CLASSPATH路径下我们自己写的类,是拓展类加载器的子类。

如果生成的class文件不在这三个类加载器路径下,它首先会由子加载器依次往父加载器去找,最后找不到就会报错ClassNotFoundException,加载失败。

如果在这些路径下,通过Class.forName("com.company.Main"),通过全路径加载进来。然后用Student.class.getClassLoader()得到它的类加载器,得到的是AppClassLoader(即系统类加载器),如果用Student.class.getClassLoader().getParent()得到的是它的父加载器ExtClassLoader(即拓展类加载器),然后用Student.class.getClassLoader().getParent().getParent()得到将会是Null,因为启动类加载器是用C++写的,无法通过程序直接得到。

例如:

class TestClass{

}

public class Main {

    public static void main(String[] args) {
        TestClass testClass = new TestClass();
        System.out.println(testClass.getClass().getClassLoader());
        System.out.println(testClass.getClass().getClassLoader().getParent());
        String s = new String();
        System.out.println(s.getClass().getClassLoader());
    }
}

输出:

TestClass为自己定义的类,使用应用加载器加载,它的父类就是扩展加载器,String类使用的Bootstrap加载器,它由c++实现,因此得到的只能是null。

从子类到父类流程是:从子类到父类查找之前是否加载过这个类,如果加载过,就返回当前类的Class对象,若没有加载过,委托给父类加载器,查找是否加载过。

若都没有加载过当前类,从启动类开始尝试加载(有没有在加载路径下),若加载失败就委托给子类加载器进行加载。

1.3.双亲委派模型

这几种类加载器的层次关系,称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。简单描述其过程:

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

其优点:

(1).避免类的重复加载

如需要定义一个 System 的 Class 对象,并且只需要一份,如果不使用委托机制,而是自己加载自己的,那么类 A 打印的时候就会加载一份 System 字节码,类 B 打印的时候又会加载一份 System 字节码。而使用委托机制就可以有效的避免这个问题。

(2).安全性

如果在String类中嵌套了病毒,若没有用该模型直接加载的话,病毒就会攻击jvm,造成破坏,因此像String这种系统类都是由Bootstrap加载器去加载的,保证了其安全性。

1.4 类加载时机

  • new对象时
  • 调用静态变量/方法(在反汇编中,invokestatic指令专门用于调用静态方法变量的,invokespecial指令用于调用构造方法的,invokevirtual指令用于调用实例方法变量的)
  • 获取当前类的Class对象

2. 链接阶段

2.1验证阶段

该阶段的任务是为了保证Class文件的字节流中包含的信息符合当前虚拟机要求,如魔数,jdk版本号,各种数据验证等,并且不会危害虚拟机自身的安全。

2.2准备阶段

为类变量分配内存并设置类变量(静态变量)初始值(静态变量的默认值)的阶段,这些变量所使用的内存都将在方法区中进行分配。

private static int a = 10;

2.3解析阶段

将符号引用解析为直接引用的过程。

例如:

class test{
    String str = "a";
}

str内容对应的地址被保存在磁盘上,通过磁盘上的地址去访问该内容就是符号引用,而通过堆去访问就是直接引用,解析的作用就是进行二者的转换。

 

3. 初始化阶段

给静态变量进行赋值操作。

 

二、反射

1.定义

Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

2.用途

在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。当然,也不是所有的都适合反射,有的案例通过反射得到的结果与预期不符。阅读源码发现,经过层层调用后在最终返回结果的地方对应用的权限进行了校验,对于没有权限的应用返回值是没有意义的缺省值,否则返回实际值起到保护用户的隐私目的,因此如果源码中明确进行了权限验证,而你的应用又无法获得这个权限的话,建议就不要浪费时间反射了。

3.步骤

①获取Class对象

  • Class c = People.class;
  • Class c = Class.forName("People");
  • People p = new People();  Class c = p.getClass();

②获取当前类的构造函数

Constructor con = c.getConstructor();

③通过new对象进行构造函数生成

4.代码实现

①无参方法反射:

package com.company;


import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

class people{
    private people(){
        System.out.println("构造函数");
    }

    public void eat(){
        System.out.println("eating");
    }
}

public class Main {

    public static void main(String[] args) {
        //获取当前类的Class对象
        Class<people> peopleClass = people.class;
        try {
            //获取无参构造函数,getDeclaredConstructor在private,public,protected中查找,getConstructor在public方法中查找
            Constructor constructor = peopleClass.getDeclaredConstructor();
            //设置为可访问权限
            constructor.setAccessible(true);
            //根据无参构造函数new对象
            Object object = constructor.newInstance();
            //根据对象用用函数
            //1.获取哪个方法
            Method method = peopleClass.getMethod("eat");
            //2.根据对象object调用方法
            method.invoke(object);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

其中:getDeclaredConstructor在private,public,protected中查找,getConstructor在public方法中查找,私有的构造方法用getConstructor去调用会报错,并且要给constuctor对象设置访问权限。

②属性修改反射

package com.company;


import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

class people{
    private String name;

    private people(){
        System.out.println("构造函数");
    }

    public void eat(){
        System.out.println("eating");
    }

    public String getName() {
        return name;
    }

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

public class Main {

    public static void main(String[] args) {
        //获取当前类的Class对象
        Class<people> peopleClass = people.class;
        try {
            //获取无参构造函数,getDeclaredConstructor在private,public,protected中查找,getConstructor在public方法中查找
            Constructor constructor = peopleClass.getDeclaredConstructor();
            //设置为可访问权限
            constructor.setAccessible(true);
            //根据无参构造函数new对象
            Object object = constructor.newInstance();
            //根据对象用用函数
            //1.获取哪个属性
            Field field = peopleClass.getDeclaredField("name");
            //2.进行修改
            field.setAccessible(true);
            field.set(object,"tom");
            System.out.println(field.get(object));

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

③有参方法

package com.company;


import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

class people{
    private String name;

    private people(String name){
        this.name = name;
    }

    private void eat(Integer a){
        System.out.println(a+"个人在eating");
    }

}

public class Main {

    public static void main(String[] args) {
        //获取当前类的Class对象
        Class<people> peopleClass = people.class;
        try {
            //获取无参构造函数,getDeclaredConstructor在private,public,protected中查找,getConstructor在public方法中查找
            Constructor constructor = peopleClass.getDeclaredConstructor(String.class);
            //设置为可访问权限
            constructor.setAccessible(true);
            //根据无参构造函数new对象
            Object object = constructor.newInstance("jack");
            //根据对象用用函数
            Method method = peopleClass.getDeclaredMethod("eat",Integer.class);
            method.setAccessible(true);
            method.invoke(object,10);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值