Java 类加载

类加载过程概述:

/*
 * 类的加载:
 * 1、加载:
 * 	 把字节码读取到内存
 * 2、连接
 * (1)验证
 * (2)准备:
 * 		例如:给类变量(静态变量)在方法区分配内存,非final的赋默认值,但是如果是final的,直接赋常量值。
 * (3)解析
 * 		虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
 * 
 * 这里1,2完成时,在方法区中已经有一个能够代表当前类的Class对象。
 * 
 * 3、类的初始化  <clinit>()
 * 
 * 类的加载大多数情况下是1、2、3一起完成的,但是有的时候3、初始化不一起完成。
 * 
 * 回忆:
 * <clinit>()是由编译器自动收集(1)静态变量的显式赋值(2)静态代码块的内容的组成。
 * 当一个类初始化时,如果发现它的父类没有初始化,那么会先初始化父类。
 * 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。即每一个类在内存中都只有唯一的一个Class对象。
 * 
 */
public class TestClassLoading{
	
}

触发类初始化和不触发类初始化的操作

/*
 * 类的加载包含类的初始化一起完成:
 * (1)当虚拟机启动,先初始化main方法所在的类
 * (2)当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
 * (3)new一个对象
 * (4)使用一个类的静态的成员(包含静态变量和静态的方法),但是这个静态的变量不能是final
 * (5)使用java.lang.reflect包的方法对类进行反射调用
 */

class Father{
	static{
		System.out.println("main方法所在的类的父类(1)");
	}
}

public class TestClinit1 extends Father{
	static{
		System.out.println("main方法所在的类(2)");
	}
	
	public static void main(String[] args) throws ClassNotFoundException {
		new A();//第一次使用A就是创建它的对象
		
		B.test();
		
		Class clazz = Class.forName("com.atguigu.test02.C");
	}
}
class A{
	static{
		System.out.println("A类初始化");
	}
}
class B{
	static{
		System.out.println("B类初始化");
	}
	public static void test(){
		System.out.println("B类的静态方法");
	}
}
class C{
	static{
		System.out.println("C类初始化");
	}
}
/*
 * 类的加载过程中,没有带上类的初始化:
 * (1)引用静态常量不会触发此类的初始化
 * (2)当访问一个静态域(静态变量,静态方法)时,只有真正声明这个域的类才会被初始化
	即当通过子类引用父类的静态变量,静态方法时,不会导致子类初始化
 * (3)通过数组定义类引用,不会触发此类的初始化
 *
 */
public class TestClinit2 {
	public static void main(String[] args) {
		System.out.println(D.NUM);//D类不会初始化,因为NUM是final的
		
		System.out.println(F.num);
		F.test();//F类不会初始化,E类会初始化,因为num和test()是在E类中声明的
		
		//G类不会初始化,此时还没有正式用的G类
		G[] arr = new G[5];//没有创建G的对象,创建的是准备用来装G对象的数组对象
	}
}
class D{
	public static final int NUM = 10;
	static{
		System.out.println("D类的初始化");
	}
}
class E{
	static int num = 10;
	static{
		System.out.println("E父类的初始化");
	}
	public static void test(){
		System.out.println("父类的静态方法");
	}
}
class F extends E{
	static{
		System.out.println("F子类的初始化");
	}
}

class G{
	static{
		System.out.println("G类的初始化");
	}
}

类加载器的类型


/*
 * 一、类加载的过程由类加载器来完成。
 * 类加载器:
 * 1、引导类加载器(Bootstrap Classloader):又称为根类加载器
 * 	加载Java的核心库(JAVA_HOME/jre/lib/rt.jar等或sun.boot.class.path路径下的内容)
 * 是用原生代码(C/C++)来实现的,并不继承自java.lang.ClassLoder,所以通过Java代码获取引导类加载器对象将会得到null
 * 
 * 2、扩展类加载器(Extension ClassLoader)
 *  sun.misc.Launcher$ExtClassLoader
 * 负责加载Java的扩展库(JAVA_HOME/jre/ext/*.jar或java.ext.dirs路径下的内容) 
 * 
 * 3、应用程序类加载器(Application Classloader)
 * sun.misc.Launcher$AppClassLoader
 * 负责加载Java应用程序类路径(classpath、java.class.path)下的内容
 * 通俗的讲:项目的路径bin文件夹下的字节码,以及如果你配置了环境变量classpath
 * 
 * 4、自定义类加载器
 * 一般什么情况下需要自定义类加载器:
 * (1)字节码需要加密和解密
 * (2)字节码的路径不在常规路径,有自定特定的路径
 * 		例如:tomcat
 * 
 * 二、如何获取某个类的类加载器对象?
 * 1、获取某个类的Class对象
 * 2、通过Class对象调用getClassLoader()方法获取类加载器对象
 * 
 * 三、Java中类加载器的双亲委托模式
 * 1、类加载器设计时,这四种类加载器是有层次结构的,但是这层次结构不是通过继承关系来实现。
 * 但是是通过组合的方式,来实现“双亲”的认亲过程。
 * 例如:应用程序类加载器把扩展类加载器称为“父加载器”,在应用程序类加载器中保留应用程序类加载器的一个引用,成员变量,把变量名称设计为parent。
 * 所有的类加载器有一个getParent(),获取父加载器的方法。
 * 
 * 2、有什么用?
 * 目的:为了安全,而且各司其职
 * 
 * 当应用程序类加载器接到加载某个类的任务时,例如:java.lang.String。
 * (1)会现在内存中,搜索这个类是否加载过了,如果是,就返回这个类的Class对象,不去加载。
 * (2)如果没有找到,即没有加载过。会把这个任务先提交给“父加载器”
 * 
 * 当扩展类加载器接到加载某个类的任务时,例如:java.lang.String。
 * (1)会现在内存中,搜索这个类是否加载过了,如果是,就返回这个类的Class对象,不去加载。
 * (2)如果没有找到,即没有加载过。会把这个任务先提交给“父加载器”
 * 
 * 当引导类加载器接到加载某个类的任务时,例如:java.lang.String。
 * (1)会现在内存中,搜索这个类是否加载过了,如果是,就返回这个类的Class对象,不去加载。
 * (2)如果没有找到,即没有加载过。会在它的负责的范围内尝试加载。
 *    如果可以找到,那么就返回这个类的Class对象。就结束了。
 *    如果没有找到,那么会把这个任务往回传,让“子加载器”扩展类加载器去加载。
 *    
 *  “子加载器”扩展类加载器接到“父加载器”返回的任务后,去它负责的范围内加载。
 *    如果可以找到,那么就返回这个类的Class对象。就结束了。
 *    如果没有找到,那么会把这个任务往回传,让“子加载器”应用程序类加载器去加载。 
 * 
 * “子加载器”应用程序类加载器接到“父加载器”返回的任务后,去它负责的范围内加载。
 *    如果可以找到,那么就返回这个类的Class对象。就结束了。
 *    如果没有找到,那么就报错ClassNotFoundException或java.lang.NoClassDefError
 * 
 * Java中是认为不同的类加载器,加载的类名相同的类,识别为不同的类。
 * 
 */
public class TestClassLoader {
	@Test
	public void test04(){
		//(1)先获取这个类的Class对象
		Class clazz = TestClassLoader.class;
		//(2)获取它的类加载器对象
		ClassLoader loader = clazz.getClassLoader();
		System.out.println("当前类的加载器:" + loader);
		
		ClassLoader parent = loader.getParent();
		System.out.println("父加载器:" + parent);
		
		ClassLoader grand = parent.getParent();
		System.out.println("爷爷加载器:" + grand);
	}
	
	@Test
	public void test03() throws ClassNotFoundException{
		//(1)先获取这个类的Class对象
		Class clazz = Class.forName("com.atguigu.ext.demo.AtGuiguExtDemo");
		//(2)获取它的类加载器对象
		System.out.println(clazz.getClassLoader());
	}
	
	@Test
	public void test02(){
		//(1)先获取这个类的Class对象
		Class clazz = String.class;
		//(2)获取它的类加载器对象
		ClassLoader loader = clazz.getClassLoader();
		System.out.println(loader);
	}
	
	@Test
	public void test01(){
		//(1)先获取这个类的Class对象
		Class clazz = TestClassLoader.class;
		//(2)获取它的类加载器对象
		ClassLoader loader = clazz.getClassLoader();
		System.out.println(loader);
	}
}

使用类加载器来加载类路径下的资源文件:


/*
 * 类加载器的作用:
 * 1、本质工作:加载类
 * 2、顺便可以用它来加载“类路径下”的资源文件
 * 	  例如:src下(编译后对应bin)有一个资源文件,配置文件:jdbc.properties
 * 
 * 如何获取某个类的类加载器对象?
 * 1、获取某个类的Class对象
 * 2、通过Class对象调用getClassLoader()方法获取类加载器对象
 * 
 * ClassLoader类加载器的类型:
 *   getResourceAsStream("资源文件的路径名")
 *   
 * SourceFolder:源代码文件夹,一般会单独用一个config这种SourceFolder来装配置文件、等价于src
 * 不同与普通的Folder
 */
public class TestClassLoader2 {
	@Test
	public void test5() throws IOException{
		Properties pro = new Properties();//集合,map,key=value
		
		Class clazz = TestClassLoader2.class;
		ClassLoader loader = clazz.getClassLoader();
		InputStream in = loader.getResourceAsStream("spring.properties");
		
		pro.load(in);
		
		System.out.println(pro);
		System.out.println(pro.getProperty("user"));
	}
	
	
	@Test
	public void test4() throws IOException{
		Properties pro = new Properties();//集合,map,key=value
		
		//在项目的根路径下,不在src里面
		pro.load(new FileInputStream("out.properties"));
		
		System.out.println(pro);
		System.out.println(pro.getProperty("out"));
	}
	
	
	@Test
	public void test3() throws IOException{
		Properties pro = new Properties();//集合,map,key=value
		
		Class clazz = TestClassLoader2.class;
		ClassLoader loader = clazz.getClassLoader();
		InputStream in = loader.getResourceAsStream("com/atguigu/test04/demo.properties");
		
		pro.load(in);
		
		System.out.println(pro);
		System.out.println(pro.getProperty("name"));
	}
	
	@Test
	public void test2() throws IOException{
		Properties pro = new Properties();//集合,map,key=value
		
		Class clazz = TestClassLoader2.class;
		ClassLoader loader = clazz.getClassLoader();
		InputStream in = loader.getResourceAsStream("jdbc.properties");
		
		pro.load(in);
		
		System.out.println(pro);
		System.out.println(pro.getProperty("user"));
	}
	
	
	@Test
	public void test1() throws IOException{
		//相对路径在项目day24_code根目录下,不是相对src下
		FileReader fr = new FileReader("jdbc.properties");
		BufferedReader br = new BufferedReader(fr);
		String str = br.readLine();
		System.out.println(str);
		br.close();
		fr.close();
	}
}

Class对象


/*
 * 1、java.lang.Class类型:
 * 		所有的Java类型(包括基本数据类型、引用数据类型、void)
 * 		被加载到内存后,或者是编译器自动编译生成的class字节码,最终都会用一个Class对象来表示。
 * 		即,所有的Java类型,在内存中都表示为一个Class对象。
 * 
 * 2、如何获取Class对象?4种   重点
 * (1)类型名.class
 * 优点:最简洁
 * 缺点:要求编译期这个类型就要存在
 * 
 * (2)对象.getClass()
 * 这个方法在java.lang.Object类型中声明的,返回对象的运行时类型
 * 
 * 适用于:你的先有对象
 * 
 * (3)Class.forName("类型全名称")
 * 类型全名称:包.类名
 * 优势:这个类型可以在编译期间未知,这个类名称可以在代码中出现,也可以配置在配置文件中,或者键盘输入等方式来指定。
 * 
 * (4)使用类加载器对象.loadClass("类型全名称")
 * 一般都是用在自定义类加载器对象去加载指定路径下的类
 * 
 * 这四种方式:都可以选择,你就看当前的情况,哪种能用,就用哪种,如果都能用,就用最简便的。
 */
public class TestClass {
	@Test
	public void test05() throws ClassNotFoundException{
		Class c = TestClass.class;
		ClassLoader loader = c.getClassLoader();
		
		Class c2 = loader.loadClass("com.atguigu.test05.Employee");
		Class c3 = Employee.class;
		System.out.println(c2 == c3);
	}
	
	
	@Test
	public void test04() throws ClassNotFoundException{
		Class c = Class.forName("com.atguigu.ext.demo.AtGuiguExtDemo");
		System.out.println(c);
	}
	
	@Test
	public void test03() throws ClassNotFoundException{
		Class c2 = String.class;
		Class c1 = "".getClass();
		
		Class c3 = Class.forName("java.lang.String");
		
		System.out.println(c1 == c2);
		System.out.println(c1 == c3);
		
	}
	
	@Test
	public void test02(){
		Class c2 = String.class;
		Class c1 = "".getClass();
		
		System.out.println(c1 == c2);
	}
	
	@Test
	public void test01(){
		Class c1 = int.class;//基本数据类型
		
		Class c2 = void.class;//特殊的空类型
		
		Class c3 = String.class;//系统定义的类类型
		Class c4 = TestClass.class;//自定义的类的类类型
		
		Class c5 = Serializable.class;//接口类型
		Class c6 = ElementType.class;//枚举类型    
		Class c7 = Override.class;//注解类型
		
		Class c8 = int[].class;
		
//		Class c9 = Student.class;//错误的,因为编译期间不存在
		
//		Class c10 = com.atguigu.ext.demo.AtGuiguExtDemo.class;//错误的,不在当前的类路径下
	}
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值