• 注解和反射


    注解

    一、元注解

    ​ 元注解的作用就是注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型做说明。

    4个元注解分别为:

    • @Target:用于描述注解的使用范围

      • ElementType.TYPE 针对类、接口

      • ElementType.FIELD 针对成员变量

      • ElementType.METHOD 针对成员方法

      • ElementType.PARAMETER 针对方法参数

      • ElementType.CONSTRUCTOR 针对构造器

      • ElementType.PACKAGE 针对包

      • ElementType.ANNOTATION_TYPE 针对注解

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

      • RetentionPolicy.SOURCE 源代码级别,由编译器处理,处理之后就不再保留

      • RetentionPolicy.CLASS 注解信息保留到类对应的 class 文件中

      • RetentionPolicy.RUNTIME 由 JVM 读取,运行时使用

    • @Document:说明该注解将被包含在javadoc(文档)中

    • @Inherited:说明子类可以继承父类中的该注解

    二、自定义注解

    public class Test {
        //如果注解的参数没有默认值,则必须给该参数赋值
        @MyAnnotation(schools = {"北京大学","清华大学"})
        public void test(){
        }
        //如果注解只有一个参数且参数名为value,则value可以省略不写
        @MyAnnotation2("aa")
        public void test2(){
    
        }
    }
    //注解的写法
    @Target({ElementType.TYPE,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyAnnotation{
        //注解的参数:类型+参数名() [default 默认值];
        String name() default "";
        int age() default 0;
        int id() default -1;  //如果默认值为-1,代表不存在
        String[] schools();
    }
    
    @Target({ElementType.TYPE,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyAnnotation2{
        //如果注解只有一个参数,一般参数名取为value
        String[] value();  //不成文的一个规范
    }
    

    反射

    一、反射概述

    ​ Java不是动态语言,但Java可以称为“准动态语言”。因为Java的反射机制可以让Java获得类似动态语言的特性,即运行时代码可以根据一些条件来改变自身的结构。

    ​ 反射机制允许程序在运行期间借助于Reflection API获取任何类的内部信息(比如类名,类的接口,类的方法,字段,属性…),并且能够直接操作任意对象的内部属性及方法。

    ​ 加载完类之后,在堆内存中的方法区中间产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

    反射机制提供的功能:

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

    二、Class对象

    ​ 一个类在内存中只有一个Class对象,不可能有多个。

    ​ 一个类被被加载后,类的整个结构都会被封装在Class对象中。

    ​ Class对象只能由系统创建,我们只负责使用。我们要想使用反射机制,首先得要获得相应的Class对象。

    1. 获得Class对象的方式

    (1)如果已知具体的类,通过类的class属性获取,是最为安全可靠且性能最高的方法。

    Class clz = 类名.class;

    (2)如果已知某个类的对象,调用此对象的getClass()方法获取Class对象。

    Class clz = 对象名.getClass();

    ​ 【注】getSuperClass()方法可以返回当前对象的父类Class对象。

    (3)已知一个类的全限定类名且该类在类路径下,可以通过Class类的静态方法forName()获取,需要处理异常ClassNotFoundException。

    Class clz = Class.forName("类的全限定类名")

    (4)内置基本数据类型可以直接使用类名.TYPE,比如:

    Class clz = Integer.TYPE; //基本内置类型的包装类都有一个 TYPE属性

    (5)还可以用ClassLoader。

    2. 通过Class对象获取运行时类的完整结构

    (1)获取类的名字

    • clz.getName() 获取包名+类名
    • clz.getSimpleName 获取类名

    (2)获取类的属性

    • clz.getFields() 获取本类public属性

    • clz.getDeclaredFields() 可以获取到本类所有属性,包括私有属性

    • clz.getDeclaredField("属性名") 获取指定的属性

    (3)获取类的方法

    • clz.getMethods() 获取本类及其父类的所有public方法

    • clz.getDeclaredMethods() 获取本类的所有方法,包括私有方法,不能获取父类的方法

    • clz.getMethod("方法名", ...方法参数类型.class)

      clz.getDeclaredMethod("方法名", ...方法参数类型.class)

      获取指定方法,若方法无参,则传递null

    (4)获取类的构造方法

    • clz.getConstructors() 获取本类public构造方法
    • clz.getDeclaredConstructors() 获取本类所有构造方法
    • clz.getDeclaredConstructor(...构造方法参数类型.class) 获取指定构造方法

    3. 获取到类的结构信息后能做什么

    (1)通过反射创建类的对象

    ​ 方式1:调用Class对象的newInstance()方法

    ​ 前提:

    ​ 必须要有一个无参构造器;

    ​ 类的构造器的访问权限要够。

    `User user = (User) clz.newInstance(); //本质上调用了类的无参构造器`
    

    ​ 方式2:通过指定的构造器创建

    ​ 首先通过Class对象获取指定的构造器,然后调用Constructor对象的newInstance(...构造方法参数值)方法。

    Constructor constructor = clz.getDeclaredConstructor(String.class, int.class);
    User user = (User) constructor.newInstance("小明", 18);
    

    (2)通过反射调用方法

    User user = (User) clz.newInstance();
    //通过反射获取一个方法
    Method setName = clz.getMethod("setName", String.class);
    
    //invoke: 激活的意思  (对象, "方法需要的参数")
    setName.invoke(user,"小张");
    System.out.println(user.getName());//输出:小张
    

    Object invoke(Object obj, Object ... args)

    • invoke方法的返回值对应原方法的返回值,若原方法无返回值,则返回null。
    • 若原方法为静态方法,此时形参Object obj可为null。
    • 若原方法形参列表为空,则Object ... args为null。
    • 若原方法声明为private,则需要在调用此invoke()方法之前,显示调用Method对象的setAccessible(true)方法,这样才可以访问私有方法。

    (3)通过反射操作属性

    User user= (User) clz.newInstance();
    Field name = clz.getDeclaredField("name");
    
    //不能直接操作私有属性,我们需要关闭程序的安全检查,Field或Method对象调用setAccessible(true)方法
    //setAccessible 默认为false,如果没有关闭将会报没有访问private的权限
    //can not access a member of class com.javacto.reflection.User with modifiers "private"
    name.setAccessible(true);
    name.set(user,"小红"); //修改属性值
    System.out.println(user.getName());//输出:小红
    

    【注】使用反射机制会影响程序的运行效率。关闭安全检查在一定程度上能改善一些反射的效率问题。

    三、通过反射获取泛型信息

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

    ​ 思考该怎么获取泛型信息呢? (之前有说过,类加载的时候就产生了Class对象,故class对象里面应该是有保留泛型信息的)

    public class Test {
    
        /**
         * 通过泛型传参
         * @param map
         * @param list
         */
        public void test01(Map<String,User> map, List <User> list){
            System.out.println("test01");
        }
    
        /**
         * 通过泛型返回值
         * @return
         */
        public Map<String,User> test02(){
            System.out.println("test02");
            return null;
        }
    
    
        public static void main(String[] args) throws NoSuchMethodException {
            Method method = Test.class.getMethod("test01", Map.class, List.class);
            //获取参数类型  即Map 和 List
            Type[] genericParameterTypes = method.getGenericParameterTypes();
            for (Type genericParameterType : genericParameterTypes) {
                System.out.println("1:"+genericParameterType);
    
                //判断genericParameterType参数类型 是否属于 ParameterizedType 参数化类型
                if (genericParameterType instanceof ParameterizedType){
                    //如果属于参数化类型,获得他的真实类型 getActualTypeArguments
                    Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
    
                    //再次输出真实的泛型信息
                    for (Type actualTypeArgument : actualTypeArguments) {
                        System.out.println("2:"+actualTypeArgument);
                    }
                }
            }
            
            //获得返回值类型
            method = Test.class.getMethod("test02",null);
            Type genericReturnType = method.getGenericReturnType();
            if (genericReturnType instanceof ParameterizedType){
                //如果genericReturnType返回值类型属于参数化类型,获得他的真实类型 getActualTypeArguments
                Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                
                //再次输出真实的泛型信息
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("3:"+actualTypeArgument);
                }
            }
        }
    }
    

    四、通过反射获取注解信息

    public class Test {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
            Class clz = Class.forName("com.javacto.reflection.Student");
    
            //通过反射获取注解
            Annotation[] annotations = c1.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
    
            //获得注解的value的值   获取指定注解值
            MyTable myTable =(MyTable) c1.getAnnotation(MyTable.class);
            String value = myTable.value();
            System.out.println(value);
    
            //获得类指定的注解
            System.out.println("=====获得类指定的注解======");
            Field field= clz.getDeclaredField("name");
            MyField annotation = field.getAnnotation(MyField.class);
            System.out.println(annotation.columnName());
            System.out.println(annotation.type());
            System.out.println(annotation.length());
        }
    
    }
    
    @MyTable("db_students")
    class Student{
        @MyField(columnName = "db_id",type = "int",length = 10)
        private int id;
        @MyField(columnName = "db_age",type = "int",length = 10)
        private int age;
        @MyField(columnName = "db_name",type = "varchar",length = 50)
        private String name;
    
        public Student() {
        }
    
        public Student(int id, int age, String name) {
            this.id = id;
            this.age = age;
            this.name = name;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "id=" + id +
                    ", age=" + age +
                    ", name=" + name +
                    '}';
        }
    }
    
    //类名的注解
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyTable{
        String value();
    }
    
    //属性的注解
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyField{
        String columnName(); //列名
        String type(); //类型
        int length(); //长度
    }
    
  • 相关阅读:
    Eclipse的常见使用错误及编译错误
    Android学习笔记之Bundle
    Android牟利之道(二)广告平台的介绍
    Perl dbmopen()函数
    Perl子例程(函数)
    Perl内置操作符
    Perl正则表达式
    Linux之间配置SSH互信(SSH免密码登录)
    思科路由器NAT配置详解(转)
    Windows下查看端口被程序占用的方法
  • 原文地址:https://www.cnblogs.com/jiajun107/p/15063213.html
Copyright © 2020-2023  润新知