注解与反射

注解和反射

一、注解(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类的方式

  1. 类名.class

  2. 类对象.getClass()

  3. 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内存分析:
Java内存分析png

2.类的加载过程:
类的加载过程png

3.类的加载与ClassLoader的理解:
类的加载与ClassLoader的理解png

4.什么时候会发生类的初始化?
什么时候会发生类的初始化

5.类加载器的作用
image

6.类的加载器的类型
image

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 动态创建对象执行方法

创建类的对象:

  1. 调用类对象的newInstance()方法
  • 该类必须有空参构造器

  • 该类的空参构造器的访问权限需要足够

  1. 调用类中的构造器,将参数传递进去
  • 调用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
posted @   zlonger  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示