目录

一、什么是注解

二、内置注解

三、元注解

四、自定义注解

五、反射

​编辑

六、Class 类

七、可以用class对象的类型

八、Java的内存分析

九、什么时候会发送类初始化

十、类加载器的作用

十一、创建运行时类的对象

十二、通过反射获取泛型信息

十三、通过反射获取注解信息


一、什么是注解

Annotation 是从 JDK5.0 开始引入的新技术.

Annotation 的作用:

        不是程序本身,可以对程序作出解释.(这一点和注释 (comment) 没什么区别)

        可以被其他程序 (比如:编译器等) 读取.

Annotation 的格式:

        注解是以 "@注释名" 在代码中存在的,还可以添加一些参数值,例如:@SuppressWarnings         (value="unchecked").

Annotation 在哪里使用?

        可以附加在 package, class, method, field 等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问

二、内置注解

@Override:定义在java.lang.Override中,此注释只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明。

@Deprecated:定义在java.lang.Deprecated中,此注释可以用于修辞方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择。

@SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息。 与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了。

- @SuppressWarnings("all")

- @SuppressWarnings("unchecked")

- @SuppressWarnings(value={"unchecked","deprecation"})

- 等等……

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

//什么是注解
@SuppressWarnings("all")
public class Test01 extends Object {

    //@Override  重写的注解
    @Override
    public String toString() {
        return super.toString();
    }

    //Deprecated 不推荐程序员使用 ,但是可以使用. 或者存在更好的方式
    @Deprecated
    public static void test(){
        System.out.println("Deprecated");
    }

    public void test02(){
        List list = new ArrayList();
    }

    public static void main(String[] args) {
        test();
    }

}

三、元注解

//定义一个注解
//Target 表示我们的注解可以用在哪些地方
@Target(value = {ElementType.METHOD, ElementType.TYPE})

//Retention 表示我们的注解在什么地方还有效
// runtime>class>sources
@Retention(value = RetentionPolicy.RUNTIME)

//Documented 表示是否将我们的注解生成在JAVAdoc中
@Documented

//Inherited 子类可以继承父类的注解
@Inherited
@interface MyAnnotation {

}

四、自定义注解

使用 `@interface` 自定义注解时,自动继承了 `java.lang.annotation.Annotation` 接口

分析:

- `@interface` 用来声明一个注解,格式:`public @interface 注解名 { 定义内容 }`

- 其中的每一个方法实际上是声明了一个配置参数 - 方法的名称就是参数的名称

- 返回值类型就是参数的类型(返回值只能是基本类型、Class、String、enum)

- 可以通过 `default` 来声明参数的默认值

- 如果只有一个参数成员,一般参数名为 `value`

- 注解元素必须要有值,定义注解元素时,经常使用空字符串、`0` 作为默认值

// 注解可以显示赋值,如果没有默认值,我们就必须给注解赋值
@MyAnnotation2(age = 18, name = "秦疆")
public void test(){}

@MyAnnotation3("秦疆")
public void test2(){}


@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
    // 注解的参数:参数类型 + 参数名();
    String name() default "";
    int age();
    int id() default -1; // 如果默认值为-1,代表不存在
    
    String[] schools() default {"西部开源", "清华大学"};
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
    String value();
}

1. 自定义注解的格式

@interface 注解名 {
    // 这里面写的不是方法,是【注解的参数】!
}

重点:

  • @interface 代表自定义注解
  • 里面写的 String name(); 不是方法,是注解的参数

2. 注解参数怎么写?

参数类型 参数名();

例子:

String name(); // 字符串类型参数 name
int age();     // 整数类型参数 age

你还能给参数默认值

String name() default ""; // 默认是空字符串
int id() default -1;      // 默认是 -1

3. 什么时候必须传参?

  • 没有默认值的参数 → 必须传值
  • 有默认值 → 可以不传
@interface MyAnnotation2{
    String name() default ""; // 有默认值
    int age();                // 没默认值 → 必须传!
    int id() default -1;
}
@MyAnnotation2(age = 18, name = "秦疆")

因为 age 没有默认值,必须写


4. 特殊情况:参数名叫 value

@interface MyAnnotation3{
    String value();
}

使用时可以不写参数名,直接写值:

@MyAnnotation3("秦疆")

等价于:

@MyAnnotation3(value = "秦疆")

这是 Java 注解的语法糖,只有 value 可以这么写!


5. 数组参数

String[] schools() default {"西部开源","清华大学"};

传参可以这样:

schools = {"北大","清华"}

6. 两个元注解是干嘛的?

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
  • @Target:注解能用在哪里(类、方法…)
  • @Retention:注解存活到什么时候(运行时)
  • 五、反射

  • Java Reflection - Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
  • `Class c = Class.forName("java.lang.String")`
  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射
  • 正常方式:引入需要的"包类"名称 → 通过new实例化 → 取得实例化对象
  • 反射方式:实例化对象 → getClass()方法 → 得到完整的“包类”名称
  • package com.kuang.reflection;
    
    //什么叫反射
    public class Test02 {
        public static void main(String[] args) throws ClassNotFoundException {
            //通过反射获取类的Class对象
            Class c1 = Class.forName("com.kuang.reflection.User");
            System.out.println(c1);
    
            Class c2 = Class.forName("com.kuang.reflection.User");
            Class c3 = Class.forName("com.kuang.reflection.User");
            Class c4 = Class.forName("com.kuang.reflection.User");
    
            //一个类在内存中只有一个Class对象
            //一个类被加载后,类的整个结构都会被封装在Class对象中.
            System.out.println(c2.hashCode());
            System.out.println(c3.hashCode());
            System.out.println(c4.hashCode());
        }
    }
    
    //实体类:pojo , entity
    class User{...}

    这段代码是在演示 Java 反射中 Class 对象的核心特性

    1. 获取 Class 对象Class.forName("com.kuang.reflection.User") 是反射中获取类的 Class 对象的方式之一。它通过类的全限定名(包名 + 类名),在运行时加载并获取该类的 Class 对象。

    2. 关键特性:一个类只有一个 Class 对象代码中多次调用 Class.forName("com.kuang.reflection.User"),得到 c1c2c3c4 四个变量。但它们的 hashCode() 输出值是完全相同的,因为JVM 中每个类只会被加载一次,且只会产生一个唯一的 Class 对象,后续的获取操作都只是拿到同一个对象的引用。

    3. Class 对象的作用注释提到:“一个类被加载后,类的整个结构都会被封装在 Class 对象中”。这个 Class 对象就像一面镜子,保存了 User 类的所有信息(字段、方法、构造器等),后续反射操作就是通过这个对象来读取和操作这些信息的。

    4. 异常处理throws ClassNotFoundExceptionClass.forName 方法的受检异常,表示如果找不到指定类时会抛出此异常,需要处理或声明抛出。

  • 六、Class 类

    对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构 (class/interface/enum/annotation/primitive type/void/[]) 的有关信息。

    • Class 本身也是一个类
    • Class 对象只能由系统建立对象
    • 一个加载的类在 JVM 中只会有一个 Class 实例
    • 一个 Class 对象对应的是一个加载到 JVM 中的一个.class 文件
    • 每个类的实例都会记得自己是由哪个 Class 实例所生成
    • 通过 Class 可以完整地得到一个类中的所有被加载的结构
    • Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class 对象
  • // 方式一:通过对象获得
    Class c1 = person.getClass();
    System.out.println(c1.hashCode());
    
    // 方式二:forName获得
    Class c2 = Class.forName("com.kuang.reflection.Student");
    System.out.println(c2.hashCode());
    
    // 方式三:通过类名.class获得
    Class c3 = Student.class;
    System.out.println(c3.hashCode());
    
    // 方式四:基本内置类型的包装类都有一个TYPE属性
    Class c4 = Integer.TYPE;
    System.out.println(c4);
    
    // 获得父类类型
    Class c5 = c1.getSuperclass();
    System.out.println(c5);
    1. 方式一:对象.getClass()

      • 已经有对象实例时,直接调用 getClass() 获取它的 Class 对象。

      • 例子里 personStudent 实例,所以 c1 就是 StudentClass 对象。

    2. 方式二:Class.forName("全类名")

      • 传入完整包名 + 类名,动态加载类并获取其 Class 对象。

      • 这是反射里最常用的方式,适合类名在运行时才确定的场景。

      • 必须处理 ClassNotFoundException

    3. 方式三:类名.class

      • 直接用 .class 语法,编译期就能确定。

      • 最简单直接,不需要异常处理。

    4. 方式四:基本类型 / 包装类的 TYPE 属性

      • Integer.TYPE 代表 int 基本类型的 Class 对象。

      • 类似的还有 Double.TYPEBoolean.TYPE 等,专门用来获取基本数据类型Class 对象。

      • 输出结果是 int,而不是 class java.lang.Integer

    5. getSuperclass():获取父类的 Class 对象

      • c1.getSuperclass() 会返回 Student 的父类(也就是 Person)的 Class 对象。

      • 可以用它做反射中的继承链分析。


    核心结论

    • 方式 1/2/3 拿到的都是同一个 Student 类的 Class 对象,所以它们的 hashCode 完全一样。

    • 方式 4 是专门给基本数据类型用的特殊方式。

    • getSuperclass() 是反射中向上追溯继承关系的常用方法。

  • 七、可以用class对象的类型

  • 只要元素类型与维度一样 那么就是同一个class。

  • 八、Java的内存分析

  • java内存分为堆,栈和方法区

  • 堆:存放new的对象和数组  可以被所有的线程共享,不会存放别的对象引用

  • 栈:存在基本变量类型(会包含这个基本类型的具体数值)

  •         引用对象的变量(会存放这个引用在堆里面的具体地址)

  • 方法区:可以被所有的线程共享    包含了所有的class和static变量

  • 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类的无参构造初始化");
        }
    }

  • 1.加载到内存,会产生一个类对应的class对象

  • 2.链接,链接结束后 m=0

  • 3.初始化 

  •         <clinit>(){

  •                         system.out.println("A类静态代码块初始化")

  •                         m=300;

  •                         m=100;

  •         }

  •         m=100

  • 九、什么时候会发送类初始化

  • 类的主动引用(一定会发生类的初始化)

    • 当虚拟机启动,先初始化main方法所在的类

    • new一个类的对象

    • 调用类的静态成员(除了final常量)和静态方法

    • 使用java.lang.reflect包的方法对类进行反射调用

    • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类


    类的被动引用(不会发生类的初始化)

    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化

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

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

  • public static void main(String[] args) throws ClassNotFoundException {
        // 1. 主动引用
        // Son son = new Son();
    
        // 反射也会产生主动引用
        // Class.forName("com.kuang.reflection.Son");
    
        // 不会产生类的引用的方法
        // System.out.println(Son.b);
    
        // Son[] array = new Son[5];
    
        System.out.println(Son.M);
    }
    
    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;
    }
    序号 代码场景 触发类型 控制台输出 关键原理
    1 Son son = new Son(); 主动引用 父类被加载 子类被加载 创建子类对象触发初始化,先父后子
    2 Class.forName("com.kuang.reflection.Son"); 主动引用 父类被加载 子类被加载 反射调用触发类初始化,顺序同上
    3 System.out.println(Son.b); 被动引用 父类被加载 2 访问的是父类的静态变量,仅初始化声明变量的父类
    4 Son[] array = new Son[5]; 被动引用 无输出 仅声明数组引用,不触发 Son 类初始化
    5 System.out.println(Son.M); 被动引用 1

    static final 常量在编译期存入常量池,不触发初始化

    • 主动引用 = 一定会执行静态代码块(且父类优先于子类)。

    • 被动引用 = 不执行静态代码块,常量 / 数组 / 父类静态变量都不会触发子类初始化。

    • 初始化的核心原则:只有声明成员的类,才会被初始化

  • 十、类加载器的作用

  • - 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的`java.lang.Class`对象,作为方法区中类数据的访问入口。

  • - 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

  • 类加载器是用来把类(class)装载进内存的。JVM规范定义了如下类型的类加载器

  • ```java
    public class Test07 {
        public static void main(String[] args) throws ClassNotFoundException {
            //获取系统类的加载器
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            System.out.println(systemClassLoader);
    
            //获取系统类加载器的父类加载器-->扩展类加载器
            ClassLoader parent = systemClassLoader.getParent();
            System.out.println(parent);
    
            //获取扩展类加载器的父类加载器-->根加载器(C/c++)
            ClassLoader parent1 = parent.getParent();
            System.out.println(parent1);
    
            //测试当前类是哪个加载器加载的
            ClassLoader classLoader = Class.forName("com.kuang.reflection.Test07").getClassLoader();
            System.out.println(classLoader);
    
            //测试当前类是哪个加载器加载的
            classLoader = Class.forName("java.lang.Object").getClassLoader();
            System.out.println(classLoader);
        }
    }
    ```
    输出内容 含义
    sun.misc.Launcher$AppClassLoader@18b4aac2 系统类加载器(也叫应用类加载器),负责加载用户自定义的类
    sun.misc.Launcher$ExtClassLoader@4554617c 扩展类加载器,负责加载 JDK 扩展目录下的类
    null 根加载器(Bootstrap ClassLoader),由 C/C++ 实现,Java 中看不到它的实例,所以返回null
    sun.misc.Launcher$AppClassLoader@18b4aac2 你自己写的Test07类,是由系统类加载器加载的
    null java.lang.Object 是所有类的父类,它由根加载器加载,所以返回null
  • 十一、创建运行时类的对象

  • 1.获取运行时类的完整结构

  • 通过反射获取运行时类的完整结构

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

  • import java.lang.reflect.*;
    
    // 测试:反射获取类的所有信息
    public class TestGetClassInfo {
        public static void main(String[] args) throws ClassNotFoundException {
            
            // 1. 获取Class对象
            Class c = User.class;
    
            System.out.println("===== 类名 =====");
            System.out.println(c.getName()); // 全类名
            System.out.println(c.getSimpleName()); // 简单类名
    
            System.out.println("\n===== 字段 =====");
            Field[] fields = c.getDeclaredFields(); // 获取所有字段(含private)
            for (Field f : fields) System.out.println(f);
    
            System.out.println("\n===== 方法 =====");
            Method[] methods = c.getDeclaredMethods(); // 获取所有方法
            for (Method m : methods) System.out.println(m);
    
            System.out.println("\n===== 构造器 =====");
            Constructor[] cons = c.getDeclaredConstructors(); // 获取所有构造器
            for (Constructor co : cons) System.out.println(co);
    
            System.out.println("\n===== 父类 =====");
            System.out.println(c.getSuperclass());
        }
    }
    
    // 实体类
    class User {
        private int id;
        public String name;
    
        public User() {}
        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        public void study(){}
        private void eat(){}
    }

    • 获取 Class 对象Class c = 类名.class;

    • 获取所有字段getDeclaredFields() → public、private 全能拿

    • 获取所有方法getDeclaredMethods()

    • 获取所有构造器getDeclaredConstructors()

    • 获取父类getSuperclass()

  • 2.动态创建执行方法(通过反射)

      1. 类必须有一个无参数的构造器。
      2. 类的构造器的访问权限需要足够。

    思考:难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确调用类中的构造器,并将参数传递进去之后,就可以实例化操作。

    步骤如下:

    1. 通过Class类的getDeclaredConstructor(Class... parameterTypes)取得本类的指定形参类型的构造器。
    2. 向构造器的形参中传递一个对象数组,里面包含了构造器中所需的各个参数。
    3. 通过Constructor实例化对象。
  • import java.lang.reflect.Constructor;
    
    public class TestCreateObject {
        public static void main(String[] args) throws Exception {
            Class<User> c = User.class;
    
            // 方式1:通过无参构造器创建(对应 newInstance())
            User u1 = c.newInstance();
            System.out.println("无参创建:" + u1);
    
            // 方式2:通过有参构造器创建(对应 getDeclaredConstructor 步骤)
            // 1. 获取指定参数类型的构造器
            Constructor<User> constructor = c.getDeclaredConstructor(int.class, String.class);
            // 2. 传入参数,实例化对象
            User u2 = constructor.newInstance(1, "张三");
            System.out.println("有参创建:" + u2);
        }
    }
    
    // 实体类(无无参构造器也能创建对象)
    class User {
        int id;
        String name;
    
        // 只有有参构造器,没有无参构造器
        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{id=" + id + ", name='" + name + "'}";
        }
    }
    创建方式 依赖条件 关键方法
    newInstance() 必须有public无参构造器 Class.newInstance()
    Constructor.newInstance() 可以指定任意构造器(包括有参、private) getDeclaredConstructor() + newInstance()
  • 十二、通过反射获取泛型信息

    知识点

    • getGenericReturnType():获取方法的完整返回类型(包含泛型)。

    • ParameterizedType:带泛型参数的类型,getActualTypeArguments() 可获取真实泛型参数。

    • getGenericParameterTypes():获取方法参数的泛型信息。

    代码示例

    java

    运行

    import java.lang.reflect.Method;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.List;
    import java.util.Map;
    
    class GenericDemo {
        // 方法带泛型返回值和泛型参数
        public List<String> getList(Map<Integer, Object> map) { return null; }
    }
    
    public class GenericInfoDemo {
        public static void main(String[] args) throws Exception {
            Class<GenericDemo> clazz = GenericDemo.class;
            Method method = clazz.getMethod("getList", Map.class);
    
            // 1. 获取方法返回值的泛型信息
            Type returnType = method.getGenericReturnType();
            if (returnType instanceof ParameterizedType) {
                ParameterizedType pType = (ParameterizedType) returnType;
                Type[] args = pType.getActualTypeArguments();
                System.out.println("返回值泛型:" + args[0]);
            }
    
            // 2. 获取方法参数的泛型信息
            Type[] paramTypes = method.getGenericParameterTypes();
            for (Type t : paramTypes) {
                if (t instanceof ParameterizedType) {
                    ParameterizedType pType = (ParameterizedType) t;
                    System.out.println("参数泛型:" + pType.getActualTypeArguments()[0]
                            + ", " + pType.getActualTypeArguments()[1]);
                }
            }
        }
    }
    

    十三、通过反射获取注解信息

    知识点

    • 注解必须加 @Retention(RetentionPolicy.RUNTIME) 才能被反射获取。

    • getAnnotation(Class):获取指定类型的注解。

    • getAnnotations():获取所有注解。

    • getDeclaredAnnotation():获取直接声明的注解(不含继承)。

    代码示例

    java

    运行

    import java.lang.annotation.*;
    import java.lang.reflect.Field;
    
    // 自定义运行时注解
    @Target({ElementType.TYPE, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyColumn {
        String value();
    }
    
    @MyColumn("user_table")
    class User {
        @MyColumn("user_name")
        private String name;
    }
    
    public class AnnotationInfoDemo {
        public static void main(String[] args) throws Exception {
            Class<User> clazz = User.class;
    
            // 1. 获取类上的注解
            MyColumn classAnno = clazz.getAnnotation(MyColumn.class);
            System.out.println("类注解值:" + classAnno.value());
    
            // 2. 获取字段上的注解
            Field field = clazz.getDeclaredField("name");
            MyColumn fieldAnno = field.getAnnotation(MyColumn.class);
            System.out.println("字段注解值:" + fieldAnno.value());
        }
    }
    
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐