Java学习笔记(No.20)

本文详细介绍了Java的注解(Annotation)和反射(Reflection)机制。注解提供了一种安全的方式向编译器和运行时系统提供元数据,而反射则允许程序在运行时检查和操作类、接口、方法等。文章涵盖了内置注解、元注解、自定义注解以及类加载过程,同时深入探讨了反射的使用,包括创建对象、调用方法、访问属性和获取泛型信息等。此外,还讨论了类加载器在类加载过程中的角色。
摘要由CSDN通过智能技术生成

Java注解和反射(No.20)

1、注解(Annotation)

从JDK5.0开始,增加注解新特性

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

1.1、注解格式(Annotation Format)

  • 1.1.1、注解是以"@注释名"在代码中存在的,也可以添加一些参数值,如“@SuppressWarnings(value=“all”)”

1.2、注解功能(Annotation Function)

  • 1.2.1、注解本身不是程序,可以对程序作出解释(这一点与注释(Comment)一样)
  • 1.2.2、注解可以被其他程序(如“编译器”等)读取

1.3、三种注解类型(Three Annotation Types)

1.3.1、内置注解(Built-In Annotation)
  • 1.3.1.1、@Override

    定义在java.lang.Override中,此注解只适用于修饰方法,表示一个方法声明重写超类中另一个方法声明

  • 1.3.1.2、@Deprecated

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

  • 1.3.1.3、@SuppressWarnings

    定义在java.lang.SuppressWarnings中,此注解用来抑制编译时的警告信息

    与以上两个注解不同的是,此注解添加一个参数才能正常使用,且这些参数都早已定义(直接选择性使用即可),如下所示

    • 1.3.1.3.1、@SuppresssWarnings(“all”)
    • 1.3.1.3.2、@SuppresssWarnings(“unchecked”)
    • 1.3.1.3.3、@SuppresssWarnings(value={“unchecked”,“deprecation”})
    • 等等…
  • 1.3.1.4、示例代码(Sample Code)

    其示例代码,如下所示

    package com.xueshanxuehai.annotationandreflection.annotation;
    import java.util.ArrayList;
    import java.util.List;
    /**
     * 内置注解
     */
    public class BuiltInAnnotation extends Object{
        //@Override:此注解代表“重写此方法”
        @Override
        public String toString() {
            //return super.toString();
            return "@Override内置注解";
        }
        //@Deprecated:此注解代表“可以使用此方法,但不推荐使用或者有更好的方法选择”
        @Deprecated
        public static void test1(){
            System.out.println("@Deprecated内置注解");
        }
        //@SuppressWarnings:此注解代表“用来抑制编译时的警告信息”
        @SuppressWarnings("all")
        public void test2(){
            List list=new ArrayList();
            System.out.println("@SuppressWarnings内置注解");
        }
        public static void main(String[] args) {
            System.out.println(new BuiltInAnnotation().toString());
            test1();
            new BuiltInAnnotation().test2();
        }
    }
    
  • 1.3.1.5、运行结果(Run Result)

    其运行结果,如下所示

    @Override内置注解
    @Deprecated内置注解
    @SuppressWarnings内置注解
    
1.3.2、元注解(Meta Annotation)

元注解就是负责注解其它注解

Java定义了4个标准的元注解(Meta Annotation)类型(@Target、@Retention、@Documented、@Inherited),它们被用来对其它注解(Annotation)类型作说明,且这些元注解或注解类型和它们所支持的类都在包(java.lang.annotation)中可以找到

  • 1.3.2.1、@Target

    此元注解用于描述注解的使用范围(即,被描述的注解可以用在什么地方)

  • 1.3.2.2、@Retention

    此元注解用于描述注解的生命周期(SOURCE<CLASS<RUNTIME),表示需要在什么级别保存该注解信息

  • 1.3.2.3、@Documented

    此元注解用于描述注解被包含在javadoc中

  • 1.3.2.4、@Inherited

    此元注解用于描述子类可以继承父类的注解

  • 1.3.2.5、示例代码(Sample Code)

    其示例代码,如下所示

    package com.xueshanxuehai.annotationandreflection.annotation;
    import java.lang.annotation.*;
    /**
     * 元注解
     */
    @MetaAnnotationTest
    public class MetaAnnotation {
        @MetaAnnotationTest
        public void test(){
            System.out.println("@MetaAnnotationTest元注解");
        }
        public static void main(String[] args) {
            new MetaAnnotation().test();
        }
    }
    //@Target:此注解代表“可以用在哪些地方(如:类或方法)”
    @Target(value = {ElementType.TYPE,ElementType.METHOD})
    //@Retention:此注解代表“在什么地方还有效(如:在源代码文件时或Class文件时或运行时)”
    @Retention(value = RetentionPolicy.RUNTIME)
    //@Documented:此注解代表“是否将我们的注解生成在JavaDoc中”
    @Documented
    //@Inherited:此注解代表“子类可以继承父类的注解”
    @Inherited
    @interface MetaAnnotationTest{
    }
    
  • 1.3.2.6、运行结果(Run Result)

    其运行结果,如下所示

    @MetaAnnotationTest元注解
    
1.3.3、自定义注解(Custom Annotation)
  • 1.3.3.1、@interface

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

    • 1.3.3.1.1、声明"自定义注解"的格式:“public @interface 自定义注解名 {自定义内容}”
    • 1.3.3.1.2、其中每一个方法实际上是声明了一个配置参数
    • 1.3.3.1.3、其中方法的名称就是参数的名称
    • 1.3.3.1.4、其中返回值类型就是参数的类型(返回值类型只能是“基本类型、Class、String、Enum”)
    • 1.3.3.1.5、其中参数的默认值可以通过default来声明
    • 1.3.3.1.6、其中若只有一个参数成员,一般参数名为"value"
    • 1.3.3.1.7、其中注解元素必须要有值,在定义注解元素时,经常使用空字符串、数值0作为默认值
  • 1.3.3.2、示例代码(Sample Code)

    其示例代码,如下所示

    package com.xueshanxuehai.annotationandreflection.annotation;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    /**
     * 自定义注解
     */
    public class CustomAnnotation {
        //注解可以显示赋值,若没有默认值,则必须给注解赋值
        @CustomAnnotationTest1()
        public void test1(){
            System.out.println("@CustomAnnotationTest1自定义注解(方式1)");
        }
        @CustomAnnotationTest1(id=1,age=18,name="学山学海",sex='男')
        public void test2(){
            System.out.println("@CustomAnnotationTest1自定义注解(方式2)");
        }
        @CustomAnnotationTest2("学山学海")
        public void test3(){
            System.out.println("@CustomAnnotationTest2自定义注解(方式1)");
        }
        @CustomAnnotationTest2(value="学山学海")
        public void test4(){
            System.out.println("@CustomAnnotationTest2自定义注解(方式2)");
        }
        public static void main(String[] args) {
            new CustomAnnotation().test1();
            new CustomAnnotation().test2();
            new CustomAnnotation().test3();
            new CustomAnnotation().test4();
        }
    }
    @Target({ElementType.TYPE,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface CustomAnnotationTest1{
        //注解的参数:参数类型+参数名()
        int id() default -1;//若默认值为-1,则代表不存在
        int age() default -1;//若默认值为-1,则代表不存在
        String name() default "";//若默认值为"",则代表不存在
        char[] sex() default {'男','女'};
    }
    @Target({ElementType.TYPE,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface CustomAnnotationTest2{
        String value();//默认名为“value”
    }
    
  • 1.3.3.3、运行结果(Run Result)

    其运行结果,如下所示

    @CustomAnnotationTest1自定义注解(方式1)
    @CustomAnnotationTest1自定义注解(方式2)
    @CustomAnnotationTest2自定义注解(方式1)
    @CustomAnnotationTest2自定义注解(方式2)
    

2、反射(Reflection)

反射是Java被视为动态语言的关键,反射机制允许程序在执行期间借助Reflection API获取任何类的内部信息,并能直接操作任意对象的内部属性及方法。如以下代码所示

Class c=Class.forName("java.lang.String");

加载完类后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息,通过这个对象可以看到类的结构信息。因此,这个对象就像一面镜子,透过这个镜子看到类的结构,从而这种方式被形象的称为“反射(Reflection)”,如下图所示

正常方式与反射方式示意图

2.1、功能(Function)

  • 2.1.1、在运行时判断任意一个对象所属的类
  • 2.1.2、在运行时构造任意一个类的对象
  • 2.1.3、在运行时判断任意一个类所具有的成员变量和方法
  • 2.1.4、在运行时获取泛型信息
  • 2.1.5、在运行时调用任意一个对象的成员变量和方法
  • 2.1.6、在运行时处理注解
  • 2.1.7、生成动态代理等

2.2、优缺点(Advantages And Disadvantages)

  • 2.2.1、优点(Advantages)
    • 2.2.1.1、可以实现动态创建对象和编译,体现出很大的灵活性
  • 2.2.2、缺点(Disadvantages)
    • 2.2.2.1、对性能有影响。(使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求,而这类操作总是慢于“直接执行相同的操作”)

2.3、API(Application Programming Interface)

  • 2.3.1、java.lang.Class:代表一个类
  • 2.3.2、java.lang.reflect.Method:代表类的方法
  • 2.3.3、java.lang.reflect.Field:代表类的成员变量
  • 2.3.4、java.lang.reflect.Constructor:代表类的构造器等

2.4、示例代码(Sample Code)

其示例代码,如下所示

package com.xueshanxuehai.annotationandreflection.reflection;
/**
 * 反射与实体类
 */
public class ReflectionAndEntityClass {
    public static void main(String[] args) throws ClassNotFoundException {
        /**
         * 通过反射获取类的Class对象
         * 一个类在内存中只有一个Class对象
         * 一个类被加载后,类的整个结构都会被封装在Class对象中
         */
        Class c1 = Class.forName("com.xueshanxuehai.annotationandreflection.reflection.User");
        System.out.println("c1 = " + c1);
        System.out.println("c1.hashCode() = " + c1.hashCode());
        Class c2 = Class.forName("com.xueshanxuehai.annotationandreflection.reflection.User");
        System.out.println("c2 = " + c2);
        System.out.println("c2.hashCode() = " + c2.hashCode());
        Class c3=Class.forName("com.xueshanxuehai.annotationandreflection.reflection.User");
        System.out.println("c3 = " + c3);
        System.out.println("c3.hashCode() = " + c3.hashCode());
    }
}
//实体类
class User {
    private int id;//编号
    private String name;//姓名
    private int age;//年龄
    public User() {
    }
    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        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;
    }
}

2.5、运行结果(Run Result)

其运行结果,如下所示

c1 = class com.xueshanxuehai.annotationandreflection.reflection.User
c1.hashCode() = 1324119927
c2 = class com.xueshanxuehai.annotationandreflection.reflection.User
c2.hashCode() = 1324119927
c3 = class com.xueshanxuehai.annotationandreflection.reflection.User
c3.hashCode() = 1324119927

3、Class类

在Object类中定义了以下的方法,此方法将被所有子类继承。如下所示

/**
 * 此方法返回值类型是一个CLass类,此类是Java反射的源头。
 * 实际上所谓反射从程序的运行结果来看也很好理解,即,“可以通过对象反射求出类的名称”。
 */
Class c=Class.forName("java.lang.String");

对象照镜子(即,“反射”)后可以得到的信息:某个类的属性、方法、构造器、实现接口,对于每个类而言,JRE都为其保留一个不变的Class类型对象,一个Class类型对象包含了特定某个结构()的有关信息,如下所示

1Class本身也是一个类
2Class对象只能由系统建立
3、一个类加载时只会在JVM中存在一个Class实例
4、一个Class对象对应的是一个加载到JVM中的一个.class文件
5、每个类的实例都会记得自己是由哪个Class实例所生成
6、通过Class可以完整的得到一个类中的所有被加载的结构
7Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

3.1、常用方法(Common Method)

Class类的常用方法,如下表所示

方法名功能说明
static Class<?> forName(String className)返回指定类名name的Class对象
T newInstance()调用缺省构造函数,返回Class对象的一个实例
String getName()返回此Class对象所表示的实体(类、接口、数组类或void)的名称
Class<? super T> getSuperclass()返回当前Class对象的父类的Class对象
Class<?>[] getInterfaces()返回当前Class对象的实现接口
ClassLoader getClassLoader()返回该类的类加载器
Constructor<?>[] getConstructors()返回一个包含某些Constructor对象的数组
Method getMethod(String name, Class<?>… parameterTypes)返回一个Method对象,此对象的形参类型为parameterType
Field[] getDeclaredFields()返回一个Field对象的数组

3.2、获取实例的5种方式(Five Ways Of Obtain Instance)

  • 3.2.1、若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高,如下所示

    Class c=Person.class;
    
  • 3.2.2、已知某个类的实例,调用该实例的getClass()方法获取Class对象,如下所示

    Class c=Person.getClass();
    
  • 3.2.3、已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,同时可能会抛出异常“ClassNotFoundException”,如下所示

    Class c=Class.forName("com.xueshanxuehai.annotationandreflection.reflection.Person");
    
  • 3.2.4、内置基本数据类型可以直接用“类名.TYPE”获取,如下所示

    Class c=Integer.TYPE;
    
  • 3.2.5、通过类加载器(ClassLoader)获取,如下所示

    ClassLoader cl=ClassLoader.getSystemClassLoader();
    
  • 3.2.6、示例代码(Sample Code)

其示例代码,如下所示

package com.xueshanxuehai.annotationandreflection.reflection;
/**
 * 获取Class类的实例
 */
public class ObtainClassInstance {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person=new Teacher();
        System.out.println("person.name = " + person.name);
        //方式一:通过对象获得
        Class c1=person.getClass();
        System.out.println("c1.hashCode() = " + c1.hashCode());
        //方式二:通过"Class.forName()"方法获得
        Class c2=Class.forName("com.xueshanxuehai.annotationandreflection.reflection.Teacher");
        System.out.println("c2.hashCode() = " + c2.hashCode());
        //方式三:通过"类名.class"属性获得
        Class c3=Teacher.class;
        System.out.println("c3.hashCode() = " + c3.hashCode());
        //方式四:内置基本数据类型的包装类可以直接通过“类名.TYPE”属性获得
        Class c4=Integer.TYPE;
        System.out.println("c4.hashCode() = " + c4.hashCode());
        //方式五:通过"类加载器"获得
        ClassLoader c5=ClassLoader.getSystemClassLoader();
        System.out.println("c5.hashCode() = " + c5.hashCode());
        //获得对象的父类类型
        Class c6=c1.getSuperclass();
        System.out.println("c6 = " + c6);
    }
}
//Person类
class Person{
    public String name;
    public Person() {
    }
    public Person(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
//Teacher类
class Teacher extends Person{
    public Teacher() {
        this.name="教师";
    }
}
  • 3.2.7、运行结果(Run Result)

其运行结果,如下所示

person.name = 教师
c1.hashCode() = 990368553
c2.hashCode() = 990368553
c3.hashCode() = 990368553
c4.hashCode() = 685325104
c5.hashCode() = 1725154839
c6 = class com.xueshanxuehai.annotationandreflection.reflection.Person

3.3、Class对象类型(Class Object Type)

  • 3.2.1、class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

  • 3.2.2、interface:接口

  • 3.2.3、[]:数组

  • 3.2.4、enum:枚举

  • 3.2.5、annotation:注解(@interface)

  • 3.2.6、primitive type:基本数据类型

  • 3.2.7、void:空类型

  • 3.2.8、示例代码(Sample Code)

其示例代码,如下所示

package com.xueshanxuehai.annotationandreflection.reflection;
import java.lang.annotation.ElementType;
/**
 * Class对象类型
 * 只要数组中的元素类型与维度一样,那么这些数组对象都是同一个Class。
 */
public class ClassObjectType {
    public static void main(String[] args) {
        Class c1=Object.class;//类
        System.out.println("c1 = " + c1);
        Class c2=Runnable.class;//接口
        System.out.println("c2 = " + c2);
        Class c3=String[].class;//一维数组
        System.out.println("c3 = " + c3);
        Class c4=String[][].class;//二维数组
        System.out.println("c4 = " + c4);
        Class c5=Override.class;//注解
        System.out.println("c5 = " + c5);
        Class c6= ElementType.class;//枚举
        System.out.println("c6 = " + c6);
        Class c7= Integer.class;//基本数据类型
        System.out.println("c7 = " + c7);
        Class c8= void.class;//空类型
        System.out.println("c8 = " + c8);
        Class c9= Class.class;//Class
        System.out.println("c9 = " + c9);
    }
}
  • 3.2.9、运行结果(Run Result)

其运行结果,如下所示

c1 = class java.lang.Object
c2 = interface java.lang.Runnable
c3 = class [Ljava.lang.String;
c4 = class [[Ljava.lang.String;
c5 = interface java.lang.Override
c6 = class java.lang.annotation.ElementType
c7 = class java.lang.Integer
c8 = void
c9 = class java.lang.Class

3.4、类的加载(Class Load)

3.4.1、加载过程(Load Process)

当程序主动使用某个类时,若该类还未加载到内存中,则系统会通过3个步骤(加载[Load]、链接[Link]、初始化[Initialize])来对该类进行初始化,如下所示

  • 3.4.1.1、步骤1:“加载”。将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的“java.lang.Class”对象
  • 3.4.1.2、步骤2:“链接”。将Java类的二进制代码合并到JVM的运行状态之中的过程
    • 3.4.1.2.1、验证:确保加载的类信息符合JVM规范,没有安全方面的问题
    • 3.4.1.2.2、准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
    • 3.4.1.2.3、解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  • 3.4.1.3、步骤3:“初始化”
    • 3.4.1.3.1、执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)
    • 3.4.1.3.2、当初始化一个类的时候,若发现其父类还没有进行初始化,则需要先触发其父类的初始化
    • 3.4.1.3.3、虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步
3.4.2、类的主动与被动引用(Active And Passive Reference Of Class)
  • 3.4.2.1、类的主动引用(一定会发生类的初始化)

    • 3.4.2.1.1、当虚拟机启动,先安初始化main方法所在的类
    • 3.4.2.1.2、new一个类的对象
    • 3.4.2.1.3、调用类的静态成员(除了final常量)和静态方法
    • 3.4.2.1.4、使用java.lang.reflect包的方法对类进行反射调用
    • 3.4.2.1.5、当初始化一个类,若其父类没有初始化,则会先初始化它的父类
  • 3.4.2.2、类的被动引用(不会发生类的初始化)

    • 3.4.2.2.1、当访问一个静态域时,只有真正声明这个域的类才会被初始化,如“当通过子类引用父类的静态变量,不会导致子类初始化”
    • 3.4.2.2.2、通过数组定义类引用,不会触发此类的初始化
    • 3.4.2.2.3、引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中)
  • 3.4.2.3、示例代码(Sample Code)

    其示例代码,如下所示

    package com.xueshanxuehai.annotationandreflection.reflection;
    /**
     * 类的加载
     * 1、类的主动引用(发生类的初始化)
     * 2、类的被动引用(不发生类的初始化)
     */
    public class ClassLoadInitialization {
        public static void main(String[] args) throws ClassNotFoundException {
            //类的主动引用(发生类的初始化)
            System.out.println("类的主动引用(发生类的初始化)");
            System.out.println("-------------------------------------------------");
            Son son=new Son();
            System.out.println("**********************************");
            Class.forName("com.xueshanxuehai.annotationandreflection.reflection.Son");
            System.out.println("**********************************");
            System.out.println("Son.s = " + Son.s);
            System.out.println("-------------------------------------------------");
            //类的被动引用(不发生类的初始化)
            System.out.println("类的被动引用(不发生类的初始化)");
            System.out.println("-------------------------------------------------");
            System.out.println("Son.f = " + Son.f);
            System.out.println("**********************************");
            Son[] sonArr=new Son[1];
            System.out.println("**********************************");
            System.out.println("Son.S = " + Son.S);
            System.out.println("-------------------------------------------------");
        }
    }
    //父类
    class Father{
        static int f=1;
        static{
            System.out.println("父类的静态代码块初始化");
        }
        public Father() {
            System.out.println("父类的无参构造器初始化");
        }
    }
    //子类
    class Son extends Father{
        static{
            System.out.println("子类的静态代码块初始化");
        }
        static int s=2;
        static final int S=3;
        public Son() {
            System.out.println("子类的无参构造器初始化");
        }
    }
    
  • 3.4.2.4、运行结果(Run Result)

    其示例代码,如下所示

    类的主动引用(发生类的初始化)
    -------------------------------------------------
    父类的静态代码块初始化
    子类的静态代码块初始化
    父类的无参构造器初始化
    子类的无参构造器初始化
    **********************************
    **********************************
    Son.s = 2
    -------------------------------------------------
    类的被动引用(不发生类的初始化)
    -------------------------------------------------
    Son.f = 1
    **********************************
    **********************************
    Son.S = 3
    -------------------------------------------------
    

3.5、获取类运行时完整结构(Get Class Runtime Complete Structure)

  • 3.5.1、实现的全部接口(Interface)

  • 3.5.2、继承的父类(SuperClass)

  • 3.5.3、全部的构造器(Constructor)

  • 3.5.4、全部的方法(Method)

  • 3.5.5、全部的属性(Field)

  • 3.5.6、全部的注解(Annotation)

  • 3.5.7、示例代码(Sample Code)

    其示例代码,如下所示

    package com.xueshanxuehai.annotationandreflection.reflection.classload;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    /**
     * 类运行时完整结构
     */
    public class ClassRuntimeCompleteStructure {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
            Class c = Class.forName("com.xueshanxuehai.annotationandreflection.reflection.classload.User001");
            //获取类名
            System.out.println("获取类名");
            System.out.println("----------------------------------------------");
            System.out.println("c.getName() = " + c.getName());//包名+类名
            System.out.println("c.getSimpleName() = " + c.getSimpleName());//类名
            System.out.println("----------------------------------------------");
            //获取类属性
            System.out.println("获取类属性");
            System.out.println("----------------------------------------------");
            Field[] fields = c.getFields();//获取本类所有public属性
            for (Field field : fields) {
                System.out.println(field);
            }
            System.out.println("******************************");
            Field[] declaredFields = c.getDeclaredFields();//获取本类所有属性
            for (Field declaredField : declaredFields) {
                System.out.println(declaredField);
            }
            System.out.println("----------------------------------------------");
            //获取指定类属性
            System.out.println("获取指定类属性");
            System.out.println("----------------------------------------------");
            Field name = c.getDeclaredField("name");
            System.out.println("name = " + name);
            System.out.println("----------------------------------------------");
            //获取类方法
            System.out.println("获取类方法");
            System.out.println("----------------------------------------------");
            Method[] methods = c.getMethods();//获取本类及其父类的所有public方法
            for (Method method : methods) {
                System.out.println(method);
            }
            System.out.println("******************************");
            Method[] declaredMethods = c.getDeclaredMethods();//获取本类的所有方法
            for (Method declaredMethod : declaredMethods) {
                System.out.println(declaredMethod);
            }
            System.out.println("----------------------------------------------");
            //获取指定类方法
            System.out.println("获取指定类方法");
            System.out.println("----------------------------------------------");
            Method getName = c.getMethod("getName", null);
            System.out.println("getName = " + getName);
            System.out.println("******************************");
            Method setName = c.getMethod("setName", String.class);
            System.out.println("setName = " + setName);
            System.out.println("----------------------------------------------");
            //获取类构造器
            System.out.println("获取类构造器");
            System.out.println("----------------------------------------------");
            Constructor[] constructors = c.getConstructors();//获取本类所有public构造器
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }
            System.out.println("******************************");
            Constructor[] declaredConstructors = c.getDeclaredConstructors();//获取本类所有构造器
            for (Constructor declaredConstructor : declaredConstructors) {
                System.out.println(declaredConstructor);
            }
            System.out.println("----------------------------------------------");
            //获取指定类构造器
            System.out.println("获取指定类构造器");
            System.out.println("----------------------------------------------");
            Constructor declaredConstructor = c.getDeclaredConstructor(String.class);
            System.out.println("declaredConstructor = " + declaredConstructor);
            System.out.println("----------------------------------------------");
        }
    }
    class User001 extends User{
        private String name;
        private void test1(){
            System.out.println("test1");
        }
        public User001() {
        }
        public User001(String name) {
            this.name = name;
        }
        @Override
        public String getName() {
            return name;
        }
        @Override
        public void setName(String name) {
            this.name = name;
        }
    }
    
  • 3.5.8、运行结果(Run Result)

    其运行结果,如下所示

    获取类名
    ----------------------------------------------
    c.getName() = com.xueshanxuehai.annotationandreflection.reflection.classload.User001
    c.getSimpleName() = User001
    ----------------------------------------------
    获取类属性
    ----------------------------------------------
    ******************************
    private java.lang.String com.xueshanxuehai.annotationandreflection.reflection.classload.User001.name
    ----------------------------------------------
    获取指定类属性
    ----------------------------------------------
    name = private java.lang.String com.xueshanxuehai.annotationandreflection.reflection.classload.User001.name
    ----------------------------------------------
    获取类方法
    ----------------------------------------------
    public java.lang.String com.xueshanxuehai.annotationandreflection.reflection.classload.User001.getName()
    public void com.xueshanxuehai.annotationandreflection.reflection.classload.User001.setName(java.lang.String)
    public void com.xueshanxuehai.annotationandreflection.reflection.classload.User.setId(int)
    public int com.xueshanxuehai.annotationandreflection.reflection.classload.User.getAge()
    public void com.xueshanxuehai.annotationandreflection.reflection.classload.User.setAge(int)
    public int com.xueshanxuehai.annotationandreflection.reflection.classload.User.getId()
    public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    public final void java.lang.Object.wait() throws java.lang.InterruptedException
    public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    public boolean java.lang.Object.equals(java.lang.Object)
    public java.lang.String java.lang.Object.toString()
    public native int java.lang.Object.hashCode()
    public final native java.lang.Class java.lang.Object.getClass()
    public final native void java.lang.Object.notify()
    public final native void java.lang.Object.notifyAll()
    ******************************
    private void com.xueshanxuehai.annotationandreflection.reflection.classload.User001.test1()
    public java.lang.String com.xueshanxuehai.annotationandreflection.reflection.classload.User001.getName()
    public void com.xueshanxuehai.annotationandreflection.reflection.classload.User001.setName(java.lang.String)
    ----------------------------------------------
    获取指定类方法
    ----------------------------------------------
    getName = public java.lang.String com.xueshanxuehai.annotationandreflection.reflection.classload.User001.getName()
    ******************************
    setName = public void com.xueshanxuehai.annotationandreflection.reflection.classload.User001.setName(java.lang.String)
    ----------------------------------------------
    获取类构造器
    ----------------------------------------------
    public com.xueshanxuehai.annotationandreflection.reflection.classload.User001(java.lang.String)
    public com.xueshanxuehai.annotationandreflection.reflection.classload.User001()
    ******************************
    public com.xueshanxuehai.annotationandreflection.reflection.classload.User001(java.lang.String)
    public com.xueshanxuehai.annotationandreflection.reflection.classload.User001()
    ----------------------------------------------
    获取指定类构造器
    ----------------------------------------------
    declaredConstructor = public com.xueshanxuehai.annotationandreflection.reflection.classload.User001(java.lang.String)
    ----------------------------------------------
    

3.6、通过反射动态创建类对象并调用类方法(Dynamic Create Class Object And Call Class Method By Reflect)

  • 3.6.1、创建类对象:“调用Class对象的newInstance()方法”

    • 3.6.1.1、类必须有一个无参构造器
    • 3.6.1.2、类构造器的访问权限需要足够
  • 3.6.2、调用类方法

    • 3.6.2.1、通过Class类对象的getMethod(String name, Class<?>… parameterTypes)方法取得一个Method类对象,并设置此方法操作时所需要的参数类型
    • 3.6.2.2、调用Object invoke(Object obj, Object… args)方法,并向此方法中传递要设置的obj对象的参数信息
      • 3.6.2.2.1、Object对应原方法的返回值,若原方法无返回值,则返回null
      • 3.6.2.2.2、若原方法为静态方法,此时形参“Object obj”可为null
      • 3.6.2.2.3、若原方法形参列表为空,则形参“Object… args”为null
      • 3.6.2.2.4、若原方法声明为private,则需要在调用此invoke()方法前,显示调用此方法对象的setAccessible(true)方法后,则可访问private的此方法
    • 3.6.2.3、调用void setAccessible(boolean flag)方法
      • 3.6.2.3.1、Method和Field、Constructor对象都有setAccessible()方法
      • 3.6.2.3.2、setAccessible()方法的作用是启用和禁用访问安全检查的开关
      • 3.6.2.3.3、其中形参“boolean flag”的值为“true”时,则指示反射的对象在使用时应该取消Java语言的访问安全检查,且其还有如下好处
        • 3.6.2.3.3.1、提高反射效率(若必须用反射,且该反射代码需频繁调用,则设置形参值为true)
        • 3.6.2.3.3.2、可以使原本无法访问的私有成员也能被访问
      • 3.6.2.3.4、其中形参“boolean flag”的值为“false”时,则指示反射的对象在使用时应该实施Java语言的访问安全检查
  • 3.6.3、示例代码(Sample Code)

    其示例代码,如下所示

package com.xueshanxuehai.annotationandreflection.reflection.classload;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
 * 通过反射动态创建类对象并调用类方法
 */
public class DynamicCreateClassObject {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //创建Class对象
        Class c = Class.forName("com.xueshanxuehai.annotationandreflection.reflection.classload.User001");
        /**
         * 创建类对象(方法1):本质上是调用了类的无参构造器,且必须要声明类的无参构造器,否则会抛出实例化异常"InstantiationException"
         */
        System.out.println("创建类对象(方法1):通过调用类的newInstance()方法创建类对象");
        System.out.println("------------------------------------------------------------");
        User001 user1= (User001) c.newInstance();//调用类无参构造器(必须要声明类的无参构造器)
        System.out.println("user1 = " + user1);
        System.out.println("user1.getName() = " + user1.getName());
        System.out.println("------------------------------------------------------------");
        /**
         * 创建类对象(方法2):通过反射动态创建类对象,本质上是调用了类的有参构造器,且必须要声明类的有参构造器,否则会抛出实例化异常"NoSuchMethodException"
         */
        System.out.println("创建类对象(方法2):通过反射动态创建类对象");
        System.out.println("------------------------------------------------------------");
        Constructor declaredConstructor = c.getDeclaredConstructor(String.class);//获取反射构造器
        User001 user2 = (User001) declaredConstructor.newInstance("通过反射动态创建类对象");
        System.out.println("user2 = " + user2);
        System.out.println("user2.getName() = " + user2.getName());
        System.out.println("------------------------------------------------------------");
        //通过反射调用方法
        System.out.println("通过反射调用方法");
        System.out.println("------------------------------------------------------------");
        Method setName = c.getDeclaredMethod("setName", String.class);//获取反射方法
        setName.invoke(user2,"通过反射调用方法");//激活
        System.out.println("user2.getName() = " + user2.getName());
        System.out.println("------------------------------------------------------------");
        //通过反射调用属性
        System.out.println("通过反射调用属性");
        System.out.println("------------------------------------------------------------");
        Field name = c.getDeclaredField("name");
        //不能直接操作私有属性或方法,必须关闭属性或方法的访问安全检查才可以操作(即,设置属性或方法的setAccessible方法中参数为“true”即可)。
        name.setAccessible(true);//关闭属性的访问安全检查
        name.set(user2,"通过反射调用属性");
        System.out.println("user2.getName() = " + user2.getName());
        System.out.println("------------------------------------------------------------");
    }
}
  • 3.6.4、运行结果(Run Result)

    其运行结果,如下所示

创建类对象(方法1):通过调用类的newInstance()方法创建类对象
------------------------------------------------------------
user1 = com.xueshanxuehai.annotationandreflection.reflection.classload.User001@3d075dc0
user1.getName() = null
------------------------------------------------------------
创建类对象(方法2):通过反射动态创建类对象
------------------------------------------------------------
user2 = com.xueshanxuehai.annotationandreflection.reflection.classload.User001@214c265e
user2.getName() = 通过反射动态创建类对象
------------------------------------------------------------
通过反射调用方法
------------------------------------------------------------
user2.getName() = 通过反射调用方法
------------------------------------------------------------
通过反射调用属性
------------------------------------------------------------
user2.getName() = 通过反射调用属性
------------------------------------------------------------

3.7、通过反射动态性能对比分析(Dynamic Comparsion Analysis Performance By Reflect)

  • 3.7.1、示例代码(Sample Code)

    其示例代码,如下所示

package com.xueshanxuehai.annotationandreflection.reflection.classload;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
 * 通过反射动态性能对比分析
 */
public class DynamicComparsionAnalysisPerformance {
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        test1();
        test2();
        test3();
    }
    //普通方式调用类方法
    public static void test1(){
        User001 user=new User001();
        long st=System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        long et=System.currentTimeMillis();
        System.out.println("普通方式调用10亿次类方法共耗时" + (et - st)+"毫秒");
    }
    //反射方式(默认未关闭访问安全检查)调用类方法
    public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User001 user=new User001();
        Class c = user.getClass();
        Method getName = c.getDeclaredMethod("getName", null);
        long st=System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long et=System.currentTimeMillis();
        System.out.println("反射方式(默认未关闭访问安全检查)调用10亿次类方法共耗时" + (et - st)+"毫秒");
    }
    //反射方式(关闭访问安全检查)调用类方法
    public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User001 user=new User001();
        Class c = user.getClass();
        Method getName = c.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        long st=System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long et=System.currentTimeMillis();
        System.out.println("反射方式(关闭访问安全检查)调用10亿次类方法共耗时" + (et - st)+"毫秒");
    }
}
  • 3.7.2、运行结果(Run Result)

    其运行结果,如下所示

普通方式调用10亿次类方法共耗时3毫秒
反射方式(默认未关闭访问安全检查)调用10亿次类方法共耗时2386毫秒
反射方式(关闭访问安全检查)调用10亿次类方法共耗时1522毫秒

3.8、通过反射动态获取泛型信息(Dynamic Get Generic Information By Reflect)

Java采用泛型擦除机制引入泛型,Java中泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是一旦编译完成,所有和泛型有关的类型全部擦除

为了通过反射操作这些泛型有关的类型,Java新增了几种类型来代表不能被归一到Class类中的类型,但是又和原始类型齐名的类型,如下所示

ParameterizedType:表示一种参数化类型(如“Collection<String>)
GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
TypeVariable:是各种类型变量的公共父接口
WildcardType:代表一种通配符类型表达式
  • 3.8.1、示例代码(Sample Code)

    其示例代码,如下所示

package com.xueshanxuehai.annotationandreflection.reflection.classload;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
/**
 * 通过反射动态获取泛型信息
 */
public class DynamicGetGenericInformation {
    public void genericMethod1(Map<String,User001> map, List<User001> list){
        System.out.println("genericMethod1");
    }
    public Map<String,User001> genericMethod2(){
        System.out.println("genericMethod2");
        return null;
    }
    public static void main(String[] args) throws NoSuchMethodException {
        //通过反射动态获取无返回值方法的泛型参数信息
        System.out.println("通过反射动态获取无返回值方法的泛型参数信息");
        System.out.println("------------------------------------------------------------");
        Method method1 = DynamicGetGenericInformation.class.getMethod("genericMethod1", Map.class, List.class);
        Type[] genericParameterTypes = method1.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println(genericParameterType);
            if(genericParameterType instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }
        System.out.println("------------------------------------------------------------");
        //通过反射动态获取方法的泛型返回值信息
        System.out.println("通过反射动态获取方法的泛型返回值信息");
        System.out.println("------------------------------------------------------------");
        Method method2 = DynamicGetGenericInformation.class.getMethod("genericMethod2", null);
        Type genericReturnType = method2.getGenericReturnType();
        System.out.println("genericReturnType = " + genericReturnType);
        if(genericReturnType instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
        System.out.println("------------------------------------------------------------");
    }
}
  • 3.8.2、运行结果(Run Result)

    其运行结果,如下所示

通过反射动态获取无返回值方法的泛型参数信息
------------------------------------------------------------
java.util.Map<java.lang.String, com.xueshanxuehai.annotationandreflection.reflection.classload.User001>
class java.lang.String
class com.xueshanxuehai.annotationandreflection.reflection.classload.User001
java.util.List<com.xueshanxuehai.annotationandreflection.reflection.classload.User001>
class com.xueshanxuehai.annotationandreflection.reflection.classload.User001
------------------------------------------------------------
通过反射动态获取方法的泛型返回值信息
------------------------------------------------------------
genericReturnType = java.util.Map<java.lang.String, com.xueshanxuehai.annotationandreflection.reflection.classload.User001>
class java.lang.String
class com.xueshanxuehai.annotationandreflection.reflection.classload.User001
------------------------------------------------------------

3.9、通过反射动态获取注解信息(Dynamic Get Annotation Information By Reflect)

练习:“使用注解和反射完成类和表结构的映射关系”,即“ORM(Object Relationship Mapping,对象关系映射)”,其特点如下所示

ORM(Object Relationship Mapping,对象关系映射)中,
1、类和数据表结构对应
2、属性和数据表字段对应
3、对象和数据表行记录对应
  • 3.9.1、示例代码(Sample Code)

    其示例代码,如下所示

package com.xueshanxuehai.annotationandreflection.reflection.classload;
import java.lang.annotation.*;
import java.lang.reflect.Field;
/**
 * 通过反射动态获取注解信息
 */
public class DynamicGetAnnotationInformation {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c = Class.forName("com.xueshanxuehai.annotationandreflection.reflection.classload.Student");
        //通过反射获取注解
        System.out.println("通过反射获取注解");
        System.out.println("------------------------------------------------------------");
        Annotation[] annotations = c.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        System.out.println("------------------------------------------------------------");
        //获取指定类名(对应数据表结构)注解的值
        System.out.println("获取指定类名(对应数据表结构)注解的值");
        System.out.println("------------------------------------------------------------");
        DataTable dataTable = (DataTable)c.getAnnotation(DataTable.class);
        String value = dataTable.value();
        System.out.println("value = " + value);
        System.out.println("------------------------------------------------------------");
        //获取指定属性(对应数据表字段)注解的值
        System.out.println("获取指定属性(对应数据表字段)注解的值");
        System.out.println("------------------------------------------------------------");
//        Field[] declaredFields = c.getDeclaredFields();
//        for (Field declaredField : declaredFields) {
//            System.out.println("declaredField = " + declaredField);
//            Annotation[] annotations1 = declaredField.getAnnotations();
//            for (Annotation annotation : annotations1) {
//                System.out.println("annotation = " + annotation);
//            }
//        }
        Field id = c.getDeclaredField("id");
        DataTableField1 annotation1 = id.getAnnotation(DataTableField1.class);
        System.out.println("annotation1 = " + annotation1);
        System.out.println("annotation1.fieldName() = " + annotation1.fieldName());
        System.out.println("annotation1.fieldType() = " + annotation1.fieldType());
        Field name = c.getDeclaredField("name");
        DataTableField2 annotation2 = name.getAnnotation(DataTableField2.class);
        System.out.println("annotation2 = " + annotation2);
        System.out.println("annotation2.fieldName() = " + annotation2.fieldName());
        System.out.println("annotation2.fieldType() = " + annotation2.fieldType());
        System.out.println("annotation2.fieldLength() = " + annotation2.fieldLength());
        Field age = c.getDeclaredField("age");
        DataTableField1 annotation3= age.getAnnotation(DataTableField1.class);
        System.out.println("annotation3 = " + annotation3);
        System.out.println("annotation3.fieldName() = " + annotation3.fieldName());
        System.out.println("annotation3.fieldType() = " + annotation3.fieldType());
        System.out.println("------------------------------------------------------------");
    }
}
//自定义注解类
@DataTable("db_student")
class Student{
    @DataTableField1(fieldName = "db_id",fieldType = "int")
    private int id;
    @DataTableField2(fieldName = "db_name",fieldType = "varchar",fieldLength = 8)
    private String name;
    @DataTableField1(fieldName = "db_age",fieldType = "int")
    private int age;
    public Student() {
    }
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        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;
    }
}
//类名(对应数据表结构)注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface DataTable{
    String value();//数据表名
}
//属性(对应数据表字段)注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface DataTableField1{
    String fieldName();//字段名
    String fieldType();//字段类型
}
//属性(对应数据表字段)注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface DataTableField2{
    String fieldName();//字段名
    String fieldType();//字段类型
    int fieldLength();//字段长度
}
  • 3.9.2、运行结果(Run Result)

    其运行结果,如下所示

通过反射获取注解
------------------------------------------------------------
@com.xueshanxuehai.annotationandreflection.reflection.classload.DataTable("db_student")
------------------------------------------------------------
获取指定类名(对应数据表结构)注解的值
------------------------------------------------------------
value = db_student
------------------------------------------------------------
获取指定属性(对应数据表字段)注解的值
------------------------------------------------------------
annotation1 = @com.xueshanxuehai.annotationandreflection.reflection.classload.DataTableField1(fieldName="db_id", fieldType="int")
annotation1.fieldName() = db_id
annotation1.fieldType() = int
annotation2 = @com.xueshanxuehai.annotationandreflection.reflection.classload.DataTableField2(fieldName="db_name", fieldType="varchar", fieldLength=8)
annotation2.fieldName() = db_name
annotation2.fieldType() = varchar
annotation2.fieldLength() = 8
annotation3 = @com.xueshanxuehai.annotationandreflection.reflection.classload.DataTableField1(fieldName="db_age", fieldType="int")
annotation3.fieldName() = db_age
annotation3.fieldType() = int
------------------------------------------------------------

3.10、注意事项(Matters Needing Attention)

  • 3.10.1、在实际开发中,并不会经常使用获取类信息的操作代码
  • 3.10.2、一定要熟悉java.lang.reflect包的作用(即,反射机制)

4、ClassLoader类

即,“类加载器”

4.1、作用(Function)

类加载器就是用来把类(class)装载进内存的,其中JVM规范定义了几种类型的类加载器,如下图所示

类加载器作用示意图

4.2、示例代码(Sample Code)

其示例代码,如下所示

package com.xueshanxuehai.annotationandreflection.reflection.classloader;
/**
 * 类加载器
 */
public class ClassLoaderReflection {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取系统类的加载类(应用类加载器,又称为系统类加载器)
        ClassLoader classLoader=ClassLoader.getSystemClassLoader();
        System.out.println("classLoader = " + classLoader);
        //获取系统类加载器的父类加载器(扩展类加载器)
        ClassLoader parent = classLoader.getParent();
        System.out.println("parent = " + parent);
        //获取扩展类加载器的父类加载器(引导类加载器,又称为根加载器(C/C++))
        ClassLoader parent1 = parent.getParent();
        System.out.println("parent1 = " + parent1);
        //获取当前类的加载器
        ClassLoader c1 = Class.forName("com.xueshanxuehai.annotationandreflection.reflection.classloader.ClassLoaderReflection").getClassLoader();
        System.out.println("c1 = " + c1);
        //获取JDK内置类的加载器
        ClassLoader c2 = Class.forName("java.lang.String").getClassLoader();
        System.out.println("c2 = " + c2);
        //获取系统类加载器的加载路径
        System.out.println("System.getProperty(java.class.path) = " + System.getProperty("java.class.path"));
    }
}

4.3、运行结果(Run Result)

其示例代码,如下所示

classLoader = jdk.internal.loader.ClassLoaders$AppClassLoader@66d3c617
parent = jdk.internal.loader.ClassLoaders$PlatformClassLoader@6d311334
parent1 = null
c1 = jdk.internal.loader.ClassLoaders$AppClassLoader@66d3c617
c2 = null
System.getProperty(java.class.path) = E:\Environment\Java\IDEA\StageOne\javase\out\production\javase;E:\Environment\Java\IDEA\StageOne\javase\lib\commons-io-2.11.0.jar

参考资料(Reference Data):

学习网站地址(即"学习网址",Learning Website Address):Java注解和反射

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值