8-注解与反射【狂神】

注解-注解是给程序看的,注释是给人看的

//什么是注解
public class Test01 extends Object{
    //重写的注解
    @Override
    public String toString() {
        return "Hello World";
    }
}

内置注解

package annotation;


import java.util.ArrayList;
import java.util.List;

@SuppressWarnings("all")//压制警告,黄色的提示
//什么是注解
public class Test01 extends Object{
   
    //重写的注解
    @Override
    public String toString() {
        return "Hello World";
    }
    //不推荐程序员使用,但是可以使用 或者存在更好的方式
    @Deprecated
    public static void test(){
        System.out.println("Hello World");
    }
   
    public void test2(){
        List arrayList = new ArrayList();
    }
    public static void main(String[] args) {
        test();
        
    }
    
}

元注解

package annotation;

import java.lang.annotation.*;

//测试元注解
//一个类中只有一个public类
public class Test02 {
	@MyAnnotation
    public void test(){

    }
}
//表示下面的注解只能用在哪些地方
@Target(value= ElementType.METHOD)//表示下面的注解只能用于方法
    @Retention(value= RetentionPolicy.RUNTIME)//表示注解的生命周期,在运行时有效,runtime>class>source
    @Documented//表示注解应该生成在javadoc中
    @Inherited//表示子类可以继承父类的注解
@interface MyAnnotation {//自定义一个注解
}

自定义注解,承接上面元注解

package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//自定义注解
public class Test03 {
    //注解可以显示赋值,如果定义了参数但是没有默认值,就必须给注解赋值 
    @MyAnnotation(name="haha")
    public void test() {
        
    }
    @MyAnnotation2("hello")
    public void test2() {
        
    }
}
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    //注解的参数:参数类型、参数名()
    //有了参数要不就在下面定义默认值,又不在上面赋值,也可以在上面重新赋值
    String name();//String name() default '';这样的话就是默认值为空,所以上面的注解就不用加参数
    int age() default 18;
    int id() default -1;//如果默认值 ,代表不存在,indexof,如果找不到,就返回-1
    String[] schools() default {"清华大学","北京大学"};
}
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2 {
    String value();//用了value,上面不可以不用加参数,直接赋值就行
}

反射-JAVA通过反射变成了动态语言

案例补充
当new一个对象时,是先生成class对象,然后才生成new对象吗?

步骤 行为 关键说明
首次访问类 当首次 new 对象、访问静态成员或调用静态方法时 如果类尚未加载,JVM 会触发完整的类加载流程。如果已加载,则跳过此过程。
类加载阶段 JVM 加载、验证、准备、解析(Linking),并初始化类。 - 加载(Loading): 加载字节码生成对应的 Class 对象。
- 初始化(Initialization): 执行静态代码块和显式赋值。
创建实例 为对象分配内存,调用构造函数 (new) 此时类的 Class 对象已完成初始化,据此创建实例。
代码示例
java

public class Cat {
    static {
        System.out.println("1. 静态代码块执行 → 类被初始化");
    }

    public Cat() {
        System.out.println("3. 构造函数执行 → 实例被创建");
    }
}

public class Test {
    public static void main(String[] args) {
        System.out.println("0. 开始创建实例");
        new Cat(); // 首次访问Cat类,触发类加载和初始化
    }
}
输出结果
0. 开始创建实例
1. 静态代码块执行 → 类被初始化
3. 构造函数执行 → 实例被创建

关键结论
当首次 new 对象时:
类的加载(生成 Class 对象)和初始化必须完成,然后才能创建实例。因此 Class 对象的生成早于实例的创建。

如果类已加载过:
直接复用已有的 Class 对象,不再重复加载,直接创建实例。

触发类加载的典型场景:

new 实例。
访问类的静态字段或方法(如 Cat.staticMethod())。
反射(如 Class.forName("Cat"))。
流程图解
new Cat()


类 Cat 是否已加载? 否 ➔ 执行类加载(生成Class对象) ➔ 初始化(静态块、静态变量赋值)
│ 是

调用构造函数创建实例
总结
在 Java 中,首次通过 new 创建对象时,一定会先确保该类的 Class 对象存在并完成初始化。后续的 new 操作则无需重复加载类,直接基于已有的 Class 对象创建新实例。这是保证运行时一致性和安全性的关键机制。

以上的案例:输出结果中1. 静态代码块执行 → 类被初始化,是类加载的结果(生成class对象,然后执行static语句,初始化,然后才执行new对象中的构造方法)

总结:当你new一个对象时,会先生成class对象,然后再执行static代码块,然后才执行构造器,

new出来的对象是正射,

获得反射对象-class类型的对象,包含了这个类所有的信息,存在方法区


package reflection;
//什么叫反射
public class Test02 {
    public static void main(String[] args)throws Exception {
        //通过反射获取类的Class对象
        Class<?> c1 = Class.forName("reflection.User");
        System.out.println(c1);
        Class<?> c2 = Class.forName("reflection.User");
        Class<?> c3 = Class.forName("reflection.User");
        //一个类在内存中哈哈有一个class对象
        //一个类被加载后,类的整个结构都会被封装在这个class对象中
        System.out.println(c1.hashCode());//189568618
        System.out.println(c2.hashCode());//189568618
        System.out.println(c3.hashCode());//189568618
    }
}
//实体类
class User {
    private String name;
    private int age;
    private int id;
    public User() {
    }
    public User(String name, int age, int id) {
        this.name = name;
        this.age = age;
        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;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }

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

得到class类的几种方式



package reflection;
//测试class类创建方式有哪些
public class Test03 {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person =new Student();
        System.out.println("这个人是:"+person.name);
        
        //方式一:通过对象获得
        Class c1 = person.getClass();
        System.out.println(c1.hashCode());//793589513
        //方式二:forname获得
        Class<?> c2 = Class.forName("reflection.Student");
        System.out.println(c2.hashCode());//793589513
        //方式三:类名.class获得
        Class c3 = Student.class;
        System.out.println(c3.hashCode());//793589513
        //方式四:基本内置类型的包装类都有一个TYPE属性,可以获得对应的Class对象
        Class c4 = Integer.TYPE;
        System.out.println(c4);//int
        //获得父类类型
        Class c5 = c1.getSuperclass();
        System.out.println(c5);//class reflection.Person


    }
}
class Person {
    public String name;

    public Person() {

    }

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + "]";
    }
}

class Student extends Person {
    public Student() {
        this.name = "Student";
    }

}

class Teacher extends Person {
    public Teacher() {
        this.name = "Teacher";
    }
}

哪些类型可以有Class对象

package reflection;

import java.lang.annotation.ElementType;

//所有类型的class
public class Test04 {
    public static void main(String[] args) throws Exception {
        Class<Object> c1 = Object.class;//类
        Class<Comparable> c2 = Comparable.class;//接口也有class对象
        Class<String[]> c3 = String[].class;//数组也有class对象
        Class<int[][]> c4 = int[][].class;//二维
        Class<Override> c5 = Override.class;//注解
        Class c6 = ElementType.class;//枚举
        Class<Integer> c7 = Integer.class;//基本数据类型
        Class<Void> c8 = void.class;//void
        Class<Class> c9 = Class.class;//Class
        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);
        //class java.lang.Object
        //interface java.lang.Comparable
        //class [Ljava.lang.String;
        //class [[I
        //interface java.lang.Override
        //class java.lang.annotation.ElementType
        //class java.lang.Integer
        //void
        //class java.lang.Class
        int[]a=new int[10];
        int[]b=new int[100];
        System.out.println(a.getClass().hashCode());
        System.out.println(b.getClass().hashCode());//都是189568618,证明一个类只有一个class对象


    }
}

类加载的内存分析





将class文件加载到内存中
第一步:将类的信息加载到方法区中-加载
类的数据信息1,静态变量。2,静态方法3,常量池(类名)4,代码
第二步:生成一个class对象到堆中-加载
第三步:开始执行main方法(在栈中),给静态变量初始值static m=0
第四步:new A(),A类的对象,这个对象指向Class A对象,能拿到信息- 在堆中
第五步:将静态方法和静态变量合并起来,形成《clinit》-初始化

ackage reflection;

public class Test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);
        //A类静态代码块初始化
        //A类无参构造函数初始化
        //2

        /**
          1、加载到内存,会产生一个类对应的class对象
          2、链接,链接结束后m=0
         3,初始化,
         <clinit>(){
         System.out.println("A类静态代码块初始化");
         m=3;
         m=2;
         }
         m=2;
         */


    }
}
class A {
    static {//会先加载静态代码块
        System.out.println("A类静态代码块初始化");
        m=3;
    }
    static int m=2;
    public A() {
        System.out.println("A类无参构造函数初始化");
    }

}

案例补充!!
创建 Class 对象
当 JVM 首次使用一个类时(比如 new、调用静态方法),会触发以下流程:

加载(Loading):
找到 .class 文件(本地、网络、动态生成等),将字节码加载到内存。
此时生成此类的 Class 对象(每个类对应一个唯一的 Class 实例)。

连接(Linking)
验证(Verification):确保字节码符合 JVM 规范。
准备(Preparation):为静态变量分配内存并初始默认值。
解析(Resolution):将符号引用转换为直接引用(地址)。

初始化(Initialization)
执行类的 方法(静态变量赋值、静态代码块)。

至此,Class 对象包含完整的类元数据(方法、字段、注解等),并可通过反射访问。

代码示例
步骤 1:定义测试类
用一个包含 静态变量 和 静态代码块 的类 MyClass,观察各阶段变化:

java

public class MyClass {
    // 初始化阶段的显式赋值,初始值为 "JVM默认值" (准备阶段时会被改为默认值0)
    public static int year = 2024; 
    public static String author = "Unknown";

    static {
        // 静态代码块将在初始化阶段执行
        System.out.println("### 初始化阶段:执行静态代码块 ###");
        author = "程序员";
    }
}
步骤 2:分阶段观察 Class 对象
通过 Class.forName() 控制类加载流程,观察 year 和 author 的默认值变化:

java

public class ClassLoadingDemo {
    public static void main(String[] args) throws Exception {
        // 1. 强制触发类加载(加载、验证、准备),但不初始化
        Class<?> clazz = Class.forName("MyClass", false, ClassLoader.getSystemClassLoader());
        System.out.println("[已加载类] Class对象创建完成");

        // 2. 准备阶段:查看静态变量默认值(int=0, String=null)
        Field yearField = clazz.getField("year");
        Field authorField = clazz.getField("author");
        System.out.println("\n[准备阶段] 静态变量默认值:");
        System.out.println("year = " + yearField.getInt(null));  // 输出:0
        System.out.println("author = " + authorField.get(null)); // 输出:null

        // 3. 强制触发初始化阶段(此时执行静态代码块和显式赋值)
        Class.forName("MyClass", true, ClassLoader.getSystemClassLoader());
        System.out.println("\n[初始化阶段] 静态变量最终值:");
        System.out.println("year = " + yearField.getInt(null));  // 输出:2024
        System.out.println("author = " + authorField.get(null)); // 输出:程序员
    }
}
输出结果


[已加载类] Class对象创建完成

[准备阶段] 静态变量默认值:
year = 0
author = null

### 初始化阶段:执行静态代码块 ###

[初始化阶段] 静态变量最终值:
year = 2024
author = 程序员

关键机制总结
Class 对象诞生于加载阶段:即使类未初始化,Class 对象已生成,可通过反射查看字段和方法等的元数据。
准备阶段的默认值:静态变量在此时被赋予类型默认值(如 int → 0),而非代码中的显式初始值。
初始化阶段的动态性:通过 方法执行用户编写的静态逻辑(显式赋值和静态代码块),形成最终状态。
通过这个案例,你会发现 Class 对象从诞生到完成的整个过程,其实就是 JVM 一步步将“静态的字节码”转化为“可执行的动态类”的旅程。

分析类的初始化

package reflection;
//测试类什么时候会初始化
public class Test06 {
    static {
        System.out.println("Main类被加载");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        //1.主动引用
        //Son son = new Son();
        //Main类被加载
        //父类被加载
        //子类被加载
        //反射也会产生主动引用
        //Class.forName("reflection.Son");//和主支引用的结果一样

        //2.不会产生类的引用的方法
        //System.out.println(Son.b);
        //Main类被加载
        //父类被加载
        //2
        /**
         * 在Java中,静态变量(使用static修饰的变量)是属于类的,而不是属于类的实例。因此,子类不会继承父类的静态变量。子类可以访问父类的静态变量,但这并不意味着继承。实际上,子类是通过父类名来访问这个静态变量的。
         * 在你的代码中,Son.b实际上是访问了Father类中的静态变量b,因为b是在Father类中定义的静态变量。这就是为什么在执行System.out.println(Son.b);时,会加载Father类并执行其静态代码块的原因。Java规范规定,访问一个类的静态成员会触发该类的初始化,如果该类有直接或间接的父类,父类也会被初始化。
         */
        //Son[] array = new Son[10];//Main类被加载`
        System.out.println(Son.M);//Main类被加载\1

    }
}
class Father{
    static int b=2;
    static {
        System.out.println("父类被加载");
    }
}

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

类加载器


package reflection;

public class Test07 {
    public static void main(String[] args) throws ClassNotFoundException {
        //获得系统类的加载器
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);//jdk.internal.loader.ClassLoaders$AppClassLoader@4617c264
        //获取系统类加载器的父类加载器
        ClassLoader parent = classLoader.getParent();
        System.out.println(parent);//jdk.internal.loader.ClassLoaders$PlatformClassLoader@b4c966a
        //获取扩展类加载器的父类加载器--根加载器(C/C++)
        ClassLoader ext = parent.getParent();
        System.out.println(ext);//null
        //测试当前类是哪个加载器加载的
        ClassLoader classLoader1 = Class.forName("reflection.Test07").getClassLoader();
        System.out.println(classLoader1);//jdk.internal.loader.ClassLoaders$AppClassLoader@4617c264
        //测试JDK内置类是哪个加载器加载的
        ClassLoader classLoader2 = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader2);//null
        //如何获得系统类加载器可以加载的路径
        System.out.println(System.getProperty("java.class.path"));//C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_
        //双亲委派机制,如果你手写了跟内置类同名的类,根加载器会优先加载内置类,而不会加载你手写的类。
    }
}

获取类的运行时结构

package reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

//获得类的信息
public class Test08 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class<?> c1 = Class.forName("reflection.User");
        //获得类的名字
        System.out.println(c1.getName());//reflection.User包名+类名
        System.out.println(c1.getSimpleName());//User类名
        //通过对象获得类的信息
        User user = new User();
        Class<?> c2 = user.getClass();
        System.out.println(c2.getName());
        System.out.println(c2.getSimpleName());

        //获得类的属性
        Field[] fields = c1.getFields();//只能找到public的属性
        fields = c1.getDeclaredFields();//找到全部的属性
        for (Field field : fields) {
            System.out.println(field);
        }
        //private java.lang.String reflection.User.name
        //private int reflection.User.age
        //private int reflection.User.id

        //获得指定属性的值
        Field name = c1.getDeclaredField("name");
        System.out.println(name);

        //获得类的方法
        Method[] methods = c1.getMethods();
        methods = c1.getDeclaredMethods();
        for (Method method : methods) {//获得本类及其父类的全部public方法
            System.out.println(method);
        }
        methods = c1.getDeclaredMethods();//获得本类全部的方法
        for (Method method : methods) {
            System.out.println(method);
        }
        //获得指定方法
        Method getName = c1.getMethod("getName",null);
        System.out.println(getName);//public java.lang.String reflection.User.getName()
        Method setName = c1.getMethod("setName", String.class);
        System.out.println(setName);//public void reflection.User.setName(java.lang.String)

        //获得指定的构造器
        Constructor[] constructors = c1.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }


    }
}

动态创建对象执行方法




package reflection;

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

//动态的创建对象,通过反射
public class Test09 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //获得class对象
        Class<?> c1 = Class.forName("reflection.User");
        //构造一个对象
        User user = (User) c1.newInstance();//本质是调用了类的的无参构造方法,适合无参构造器New对象的方法,如果构造器是有参构造器就得用下面了
        System.out.println(user);//User{name='null', age=0, id=0}

        //通过构造器创建对象
        Constructor<?> constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        User user2 = (User) constructor.newInstance("Tom", 20, 100);
        System.out.println(user2);//User{name='Tom', age=20, id=100}

        //通过反射调用普通方法
        User user3 = (User) c1.newInstance();
        //通过反射获取一个方法
        Method setName = c1.getDeclaredMethod("setName", String.class);
        //invoke;激活的意思
        //(对象,方法的值 )
        setName.invoke(user3, "jeff");
        System.out.println(user3.getName());//jeff

        //通过反射操作属性
        User user4 = (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");
        //不能直接操作私有属性,需要设置访问权限。关闭程序的安全检查,才能操作私有属性
        name.setAccessible(true);
        name.set(user4, "haha");
        System.out.println(user4.getName());//haha


    }
}

性能对比分析

package reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

//分析性能问题

public class Test10 {

    //普通方式调用
    public static void test1() {
        User user = new User();
        long starttime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        long endtime = System.currentTimeMillis();
        System.out.println(endtime - starttime);
    }

    //反射方式调用
    public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class<? extends User> c1 = user.getClass();

        Method getName = c1.getDeclaredMethod("getName");
        long starttime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user);
        }
        var endtime = System.currentTimeMillis();
        System.out.println(endtime - starttime);
    }
            //反射方式调用 关闭检测
public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            User user = new User();
            Class<? extends User> c1 = user.getClass();

            Method getName = c1.getDeclaredMethod("getName");
            getName.setAccessible(true);
            long starttime = System.currentTimeMillis();
            for (int i = 0; i < 1000000000; i++) {
                getName.invoke(user);
            }
            var endtime = System.currentTimeMillis();
            System.out.println(endtime - starttime);
}

    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        test1();//5
        test2();//5134
        test3();//4290
    }
}

获取泛型信息

反射操作注解

posted @ 2025-03-01 17:32  乘加法  阅读(20)  评论(0)    收藏  举报