目录
类的加载过程*
(1)加载:load
将class文件加载到内存
(2)链接:link
① 验证:校验被加载的class文件的合法性,并且不会危害虚拟机的自身安全
(所有的java的字节码文件 cafebaby 开头)。
② 准备:为 类变量(成员变量) 分配内存 (方法区中) 并设置默认值 (0,null,false),为静态常量赋初始值 (常量池中)。
③ 解析:把字节码中的符号引用 (类似全类名) 替换为对应的直接地址引用
(3)初始化:initialize
1. 静态成员变量 显示赋值语句
2. 静态代码块内容
- 注意 哪些操作会导致类初始化?
<clinit>()
1.创建对象 (new)
2.使用了当前类中的静态方法 (静态方法、静态属性)
3.反射操作
注:类的初始化只会执行一次 (双亲委派机制)
- 哪些操作会导致类初始化滞后?
滞后:已经使用了类型,但是没有导致类的初始化发生
1.创建该类型数组
2.使用了类中的静态常量
3.子类使用了从父类继承的静态资源,只会导致父类初始化,不会导致子类初始化
类的加载 与 类加载器(ClassLoader)的理解
不同的类通过不同的类加载器完成加载
类加载器的分类:
(1)引导类加载器(Bootstrap ClassLoader)又称为根类加载器
它负责加载jre/lib/rt.jar核心类库,它本身不是Java代码实现的(HotSpot VM 中C++实现的),也不是ClassLoader的子类,获取它的对象时往往返回null。
(2)扩展类加载器 (Extension ClassLoader)。
它负责加载jre/lib/ext扩展库,它是ClassLoader的子类,Java代码编写。
(3)应用程序类加载器 (Application ClassLoader)。
也称为系统类加载器System Class Loader,它负责加载项目的classpath路径下的类,它是ClassLoader的子类,Java代码编写。(加载自己写的类)
(4)自定义类加载器。(略)
Tomcat
//对于自定义类,使用类加载器进行加载
ClassLoader classLoader = test.class.getClassLoader();
System.out.println(classLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2
//调用系统类加载器的gerParent();获取扩展类加载器
ClassLoader classLoader1 = classLoader.getParent(); //获取上一层
System.out.println(classLoader1); //sun.misc.Launcher$ExtClassLoader@4554617c
//调用扩展类加载器的getParent();无法获取引导类加载器
//引导类加载器主要负责加载java的核心类库,无法加载自定义类
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2); //null BootstrapClassLoader用C++编写,无法获取
双亲委托机制(双亲委派)
如果一个类加载器收到了类加载请求,它并不会自己先加载,而是把这个请求委托给父类的加载器去执行,父类加载失败返回子类处理。如果都不能则抛出 ClassNotFoundException。
- 尝试篡改核心类
自定义类:java.lang.String (没用,找不到main方法)
自定义类:java.lang.ShkStart(报错:阻止创建 java.lang开头的类)
为什么需要双亲委派模型?
① 避免类的重复加载
② 保护程序安全,防止核心API被随意篡改
反射机制概述
-
Reflection(反射) 是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。(在程序运行时获取对象)。
-
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
- 动态语言 vs 静态语言
java.lang.Class类
关于java.lang.Class类的理解
类的加载过程:
程序在经过javac.exe命令(编译)后,会生成一个或多个字节码文件(.class结尾)。(编译)接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。过程就称为"类的加载"。
加载到内存中的类,我们就称为运行时类 ---此运行时类,就作为Class的一个实例。
换句话说,Class的实例就对应着一个运行时类。这个Class对象封装了类在方法区内的数据信息,并且向Java程序员提供了访问方法区内的类数据信息的接口。
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
获取CLass对象的四种方式:
//获取Class的实例的方式(前三种方式使用较多)
//方式一:调用运行时类的属性: .class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
System.out.println(clazz1 == clazz2); //true
//方式三:调用Class的静态方法: forName(String classPath(全类名))//包名+类名
Class clazz3 = Class.forName("com.Reflection.Person");
System.out.println(clazz3);
//方式四:使用类的加载器: ClassLoader
Class personClass = Person.class;
ClassLoader classLoader = personClass.getClassLoader();
Class clazz4 = classLoader.loadClass("Temp");
//Class对象是单例的,永远只有一个
System.out.println(clazz3 == clazz4); //true
Class实例可以是哪些结构?
Class实例可以是那些结构的说明
//Class实例可以是那些结构的说明:
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要数组的元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);
体验动态性:通过反射,灵活创建对象:
@Test
public void test1() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Scanner in = new Scanner(System.in);
System.out.println("请输入要创建对象的全路径名:");
String message = in.next();
Class<?> aClass = Class.forName(message);
Object o = aClass.newInstance();
System.out.println("o = " + o);
} + o);
Java反射机制研究及应用
Java反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
- 反射相关API
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类当方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
...
- 代码测试: 反射前后对比
//反射之前,对于Person类的操作
@Test
public void test1(){
//1. 创建Person类的对象(Person类的实例化)
Person p1 = new Person("Tom",12);
//2. 通过对象,调用其内部的属性和方法
//调用属性
p1.age = 10;
System.out.println(p1);
//调用方法
p1.show();
//对于Person类外部,不可以通过Person类对象调用其内部私有结构
//比如: name、showNation()、私有构造器
}
//反射之后,对于Person类的操作
@Test
public void test2() throws Exception {
Class clazz = Person.class;
//1. 通过反射,创建Person类的对象
Constructor cons = clazz.getConstructor(String.class,int.class);
Object obj = cons.newInstance("Tom", 12);
Person p = (Person) obj;
System.out.println(p); //Person{name='Tom', age=12}
//2. 通过反射,调用对象指定的属性、方法
//调用属性
Field age = clazz.getDeclaredField("age");
age.set(p,10);
System.out.println(p); //Person{name='Tom', age=10}
//调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(p); //你好,我是一个人
System.out.println("======================");
//通过反射,可以调用Person类的私有结构的
//比如: 私有的构造器、方法、属性
//调用私有构造器
Constructor con1 = clazz.getDeclaredConstructor(String.class);
con1.setAccessible(true);
Person p1 = (Person) con1.newInstance("Jerry");
System.out.println(p1); //Person{name='Jerry', age=0}
//调用私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"PePe");
System.out.println(p1); //Person{name='PePe', age=0}
//调用私有方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
String nation = (String) showNation.invoke(p1,"中国");//相当于String nation = p1.shouNation("中国");
System.out.println(nation);
}
//疑问? 通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用哪个?
//建议: 直接new的方式。
//我们什么时候会使用: 反射的方式。 反射的特征: 动态性
//疑问? 反射的机制与面向对象的封装性是不是矛盾的?如何看待两个技术?
//不矛盾,
反射机制有什么用?
通过java语言的反射机制可以操作字节码文件
java.lang.reflect包下
反射机制相关的重要的类有哪些:
代表整个字节码,一个类型 java.lang.class
代表字节码方法字节码 java.lang.reflect.Method
代表字节码文件构造方法 java.lang.reflect.Constructor
代表字节码文件属性字节码 java.lang.reflect.Field