java类的加载机制和反射

参考java疯狂讲义

一、概述:         如果一个类还未被加载到内存中,此时程序需要主动使用该类,那么系统就会对该类进行如下三个步骤:1、 加载、2、连接、3、初始化。(ps: 每一个类都是java.lang.class的对象)

         类的加载:就是将类的class的文件加载到内存,并未之创建一个java.lang.Class的对象。

         类的连接:分三个阶段:1、验证 (检查被加载的类是否有正确的内部结构,并和其他的类协调一致)

                                              2、准备 (未类的静态fileld分配内存,并设置默认的初始值)

                                              3、解析(将类的二进制数据中符号引用替换成直接引用)

         类的初始化:虚拟机负责对类进行初始化,:1、声明静态Field时制定初始值;2、使用静态初始化块为静态的Field 指定初始值。

        JVM初始化一个类的步骤如下:

        1、假如这个类还没有被加载和连接,则程序先加载并连接该类

        2、假如这个类的直接父类还没有被初始化,则先初始化其直接父类

        3、假如类中有初始化语句,则系统依次执行这些初始化语句。

         PS:在执行第2步时,系统会先对其直接父类依次做上面的(1、2、3)步骤,如果其直接父类又有直接父类,则类推。所以JVM最先初始化java.lang.Object类,综上,当程序主动使用任何一个类时,系统会保证该类以及所有父类都会被初始化。

二、

     2、1 类初始化的时机:

                   1、创建类的实例、

                   2、调用某个类的静态方法

                   3、访问某个类或接口的静态的field,或为该静态的Field赋值

                    4、使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

                   5、初始某个类的子类,当初始化某个类的子类时,该子类的所有父类都会别初始化。

                   6、直接使用java.exe命令来运行某个主类,当运行某个主类时,程序会先初始化该主类

     特别指出,对于final型的静态的Field,如果该Field的值在编译的时候就确定下来,那么这个Field相当于“宏变量”,java编译器会把这个Field出现的地方直接替换成它的值,因此即使程序使用了改静态的Field,也不会导致类的初始化。但时如果该Field值在运行的时候才能确定(例如等于系统的当前时间),则会导致类的初始化。

          还有一个特殊的就是classLoader类里面的loadClass()方法只是加载类,并不会初始化,使用Class.forname()才会强制进行初始化。举例

      

public class ClassA {
	public static int a = 6;
	static{
		System.out.println("ClassA初始化了");;
	}
}

public class ClassLoadtest {
	public static void main(String[] args) throws ClassNotFoundException {
		ClassLoader classLoader = ClassLoader.getSystemClassLoader();
		//只是加载,并没有执行初始化
		classLoader.loadClass("test.classLoad.ClassA");
		
		//初始化 类test.classLoad.ClassA
		Class.forName("test.classLoad.ClassA");
	}
}

三、类的加载机制

       分为三种:1、全盘负责: 当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负                                                 责载入。

                        2、父类委托:先让父类加载器试图加载该Class,当无法加载时,才尝试从自己的类路径加载该类。

                        3、缓存机制  :缓存机制将会保证所有加载过的Class都会被缓存,当程序需要使用某个Class的时候,类加载器                           先从缓存中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转                           换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重新启动jvm,程序所修改才会生效的原因。

         类加载器加载Class的大致步骤如下:参考:https://blog.csdn.net/lixingtao0520/article/details/72927775




四、通过反射查看类信息,参考疯狂讲义

package test.classLoad;

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


//通过反射查看类信息
@SuppressWarnings(value = "unchecked")
@Deprecated
public class ClassTest {
	private ClassTest(){
		
	}
	
	public  ClassTest(String name){
	System.out.println("执行有参数的构造器");	
	}
	
	//定义一个无参数的info方法
	public void info(){
		System.out.println("执行无参数的info方法");
	}
	
	//public一个有参数的info方法
	public void info(String str){
		System.out.println("执行有参数的info方法,其参数为"+str);
	}
	
	//定义一个测试用的内部类
	class Inner{
		
	}
	
	public static void main(String[] args) throws Exception{
		//获取ClassTest的Class对象
		Class<ClassTest> clazz = ClassTest.class;
		//获取该Class对象对应的类的全部构造器
		Constructor[] ctors = clazz.getConstructors();
		
		System.out.println("ClassTest全部public构造器如下");
		for (Constructor constructor : ctors) {
			System.out.println(constructor);
		}
		
		Method[] methods = clazz.getMethods();
		System.out.println("ClassTest全部构public方法如下");

		for (Method method : methods) {
			System.out.println(method);
		}
		
		//获取指定的带参数的info的方法
		Method method = clazz.getMethod("info", String.class);
		System.out.println("获取指定的带参数的info的方法:  "+method);
		
		//获取该Class对象的全部注释
		System.out.println("获取该Class对象的全部注释:");

		Annotation[] annotations = clazz.getAnnotations();
		for (Annotation annotation : annotations) {
			System.out.println(annotation);
		}
		
		//获取该class对象对应的全部内部类
		System.out.println("获取该class对象对应的全部内部类:");

		Class<?>[] inners = clazz.getDeclaredClasses();

		for (Class<?> class1 : inners) {
			System.out.println(class1);
		}
		
		//通过ClassForName加载内部类
		Class<?> inClazz = Class.forName("test.classLoad.ClassTest$Inner");
		System.out.println("inClazz对应的外部类:"+inClazz.getDeclaringClass());
		System.out.println("ClassTest对应的包为:"+clazz.getPackage());
		System.out.println("ClassTest对应的外父类:"+inClazz.getSuperclass());
  	
	}
	
}

五。

java使用反射创建对象

(https://blog.csdn.net/u013473691/article/details/52757944)

java使用反射创建对象

1、通过Class对象的newInstance()方法来创建Class对象对应类的实例。这个方法是使用Class对象对应类的默认构造器创建对象,这就要求Class对象对应类必须要有默认构造器。

2、使用Class对象获取指定的Constructor对象,调用Constructor对象的newInstance()方法来创建Class对象对应类的实例。这个方法可以使用Class对象对应类的任意指定的构造器来创建实例。

第一种方法比较常见,在spring这些框架中,会根据配置文件自动创建类的实例并注入到依赖此类的类中。这时候用的最多的就是默认构造器。像是在spring的配置文件中,我们提供的是某个类的全类名,这种情况要创建类的实例,就必须使用反射了。
package test.classLoad;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;


public class ObjectPoolFactory {
    //定义一个对象池,前面是对象名,后面是实际对象
    private Map<String, Object> objectPool = new HashMap<> ();
    //定义一个创建对象的方法
    //该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
    private Object createObject(String clazzName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class<?> clazz = Class.forName(clazzName);
        return clazz.newInstance();
    }
    //该方法通过文件来初始化对象池
    //它会根据配置文件创建对象
    public void initPool(String fileName) throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(fileName);
            Properties props = new Properties();
            props.load(fis);
            for (String name : props.stringPropertyNames()) {
                //每取出一个键值对,就根据属性值创建对象
                //每调用createObject创建对象,并将对象添加到对象池中
                objectPool.put(name, createObject(props.getProperty(name)));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
    }
    public Object getObject(String name) {
        return objectPool.get(name);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
        ObjectPoolFactory opf = new ObjectPoolFactory();
        opf.initPool("E:\\javafun\\test\\src\\test\\classLoad\\obj.txt");
        System.out.println(opf.getObject("a"));
        System.out.println(opf.getObject("b"));
    }
}
配置文件obj.txt
a=java.util.Date
b=javax.swing.JFrame

使用第二种方法创建对象:

package test.classLoad;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class CreateFrame {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<?> clazz = Class.forName("javax.swing.JFrame");
        Constructor constructor = clazz.getConstructor(String.class);
        Object obj = constructor.newInstance("测试窗口");
        System.out.println(obj);
    }
}
输出结果:
javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,title=测试窗口,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]

六、调用方法

  当通过上面的获得某个类的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法获取全部方法或指定的方法。Method里面包含一个invoke()方法,如下:

Object invoke(Object obj,Object...args):该方法中时执行该方法的主调,后面的args时执行该方法时传入该方法的实参

package test.classLoad;

import java.util.*;  
import java.io.*;  
import java.lang.reflect.*;  
  
public class ExtendedObjectPoolFactory  
{  
    // 定义一个对象池,前面是对象名,后面是实际对象  
    private Map<String ,Object> objectPool = new HashMap<>();  
    private Properties config = new Properties();  
    // 从指定属性文件中初始化Properties对象  
    public void init(String fileName)  
    {  
        try(  
            FileInputStream fis = new FileInputStream(fileName))  
        {  
            config.load(fis);  
        }  
        catch (IOException ex)  
        {  
            System.out.println("读取" + fileName + "异常");  
        }  
    }  
    // 定义一个创建对象的方法,  
    // 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象  
    private Object createObject(String clazzName) throws InstantiationException, IllegalAccessException , ClassNotFoundException  
    {  
        // 根据字符串来获取对应的Class对象  
        Class<?> clazz =Class.forName(clazzName);  
        // 使用clazz对应类的默认构造器创建实例  
        return clazz.newInstance();  
    }  
    // 该方法根据指定文件来初始化对象池,  
    // 它会根据配置文件来创建对象  
    public void initPool()throws InstantiationException   ,IllegalAccessException , ClassNotFoundException  
    {  
        for (String name : config.stringPropertyNames())  
        {  
            // 每取出一对key-value对,如果key中不包含百分号(%)  
            // 这就标明是根据value来创建一个对象  
            // 调用createObject创建对象,并将对象添加到对象池中  
            if (!name.contains("%"))  
            {  
                objectPool.put(name , createObject(config.getProperty(name)));  
            }  
        }  
    }  
    // 该方法将会根据属性文件来调用指定对象的setter方法  
    public void initProperty()throws InvocationTargetException  
        ,IllegalAccessException,NoSuchMethodException  
    {  
        for (String name : config.stringPropertyNames())  
        {  
            // 每取出一对key-value对,如果key中包含百分号(%)  
            // 即可认为该key用于控制调用对象的setter方法设置值,  
            // %前半为对象名字,后半控制setter方法名  
            if (name.contains("%"))  
            {  
                // 将配置文件中key按%分割  
                String[] objAndProp = name.split("%");  
                // 取出调用setter方法的参数值  
                Object target = getObject(objAndProp[0]);  
                // 获取setter方法名:set + "首字母大写" + 剩下部分  
                String mtdName = "set" +  
                objAndProp[1].substring(0 , 1).toUpperCase()  
                    + objAndProp[1].substring(1);  
                // 通过target的getClass()获取它实现类所对应的Class对象  
                Class<?> targetClass = target.getClass();  
                // 获取希望调用的setter方法  
                Method mtd = targetClass.getMethod(mtdName , String.class);  
                // 通过Method的invoke方法执行setter方法,  
                // 将config.getProperty(name)的值作为调用setter的方法的参数  
                mtd.invoke(target , config.getProperty(name));  
            }  
        }  
    }  
    public Object getObject(String name)  
    {  
        // 从objectPool中取出指定name对应的对象。  
        return objectPool.get(name);  
    }  
    public static void main(String[] args)  
        throws Exception  
    {  
        ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();  
        epf.init("E:\\javafun\\test\\src\\test\\classLoad\\obj.txt");  
        epf.initPool();  
        epf.initProperty();  
        System.out.println(epf.getObject("a"));  
    }  
}  
obj.txt 如下:
a=javax.swing.JFrame
b=javax.swing.JLabel
#set the title of a
a%title=Test Title

七、访问属性值

package test.classLoad;

public class Person {
	private String name;
	private int age;
	
	public String toString(){
		return "该person的名字为:"+name+"年纪为: "+age;
	}
}

package test.classLoad;

import java.lang.reflect.Field;

/**
 * 通过反射的方法获取属性值
 * @author 王sir
 *
 */
public class FieldTest {
	
	public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		 Person p = new Person();
	     //获取Class对象
	     Class<?> pclazz = p.getClass();
	     //获取Person的名为name的Field
	     //通过getDeclaredField()可获得各种访问控制符的Field,注意这里不能使用getField(),引文getField()只能获取public访问控制的Field.
	     Field  nameField = pclazz.getDeclaredField("name");
	     //设置 通过反射访问该Field时取消访问权限
	     nameField.setAccessible(true);
	     nameField.set(p, "[通过反射获取的名字哦]");
	     
	     //同理下面 为age复制
	     Field ageField = pclazz.getDeclaredField("age");
	     ageField.setAccessible(true);
	     ageField.setInt(p, 12);
	     
	     System.out.println(p);
	     
	}
}





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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值