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 对象的核心特性:
-
获取 Class 对象
Class.forName("com.kuang.reflection.User")是反射中获取类的Class对象的方式之一。它通过类的全限定名(包名 + 类名),在运行时加载并获取该类的Class对象。 -
关键特性:一个类只有一个 Class 对象代码中多次调用
Class.forName("com.kuang.reflection.User"),得到c1、c2、c3、c4四个变量。但它们的hashCode()输出值是完全相同的,因为JVM 中每个类只会被加载一次,且只会产生一个唯一的Class对象,后续的获取操作都只是拿到同一个对象的引用。 -
Class 对象的作用注释提到:“一个类被加载后,类的整个结构都会被封装在 Class 对象中”。这个
Class对象就像一面镜子,保存了 User 类的所有信息(字段、方法、构造器等),后续反射操作就是通过这个对象来读取和操作这些信息的。 -
异常处理
throws ClassNotFoundException是Class.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);-
方式一:
对象.getClass()-
已经有对象实例时,直接调用
getClass()获取它的Class对象。 -
例子里
person是Student实例,所以c1就是Student的Class对象。
-
-
方式二:
Class.forName("全类名")-
传入完整包名 + 类名,动态加载类并获取其
Class对象。 -
这是反射里最常用的方式,适合类名在运行时才确定的场景。
-
必须处理
ClassNotFoundException。
-
-
方式三:
类名.class-
直接用
.class语法,编译期就能确定。 -
最简单直接,不需要异常处理。
-
-
方式四:基本类型 / 包装类的
TYPE属性-
Integer.TYPE代表int基本类型的Class对象。 -
类似的还有
Double.TYPE、Boolean.TYPE等,专门用来获取基本数据类型的Class对象。 -
输出结果是
int,而不是class java.lang.Integer。
-
-
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);被动引用 1static 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 中看不到它的实例,所以返回 nullsun.misc.Launcher$AppClassLoader@18b4aac2你自己写的 Test07类,是由系统类加载器加载的nulljava.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.动态创建执行方法(通过反射)
-
-
- 类必须有一个无参数的构造器。
- 类的构造器的访问权限需要足够。
思考:难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确调用类中的构造器,并将参数传递进去之后,就可以实例化操作。
步骤如下:
- 通过
Class类的getDeclaredConstructor(Class... parameterTypes)取得本类的指定形参类型的构造器。 - 向构造器的形参中传递一个对象数组,里面包含了构造器中所需的各个参数。
- 通过
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()); } } -
更多推荐




所有评论(0)