一、类加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化。
不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。
类加载:
将class文件读入内存,并为之创建一个java.lang.Class对象
任何类被使用,系统都会为之创建一个java.lang.Class对象
类连接:
验证阶段:用于检验被加载的类是否有正确的内部结果,和其他类协调一致
准备阶段:负责为类的类变量分配内存,并设置默认初始化值
解析阶段:将类的二进制数据中的符号引用替换为直接引用
类的初始化:
主要对类变量进行初始化
类初始化的步骤:
假如类还未被加载和连接,则程序先加载并连接该类
假如该类的直接父类还未被初始化,则先初始化其父类(所以Java虚拟机最先初始化的是Object类)
假如类中有初始化语句,则系统依次执行这些初始化语句
注意:在执行第二个步骤时,系统对直接父类的初始化步骤也要遵循上面三步
类的初始化时机:
一定是首次,因为一个类一旦被载入虚拟机,同一个类就不会再次被载入
创建类的实例
调用类的类方法(如静态方法)
访问类或者接口的类变量,或者为该类变量赋值
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类(因为初始化子类一定会先初始化父类)
直接使用java.exe命令来运行某个主类
也可以看一下下面的文章更深入的了解一下
https://blog.csdn.net/weixin_51351637/article/details/127704624
二、类加载器
什么是类加载器?
专门负责加载类的命令/工具
负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象
JDK自带3个类加载器
启动类加载器 (父加载器)
扩展类加载器 (母加载器)
应用类加载器
双亲委派机制(一种安全机制):
java中为了保证类加载的安全,使用了双亲委派机制。优先从启动类加载器加载,这个成为“父加载器”,
当父加载器无法加载到,再从扩展类加载器中加载,这个成为"母加载器"。
双亲委派,如果都加载不到,才会考虑从应用类加载器中加载,知道加载到为止
代码在开始执行之前,会将所需要的类全部加载到JVM当中,通过类加载器加载
比如:
String s = "abc";
类加载器会找到String.class文件进行加载。
加载时首先会从启动类加载器加载: 专门加载jdk\jre\lib\rt.jar 的包,里面都是JDK最核心的类库
启动类加载器加载不到的时候。会通过扩展类加载器加载:jdk\jre\lib\ext\*.jar 的jar包
如果扩展类加载器中也加载不到的话,会通过应用类加载器加载:应用类加载器专门加载classPath的jar包
类加载机制
三、反射
高级框架底层实现原理,都采用了反射机制,挺重要的
反射的作用:可以操作字节码文件(.class文件)
java.lang. Class 代表 字节码文件,代表整个类
java.lang.reflect. Method 代表字节码中的 方法字节码,代表类中的方法
java.lang.reflect. Constructor 代表字节码中的 构造方法字节码,代表类中的构造方法
java.lang.reflect. Field 代表字节码中的 属性字节码,代表类中的成员变量
3.1 获取Class的三种方式
3.1.1 第一种方式
// 此时c1就代表java.lang.String的class文件,或者说c1代表String类型
// 静态方法
// 方法的参数是一个字符串,字符串需要一个带有包名的完成类名
Class c1 = Class.forName("java.lang.String");
3.1.1.1 静态代码块执行Class.forName(完整类名)
如果我们只希望一个类的静态代码块执行,其它代码一律不执行,那我们可以使用Class.forName(完整类名)
public class ReflectTest04 {
public static void main(String[] args) throws ClassNotFoundException {
// Class.forName的执行会导致类加载,类加载的执行必然会导致静态代码块的执行
Class.forName("com.example.reflect.MyClass");
}
}
class MyClass {
// 静态代码块在类加载时执行,并且只执行一次
static {
System.out.println("静态代码块执行了");
}
}
3.1.2 第二种方式
// 我们的老祖宗Object类中有一个方法,getClass()
// 我们是字符串类型调用的getClass方法,那这个x指的就是String.class字节码文件或者说代表String类型
String s = "aaa";
Class x = s.getClass();
那我们同样都是String的字节码文件,那x和c1是否相等?
System.out.println(x ==c1 );
内存图如下所示:
3.1.3 第三种方式
// 第三种方式,java语言中任何一个类型,包括基本数据类型,都有.class属性
// 下面的z代表String类型
Class z = String.class;
3.2 通过反射实例化对象
通过反射机制,获取class,通过Class来实例化对象
// 通过反射机制,获取class,通过class来实例化对象
Class c = Class.forName("com.example.reflect.User");
// 使用newInstance()创建对象,此方法会调用User这个类的无参数构造方法,完成对象的创建
Object obj = c.newInstance();
System.out.println(obj);
public class User {
public User(){
System.out.println("无参构造方法");
}
}
此处还可以添加泛型
这样我们创建出来的对象就是User对象
// 通过反射机制,获取class,通过class来实例化对象
Class<User> c = (Class<User>)Class.forName("com.example.reflect.User");
// 使用newInstance()创建对象,此方法会调用User这个类的无参数构造方法,完成对象的创建
User user = c.newInstance();
那如果我们User并没有提供无参构造器,而是提供了一个有参构造器,这样会出现什么情况呢?
出现实例化异常,没有这个无参构造
3.2.1 验证反射机制的灵活性
java代码写一遍,不改变java源代码的基础上,可以做到不同对象的实例化。
符合OCP开闭原则:对扩展开发,对修改关闭
创建配置文件
读取属性文件实例化对象
public class ReflectTest03 {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
// 通过IO流读取classinfo.properties
FileReader reader = new FileReader("D:\\project\\springboot\\Swagger\\classinfo.properties");
// 创建属性类对象Map
Properties pro = new Properties();
// 加载,此时文件中的内容就加载到map集合中了
pro.load(reader);
// 关闭流
reader.close();
// 通过key获取value
String className = pro.getProperty("className");
System.out.println(className);
// 通过反射机制实例化对象
// 获取className的字节码文件
Class<?> c = Class.forName(className);
// 创建对象
Object o = c.newInstance();
System.out.println(o);
}
}
下面我们修改一下properties的内容
className=java.util.Date
不动java代码再输出
3.2.2获取类路径下的绝对路径
下面这样写路径有一个缺点,移植性差,在IDEA中默认的当前路径是project的根
那就是只能在idea中找到对应的文件,如果我们把代码换到其他的地方,当前路径就不是project的根了,这是这个路径就无效了。
FileReader reader = new FileReader("Swagger/classinfo.properties");
下面写一种通用的路径,即使代码换位置了,这样的编写仍是通用的,但是!!!文件只能在类路径下
凡是在src目录下的都是类路径下
记住!!!!一定确定好根目录下
getResource(String name),其中name启动是从根路径开始写!!!!!一定注意,否则会出现空指针异常
这样的好处是,不论以后移植到什么环境、谁的电脑都能获取path内容,类似动态获取
public class AboutPath {
public static void main(String[] args) {
// Thread.currentThread() 获取当前线程对象
// getContextClassLoader()是线程对象的方法,可以获取到当前线程的类加载器对象(类加载器挺多的)
// getResource(String name)方法是类加载器的方法,当前线程的类加载器默认从类的根路径下(src目录下)加载资源
String path=Thread.currentThread().getContextClassLoader().getResource("classinfo2.properties").getPath();
System.out.println(path);
}
}
假如是maven项目呢? 如下所示
String path=Thread.currentThread().getContextClassLoader().getResource("classinfo3.properties").getPath();
3.2.3 灵活获取路径进行反射
这个程序相当于将3.2.1与3.2.2结合起来了
public class IoPropertiesTest {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
// 获取path路径
String path=Thread.currentThread().getContextClassLoader().getResource("classinfo3.properties").getPath();
// 通过IO流读取classinfo.properties
FileReader reader = new FileReader(path);
// 创建属性类对象Map
Properties pro = new Properties();
// 加载,此时文件中的内容就加载到map集合中了
pro.load(reader);
// 关闭流
reader.close();
// 通过key获取value
String className = pro.getProperty("className");
System.out.println(className);
// 通过反射机制实例化对象
// 获取className的字节码文件
Class<?> c = Class.forName(className);
// 创建对象
Object o = c.newInstance();
System.out.println(o);
}
}
但是,我们上面的程序还能再简单一点点
由两行代码编程一行
// 获取path路径
// String path=Thread.currentThread().getContextClassLoader().getResource("classinfo3.properties").getPath();
// 通过IO流读取classinfo.properties
// FileReader reader = new FileReader(path);
// 直接以流的形式返回,这个地方还是类路径下
InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("classinfo3.properties");
3.2.4 补充:资源绑定器
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容(如果是Maven工程,则是获取的resource目录下的内容),但是条件有一点苛刻,文件的扩展名必须是properties结尾
public class ResourceBundleTest {
public static void main(String[] args) {
// 前提:类路径下,扩展名必须是properties,并且在写路径的时候,不写文件的扩展名
ResourceBundle bundle = ResourceBundle.getBundle("classinfo3");
String className = bundle.getString("className");
System.out.println(className);
}
}
再补充: 和上面没有关系
当我们接触springboot之后很少用这种文件,而是用yaml形式,如下文章所示
https://blog.csdn.net/weixin_51351637/article/details/124048275
或者使用下面的Environment对象进行获取yaml结尾的文件
//import org.springframework.core.env.Environment;
@Autowired
private Environment environment;
@Test
public void methodTest(){
String personName = environment.getProperty("person.name");
String hobby = environment.getProperty("person.hobby[2]");
System.out.println("name = " + name);
System.out.println("hobby = " + hobby);
}
3.3 获取Field—反射属性
3.3.1创建实体类并获取
public class Student {
// 4个Field采用不同的访问权限控制符
public int no;
private String name;
protected int age;
boolean sex;
private static final double MATH_PI = 3.1415926;
}
获取整个类
// 获取整个类
Class studentClass = Class.forName("com.example.reflect.Student");
System.out.println("完整类名:"+studentClass.getName());
System.out.println("简类名:"+studentClass.getSimpleName());
3.3.2 获取public 修饰的Field
Field[] fields = studentClass.getFields();
System.out.println("studentClass.getFields() 获取的数组长度:"+fields.length); //长度是2,说明只有2个元素
Field f = fields[0];
System.out.println(f.getName());
3.3.3 获取所有Field字段并进行解析
//获取所有的Field
Field[] declaredField = studentClass.getDeclaredFields();
System.out.println("studentClass.getDeclaredFields() 获取的数组长度:"+declaredField.length); //5
// 对所有的Field挨个解析
for (Field field : declaredField){
// 修饰符列表可能有多个,比如 public static final double MATH_PI = 3.1415926 中 public static final 就是修饰符列表
int modifiers = field.getModifiers(); //返回的修饰符是一个数字,每个数字是修饰符的代号
// 可以将这个数字转换成字符串嘛?可以,如下所示
System.out.println("获取属性field的修饰符列表"+ Modifier.toString(modifiers));
System.out.println("获取属性field的类型"+field.getType());
System.out.println("获取属性field的名字:"+field.getName());
System.out.println("************************");
}
3.3.4 获得具体的某个字段
public class ReflectTest07 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
// 获取整个类
Class studentClass = Class.forName("com.example.reflect.Student");
// 获取对象
Object obj = studentClass.newInstance();
// 获取name属性
Field noField = studentClass.getDeclaredField("name");
System.out.println(noField);
}
}
3.3.5 反编译Field
将下面这个类反编译出来,看一下反射机制的威力
public class Student {
// 4个Field采用不同的访问权限控制符
public int no;
private String name;
protected int age;
boolean sex;
private static final double MATH_PI = 3.1415926;
}
public class ReflectTest06 {
public static void main(String[] args) throws ClassNotFoundException {
// 获取整个类
Class studentClass = Class.forName("com.example.reflect.Student");
// 用于下面的字符串拼接
StringBuilder stringBuilder = new StringBuilder();
// 类名开始
stringBuilder.append("public class " + studentClass.getSimpleName() + " {\n");
// 获取所有字段
Field[] fields = studentClass.getDeclaredFields();
// 解析所有字段
for (Field field : fields) {
// 修饰符列表
stringBuilder.append(Modifier.toString(field.getModifiers())+" ");
// 类型
stringBuilder.append(field.getType().getSimpleName()+" ");
// 字段名
stringBuilder.append(field.getName()+";\n");
}
// 类的结束
stringBuilder.append("\n}");
System.out.println(stringBuilder);
}
}
很完美啊!!!!!!!相当于获取源代码
3.3.6 通过反射机制访问对象属性
public class ReflectTest07 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
// 获取整个类
Class studentClass = Class.forName("com.example.reflect.Student");
// 虽然我们不是直接new对象,但是三要素还是缺一不可:obj对象、属性、字段值
// 获取对象
Object obj = studentClass.newInstance();
// 获取no属性,如果这个属性是private修饰的,将访问不到,依然是按照之前的访问原则进行
Field noField = studentClass.getDeclaredField("no");
// 给no属性赋值 这段话的意思是给obj对象的no属性赋值为2222
noField.set(obj,2222);
// 读取属性的值
System.out.println(noField.get(obj));
}
}
如果我们访问一下私有的属性呢?
很显然是出现异常,private修饰的访问不到
Field nameField = studentClass.getDeclaredField("name");
nameField.set(obj,"zhangingqi");
那还有没有其他的办法?
有!!打破封装!但是打破封装可能会给不法分子留下机会(反射的缺点)
Field nameField = studentClass.getDeclaredField("name");
// 打破封装
nameField.setAccessible(true);
nameField.set(obj,"zhangingqi");
System.out.println(nameField.get(obj));
3.5 反射Method及反编译Method
public class ReflectTest08 {
public static void main(String[] args) throws ClassNotFoundException {
// 获取整个类
Class userServiceClass = Class.forName("com.example.reflect.UserService");
// 这个语句获取了所有的方法,包括Object类的
// Method[] methods = userServiceClass.getMethods();
// 获取所有的Method(包括私有的)
Method[] methods = userServiceClass.getDeclaredMethods();
System.out.println(methods.length);
for (int i=0 ; i<methods.length;i++){
// 获取形式参数列表
int modifiers = methods[i].getModifiers();
System.out.print(Modifier.toString(modifiers)+" ");
// 获取返回值类型
System.out.print(methods[i].getReturnType()+" ");
// 获取方法名
System.out.print(methods[i].getName()+" ");
// 获取修饰符列表(一个方法的参数可能会有多个)
Class<?>[] parameterTypes = methods[i].getParameterTypes();
for (Class parameterType: parameterTypes){
System.out.print(parameterType.getSimpleName()+" ");
}
System.out.println();
}
}
}
3.5.1 通过反射机制调用方法(反射机制中最重要的)
public class ReflectTest09 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
// 获取整个类
Class userServiceClass = Class.forName("com.example.reflect.UserService");
// 获取对象
Object obj = userServiceClass.newInstance();
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
// 给形式参数列表赋值
Object retValue = loginMethod.invoke(obj,"admin", "123");
System.out.println(retValue);
}
}
public class UserService {
int no;
int age;
//登录
public boolean login(String name,String password){
if("admin".equals(name) && "123".equals(password)){
return true;
}
return false;
}
//退出
public void logout(){
System.out.println("系统已经安全退出");
}
}
3.6 反射Constructor
3.6.1 反编译Constructor
public class ReflectTest11 {
public static void main(String[] args) throws ClassNotFoundException {
StringBuilder s = new StringBuilder();
Class<?> vipClass = Class.forName("com.example.reflect.Vip");
s.append(Modifier.toString(vipClass.getModifiers()));
s.append(" class");
s.append(" " + vipClass.getSimpleName() + " {\n");
// 拼接构造方法
Constructor<?>[] declaredConstructors = vipClass.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors) {
// 修饰符列表
s.append(Modifier.toString(constructor.getModifiers()) + " ");
// 构造器名
s.append(vipClass.getSimpleName());
s.append("(");
// 拼接形式参数列表
Class[] parameterTypes = constructor.getParameterTypes();
for (Class parameterType : parameterTypes) {
s.append(parameterType.getSimpleName() + ",");
}
// 去掉最后一个逗号
if (declaredConstructors.length > 0) {
s.deleteCharAt(s.length() - 1);
}
s.append("){}\n");
}
s.append("\n}");
System.out.println(s);
}
}
3.6.2 反射机制调用构造方法
public class ReflectTest12 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<?> c = Class.forName("com.example.reflect.Vip");
// 调用无参构造方法
Object obj = c.newInstance();
// 调用有参数构造方法
// 第一步:先获取有参数构造方法
Constructor<?> con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class);
// 第二步:调用构造方法new对象
con.newInstance(1,"2222","22333",false);
// 另一种方式无参构造方法
Constructor<?> con2 = c.getDeclaredConstructor();
con2.newInstance();
}
}
3.7 获取父类和父接口
public class ReflectTest13 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> stringClass = Class.forName("java.lang.String");
// 获取String的父类
Class<?> superclass = stringClass.getSuperclass();
// 拿到父类
System.out.println(superclass.getName());
// 获取String类实现的所有接口
Class<?>[] interfaces = stringClass.getInterfaces();
for(Class interfaceClass : interfaces){
System.out.println(interfaceClass.getName());
}
}
}
3.8 反射注解
//只允许该注解可以标注类、方法
@Target({ElementType.TYPE,ElementType.METHOD})
//希望这个注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
@MyAnnotation
public class MyAnnotationTest {
int i;
@MyAnnotation
public void doSome(){
}
public MyAnnotationTest() {
}
}
public class ReflectAnnotationTest {
public static void main(String[] args) throws ClassNotFoundException {
// 获取类
Class<?> c = Class.forName("com.example.annotation2.MyAnnotationTest");
// 判断类上是否有@MyAnnotation
boolean isHaveMyAnnotation = c.isAnnotationPresent(MyAnnotation.class);
if (isHaveMyAnnotation) {
// 获取该注解
MyAnnotation myAnnotation = c.getAnnotation(MyAnnotation.class);
System.out.println("类上面的注解对象:" + myAnnotation);
// 获取注解对象的属性
System.out.println("注解对象的属性value:"+myAnnotation.value());
}
}
}
3.8.1 通过反射获取注解对象属性的值
四、可变长参数
int... args 这就是可变长度参数
语法: 类型...
public static void m(int... args) {
}
可变长度参数要求的参数个数是:0~N个
public static void main(String[] args) {
m();
m(10);
m(10, 20, 30);
}
//可变长度参数,类型后面加三个点
//可变长度参数要求的参数个数是:0~N个
public static void m(int... args) {
System.out.println("m方法执行了!");
}
可变长度参数在参数列表中必须在最后一个位置上,并且可变长度参数只能有一个
// 下面这样写会出现错误,并且说明,可变长参数必须是在列表的最后一个
// public static void m2(String... args1,int... args2){
// }
// 这样写是完全没有问题的
public static void m3(String m ,int... args){
}
访问可变长度参数
public static void main(String[] args) {
m(10, 20, 30);
}
//可变长度参数,类型后面加三个点
//可变长度参数要求的参数个数是:0~N个
public static void m(int... args) {
for(int i=0;i< args.length;i++){
System.out.println(args[i]);
}
}
五、注解 Annotation
5.1 注解介绍
注解Annotation 是一种引用数据类型,编译之后也生成xxx.class文件
自定义注解的语法格式
修饰符列表 @interface 注解类型名{}
//自定义注解
public @interface MyAnnotation {
}
注解使用语法格式
@注解类型名
注解可以出现在类上、属性上、方法上、变量上等....
注解还可以出现在注解类型上
@MyAnnotation
public class AnnotationTest01 {
@MyAnnotation
private int no;
@MyAnnotation
public void m(){
@MyAnnotation
int i =100;
}
}
5.2 元注解
用来标注"注解类型"的注解,成为元注解
常见的元注解:@Target、@Retention,如下所示
@Target注解用来标注“被标注的注解”可以出现在哪些位置上
@Target(ElementType.METHOD)表示被标注的注解出现在方法上
@Retention用来标注“被标注的注解”最终可以保存在哪
@Retention(RetentionPolicy.SOURCE): 该注解只被保留在java源文件中
@Retention(RetentionPolicy.CLASS): 表示该注解被保存在class文件中
@Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
5.3 @Deprecated注解
但是不鼓励这么使用,因为它很危险或者存在更好的选择
如果用在方法上表示这个方法已经过时
public class AnnotationTest03 {
public static void main(String[] args) {
AnnotationTest03.doSome();
}
@Deprecated
public static void doSome() {
System.out.println("do something....");
}
public static void doOther() {
System.out.println("do other....");
}
}
5.4 注解中的属性
5.4.1 定义属性并使用
//自定义注解
public @interface MyAnnotation {
/**
* 我们通常在注解当中定义属性,以下是name属性
* 看着像方法但其实不是
* @return
*/
String name() default "";
// 如果我们没有给age属性赋值,则默认为25
int age() default 25;
}
public class MyAnnotationTest {
public static void main(String[] args) {
}
// 如果一个注解当中有属性,必须给属性赋值
@MyAnnotation(name= "zhangingqi")
public void doSome(){
}
}
5.4.2 属性是value时可以省略
public @interface MyAnnotation {
String value() default "";
}
5.4.3 属性支持的类型
byte、short、int、long、float、double、boolean、char、String、Class、枚举类型以及以上每一种数组形式