Java注解与反射

2021/5/9 1:25:33

本文主要是介绍Java注解与反射,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

(一)注解

注解Annotation:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。

注解Annotation的作用:

  • 不是程序本身,可以对程序作出解释;
  • 可以被其他程序读取;
  • 注解一般是以“@注释名”在代码中存在;
  • 可以通过反射机制编程实现对这些元数据的访问;

内置注解

@Override:检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。

@Deprecated:标记过时方法,不安全的,废弃的。如果使用该方法,会报编译警告。

@SuppressWarnings :指示编译器去忽略注解中声明的警告。该注解可以添加参数:

​ @SuppressWarnings(“all”)

​ @SuppressWarnings(“unchecked”)

​ @SuppressWarnings(value={“unchecked”,”deprecation”})

public class Test {
    public static void main(String[] args) {
        Test00 test00 = new Test00();
        test00.test00();		//@Deprecated,使用了不建议的方法

    }
    @SuppressWarnings("all")            //指示编译器去忽略注解中声明的警告
   private static class Test00 implements Runnable{
        @Override                       //检查该方法是否是重写方法
        public void run() {
        }
        @Deprecated
        public void test00(){
            System.out.println("!");
        }
    }
}

元注解

元注解的作用是负责注解其他注解,如:@Retention,@Documented,@Target ,@Inherited

  • @Target:用于描述注解的使用范围(即在什么情况下可以使用);
  • @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期;
  • @Documented:说明该注解被包含在javadoc中;
  • @Inherited :说明子类可以继承父类中的该注解;
 @Test01         //利用注解Target可以将接口作用在方法上
  public void test02(){
  }

//表示是否将注解生成在JAVAdoc中
@Documented
//子类可以继承父类的注解
@Inherited
//描述注解的使用范围,在方法中,类中使用
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//需要在运行保存该注释信息      (RUNTIME>CLASS>SOURCE)
@Retention(value = RetentionPolicy.RUNTIME)
 @interface Test01{}    //自定义注解,而非接口

注:如果注解只有一个值,建议使用value,value在赋值时可以省略。

自定义注解

自定义注解一般使用@interface来定义。

  • @interface用来声明一个注解,格式为:public @interface 注解名{定义内容}
  • 注解中的每个方法实际上是声明了一个参数
  • 方法名称是参数名称,返回值类型是基本类型的参数
  • 可以使用default来设置参数的默认值,以致于在引用注解时,不用再去赋值(通常用0,“”)
  • 如果注解中的参数只有一个的话,一般使用value,引用时可以省略value
public class Test {
    //注解可以显示赋值,  如果没有设置默认值,就必须给注解赋值
//    @MyAnnotation(name = "vxzx", id = 1, age = 12,school = {"xxxx"})
    @MyAnnotation()     //有默认值,可以省略
    @MyAnnotaion1("VXZX")
    public static void main(String[] args) {
    }
}

//自定义注解@interface
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{	//里面的数据如果有默认值,就可以省略
    //注解的参数:参数类型  + 参数名()
    String name() default "";
    int id() default -1;    //-1不存在
    int age() default 0;
    String[] school() default {"xxxx"};
}
@interface MyAnnotaion1{
    //如果注解只有一个值,建议使用value,value在赋值时可以省略,其他的则不行
    String value();
}

(二)反射

反射(Reflection)时Java被视为动态语言的关键,反射机制运行程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法、注解等。

image-20210428134432210

Java反射机制提供的功能:

  • 运行时判断任意一个对象所属的类;

  • 运行时构造任意一个类的对象;

  • 运行时判断任意一个类所具有的成员变量和方法;

  • 运行时获取泛型信息;

  • 运行时调用任意一个对象的成员变量和方法;

  • 运行时处理注解;

  • 生成动态代理;

Class类

一个Class对象包含了特定的某个结构的有关信息,一个加载的类在JVM中只会有一个Class实例。

Class类的常用方法:

image-20210428135628016

public class ClassP02 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1 = Class.forName("annotation.demo2.User");

        //获取类的名字:
        System.out.println("包名 + 类名:" + c1.getName());       //包名 + 类名
        System.out.println("类名:" + c1.getSimpleName()); //类名

        System.out.println("================");
        //获取类的属性:
        //获取该类中的public属性
//        Field[] fields = c1.getFields();
//        for (Field field : fields) {
//            System.out.println("获取该类中的public属性:" + field);
//        }
        //获取该类中的所有属性:
        Field[] fields1 = c1.getDeclaredFields();
        for (Field field : fields1){
            System.out.println("获取该类中的所有属性:" + field);
        }

        //获得指定属性的值:
        Field address = c1.getDeclaredField("address");
        System.out.println("获得指定属性的值:" + address);

        System.out.println("+++++++++++++++");
        //获得类的方法:
        //获得本类和父类的public方法:
        Method[] methods = c1.getMethods();
        for (Method method : methods){
            System.out.println("本类和父类的public方法:" + method);
        }
        //获得本类的所有方法:
        Method[] declaredMethods = c1.getDeclaredMethods();
        for (Method method1 : declaredMethods){
            System.out.println("本类的所有方法:" + method1);
        }
        //获得指定的方法:
        Method getName = c1.getMethod("getName", null);
        Method setName = c1.getMethod("setName", String.class);
        System.out.println("指定的方法:" + getName);
        System.out.println("指定的方法:" + setName);

        System.out.println("**********************");
        //获得构造器:
        Constructor[] constructors = c1.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        constructors = c1.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("获得所有构造器:" + constructor);
        }
        //获得指定构造器:
        Constructor constructor = c1.getConstructor(String.class, Integer.class, String.class);
        System.out.println("获得指定构造器:" + constructor);
    }
}

获取Class类的实例:

1)若已知具体的类,通过类的class属性获取,该方法最安全,程序性能最高。

		Class c1 = Person.class;

2)已知某个类的实例,调用该实例的getClass()方法获取对象:

		Person person = new Person();
		Class c2 = person.getClass();

3)已知一个类的全名,且该类在类的路径下,可通过Class类的静态方法forName()获取,可能抛出异常:

		Class c3 = Class.forName(包名.类名.类);

4)内置基本数据类型可以直接使用类名.Type

		Class c4 = Integer.TYPE;
        System.out.println(c4);
  //一个类中只能有一个类对象可以获取:
        Class class2 = Class.forName("annotation.demo2.User");
        Class class3 = Class.forName("annotation.demo2.User");
        Class class4 = Class.forName("annotation.demo2.User");
        System.out.println(class2.hashCode());
        System.out.println(class3.hashCode());
        System.out.println(class4.hashCode());
//所打印出来的哈希值都是一样的。

Class可以对以下属性进行使用:

    public static void main(String[] args) {
        Class c1 = Object.class;        //类
        Class c2 = Comparable.class;    //接口
        Class c3 = String[].class;      //数组
        Class c4 = int[][].class;       //二维数组
        Class c5 = Override.class;      //注解
        Class c6 = ElementType.class;   //枚举类型
        Class c7 = Integer.class;       //基本数据类型
        Class c8 = void.class;          //void
        Class c9 = Class.class;         //Class
    }

类的加载与ClassLoader的理解

加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时的数据结构,生成一个class对象。

链接:将Java类的二进制代码合并到JVM的运行状态中的过程。

  • 验证:检查是否符合JVM规范和安全;
  • 准备:为类变量(static)分配内存,设置默认值;
  • 解析:常量池内的符号引用(常量名)替换为直接引用(地址);

初始化:

  • 执行()方法,将类变量和static代码块整合到一起;
  • 初始化类时,如果父类没初始化,则会先初始化父类;
  • 保证类的()方法在多线程环境中被正确加锁和同步;

image-20210504003023781

public class Reflect04 {
    public static void main(String[] args) {
        Testa a = new Testa();
        System.out.println(a.m);	
    }
}
class Testa{
    static {
        int m  = 300;
        System.out.println("静态代码块");
        System.out.println(m);
    }
    static int m = 100;

    public Testa(){
        System.out.println("~~~~~~~");
    }
}
/*输出结果:
静态代码块
300
~~~~~~~
100
*/

JVM在执行程序时,始终第一个先调用static方法块,再依次执行其他。

类的初始化:

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

  • JVM启动时,先初始化main()方法所在的类;
  • new一个类的对象;
  • 调用类的静态成员和静态方法;
  • 使用反射对类进行操作;
  • 初始化一个类,父类没被初始,则会先初始化该父类;

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

  • 访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化;
  • 通过数组定义类引用,不会触发该类的初始化;
  • 引用常量不会触发该类的初始化;(常量已经在常量池中了)
/测试类的初始化:
public class Reflect04_1 {
    static {    //始终第一个被初始化
        System.out.println("main()方法所在的类");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        //主动引用:
        //先加载父类初始化,在进行子类初始化:
//        Son son = new Son();

        //反射的主动引用:
//        Class c1 = Class.forName("annotation.demo2.Son");

        //子类访问父类的静态变量,不会主动引用:子类不会被加载
//        System.out.println(Son.f);
        //通过定义数组,不会引起主动引用:
//        Son[] sons = new Son[5];
        //通过引用常量,不会引起主动引用:
        System.out.println(Son.z);
    }
}
class Father{
    static int f = 100;
    static {
        System.out.println("父类的引用");
    }
}
class Son extends Father{
    static {
        System.out.println("子类的引用");
        int m = 300;
    }
    static int m = 100;
    static final int z = 1;
}

类加载器:

类加载器的作用:将class文件字节码内容加载到内存中,作为方法区中类数据的访问入口。

类缓存:某个类被加载到加载器中,将会维持一段时间。最后可以用JVM垃圾回收这些对象。

类加载器的分类:

  • 引导类加载器(Bootstap Classloader);
  • 扩展类加载器(Extension Classloader);
  • 系统类加载器(System Classloader);

检查类是否已加载:自底向上

尝试加载类:自顶向下

image-20210504002925654

 public static void main(String[] args) throws ClassNotFoundException {
        //获取系统类的加载器:getSystemClassLoader()
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        //获取系统类加载器的父类加载器-->扩展类加载器:
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);
        //获取扩展类加载器的父类加载器-->根加载器:
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);
        //测试当前类时哪个类加载器加载的:
        ClassLoader c1 = Class.forName("annotation.demo2.ClassP01").getClassLoader();
        System.out.println(c1);
        //测试JDK内的类是哪个类加载的:
        c1 = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(c1);
        //如何获得系统类加载器可以加载的路径:
        String property = System.getProperty("java.class.path");
        System.out.println(property);
 }

利用反射创建一个class对象

创建类的对象:

  • 通过Class类的getDeclaredConstructor(Class....parameterTypes)取得本类的指定形参类型的结构器;再用newInstance(指定形参的值)再插入对象的值;
  • 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数;
  • 通过Constructor实例化对象;
public class Reflect05 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class c1 = Class.forName("annotation.demo2.User");

        //构造一个对象:
        User user = (User) c1.newInstance();
        System.out.println(user);

        //通过构造器创建对象:
         User user1 = (User) c1.getDeclaredConstructor(String.class, Integer.class, 								String.class).newInstance("VXZX", 001, "广州");
        System.out.println(user1);
	 }
}

利用反射调用指定的方法

通过反射,调用类中的方法,通过Method类完成。

  • 通过Class类的getMethod(String name,Class....parameterTypes)方法取得一个Method对象,并设置该方法操作时所需要的参数类型。
  • 然后,使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象参数信息。

注意:若原方法声明为private,则需要在调用invoke()之前,显式调用setAccessible(true)方法,才可以访问private方法的数据。

setAccessible:作用是启动和关闭访问安全检查的开关。

  • Method、Field和Constructor对象都有setAccessable()方法
  • true:表示反射对象在使用访问时,会取消Java语言访问检查
  • false:表示反射对象在使用访问时,会开启Java语言访问检查
		//通过反射调用普通方法:
        User user2 = (User) c1.newInstance();
        //通过反射获取一个方法:
        Method setName = c1.getDeclaredMethod("setName", String.class);
        //invoke():激活对象
        //(对象,"方法值")
        setName.invoke(user2, "VXLZX");
        System.out.println(user2.getName());

        //通过反射操作属性:
        User user3 = (User) c1.newInstance();
        //获取的属性不能为private
        Field name = c1.getDeclaredField("name");
        //如果想执行操作,需要关闭权限检测:
        name.setAccessible(true);
        name.set(user3,"User3的name");
        System.out.println(user3.getName());

其中,在性能方面----- 普通方法性能 > 关闭安全检查的反射方法性能 > 反射方法性能

反射操作注解

反射的强大之处就在于,它可以跟注解结合起来一起使用,最大化简化我们的操作。

public class Reflect08 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //通过反射获取注解:
        Class c1 = Class.forName("annotation.demo2.Student2");
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //获取注解value的值:
        Table table = (Table) c1.getAnnotation(Table.class);
        String value = table.value();
        System.out.println(value);

        //获取类指定的注解:(name)
        Field f = c1.getDeclaredField("name");
        Field annotation = f.getAnnotation(Field.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }
}
@Table("db_student")
class Student2{
    private int id;
    @Field(columnName = "db_name", type = "varchar", length = 3)
    private String name;
    private int age;

    public Student2() {
    }

    public Student2(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;
    }

    @Override
    public String toString() {
        return "Student2{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

//类名的注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) //
@interface Table{
    String value();
}

//属性的注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) //
@interface Field{
    String columnName();
    String type();
    int length();
}

注解,反射基本就这些了。



这篇关于Java注解与反射的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程