• java


    反射的笔记:看注解之前必须掌握反射

    https://www.cnblogs.com/clamp7724/p/11655237.html

    https://www.cnblogs.com/clamp7724/p/11658557.html

    注解:

    注解的作用:

    1.作为注释使用   只是提示,没有实际意义

    2.校验                 提示代码错误,比如@override会校验下面的方法是不是正确重写了父类方法,如果有错会在编译前显示出来

    3.携带一些信息   作为容器携带信息,类似变量?

    注解的使用:

    package AnnotationTest;
    
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class AnnotationClass {
        //注解的使用格式:
        //@注解
    
        //注解的位置:
        //类,构造方法,方法,属性的上面
    
        //注解的作用:
        //1 作为注释使用
        //2 校验
        //比如下面Override就表示这个是重写的方法,如果方法格式不对,会编译错误(有些编译器里Override下面会有红线),让我们知道不符合重写规范。
        @Override
        public String toString() {
            return super.toString();
        }
    
        //3.携带一些信息
        @SuppressWarnings({"unused","serial"})
        String s = "aaa";
        //SuppressWarning里面加String[],可以用来消除一些警告,不建议使用,因为警告一般都是表示有编译问题
        // 比如加unused表示下面的变量没有被使用
        // serial 继承Serializable版本不添加序列号
        //deprecation  方法过时
        // uncheck  不要检查  泛型问题不检测 (偶尔会用)
        // all  不警告所有问题 (最好别用)
    
    
        //自己的注解的使用
    //注解结构: 具体看注解类定义中的注释
    //@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) //能在属性,方法,构造方法前使用,还有其他元素,用ElementType.获取 //@Retention(RetentionPolicy.RUNTIME) //运行时使用,还有源代码时,编译时,用RetentionPolicy.获取 //@Inherited //注解能被继承 //@Documented //能被变为文档, 不常用 //public @interface MyAnnotation { //结构类似interface(接口) // public static final String s = "aaa"; // public abstract String test(); //} //如果自定义的注解中有方法,使用时需要给方法传值。 方法名 = 返回值类型的值 //注解作为容器把传的值输出给别人,方法需要自己赋值,属性定义注解时已经赋值了 //如果只有一个方法,方法名叫value可以省略方法名直接写值:比如java自带的注解SuppressWarnings,里面只有一个方法 String[] value(); @MyAnnotation(testInt = 1, testString = "aaa", testStringArray = {"aaa","bbb","ccc"}) private String str; public static void main(String[] args){ try { //注解的应用: 利用反射 Class c1 = AnnotationClass.class; //获取类 Field f1 = c1.getDeclaredField("str"); //根据属性名字获取有注解的属性 //获取注解,解析内容 MyAnnotation ma1 = f1.getAnnotation(MyAnnotation.class); int value1_1 = ma1.testInt(); String value1_2 = ma1.testString(); String[] value1_3 = ma1.testStringArray(); System.out.println("value1 = " + value1_1 + "value2 = " + value1_2 + "value3[0] = " + value1_3[0] ); //value1 = 1value2 = aaavalue3[0] = aaa //感觉作用像全局变量。。。 //完全利用反射进行解析内容 Class c2 = AnnotationClass.class; //获取类 Field f2 = c2.getDeclaredField("str"); //根据属性名字获取有注解的属性 Annotation ma2 = f2.getAnnotation(MyAnnotation.class); Class clazz2 = ma2.getClass(); Method m2_1 = clazz2.getDeclaredMethod("testInt"); //一般为了方便使用,注解一般内部都是只有一个String[] value 方法,这样这里就可以统一处理了 int value2_1 = (int) m2_1.invoke(ma2); Method m2_2 = clazz2.getDeclaredMethod("testString"); String value2_2 = (String) m2_2.invoke(ma2); Method m2_3 = clazz2.getDeclaredMethod("testStringArray"); String[] value2_3 = (String[]) m2_3.invoke(ma2); System.out.println("value1 = " + value2_1 + "value2 = " + value2_2 + "value3[0] = " + value2_3[0] ); //value1 = 1value2 = aaavalue3[0] = aaa } catch (Exception e) { e.printStackTrace(); } } }

    自定义的一个注解

    package AnnotationTest;
    
    import java.lang.annotation.*;
    
    //自定义注解:
    //注解中只能包含如下类型信息:
    //基本类型
    //String
    //枚举 enum
    //注解  @
    //以上4种类型的数组 []
    
    //自定义注解使用 @interface, 需要添加元注解(java自带注解,用来说明注解)
    //元注解Target,说明当前注解可以使用的位置,里面是个enum
    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})  //属性,方法,构造方法
    
    //元注解Retention,描述当前注解存在于什么作用域中
    // 源文件.java   --编译--     字节码文件.class    ---执行--    内存
    // 源文件:注解只用来作为注释        SOURCE
    // 字节码文件:编译时使用,可以检测  CLASS
    // 内存:用来执行                    RUNTIME
    @Retention(RetentionPolicy.RUNTIME)   //运行时使用
    
    @Inherited //注解能被继承
    
    @Documented  //能被变为文档, 不常用
    
    //注解内部结构与interface基本一致
    public @interface MyAnnotation {
    
        //可以描述public static final的属性,不写修饰符也可以,默认为public static final
        public static final String s = "aaa";
    
        //方法类型为 public abstract, 没有方法体,public abstract可以省略
        //必须有返回值(interface中的方法可以用void,注解不行)
        //注解的方法主要为了动态传递信息
        public abstract int testInt();
    
        public abstract String testString();
    
        public abstract String[] testStringArray();
    }

    //注解的应用和优点,看一个例子:看不懂看我之前写的反射的笔记。。。

    模拟一个场景,两个公司合作开发项目,A写底层,B用A的底层生成对象来使用

    客户突然有一天说要加属性,A把底层Class改了 (javaBean),属性个数不同了,构造方法也要变了。

    按传统情况,B就不得不把所有上层中用到这种class的地方全改一遍!万一这个工程非常大,上百个class,工作量将会很可怕,而且容易遗漏。

    这时候就要用到Spring的思想:IOC控制反转(A的创建交给别人,不用new 构造方法的方式创建), Di依赖注入(不是用new 对象,然后调用方法的方式赋值),这样A的class的结构改变了也不会影响到B

    大幅降低耦合度,让A修改class对B的影响降到最低。

    写一个注解用来存值传值

    package AnnotationTest;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.CONSTRUCTOR})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AnAnnotation {
        String[] value();
    }

    A的底层类:  这里用注解@的方法存值,这样可以方便开发的时候修改,测试一类的。

    package AnnotationTest;
    
    
    
    public class ObjectTest {
    
        private String name;
        private Integer age;
        private String sex;
    
        @AnAnnotation({"一个名字","24", "男"})
        public ObjectTest(){
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    }

    B用来生成A所写class的对象的方法 

    package AnnotationTest;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Member;
    import java.lang.reflect.Method;
    import java.text.Format;
    
    public class AnnotationTest {
        public static void main(String[] args){
            AnnotationTest at = new AnnotationTest();
            Object obj = at.createObject("AnnotationTest.ObjectTest");
            ObjectTest objectTest = (ObjectTest)obj;//泛型
            System.out.println("名字为:" + objectTest.getName());
            System.out.println("年龄为:" + objectTest.getAge());
            System.out.println("性别为:" + objectTest.getSex());
    
    
            //那么问题来了。。。这么干的好处是什么呢!
            //可以试着把ObjectTest的Class类修改一下,比如把性别sex和相关的getSex,setSex方法去掉,修改注解@,下面这个生成对象的方法依旧可以使用,不用修改任何代码。
            //如果在项目中,上百个class互相之间相互调用,一旦一个class发生改变(比如客户要求添加数据库新字段),传统方法就不得不把所有用到这个类的对象的地方全重新改一遍,
            // 而用反射就可以只改这个class而不用改其他地方了。
            //这样可以减少类之间的耦合度,ObjectTest修改对AnnotationTest的影响大幅降低。
        }
    
        //ioc
        //写一个方法,根据class名字利用构造方法上面的注解传值,直接生成对象
        public Object createObject(String objectClassName){
            Object obj = null; //用于返回生成的对象
            try {
                Class c = Class.forName(objectClassName);//获得生成对象的class
                Constructor constructor = c.getConstructor();//获得构造函数
    
                AnAnnotation annotation = (AnAnnotation)constructor.getAnnotation(AnAnnotation.class);//获取注解内的值 -> object的属性值
                String[] values =  annotation.value();
    
                obj = constructor.newInstance(); //利用无参数构造方法生成object对象
                Field[] fields = c.getDeclaredFields(); //获得class的所有属性
                //给属性赋值
                for(int i = 0; i < fields.length; i++){
                    System.out.println("--------------开始处理第" + i +"个属性---------------");
                    Class fieldType = fields[i].getType();//获取属性类型
    
                    //获取set方法名 set + 属性名首字母大写
                    String firstFieldName = fields[i].getName().substring(0,1).toUpperCase();
                    String lastFieldName = fields[i].getName().substring(1);
                    StringBuilder setMethodName = new StringBuilder("set");
                    setMethodName.append(firstFieldName);
                    setMethodName.append(lastFieldName);
    
                    System.out.println("获得set方法:" + setMethodName);
    
                    Method setMethod = c.getMethod(setMethodName.toString(), fieldType); //用set方法名和属性类型获得set方法
    
                    Constructor constructorField = fieldType.getConstructor(String.class); //获取set方法参数的类型的String为参数的构造函数,因为values[]中值都为String
    
                    setMethod.invoke(obj,constructorField.newInstance(values[i])); //运行set方法,把values[]中对应的值传给obj中对应的方法,比如Integer类型的,就用 Integer("24")来把String变成Integer
                    System.out.println("第一个属性赋值:" + fields[i].getName() + " = " + values[i] );
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            System.out.println("---------------Object赋值结束------------------");
            return obj; //把创建赋值完毕的object返回
        }
    }

    输出结果:

    --------------开始处理第0个属性---------------
    获得set方法:setName
    第一个属性赋值:name = 一个名字
    --------------开始处理第1个属性---------------
    获得set方法:setAge
    第一个属性赋值:age = 24
    --------------开始处理第2个属性---------------
    获得set方法:setSex
    第一个属性赋值:sex = 男
    ---------------Object赋值结束------------------
    名字为:一个名字
    年龄为:24
    性别为:男

    想体会一下反射的好处,可以修改一下ObjectTest类

  • 相关阅读:
    超酷的元素周期表
    TestLink在线Excel用例转换xml
    我也学习JAVA多线程-join
    request.getSession(true/false)的区别
    nginx location配置详细解释
    RestTemplate--解决中文乱码
    扇贝-每日一句
    Hexo博客系列(三)-将Hexo v3.x个人博客发布到GitLab Pages
    C程序的内存分区(节选自黑马训练营day1)
    CodeBlocks更换界面主题界面、汉化及去掉注释及字符串的下划线(汉化包的链接来自本站的BeatificDevin大神)
  • 原文地址:https://www.cnblogs.com/clamp7724/p/11667845.html
Copyright © 2020-2023  润新知