zoukankan      html  css  js  c++  java
  • Java基础--注解、反射

    一、注解(Annotation)

    1、什么是注解?

      从JDK5开始,Java增加了Annotation(注解),Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。

    2、Annotation与注释的区别:

      (1)Annotation不是程序本身,可以对程序进行解释,此处可以理解为注释。但是Annotation可以被其他程序(比如编译器)读取,并进行处理。
      (2)注解与注释最大的区别就是注解存在被处理的流程,即注解是会被程序处理的。

    3、注解的格式:

      (1)以 “@注释名” 的形式在代码中存在。
      (2)注解可以附加在程序元素( 包、类、构造器、方法、成员变量、参数、局域变量 )上面,为其添加额外的辅助信息,可以通过反射机制访问这些数据。
      (3)Annotation不能运行,其只有成员变量,没有方法。Annotation与public、final等修饰符地位类似,属于程序元素的一部分,但不能作为程序元素使用。

    4、常见注解:

      (1)@Override
        定义在java.lang.Override中,此注释只用于修饰方法,表示重写一个父类的方法。

    【举例:】
     @Override
     public String toString() {
         return "Hello";
    }

      (2)@Deprecated

        定义在java.land.Deprecated中,此注释可用于修饰方法、属性、类,表示该方法、类、属性不推荐使用(废弃)。在方法、类、属性上会有一条删除线(形如toString())。

    【举例:】
    @Deprecated
    public String toString() {
        return "TimerTaskDemo []";
    }

      (3)@SuppressWarnings
        定义在java.lang.SuppressWarnings中,用来阻止编译时的警告信息。其使用时需要设置参数。

    【参数为:】
    deprecation,使用了过时的类或方法的警告。
    unchecked,执行了未检查的转换时的异常,比如集合未指定泛型。
    fallthrough,当在switch语句发生case穿透时的警告。
    path,当类路径、源文件路径不存在时的警告。
    serial,可序列化类缺少serialVersionUID时的警告。
    finally,任何finally不能完成时的警告。
    all,以上所有警告。
    
    【格式:】
    @SuppressWarnings("all")
    或者
    @SuppressWarnings(value = { "serial", "unchecked" })
    
    注意:
      @SuppressWarnings("resource")
    
      对于J2EE,可以使用@Resource来完成依赖注入或者叫资源注入,但是当你在一个类中使用已经使用注解的类,却没有为其注入依赖,代码会提示相关信息,可以使用@SuppressWarnings("resource")来取消提示信息。

    5、元注解:

      (1)元注解的作用就是负责注解其他的注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。存在于java.lang.annotation中。

      (2)元注解分类:
        @Target
        @Retention
        @Documented
        @Inherited

      (3)@Target元注解:
        用于描述注解的使用范围。Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

    【格式:】
    public @interface Target
    {
      ElementType[] value();
    }
    
    【参数:ElementType】
    CONSTRUCTOR:用于描述构造器
    FIELD:用于描述成员变量
    LOCAL_VARIABLE:用于描述局部变量
    METHOD:用于描述方法
    PACKAGE:用于描述包
    PARAMETER:用于描述参数
    TYPE:用于描述类、接口(包括注解类型) 或enum声明
    
    【举例:】
    @Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.LOCAL_VARIABLE})

      (4)@Retention元注解:
        用于描述注解的声明周期。某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

    【格式:】
    public @interface Retention
    {
      RetentionPolicy value();
    }
    
    【参数:RetentionPoicy】
    SOURCE:在源文件中有效(即源文件保留)
    CLASS:在class文件中有效(即class保留)
    RUNTIME:在运行时有效(即运行时保留,可以通过反射机制读取)
    
    【举例:】
    @Retention(RetentionPolicy.SOURCE)

      (5)@Documented元注解:
        用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

      (6)@Inherited元注解:
        @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
    注意:
        @Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
        当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

    6、自定义注解:

      (1)自定义注解时,需要使用@interface用来声明一个注解,其会自动继承java.lang.annotation.Annotation接口。

    【格式:】
    public @interface 注解名 {定义体}
    【或者:】
    public @interface 注解名 {
      类型 value() default 默认值;   //这里是参数,不是抽象方法。
    }
    
    其中定义体实质是声明了一个配置参数(注:此处不是抽象方法)。
    1、方法名指的是参数名。
    2、返回值类型指的是参数的类型(只能为基本类型、Class、String、enum、Annotation类型、以及以上所有类型的数组)。
    3、可以通过default来声明参数的默认值。
    4、如果只有一个参数,那么参数名(方法名)一般为value。
    5、只能使用public, default两个权限修饰符。  

      (2)方法:

    判断类或者方法是否有注解
        boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
    
    获得注解对象
        <A extends Annotation> A getAnnotation(Class<A> annotationClass)  //获取指定注解
        Annotation[] getAnnotations()  //获取当前元素上的所有注解
    【举例:】
    package com.test;
    
    import java.lang.annotation.Annotation;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.lang.reflect.Field;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Table {
        String value();
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface FieldDemo {
        String columnName() default "";
    
        String type() default "";
    
        int length() default 10;
    }
    
    @Table("student")
    class Student {
        @FieldDemo(columnName = "id", length = 10, type = "int")
        private int id;
    
        @FieldDemo(columnName = "name", length = 20, type = "varchar")
        private String name;
    
        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 class AnnotationDemo {
        public static void main(String[] args) {
            try {
                // 获取Student类的信息
                Class classDemo = Class.forName("com.test.Student");
                // Class<Student> classDemo = (Class<Student>)Class.forName("com.test.Student");
                System.out.println(classDemo); // 输出class com.test.Student
    
                // 获取当前元素上的所有注解,此时获取的是@Table
                Annotation[] annotations = classDemo.getAnnotations();
                for (Annotation annotation : annotations) {
                    System.out.println(annotation);
                } // 输出@com.test.Table(value=student)
    
                // 直接获取指定的某注解
                Table table = (Table) classDemo.getAnnotation(Table.class);
                System.out.println(table.value()); // 输出student
    
                // 获取类的属性的注解
                Field field = classDemo.getDeclaredField("name");
                // 获取指定注解
                FieldDemo fieldDemo = field.getAnnotation(FieldDemo.class);
                // 输出name varchar 20
                System.out.println(fieldDemo.columnName() + " " + fieldDemo.type() + " " + fieldDemo.length());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    二、反射机制

    1、什么是反射?

      JAVA反射机制指的是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;即这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
      JAVA中与反射相关的类,放在java.lang.reflect包中。

    2、Class类

      Class对象包含了一个类的完整的结构信息。通过Class对象,可以对类进行操作,即为反射。
      (1)规则:
        Class类拥有泛型作为定义。
        Class 类的实例表示正在运行的 Java 应用程序中的类和接口。
        Class类没有public构造方法。
        Class对象是在加载类时由Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。
        一个类只有一个Class对象。

      (2)内置class实例(class对象):

    byte.class, short.class, int.class, long.class,
    char.class, float.class,  double.class, boolean.class, void.class.
    注:
    int.class != Integer.class  。
    int.class == Integer.Type  。

      (3)对于数组类型class实例:
        每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该Class 对象。

    int[] a = new int[100];
    int[] b = new int[10];
    long[] c = new long[10];
    int[][] d = new int[10][2];
    System.out.println(a.getClass());//输出class [I
    System.out.println(b.getClass());//输出class [I
    System.out.println(c.getClass());//输出class [J
    System.out.println(d.getClass());//输出class [[I
    System.out.println(a.getClass() == b.getClass());//输出true

      (4)获取Class实例的方法:

    【方法1:】
    根据传入的参数动态装载一个类,并且做类的初始化。
        Class.forName()方法
    
    【方法2:】
    获得对象运行时所指的真正对象(多态的场合返回子类的类名)。
        Class.getClass() 方法
    
    【方法3:】
    JVM将使用类A的类装载器,将类A装入内存(前提是:类A还没有装入内存),不对类A做类的初始化工作.返回类A的Class的对象。
        A.class属性

      (5)通过Class实例创建对象:

    Class.newInstance()方法 。调用默认构造函数,获得一个实例
    
    Class.newInstance方法与new的区别
    newInstance: 弱类型。低效率。只能调用无参构造。
    new:强类型。相对高效。能调用任何public构造。

      (6)常用方法:

    【获得构造器:】
    Constructor<T> getDeclaredConstructor(Class<?>...)  获得指定构造方法
    Constructor<?>[] getDeclaredConstructors()    获得所有构造方法(声明顺序)
    Constructor<T> getConstructor(Class<?>...)    获得权限为public的指定构造方法
    Constructor<?>[] getConstructors()    获得权限为public的所有构造方法
    
    【获得普通方法(成员方法):】
    Method[] getDeclaredMethods()   获得该类中定义的所有方法(不包含父类继承)
    Method getDeclaredMethod(String name, Class<?>... parameterTypes)    根据该类中定义的指定方法(不包含父类继承)
    Method[] getMethods()  获得权限为public的所有的方法 (包含父类继承)
    Method getMethod(String name, Class<?>... parameterTypes)    获得权限为public的指定的方法 (包含父类继承)
    
    【获得属性(成员变量):】
    Field[] getDeclaredFields()   获得该类中定义的所有属性(不包含继承)
    Field getDeclaredField(String name)获得该类中定义的指定属性(不包含继承)
    Field[] getFields()  获得该类中所有public的属性(包含继承)
    Field getField (String name)  获得该类中指定的public属性(包含继承)
    
    【获得内部类:】
    Class<?>[] getDeclaredClasses()  获得所有内部类 (不包含继承)
    Class<?>[] getClasses()   获得所有权限为public的内部类(包含继承)
    
    【其他:】
    Package getPackage()   获得Package对象
    String getName()     获得类的全称,即包名+类名
    String getSimpleName()  获得类的简称,即类名
    Class<? super T> getSuperclass()  获得继承的类
    Class<?>[] getInterfaces()  获得实现的接口

      (7)获得构造器后,可以进行的操作

      (8)获得成员方法后,可以进行的操作

      (9)获得成员变量后,可以进行的操作

    package com.test;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    class Teacher {
        private String name;
        private int age;
    
        public Teacher() {
        }
    
        public Teacher(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        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 class ReflectionDemo {
        public static void main(String[] args) {
            try {
                // 加载Teacher.class对象
                Class<Teacher> teacherClass = (Class<Teacher>) Class.forName("com.test.Teacher");
    
                // 获取无参构造器,若Teacher类没有无参构造方法,则会报错
                Teacher teacher = teacherClass.newInstance();
                System.out.println(teacher + ", " + teacher.getName() + ", " + teacher.getAge());
    
                // 获取有参构造器
                Constructor<Teacher> constructor = teacherClass.getDeclaredConstructor(String.class, int.class);
                Teacher teacher2 = constructor.newInstance("tom", 20);
                System.out.println(teacher2 + ", " + teacher2.getName() + ", " + teacher2.getAge());
    
                // 获取成员方法
                Teacher teacher3 = teacherClass.newInstance();
                Method method = teacherClass.getDeclaredMethod("setAge", int.class);
                method.invoke(teacher3, 30);
                System.out.println(teacher3.getAge());
    
                Method method2 = teacherClass.getDeclaredMethod("getAge");
                System.out.println(method2.invoke(teacher3));
    
                // 获取成员变量
                Teacher teacher4 = teacherClass.newInstance();
                Field field = teacherClass.getDeclaredField("age");
                field.setAccessible(true);// 忽略安全检查,可以获取private类型的数据,破坏封装性。
                System.out.println(field.get(teacher4));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    【结果:】
    com.test.Teacher@2a139a55, null, 0
    com.test.Teacher@15db9742, tom, 20
    30
    30
    0

    3、反射机制性能问题

      setAccessible,是启用和禁用安全检查的开关,其值为true时,表示禁用Java语言访问的安全性检查,为false时,表示启用安全性检查,将其值设为true,可以提高反射的效率。

    三、静态代理与动态代理

    1、静态代理

    (1)静态代理其实就是指设计模式中的代理模式。代理模式为其他对象提供一种代理以控制对这个对象的访问。代理类只能为一个接口服务,使用静态代理会产生很多代理类。
    (2)UML图:

    (3)代码演示:

    【Subject.java】
    package construction.pattern.proxy;
    
    /**
     * 定义一个代理类与被代理类的公共接口,使得被代理类出现的地方,代理类均能出现
     */
    interface Subject {
        public abstract void request();
    }
    
    
    【RealSubject.java】
    package construction.pattern.proxy;
    
    /**
     * 被代理类。定义 代理类 所代表的真实实体。
     *
     */
    public class RealSubject implements Subject {
    
        @Override
        public void request() {
            System.out.println("这是真实请求");
        }
    }
    
    【RealSubjectProxy.java】
    package construction.pattern.proxy;
    
    /**
     * 代理类,用于控制被代理类。保存一个被代理类的引用使得代理可以访问实体。
     *
     */
    public class RealSubjectProxy implements Subject {
        private RealSubject realSubject;//保存一个被代理类的引用
    
        @Override
        public void request() {
            System.out.println("代理开始......");
            if (realSubject == null) {
                realSubject = new RealSubject();
            }
            realSubject.request();
            System.out.println("代理结束......");
        }
    }
    
    【ProxyDemo.java】
    package construction.pattern.proxy;
    
    /**
     * 静态代理的小demo。
     * 被代理类能出现的地方,代理类均能出现。
     */
    public class ProxyDemo {
        public static void main(String[] args) {
            Subject subject = new RealSubjectProxy();
            subject.request();
        }
    }
    
    【执行结果:】
    代理开始......
    这是真实请求
    代理结束......

    (4)代码分析:
    静态代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于 Proxy 和 RealSubject 的功能本质上是相同的,Proxy 只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。

    2、动态代理

    (1)为了解决静态代理的问题,动态代理的想法就被提出。即在运行状态下,在需要被代理的地方,根据 Subject 和 RealSubject,动态地创建一个 Proxy,用完之后,就会销毁,可以避免类臃肿。

      即动态代理指的是 在程序运行时动态的创建目标类的代理类的对象,且通过代理对象来调用目标对象。

    (2)Java 动态代理基于经典代理模式,引入了一个 InvocationHandler,InvocationHandler 负责统一管理所有的方法调用。

    /**
      * 参数
      *  proxy - 代理的真实对象。
      *  method - 所要调用真实对象的某个方法的 Method 对象
      *  args - 所要调用真实对象某个方法时接受的参数
      */
    public interface InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable;
    }

    (3)动态代理步骤:
      step1:获取 RealSubject 上的所有接口列表;
      step2:确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX;
      step3:根据需要实现的接口信息,在代码中动态创建 该 Proxy 类的字节码;
      step4:将对应的字节码转换为对应的 class 对象;
      step5:创建 InvocationHandler 实例 handler,用来处理 Proxy 所有方法调用;
      step6:Proxy 的 class 对象 以创建的 handler 对象为参数,实例化一个 proxy 对象。

    【Proxy常用的方法】
    /**
     * loader - 一个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载。
     * interfaces - 一个 Interface 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
     * h - 一个 InvocationHandler 对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个 InvocationHandler 对象上
    */
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

    (4)UML图:

    (5)代码实现:

    【Subject.java】
    package construction.pattern.proxy.dynamicProxy;
    
    /**
     * 定义一个代理类与被代理类的公共接口,使得被代理类出现的地方,代理类均能出现
     */
    interface Subject {
        void hello();
    
        String bye();
    }
    
    【RealSubject.java】
    package construction.pattern.proxy.dynamicProxy;
    
    /**
     * 被代理类。定义 代理类 所代表的真实实体。
     *
     */
    public class RealSubject implements Subject {
    
        @Override
        public void hello() {
            System.out.println("Hello World");
        }
    
        @Override
        public String bye() {
            return "GoogBye";
        }
    }
    
    【DynamicProxy.java】
    package construction.pattern.proxy.dynamicProxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * 动态代理类。 每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实例都关联到了一个
     * Handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke
     * 方法来进行调用。
     *
     */
    public class DynamicProxy implements InvocationHandler {
    
        // 用于保存被代理对象(真实对象)
        private Object subject;
    
        /**
         * 构造方法,给被代理对象赋初值
         *
         * @param subject
         *            真实对象
         */
        public DynamicProxy(Object subject) {
            this.subject = subject;
        }
    
        /**
         * 参数:
         *  proxy - 代理的真实对象(一般在代码中不使用)。
         *  method - 所要调用真实对象的某个方法的 Method 对象
         *  args - 所要调用真实对象某个方法时接受的参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("可以在代理前,进行一些操作...");
            System.out.println("Before proxy......");
            System.out.println();
    
            // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
            System.out.println("当前调用的方法为:" + method);
            Object object = method.invoke(this.subject, args);
            System.out.println();
    
            System.out.println("可以在代理后,进行一些操作...");
            System.out.println("After proxy......");
            System.out.println();
            return object;
        }
    
    }
    
    
    【DynamicProxyDemo.java】
    package construction.pattern.proxy.dynamicProxy;
    
    import java.lang.reflect.Proxy;
    
    /**
     * 演示动态代理的小Demo
     *
     */
    public class DynamicProxyDemo {
        public static void main(String[] args) {
            // 创建一个被代理的对象(真实对象)
            Subject realSubject = new RealSubject();
            // 输出真实对象的类名
            System.out.println(realSubject.getClass().getName());
    
            // 将真实对象传入 InvocationHandler 中,即DynamicProxy中
            DynamicProxy proxy = new DynamicProxy(realSubject);
    
            // 通过Proxy类的 newProxyInstance 方法来创建一个代理对象
            // 第一个参数, proxy.getClass().getClassLoader(), 使用proxy这个类的ClassLoader对象来加载我们的代理对象
            // 第二个参数, subject.getClass().getInterfaces(), 获取真实对象的所有接口,从而使代理对象能调用被代理对象的方法
            // 第三个参数, proxy, 关联 InvocationHandler 对象
            Subject subject = (Subject) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), proxy);
    
            // 输出代理对象的类名
            System.out.println(subject.getClass().getName());
            subject.hello();//会自动触发代理类的invoke方法,
            System.out.println(subject.bye());
        }
    }
    
    【结果:】
    construction.pattern.proxy.dynamicProxy.RealSubject
    construction.pattern.proxy.dynamicProxy.$Proxy0
    可以在代理前,进行一些操作...
    Before proxy......
    
    当前调用的方法为:public abstract void construction.pattern.proxy.dynamicProxy.Subject.hello()
    Hello World
    
    可以在代理后,进行一些操作...
    After proxy......
    
    可以在代理前,进行一些操作...
    Before proxy......
    
    当前调用的方法为:public abstract java.lang.String construction.pattern.proxy.dynamicProxy.Subject.bye()
    
    可以在代理后,进行一些操作...
    After proxy......
    
    GoogBye
  • 相关阅读:
    对象池使用时要注意几点
    Flash3D学习计划(一)——3D渲染的一般管线流程
    714. Best Time to Buy and Sell Stock with Transaction Fee
    712. Minimum ASCII Delete Sum for Two Strings
    647. Palindromic Substrings(马拉车算法)
    413. Arithmetic Slices
    877. Stone Game
    338. Counting Bits
    303. Range Sum Query
    198. House Robber
  • 原文地址:https://www.cnblogs.com/huoyz/p/14380788.html
Copyright © 2011-2022 走看看