第9天学习打卡(JavaSE_注解与反射_反射:Java内存分析、类的加载、Class对象获取运行时类的完整结构、反射有了Class对象之后做什么、反射读取注解)

2.3Java内存分析

1.Java内存

  • 栈:

    • 一般用来存放用关键字new出来的数据
  • 堆:

    • 基本数据类型

    • 局部变量(在方法代码段中定义的变量),方法调用完后JVM回收

    • 引用数据类型——即需要用关键字new出来的对象所对应的引用 也是存放在栈空间中。

      此时JVM在栈空间中给对象引用分配了一个地址空间,存储引用变量,

      指向,在堆空间给该引用指向的实际对象分配的地址空间。

  • 方法区:

    • 用于存放已被加载的类信息、常量、static静态变量、即时编译器编译后的代码等数据

2.4类的加载

1.类的加载过程

  • 类加载是一个将.class字节码文件读入内存,并实例化为Class对象进行相关初始化的过程

  • java的类使用时才被加载,且类的加载过程只发生一次

java类的加载过程可分为三个步骤:加载、链接、初始化

区分“类的加载过程”和其步骤“加载”

“类的初始化”只是“类的加载过程”的一个步骤

  • 加载:

    • 将.class字节码文件读入内存,将静态数据转换成方法区的运行时数据结构

    • 生成这个类对应的Class对象

      Class对象是加载到内存中才会产生的,由JVM创建,我们只能获取,不能生成

      通过反射动态进行的

  • 链接:将Java类的二进制代码 合并到 JVM的运行状态之中

    • 验证:确保类信息符合JVM规范
    • 准备:为static变量分配内存并设置初始值(在类初始化之前做),在内存的方法区进行
    • 解析:JVM常量池内的符号引用(常量名)替换为直接引用(地址)
  • 初始化:

    • JVM执行类构造器<clinit>()方法的过程。

      类构造器<clinit>()方法是由编译期 自动收集类中所有类变量的赋值动作static代码块中的语句合并产生的。

      类构造器是构造类信息的,不是构造该类对象的构造器。

    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

    • JVM会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

代码测试:

public class Test05 {
    public static void main(String[] args) {
        A a=new A();
        System.out.println(A.m);
    }
}

class A{
    static {
        System.out.println("A类静态代码快初始化");
        m=300;
    }
    static int m=100;


    public A(){
        System.out.println("A类的无参构造初始化");
    }
}

image-20210718172415458

image-20210718080743211

过程:

  1. 加载阶段:加载Test05类和A类,在方法区分别生成其对应的Class对象

  2. 链接阶段:为static变量m,在方法区分配内存空间,并进行默认初始化

    static int m=0;
    
  3. 初始化阶段:类的<clinit>()方法将static变量赋值和static代码合并,再执行赋值

    System.out.println("A类静态代码快初始化");
    m=300;
    m=100;
    //定义阶段已经在链接阶段完成
    

2.类加载时机和初始化时机

  • **类加载时机:**从执行包含main函数的类开始加载,涉及到下面情况的类就加载

    • 使用类的静态变量或静态方法
    • 使用反射方式来强制创建某个类或接口 对应的java.lang.Class对象
    • 创建类的实例
    • 初始化某个类的子类
    • (直接使用java。exe命令来运行某个主类)
  • **类的初始化理解:**类的初始化 主要就是对类的static变量进行初始化

    区分实例变量和静态变量,实例变量在new对象时初始化

    • JVM执行类构造器<clinit>()方法的过程。

      类构造器<clinit>()方法是由编译期 自动收集类中所有类变量的赋值动作static代码块中的语句合并产生的。

      类构造器是构造类信息的,不是构造该类对象的构造器。

    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

    • JVM会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

  • 会发生类初始化的情况(类的主动引用):

    1. 当JVM启动,先初始化main方法所在的类
    2. 使用new关键字创建一个类的对象
    3. 调用该类的static变量(final的常量除外)和static方法
    4. 使用java.lang.reflect包对该类进行反射调用
    5. 当初始化一个类时,如果其父类没有被初始化,则会先初始化其父类
  • 不会发生类初始化的情况(类的被动引用):

    1. 引用static常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

    2. 当访问一个静态成员变量时,只有真正声明这个静态成员的类才会被初始化,当通过子类引用父类的静态成员变量时,不会导致子类的初始化

    3. 通过数组定义类引用,不会触发此类的初始化。

      数组只是一个名字和一片空间

测试:

//测试类什么时候会初始化
public class Test06 {
    static {
        System.out.println("Main类被加载");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        //1.主动引用,会发生类的初始化
        Son son = new Son();
        /*
        Main类被加载
        父类被加载
        子类被加载
         */

        //反射也会产生主动引用
        Class.forName("com.kuang.reflection.Son");
        /*
        Main类被加载
        父类被加载
        子类被加载
         */

        //2.被动引用,不会触发类的初始化
        //通过子类调用父类static变量或者方法
        System.out.println(Son.b);
        /*
        Main类被加载
        父类被加载
        2
         */

        //通过数组定义类引用
        Son[] array=new Son[5];
        /*
        Main类被加载
         */

        //类的常量
        System.out.println(Son.M);
        /*
        Main类被加载
        1
         */
    }
}

class Father{
    static final int M=1;
    static int b=2;
    static{
        System.out.println("父类被加载");
    }
}
class Son extends Father{
    static{
        System.out.println("子类被加载");
        m=300;
    }
    static int m=100;
}

3.类加载器

什么是类加载器?

类加载器 就是 加载字节码文件(.class)的类

image-20210719084626724

Java程序是一种具有动态性的解释性语言,类(class)只有被加载到JVM中后才能运行。

当运行指定程序时,JVM会将编译生成的.class文件按照需求和一定的规则加载到内存中,组织成为一个完整的Java应用程序。

这个加载的过程是由类加载器来完成的。具体来说,就是由ClassLoader和它的子类来实现的。

类加载器本身也是一个类,其实质是把类文件从硬盘读取到内存中

类加载的方式:

  • 隐式加载:使用new等方式创建对象,会隐式调用类加载器把对应的类加载到JVM中
  • 显式加载:通过反射直接调用Class.forName()方法把所需的类加载到JVM中。

类加载器分类:

在Java语言中,类的加载是动态的,它并不会一次性将所有类全部加载后再运行。而是保证程序运行的基础类(例如基类)完全加载到JVM中。

其他类,在需要时才加载。

  • 引导类加载器:BootStapLoader,加载最基础的文件
    • 用C++编写,是JVM自带的类加载器,负责Java平台核心类库
    • 该加载器程序无法直接获取。
    • jre/lib/rt.jar包下
  • 扩展类加载器:ExtClassLoader,加载基础的文件
    • jre/lib/ext/*.jar包下
  • 应用类加载器:AppClassLoader,加载三方jar包和自己编写的java文件
    • CLASSPATH指定的所有jar和目录

image-20210719090832845

类加载器的协调工作:双亲委派机制

3种类加载器是通过委托方式实现:

当加载一个类,JVM会自底向上去找是否已经加载;

且不能存在同名,如我们不能定义java.lang.Spring类;

双亲委派机制会检测安全性,保证创建出的类的唯一性

加载时:

  • 自底向上检查类是否已经装载

  • 自顶向下尝试加载类

获取测试:

public class Test07 {
    public static void main(String[] args) {
        //获取AppClassLoader
        ClassLoader appClassLoader=ClassLoader.getSystemClassLoader();        
        System.out.println(appClassLoader);

        //获取ExtClassLoader
        ClassLoader extClassLoader=appClassLoader.getParent();
        System.out.println(extClassLoader);

        //获取BootStapClassLoader
        ClassLoader bootStapClassLoader=extClassLoader.getParent();
        System.out.println(bootStapClassLoader);
    }
    	//测试当前变量是由那个加载器加载的
    	ClassLoader curClassLoader=Class.forName("com.kuang.reflection.Test07").getClassLoader();
}

image-20210719092301830

可以看出:

Test07类是由AppClassLoader加载的。

因为BootStapLoader先搜索指定目录找不到,其次ExtClassLoader也找不到,最后AppClassLoader在ClassPath中找到了Test07类。

【注意】BootStapLoader是用C++语言实现的,所以在Java语言中看不到它,输出null

类加载器 加载过程 分为以下3步:

  • 装载(将.class文件放入内存,生成Class对象)
  • 链接(1.检查:检查待加载.class文件的正确性;2.准备:给类中静态变量分配存储空间并默认初始化;3.解释:将符号引用转换成直接引用)
  • 初始化(对静态变量和静态代码块执行初始化工作)

2.5利用反射获取Class对象 获取运行时类的完整结构

  • Field、Method、Constructor、Superclass、Interface、Annotation

  • 在实际操作中,取得类的信息的操作代码,并不会经常开发

  • 一定要熟悉java.lang.reflect包的作用,反射机制

  • 如何取得一个类的属性、方法、构造器名称,修饰符等

测试:

public class Test08 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class<?> c1 = Class.forName("com.kuang.reflection.User");

        User user=new User();
        c1=user.getClass();

        //1.获得类的名字
        System.out.println(c1.getName()); //获得包名+类名
        System.out.println(c1.getSimpleName()); //获得类名

        //2.获得类的属性
        System.out.println("========================");
        Field[] fields = c1.getFields(); //只能找到public属性

        fields=c1.getDeclaredFields(); //找到当前类的所有属性
        for (Field field : fields) {
            System.out.println(field);
        }
        /*
        private int com.kuang.reflection.User.id
        private java.lang.String com.kuang.reflection.User.name
        private int com.kuang.reflection.User.age
         */

        //找到指定属性
        Field name = c1.getDeclaredField("name");
        System.out.println(name);

        //3.获得类的方法
        System.out.println("========================");
        Method[] methods = c1.getMethods(); //获得本类及其父类的所有public方法
        for (Method method : methods) {
            System.out.println("正常的:"+method);
        }
        methods = c1.getDeclaredMethods(); //获得本类的所有方法
        for (Method method : methods) {
            System.out.println("Declare:"+method);
        }

        Method getName = c1.getMethod("getName", null); //获取指定方法
        Method setName = c1.getMethod("setName", String.class);
        System.out.println("指定的:"+getName);
        System.out.println("指定的:"+setName);

        //4.获得类的构造方法
        System.out.println("========================");
        Constructor<?>[] constructors = c1.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("正常的:"+constructor);
        }

        constructors = c1.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("Declare:"+constructor);
        }

        Constructor<?> declaredConstructor = c1.getDeclaredConstructor(int.class, String.class, int.class);
        System.out.println("指定的:"+declaredConstructor);

    }
}

结果:

com.kuang.reflection.User
User
========================
private int com.kuang.reflection.User.id
private java.lang.String com.kuang.reflection.User.name
private int com.kuang.reflection.User.age
private java.lang.String com.kuang.reflection.User.name
========================
正常的:public java.lang.String com.kuang.reflection.User.toString()
正常的:public java.lang.String com.kuang.reflection.User.getName()
正常的:public int com.kuang.reflection.User.getId()
正常的:public void com.kuang.reflection.User.setName(java.lang.String)
正常的:public void com.kuang.reflection.User.setId(int)
正常的:public int com.kuang.reflection.User.getAge()
正常的:public void com.kuang.reflection.User.setAge(int)
正常的:public final void java.lang.Object.wait() throws java.lang.InterruptedException
正常的:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
正常的:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
正常的:public boolean java.lang.Object.equals(java.lang.Object)
正常的:public native int java.lang.Object.hashCode()
正常的:public final native java.lang.Class java.lang.Object.getClass()
正常的:public final native void java.lang.Object.notify()
正常的:public final native void java.lang.Object.notifyAll()
Declare:public java.lang.String com.kuang.reflection.User.toString()
Declare:public java.lang.String com.kuang.reflection.User.getName()
Declare:public int com.kuang.reflection.User.getId()
Declare:public void com.kuang.reflection.User.setName(java.lang.String)
Declare:public void com.kuang.reflection.User.setId(int)
Declare:public int com.kuang.reflection.User.getAge()
Declare:public void com.kuang.reflection.User.setAge(int)
指定的:public java.lang.String com.kuang.reflection.User.getName()
指定的:public void com.kuang.reflection.User.setName(java.lang.String)
========================
正常的:public com.kuang.reflection.User()
正常的:public com.kuang.reflection.User(int,java.lang.String,int)
Declare:public com.kuang.reflection.User()
Declare:public com.kuang.reflection.User(int,java.lang.String,int)
指定的:public com.kuang.reflection.User(int,java.lang.String,int)

2.6反射有了Class对象以后 能做什么?

1.创建对象

  • 直接利用newInstance()创建一个对象,适用于存在无参构造

    User user = (User) c1.newInstance();
    
  • 反射获取构造器,进而创建对象,适用于有参构造

    Constructor<?> constructor = c1.getDeclaredConstructor(int.class, String.class, int.class);
    User user2 = (User) constructor.newInstance(1, "kuangshen", 18);
    

2.反射调用类中方法

User user3=(User)c1.newInstance();
//通过反射获取一个方法
Method setName = c1.getDeclaredMethod("setName", String.class);

//invoke使用方法(对象,方法的参数)
setName.invoke(user3,"kuangshen2");
System.out.println("user3:"+user3);

3.反射操作属性

//3.利用反射操作属性
User user4=(User)c1.newInstance();
Field name = c1.getDeclaredField("name");

//不能直接操作私有属性,需要开启允许访问,关闭安全检测。方法、字段、构造器都可以设置开启允许访问
name.setAccessible(true);

//操作属性(对象,属性值)
name.set(user4,"kuangshen3");
System.out.println("user4:"+user4);

测试:

public class Test09 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //获得Class对象
        Class<?> c1 = Class.forName("com.kuang.reflection.User");

        //1.1直接利用newInstance()创建一个对象,仅适用于存在无参构造
        User user = (User) c1.newInstance();
        System.out.println("user:"+user);

        //1.2反射获取构造器,进而创建对象,使用于有参构造
        Constructor<?> constructor = c1.getDeclaredConstructor(int.class, String.class, int.class);
        User user2 = (User) constructor.newInstance(1, "kuangshen", 18);
        System.out.println("user2:"+user2);

        //2.通过反射调用方法
        User user3=(User)c1.newInstance();
        //通过反射获取一个方法
        Method setName = c1.getDeclaredMethod("setName", String.class);
        //invoke使用方法(对象,方法的值)
        setName.invoke(user3,"kuangshen2");
        System.out.println("user3:"+user3);

        //3.利用反射操作属性
        User user4=(User)c1.newInstance();
        Field name = c1.getDeclaredField("name");

        //不能直接操作私有属性,需要开启允许访问,关闭安全检测。方法、字段、构造器都可以设置开启允许访问
        name.setAccessible(true);

        name.set(user4,"kuangshen3");
        System.out.println("user4:"+user4);

    }
}

image-20210719124212410

setAccessible:

  • Method和Field、Constructor对象都有setAccessible方法。
  • 开启允许利用反射访问私有字段、方法
  • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,请设置为true

普通方法调用 和反射调用方法 性能对比:

public class Test10 {
    //普通方法调用
    public static void test01(){
        User user=new User();
        long startTime=System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        long endTime=System.currentTimeMillis();
        System.out.println("普通方式执行10亿次:"+(endTime-startTime)+"ms");
    }

    //反射调用方法
    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user=new User();
        Class<? extends User> c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);

        long startTime=System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime=System.currentTimeMillis();
        System.out.println("反射方式执行10亿次:"+(endTime-startTime)+"ms");
    }

    //反射调用方法
    public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user=new User();
        Class<? extends User> c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);
        getName.setAccessible(true);

        long startTime=System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime=System.currentTimeMillis();
        System.out.println("关闭权限检测方式执行10亿次:"+(endTime-startTime)+"ms");
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        test01();
        test02();
        test03();
    }
}

image-20210719131449638

4.反射操作获取泛型

  • Java采用泛型擦除机制 来引入泛型

    Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,Java新增了ParameterizedType、GenericArrayType、TypeVariable和WildcardType几种类型来代表不能被归一到Class类中但是又能和原始类型齐名的类型。

  • ParameterizedType 表示参数化类型,比如Collection

  • GenericArrayType 表示一种元素类型是参数化类型或者类型变量的的数组类型

  • TypeVariable 是各种类型变量的公共父接口

  • WildcardType 代表一种通配符类型表达式

测试:

public class Test11 {
    public void test01(Map<String,User> map, List<User> list){
        System.out.println("test01");
    }
    public Map<String,User> test02(){
        System.out.println("test02");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test11.class.getMethod("test01", Map.class, List.class);

        //获得参数类型及其内部泛型
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("参数类型:"+genericParameterType);
            if(genericParameterType instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("参数类型内部泛型:"+actualTypeArgument);
                }
            }
        }
        
        //获得返回类型及其内部泛型
        method = Test11.class.getMethod("test02", null);
        Type genericReturnType = method.getGenericReturnType();

        System.out.println("返回类型:"+genericReturnType);
        if(genericReturnType instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println("返回类型内部泛型:"+actualTypeArgument);
            }
        }
    }
    
}

image-20210719135425989

2.7反射读取注解

**ORM:**object relationship Mapping 对象关系映射

image-20210719235227156

类对应表

属性对应字段

对象对应记录

要求:利用注解和反射完成类和表结构的映射关系

  • 框架会在类里面定义大量的注解,通过反射去读取,生成相应的信息

  • 假设数据库有一张表,可以通过注解去定义和表对应的类型,通过注解生成一些数据库的语言,然后进行增删改查,自动创建表

反射读取注解实例:

package com.kuang.reflection;

import java.lang.annotation.*;
import java.lang.reflect.Field;

//测试ORM:对象关系映射

//使用反射读取注解信息三步:
//1.定义注解
//2.在实体类中实用注解
//3.使用反射获取注解,一般都是现成框架实现,我们手动实现
public class Test12 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //反射,Class对象可以获取类的全部信息
        Class<?> c1 = Class.forName("com.kuang.reflection.Student");
        //1.获得这个类的注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //2.获得类的指定注解,指出属性名称,只有一个属性value
        TableKuang tableKuang = c1.getAnnotation(TableKuang.class);
        System.out.println(tableKuang.value());

        //2.获取类指定注解的值,指出属性名称,三个属性
        Field name = c1.getDeclaredField("name");
        FieldKuang fieldKuang = name.getAnnotation(FieldKuang.class);
        System.out.println(fieldKuang.columnName()+"-->"+fieldKuang.type()+"-->"+fieldKuang.length());

        //我们可以根据得到的类的信息,通过JDBC生成相关的SQL语句,执行就可以动态生成数据库表
    }
}

@TableKuang("db_student")
class Student{
    @FieldKuang(columnName = "db_id",type = "int",length = 10)
    private int id;
    @FieldKuang(columnName = "db_name",type = "varchar",length = 10)
    private String name;
    @FieldKuang(columnName = "db_age",type = "int",length = 3)
    private int age;

    public Student() {
    }

    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

//表名注解,只有一个参数,建议使用value命名
@Target(value={ElementType.TYPE})
@Retention(value= RetentionPolicy.RUNTIME)
@interface TableKuang{
    String value();
}

//属性注解
@Target(value={ElementType.FIELD})
@Retention(value=RetentionPolicy.RUNTIME)
@interface FieldKuang{
    String columnName();//列名
    String type();
    int length();
}

image-20210719235506220

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值