注解与反射
注解和反射
一、注解(Annotation)
1.1 什么是注解
-
Annotation是从JDK5.0开始引入的
-
Annotation的作用:
-
不是程序本身(可以对程序做出解释【这一点和注释comment没什么区别】)
-
可以被其它程序读取(比如:编译器等)
-
-
格式:@注释名 (例如:@SuppressWarning(value="unchecked"))
-
注解在哪里使用?
-
可以在package,class,method,field等,相当于给它们添加了额外的辅助信息
-
我们可以通过反射机制编程实现对这些元数据的访问
-
1.2 内置注解
-
@Override : 此注解只适用于方法上。表示子类中改方法重写父类中的方法
-
@Deprecated : 此注解可以作用在类,属性,方法上。表示被废弃。
-
@SuppressWarnings : 此注解可以作用在类,属性,方法上。用来抑制编译时的警告信息。
1.3 元注解
元注解的作用: 就是负责注解其它注解。Java定义了4个标准的meta-annotation类型,它们被用来提供对其它annotation类型作说明。
-
@Target : 用于描述注解的使用范围
-
@Retention : 表示需要在什么时候保存该注释信息,用于描述注解的生命周期
- RUNTIME > CLASS > SOURCE
-
@Documented: 说明该注解将被包含在JavaDoc中
-
@Inherited : 说明子类可以继承父类中的该注解
1.4 自定义注解
使用 @interface 自定义注解,自动继承了java.lang.annotation.Annotation接口
分析:
-
@interface用来声明一个注解,格式:public @interface 注解名
-
其中的每一个方法实际上是声明了一个配置参数
-
方法的名称就是参数的名称
-
返回值类型就是参数的类型(返回值只能是:基本类型、Class、String、enum)
-
可以使用default来声明参数的默认值
-
如果只有一个参数成员,一般参数名为 value
-
注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
代码示例
package cn.com.longer.test.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
*/
public class TestMyAnnotation {
public static void main(String[] args) {
test();
}
// 注解可以显示默认值,如果没有默认值,就需要赋值
@MyAnnotation(age = 11, schools = {"清华", "北大"})
public static void test(){
System.out.println("myAnnotation");
}
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
String name() default "";
int age();
int id() default -1; // 如果默认值为-1,代表不存在
String[] schools() default {"北京邮电大学", "南京邮电大学"};
}
二、反射(Reflection)
2.1 什么是反射
-
Reflection(反射)是Java有动态性的关键。反射机制允许程序在执行期间借助Reflection API 获得任何类的内部信息,并且能直接操作任意对象的内部属性及方法
-
加载完类后,在堆内存的方法区中就产生了一个Class类型的对象。一个类只有一个Class对象。 这个对象包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。
2.2 静态语言 VS 动态语言
-
动态语言:
-
在运行时可以改变其结构的语言
-
主要动态语言:Object-C, C#, JavaScript, PHP, Python
-
-
静态语言:
-
与动态语言相对应的,运行时其结果不可变的语言就是静态语言
-
静态语言有:Java, C, C++
Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性 。我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
-
2.3 获取Class类的方式
-
类名.class
-
类对象.getClass()
-
Class.forName("包名+类名");
代码示例:
package cn.com.longer.test.reflection;
/**
* 获取类的类对象
*/
public class TestGetClass {
public static void main(String[] args) throws ClassNotFoundException {
// 方式一: 类名.class
Class<Person> classP1 = Person.class;
System.out.println(classP1);
// 方式二:对象.getClass()
Person person = new Person();
Class<? extends Person> classP2 = person.getClass();
System.out.println(classP2);
// 方式三: Class.forName("包名+类名");
Class<?> classP3 = Class.forName("cn.com.longer.test.reflection.Person");
System.out.println(classP3);
}
}
class Person{
private int id;
private String name;
}
控制台输出:
Connected to the target VM, address: '127.0.0.1:61947', transport: 'socket'
class cn.com.longer.test.reflection.Person
class cn.com.longer.test.reflection.Person
class cn.com.longer.test.reflection.Person
Disconnected from the target VM, address: '127.0.0.1:61947', transport: 'socket'
2.4 类加载内存分析
2.4.1 类加载的过程
1.Java内存分析:
2.类的加载过程:
3.类的加载与ClassLoader的理解:
4.什么时候会发生类的初始化?
5.类加载器的作用
6.类的加载器的类型
2.4.2 双亲委派机制
双亲委派机制是为了防止同名包、类与 jdk 中的相冲突,实际上加载类的时候,先通知 appLoader,看 appLoader 是否已经缓存,没有的话,appLoader 又委派给他的父类加载器(extLoader)询问,看他是不是能已经缓存加载,没有的话,extLoader 又委派他的父类加载器(bootstrapLoader)询问,BootstrapLoader看是不是自己已缓存或者能加载的,有就加载,没有再返回 extLoader,extLoader 能加载就加载,不能的话再返回给 appLoader 加载,再返回的路中,谁能加载,加载的同时也加缓存里。正是由于不停的找自己父级,所以才有 Parents 加载机制,翻译过来叫 双亲委派机制。
-
双亲委派机制是JVM类加载的默认使用的机制。
-
其原理是:当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader,只有当父加载器无法完成加载任务时,才会尝试自己来加载。
-
按照由父级到子集的顺序,类加载器主要包含以下几个:
-
BootstrapClassloader(启动类加载器):主要负责加载核心的类库(java.lang.*等),JVM_HOME/lib目录下的,构造ExtClassLoader和APPClassLoader。
-
ExtClassLoader(扩展类加载器):主要负责加载jre/lib/ext目录下的一些扩展的jar
-
AppletClassLoader(系统类加载器):主要负责加载应用程序的主函数类
-
自定义类加载器:主要负责加载应用程序的主函数类
-
2.5 获取类的运行时结构
通过反射可以获取运行时类的完整结构:
Field, Method, Constructor, Superclass, Interface, Annotation
代码示例:
package cn.com.longer.test.reflection;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 通过反射获取运行时类的完整结构
* Field, Method, Constructor, Superclass, Interface, Annotation
*/
public class GetRuntimeClass {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class<?> c = Class.forName("cn.com.longer.test.reflection.People");
// 1.获取类名
System.out.println(c.getName());
System.out.println(c.getSimpleName());
System.out.println("---------------------------");
// 2.获取Field
Field[] fields = c.getFields();
for (Field field : fields) {
System.out.println("getFields----:"+field);
}
fields = c.getDeclaredFields();
for (Field field : fields) {
System.out.println("getDeclaredFields----:"+field);
}
Field name = c.getField("name");
System.out.println("getField(String)----:"+name);
Field password = c.getDeclaredField("password");
System.out.println("getDeclaredField(String)----:"+password);
System.out.println("---------------------------");
// 3.获取Method
// 3.1 获取本类和父类public的方法
Method[] methods = c.getMethods();
for (Method method : methods) {
System.out.println("getMethods----:"+method);
}
// 3.2 获取本来所有的方法(public,private)
methods = c.getDeclaredMethods();
for (Method method : methods) {
System.out.println("getDeclaredMethods----:"+method);
}
Method say = c.getMethod("say", null);
System.out.println("getMethod(String)----:" + say);
Method myName = c.getDeclaredMethod("myName", String.class);
System.out.println("getDeclaredMethod(String)----:" + myName);
Method secret = c.getDeclaredMethod("secret", null);
System.out.println(("getDeclaredMethod(String)----:" + secret));
System.out.println("---------------------------");
// 3.获取Constructor
Constructor<?>[] constructors = c.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("getConstructors----:" + constructor);
}
System.out.println("---------------------------");
// 4.获取Superclass
System.out.println(c.getSuperclass());
System.out.println("---------------------------");
// 5.获取Interface
for (Class<?> anInterface : c.getInterfaces()) {
System.out.println("getInterfaces----:"+anInterface);
}
System.out.println("---------------------------");
// 6.获取Annotation
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("getAnnotations----:" + annotation);
}
}
}
class People{
public int id;
public String name;
private String password;
public People(int id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
public void myName(String name){
System.out.println(name);
}
public void say(){
System.out.println("hey");
}
private void secret(){
System.out.println("secret");
}
}
2.6 动态创建对象执行方法
创建类的对象:
- 调用类对象的newInstance()方法
-
该类必须有空参构造器
-
该类的空参构造器的访问权限需要足够
- 调用类中的构造器,将参数传递进去
-
调用Class的getDeclaredConstructor(Class ... parameterTypes)
-
向构造器的形参中传递对象数组,是构造器所需的各个参数
暴力反射:
私有的属性、方法、构造函数不能直接被操作,此时,应该打开他们的安全访问机制。即.setAccessible(true)即可。默认是false。
调用指定的方法:
Object invoke(Object obj, Object... args)
-
Object : 对应原方法的返回值,若无返回值,返回null
-
若原方法形参裂变为空,则Object[] args为null
-
若原方法为private修饰,需要在调用invoke()方法前,显示调用对象方法的setAccessible(true)方法,即可访问private修饰的方法
代码示例:
package cn.com.longer.test.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class GetRuntimeClass2 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
// 获取class对象
Class<?> c = Class.forName("cn.com.longer.test.reflection.People");
// 方法一:通过class对象,创建实例
People people = (People) c.newInstance(); // 实际上调用的是类的无参构造器
System.out.println(people);
// 方法二: 通过class的构造器
Constructor<?> declaredConstructor = c.getDeclaredConstructor(int.class, String.class, String.class);
People jack = (People) declaredConstructor.newInstance(1, "jack", "123");
System.out.println(jack);
// 通过反射调用方法
People people1 = (People) c.newInstance();
Method myName = c.getDeclaredMethod("myName", String.class);
// invoke: 激活,调用. 参数一: 对象; 参数二:方法的值
myName.invoke(people1, "longer");
Method setName = c.getDeclaredMethod("setName", String.class);
setName.invoke(people1, "zl");
System.out.println(people1.getName());
Field password = c.getDeclaredField("password");
// 不能直接操作私有属性、方法,需要关闭程序的安全检查. 属性 or 方法的setAccessible(true)
password.setAccessible(true);
password.set(people1, "1qaz2wsx");
System.out.println(people1.getPassword());
}
}
2.7 性能对比分析
结论: 普通方法 > 暴力反射方法 > 反射方法
代码示例:
package cn.com.longer.test.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 反射性能效率比较
*/
public class GetRuntimeClass3 {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
test1();
test2();
test3();
}
public static void test1(){
People people = new People();
long t1 = System.currentTimeMillis();
for (long i = 0; i < 1000000000; i++) {
people.getName();
}
long t2 = System.currentTimeMillis();
System.out.println("普通方法耗时:" + (t2 - t1));
}
public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class<People> c = People.class;
People people = c.newInstance();
Method getName = c.getDeclaredMethod("getName", null);
long t1 = System.currentTimeMillis();
for (long i = 0; i < 1000000000; i++) {
getName.invoke(people, null);
}
long t2 = System.currentTimeMillis();
System.out.println("反射方法耗时:" + (t2 - t1));
}
public static void test3() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<People> c = People.class;
Constructor<People> declaredConstructor = c.getDeclaredConstructor(int.class, String.class, String.class);
People rose = declaredConstructor.newInstance(1, "rose", "1qazxsw2");
Method getName = c.getDeclaredMethod("getName", null);
getName.setAccessible(true);
long t1 = System.currentTimeMillis();
for (long i = 0; i < 10_0000_0000; i++) {
getName.invoke(rose, null);
}
long t2 = System.currentTimeMillis();
System.out.println("暴力反射方法耗时:" + (t2 - t1));
}
}
控制台输出结果:
Connected to the target VM, address: '127.0.0.1:53835', transport: 'socket'
普通方法耗时:527
反射方法耗时:3780
暴力反射方法耗时:2962
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?