Java反射与注解_java 反向判断父类注解-程序员宅基地

技术标签: Java  注解  反射  

反射

在Java运行中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。这种动态获取信息以及调用对象方法的功能称为java语言的反射机制

1、通过Class获取类的信息

Java中认为万物皆对象,例如我们定义一个用户类User,然后通过它来实例化一个对象u1。其实User类本身也可以看作一个对象,作为java.lang.Class类的实例。u1可以通过如下三种方式获取类User的信息:

        //通过类名的静态成员属性class
        Class c1=User.class;
        //通过对象方法getClass()
        User u1=new User();
        Class c2=u1.getClass();
        //通过静态加载类
        Class c3=null;
        try {
            c3=Class.forName("modules.User");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //以上获取的三个类均为Student
        System.out.println(c1 == c2);        //输出true
        System.out.println(c2 == c3);        //输出true
        //通过c1创建User对象
        try {
            User u2=(User)c1.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

Class类提供了许多获取类的名称、成员变量、方法返回值、参数等类信息的方法。可以通过getClass()可以获取类,进而通过getXxx()获取类的属性,例如通过getField()获取成员变量,getMethods()获取方法,进一步获得这些属性的详细信息

    public static void printClassMessage(Object obj) {
        //获取对象类的类型
        Class c = obj.getClass();
        //打印类的名称
        System.out.println("类名称:" + c.getName());

        //打印类成员变量的信息
        System.out.println("类包含的成员变量如下:");
//        Field[] variables=c.getFields();        //获取所有public变量
        Field[] variables = c.getDeclaredFields();        //获取所有变量
        for (Field var : variables) {
            Class varType = var.getType();        //得到成员变量的类型
            String typeName = varType.getName();
            String varName = var.getName();       //获取成员变量名
            System.out.println(typeName + " " + varName);
        }

        //打印类构造函数信息
        System.out.println("类构造方法如下:");
        Constructor[] constructors = c.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.print(constructor.getName() + " (");        //构造方法名
            Class[] params = constructor.getParameterTypes();     //获取构造方法参数的类型
            for (Class param : params)
                System.out.print(param.getName() + ",");
            System.out.println(")");
        }

        //打印类方法的信息
        System.out.println("类包含的方法如下:");
        Method[] methods = c.getMethods();          //获取类的所有public方法,包括从父类继承的
//        Method[] methods=c.getDeclaredMethods();        //获取类自定义的所有方法,不包括父类
        for (Method method : methods) {
            //获取方法返回值类型
            Class returnType = method.getReturnType();
            System.out.print(returnType.getName() + " ");
            //获取方法名
            System.out.print(method.getName() + " (");
            //获取方法的所有参数的类型
            Class[] parameterTypes = method.getParameterTypes();
            for (Class parameter : parameterTypes) {
                System.out.print(parameter.getName() + ",");
            }
            System.out.println(")");
        }
    }



//-----------------测试打印int类的信息----------    
        int i=1;
        printClassMessage(i);

//---------------输出结果--------------------
类名称:java.lang.Integer
类包含的成员变量如下:
int MIN_VALUE
int MAX_VALUE
java.lang.Class TYPE
......
类构造方法如下:
java.lang.Integer (int,)
java.lang.Integer (java.lang.String,)
类包含的方法如下:
int numberOfLeadingZeros (int,)
int numberOfTrailingZeros (int,)
int bitCount (int,)
......

通过Class.forName()可以动态加载类,所谓动态加载即在运行时只加载需要的类。与之相对的是静态加载,一般Java的.class文件都是在编译阶段静态加载完成,即编译所有与之相关的文件,如果其中有一处错误则编译就无法通过。

例如定义两个类ClassA、ClassB均实现了CommonClass接口中的run()方法,通过动态加载,传入不同的类名,可以分别创建不同的类并执行run()方法,实现相同的代码创建不同的类对象。

//定义公共接口
public interface CommonClass {
    public void run();
}

//定义A类
public class ClassA implements CommonClass {
    @Override
    public void run() {
        System.out.println("这是A类对象");
    }
}

//定义B类
public class ClassB implements CommonClass {
    @Override
    public void run() {
        System.out.println("这是B类对象");
    }
}

//在主程序中调用
        try {
            Class o1=Class.forName("modules.ClassA");        //动态加载ClassA          
            CommonClass c=(CommonClass) o1.newInstance();
            c.run();                                    //输出这是A类对象
           
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

2、反射操作

在Java中一般通过对象调用方法,即obj.method(params...),而反射操作是指通过方法来操作对象method.invoke(obj,params...),从而实现在运行中获取类的信息,达到动态编码的效果。首先通过getClass获取对象的类,然后再通过getMethod("method",paramsType...)获取具体方法,method为方法名,paramsType为方法参数类型,这样通过方法名和参数可以唯一确定某个特定方法。之后就可以通过invoke调用方法了,如果函数有返回值则会返回Object对象,需要手动强制转换为所需类型。

例如定义类ClassA,通过反射调用其对象的add方法

public class ClassA {

    public int add(int a, int b) {
        return a + b;
    }
}

        ClassA a=new ClassA();
        Class c=a.getClass();       //获取类
        try {
            Method m=c.getMethod("add", int.class, int.class);  //获取指定类方法add
            Object o=m.invoke(a,10,20);             //通过反射调用对象a的add方法
            int res=(Integer)o;                 //返回结果为Object,需要类型转换
            System.out.println("结果为:"+res);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

通过反射可以绕过编译,在运行阶段调用对象的方法。例如在使用ArrayList时可以规定泛型Integer指明这个list只能存放整型数据,但是这个规定只在编译阶段进行检查,如果有其他类型数据会报错。但是可以通过反射调用list的add()方法在运行阶段添加其他类型数据,最后输出list的内容显示有int数据10和String字符串

        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(10);

        Class c = list.getClass();
        try {
            Method m = c.getMethod("add", Object.class);
            m.invoke(list, "字符串");        //通过反射调用add添加String类型的数据
            System.out.println(list);               //输出:[10, 字符串]
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

注解

Java注解提供了用于元素关联信息和数据的途径,广泛应用于各种框架,使代码更加简洁清晰。

JDK提供了三种自带注解@Override、@Deprecated、@SuppressWarnings。@Override用于在子类重写父类或实现接口的方法,如果被添加@Override的方法在父类中没有对应的方法名,则会报错。如果父类中的某个方法不再使用,但是无法删除,因为子类可能仍然使用这个方法,这时可以在父类的方法上添加@Deprecated,代表这个方法被弃用,子类在调用该方法会显示中划线的警告。但是如果子类坚持要使用该方法并且确保可用时,可以在子类方法上添加@SuppressWarnings("deprecation")忽略该警告。

按照运行机制注解可以分为以下三类:

  1. 源码注解:只在源码中存在,编译为.class后消失
  2. 编译时注解:在.class文件中依然存在,在编译时起作用,例如JDK的三个注解
  3. 运行时注解:在运行阶段仍然起作用,影响运行结果

自定义注解

通过关键字@interface来定义一个注解,其成员变量以无参数无异常的方式声明,并且可以通过default指定默认值,注意成员类型只能是基本类型(如int)和String、Class、Annotation、Enumeration,之后在使用时通过"="为这些变量赋值。如果只有一个成员变量,则将其声明为value,并且传入参数自动为其赋值而不必使用=。注解如果没有成员被称为标识注解。

元注解用于在自定义注解时对注解进行设置。

  1. @Target,定义注解作用的目标,可以作用域构造函数(constructor)、字段声明(Field)、参数声明(Parameter)、局部变量(Local Variable)、方法(Method)、类(Type)、包(Package)
  2. @Retention定义注解的生命周期,分别为源码注解(Source)、编译注解(Class)、运行时注解(Runtime)
  3. @Inherited,标识注解,代表父类的注解可以被子类继承
  4. @Documented,标识注解,代表注解会被添加到javadoc

通过类或者方法的getAnnotation()可以在程序运行的时候获取注解中的参数信息,从而实现动态编程

如下所示为自定义一个注解用于User类,并在运行时获取注解中的信息:

@Target({ElementType.METHOD,ElementType.TYPE})  //注解作用于方法和类
@Retention(RetentionPolicy.RUNTIME)             //运行时注解
@Inherited                                      //可被继承
@Documented                                     //生成javadoc会包含
public @interface FirstAnnotation {
    String description();
    int value() default 0;
}



@FirstAnnotation(description = "这是User类注解", value = 8)
public class User {
    private String username;

    public User() {
    }

    @FirstAnnotation(description = "这是show()方法注解")
    public void show(){
        System.out.println("这是一个User对象");
    }

    public static void main(String[] args) {
        User u=new User();
        Class c=u.getClass();
        boolean isExist=c.isAnnotationPresent(FirstAnnotation.class);   //判断注解是否存在
        if (isExist){
            //拿到类的注解
            FirstAnnotation annotation=(FirstAnnotation) c.getAnnotation(FirstAnnotation.class);
            //输出类注解的属性description的值:这是User类注解
            System.out.println(annotation.description());
        }

        Method[] methods=c.getMethods();
        for (Method m:methods){
            boolean mExist=m.isAnnotationPresent(FirstAnnotation.class);
            if (mExist){
                //获取方法的注解
                FirstAnnotation mAnnotation=(FirstAnnotation) m.getAnnotation(FirstAnnotation.class);
                //输出方法注解的属性description的值:这是show()方法注解
                System.out.println(mAnnotation.description());
            }
        }
    }
}

3、一个例子

例如通过反射和注解查询数据库不同的表与字段的值。定义两个注解@Table和@Column,在其中的value分别保存一个类在数据库中对应的表名和字段值。

例如Student类有三个变量id、name、age分别对应数据库中student表的id、Name、Age字段,则将表名和字段名保存在注解当中。之后在程序运行时,创建了student对象,从其中取出student变量值和注解中对应的字段名,即可拼接成一个查询语句,进而查询数据库获取对象信息,例如对象student对应表注解Table为student,变量id为1001,其注解Column为id,age为23,其注解Column为Age,拼接成查询语句为:SELECT * FROM student WHERE 1=1 AND id = 1001 AND Age = 23。

通过使用注解,当查询其他对象时,主要为其添加不同注解即可得到相同的结果,而不用重新写一个查询方法。例如有一个新的User对象,对应数据库user表,字段分别为username、password,则只需要在创建User类时添加@Table("user"),并为其变量添加注解@Column("username"),@Column("password"),即可。

//定义Table注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}

//定义Column注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String value();
}

//------------------在Student类定义时使用注解-------------
@Table("student")
public class Student {
    @Column("id")
    private int id;

    @Column("Name")
    private String name;

    @Column("Age")
    private int 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;
    }
}


//------------在主函数调用执行查询----------------
    public static void main(String[] args) {
        Student student = new Student();
        student.setId(1001);
        student.setAge(23);
        queryTable(student);
    }

    //定义查询方法,通过不同的传入对象拼接查询语句
    public static void queryTable(Student student) {
        StringBuffer sql = new StringBuffer();
        sql.append("SELECT * FROM ");
        Class c = student.getClass();

        //通过Table注解获得要查询的表名
        Table t = (Table) c.getAnnotation(Table.class);
        String tableName = t.value();
        sql.append(tableName).append(" WHERE 1=1");

        //遍历类所有字段获取Column注解的内容并拼接成查询语句
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            Column column = field.getAnnotation(Column.class);
            String columnName = column.value();           //从Column获取查询的字段
            //拼接字段的get方法,例如要获取name值,则将其首字母大写并在前面加上get,即getName
            String fieldName = field.getName();
            String methodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
            Object fieldValue = null;
            try {
                //调用字段的get方法获取该字段的值
                Method getMethod = c.getMethod(methodName);
                fieldValue = getMethod.invoke(student);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (fieldValue != null)
                sql.append(" AND ").append(columnName).append(" = ").append(fieldValue);
        }
        System.out.println(sql);
    }

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/theVicTory/article/details/104426942

智能推荐

python实现在 Mac 10.9 远程桌面截屏抓取_mac 监控截屏-程序员宅基地

文章浏览阅读2k次。照样画葫芦,用python编写了一段小程序,可以使用ipad的web浏览器远程监控远端iMac主机界面(每秒截屏,非流控),与大家分享。1. 首先介绍一下需要下载的第三方工具:Flask,PyscreenshotFlask用来做web服务器,Pyscreenshot是用来截屏的。用pip install 分别安装即可2. 介绍程序文件架构如下,需要simplesvr_mac 监控截屏

centos7安装后一直出现pcieport 0000:00:1c.5的解决_dpc:error containment capabilities-程序员宅基地

文章浏览阅读4.7k次,点赞3次,收藏7次。安装完centos7后进入时一直不停出现pcieport 0000:00:1c.5字样,这个的具体原因尚不完全清楚,解决方法查到的都是一种,就是在/etc/default/grub中的GRUB_CMDLINE_LINUX的内容最后添加pci=nomsi或者pci=noaer或者pcie_aspm=off,这样的确可以,但是更新的步骤需要grub2-mkconfig -o /boot/efi/EFI..._dpc:error containment capabilities

目前看到的最好的RNN、LSTM、GRU博客:Understanding LSTM Networks_humans don鈥檛 start their thinking from scratch eve-程序员宅基地

文章浏览阅读735次。原文:http://colah.github.io/posts/2015-08-Understanding-LSTMs/Recurrent Neural NetworksHumans don’t start their thinking from scratch every second. As you read this essay, yo_humans don鈥檛 start their thinking from scratch every second.

maven/conf/settings.xml完整配置(3处)_apache-maven-3.9.2\conf\settings.xml-程序员宅基地

文章浏览阅读1.4k次。<?xml version="1.0" encoding="UTF-8"?><!--Licensed to the Apache Software Foundation (ASF) under oneor more contributor license agreements. See the NOTICE filedistributed with this work for additional informationregarding copyright ownersh._apache-maven-3.9.2\conf\settings.xml

基于Python的逆向工程:ELF文件_逆向工程 python-程序员宅基地

文章浏览阅读6.4k次。当解决复杂的逆向问题时,我们常使用radare2或IDA等成熟工具进行反汇编和调试。但有时也需要深入挖掘并了解它们是如何运作的。编写一些反汇编脚本对于自动化某些流程非常有用,并且可以形成自己的逆向工具链。至少,这是我现在正在尝试的事情。配置环境如标题所说的那样,你需要先安装Python 3。如果你无法确定是否安装了Python 3,可以运行如下命令:其中capstone是..._逆向工程 python

四种方法实现:找出数组中两个只出现一次的数字_一个数组中找出出现一次的2个数字-程序员宅基地

文章浏览阅读2.6k次。//先排序然后查找void FindNumsAppearOnce1(vector&lt;int&gt; data, int* num1, int *num2) { if (data.size() &lt; 2) return; sort(data.begin(), data.end()); vector&lt;int&gt; res; for (int i = 0; i &lt;..._一个数组中找出出现一次的2个数字

随便推点

开箱即用的 WebRTC 开发环境_xujianzhu webrtc开箱即用-程序员宅基地

文章浏览阅读333次。本文是 Piasy 原创,发表于 https://blog.piasy.com,请阅读原文支持原创 https://blog.piasy.com/2017/06/17/out-of-the-box-webrtc-dev-env/在刚刚落幕的 WWDC17 上,苹果为我们带来了一个不小的惊喜 —— 其浏览器内核WebKit将正式支持 WebRTC,而未来基于 WebKit 内核的苹果浏览器,比如m..._xujianzhu webrtc开箱即用

从ResNet101到ResNet50_resnet50 使用什么代替-程序员宅基地

文章浏览阅读3.3w次,点赞5次,收藏21次。一直用VGG训练,几天前想看下ResNet的效果如何,因为SSD源码中有python实现的ResNet网络结构实现代码,包含ResNet101和ResNet152,直接拿ResNet101来训练,GTX1060配置,batchsize竟然只降到2才跑的起来,果然一直收敛不了。看了下model_libs.py里面的实现代码:def ResNet101Body(net, from_layer, u_resnet50 使用什么代替

vivado ILA在线逻辑仪使用_vivado ila 下一触发沿-程序员宅基地

文章浏览阅读1.1w次,点赞12次,收藏131次。目录:1、在线逻辑分析仪简介2、HDL 实例化调试探针流程(实验-闪烁灯)3、Hardware Manager中观察调试信号4、网表插入调试探针流程(实验-闪烁灯)1、在线逻辑分析仪简介在线逻辑分析仪借用了传统逻辑分析仪的理念以及大部分的功能,并利用 FPGA 中的逻辑资源,将这些功能植入到 FPGA 的设计当中。一般地,在线逻辑分析仪的应用原理框图如下图所示:​ 待测设计(Design Under Test,DUT)就是用户逻辑,它和片内的在线逻辑分析仪都位于 FPGA中。在线逻辑分_vivado ila 下一触发沿

数据库索引的使用_db2数据库索引的使用-程序员宅基地

文章浏览阅读3.5k次。今天发现一个问题,问题大概是这样的,查询interface的信息,在本地使用本地的数据库访问没有问题,但是发布到服务器上以后访问速度就特别的忙,需要5分钟左右才能返回数据,这肯定是无法让人接受的,刚开始以为是服务器性能的问题,为了验证就把服务器上的数据库备份到本地,发现本地的速度也马上慢了下来,到底是什么问题的。看了一下查询interface的sql语句不禁吓了一跳: _db2数据库索引的使用

win7下mysql的安装_[root@gaojiao ~]# mysql -uroot error 1045 (28000):-程序员宅基地

文章浏览阅读3.1k次。一 , 当前mysql的最新版本是5.5.25a。到http://dev.mysql.com/downloads/mysql/下载mysql安装文件 。我们这里下载mysql-5.5.25a-win32.msi就可以了,下载完,直接点击安装。mysql有好几个版本,稍微了解下各个版本之间的区别:  MySQL Community Server :社区版本 不提供官方技术支持,是免费的_[root@gaojiao ~]# mysql -uroot error 1045 (28000): access denied for user 'r

PHP微信公众平台开发高级篇--群发接口_微信公众号根据标签群发接口支持数组传参吗-程序员宅基地

文章浏览阅读2.3k次。群发消息接口订阅号:每天一条的群发权限服务号:每月(自然月)4条群发权限实例&lt;?php/** * 群发接口 * PS:群发之前调用“预览接口”进行测试 * PS:通过第三方后台调用微信上传图片素材接口,获取图片url,如:{"url":"http:\/\/mmbiz.qpic.cn\/mmbiz_jpg\/BdxWN2kspVgJOFpRHJojlWmbl0pM..._微信公众号根据标签群发接口支持数组传参吗