精华内容
下载资源
问答
  • 今天小编就为大家分享一篇关于Java注解反射原理说明,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
  • Java 注解和反射详解

    千次阅读 多人点赞 2020-09-15 09:09:28
    内置注解元注解@Target@Retention@Documented@Inherited自定义注解反射静态语言 VS 动态语言动态语言静态语言Java 反射机制概述Java 反射机制提供的功能Java 反射优点缺点反射相关的主要 API理解 Class 类并获取 ...

    注解

    什么是注解?

    • Annotation 是从 JDK 1.5 开始引入的新技术
    • Annotation 的作用
      • 不是程序本身,可以对程序作出解释(这一点和注释没什么区别)
      • 可以被其它程序(比如:编译器等)读取
    • Annotation 的格式
      • 注解是以 “@注释名” 在代码中存在的,还可以添加一些参数值,例如: @SuppressWarnings(value=“unchecked”)
    • Annotation 在哪里使用?
      • 可以附加在 package,class,method,field 等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问

    如:(重写父类方法的时候会有个 @Override 注解)
    在这里插入图片描述

    内置注解

    • @Override:定义在 java.lang.Override 中,此注解只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明
    @Target(value=METHOD)
    @Retention(value=SOURCE)
    public @interface Override
    
    • @Deprecated:定义在 java.lang.Deprecated 中,此注释可以用于修辞方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择
    @Documented
    @Retention(value=RUNTIME)
    @Target(value={CONSTRUCTOR,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER,TYPE})
    public @interface Deprecated
    
    • @SuppressWarnings:定义在 java.lang.SuppressWarings 中,用来抑制编译时的警告信息,与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了
      • @SuppressWarnings(“all”)
      • @SuppressWarnings(“unchecked”)
      • @SuppressWarnings(value={“unchecked”,“deprecation”})
      • 等等…
    @Target(value={TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})
    @Retention(value=SOURCE)
    public @interface SuppressWarnings
    

    元注解

    • 元注解的作用就是负责注解其它注解,Java 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它注解类型做说明
    • 这些类型和它们所支持的类在 java.lang.annotation 包中可以找到(@Target,@Retention,@Documented,@Inherited)

    @Target

    用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

    如:我们自定义一个注解
    在这里插入图片描述
    ElementType 是一个枚举类,该类中提供了一些简单的分类(以下是该类的部分截图)
    在这里插入图片描述
    然后我们使用该注解

    public class Test01 {
        @MyAnnotation
        public void test() {
    
        }
    }
    

    在 @MyAnnotation 中还定义了可以在类中使用,所以也可以在类上面使用该注解,如:
    在这里插入图片描述

    @Retention

    表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE < CLASS < RUNTIME
    在这里插入图片描述

    @Documented

    说明该注解将被包含在 javadoc 中
    在这里插入图片描述

    @Inherited

    说明子类可以继承父类中的该注解

    在这里插入图片描述

    自定义注解

    • 使用 @interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口
    • 分析:
      • @interface 用来声明一个注解,格式:public @interface 注解名 {定义内容}
      • 其中的每一个方法实际上是声明了一个配置参数
      • 方法的名称就是参数的名称
      • 返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)
      • 可以通过 default 来声明参数的默认值
      • 如果只有一个参数成员,一般参数名为 value
      • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0 作为默认值
    import java.lang.annotation.*;
    
    /**
     * @author Woo_home
     * @create by 2020/8/1  20:12
     */
    public class Test01 {
    
        // 注解可以显示赋值,如果没有默认值,我们就必须给注解赋值
        @MyAnnotation(age = 18, name = "lisa")
        public void test() {
    
        }
        
        @DemoAnnotation("注解测试")
        public void test1() {
    
        }
    }
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyAnnotation {
        // 注解的参数:参数类型 + 参数名()
        String name() default "";
    
        int age();
    
        // 如果默认值为 -1,代表不存在
        int id() default -1;
    
        String[] schools() default {"大学"};
    }
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface DemoAnnotation {
        String value();
    }
    

    反射

    静态语言 VS 动态语言

    动态语言

    动态语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其它结构上的变化。通俗一点说就是在运行时代码可以根据某些条件改变自身结构

    静态语言

    与动态语言相对应,运行时结构不可变的语言就是静态语言,如 Java、C、C++

    Java 不是静态语言,但是 Java 可以称之为 “准动态语言”。即 Java 有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java 的动态性让编程的时候更加灵活

    Java 反射机制概述

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

    Class c = Class.forName("java.lang.String");
    

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

    • 正常方式:引入需要的 “ 包类 ” 名称 ——> 通过 new 实例化 ——> 取得实例化对象
    • 反射方式:实例化对象 ——> getClass() 方法 ——> 得到完整的 “包类” 名称

    Java 反射机制提供的功能

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

    Java 反射优点和缺点

    优点: 可以实现动态创建对象和编译,体现出很大的灵活性

    缺点: 对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们什么要求,这类操作总是慢于直接执行相同的操作

    反射相关的主要 API

    • java.lang.Class 代表一个类
    • java.lang.reflect.Method 代表类的方法
    • java.lang.reflect.Field 代表类的成员变量
    • java.lang.reflect.Constructor 代表类的构造器

    理解 Class 类并获取 Class 实例

    先定义一个实体类

    // 定义一个实体类
    public class User {
    
        private int id;
    
        private String name;
    
        private int age;
    
        public User() {
        }
    
        public User(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 "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    主程序

    package com.java.demo.reflect;
    
    /**
     * @author Woo_home
     * @create by 2020/8/1  20:12
     */
    public class Test01 {
        public static void main(String[] args) throws Exception {
            // 通过反射获取类的 Class 对象
            Class<?> aClass = Class.forName("com.java.demo.reflect.User");
            System.out.println(aClass);
        }
    }
    

    输出:
    在这里插入图片描述
    一个类在内存中只有一个 Class 对象,一个类被加载之后,类的整个结构都会被封装在 Class 对象中

    Class 类

    在 Object 类中定义了以下方法,此方法将被所有子类继承

    public final native Class<?> getClass();
    

    上面的方法返回值的类型是一个 Class 类,此类是 Java 反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称

    对于每个类而言,JRE 都为其保留了一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class / interface / enum / annotation / primitive type/void / [])

    • Class 本身也是一个类
    • Class 对象只能由系统建立对象
    • 一个加载的类在 JVM 中只会有一个 Class 实例
    • 一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件
    • 每个类的实例都会记得自己是由哪个 Class 实例所生成
    • 通过 Class 可以完整地得到一个类中的所有被加载的结构
    • Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类、唯有先获得响应的 Class 对象
    Class 类的常用方法
    方法名功能说明
    static Class forName(String name)返回指定类名 name 的 Class 对象
    Object newInstance()调用缺省构造函数,返回 Class 对象的一个实例
    getName()返回此 Class 对象所表示的实体(类、接口、数组类、或 void)的名称
    Class getSuperClass()返回当前 Class 对象的父类的 Class 对象
    Class[] getInterfaces()获取当前 Class 对象的接口
    ClassLoader getClassLoader()返回该类的类加载器
    Constructor[] getConstructors()返回一个包含某些 Constructor 对象的数组
    Method getMethod(String name, Class… T)返回一个 Method 对象,此对象的形参类型为 paramType
    Field[] getDeclaredFields()返回 Field 对象的一个数组

    获取 Class 类的实例

    public class Test01 {
        public static void main(String[] args) throws Exception {
            // 方式一:forName 获得
            Class aClass = Class.forName("com.java.demo.reflect.User");
            System.out.println(aClass);
    
            // 方式二:通过对象获得
            Class aClass1 = new User().getClass();
            System.out.println(aClass1);
    
            // 方式三:通过类名.class 获得
            Class<User> aClass2 = User.class;
            System.out.println(aClass2);
        }
    }
    

    所有类型的 Class 对象

    public class Test01 {
        public static void main(String[] args) {
            Class objectClass = Object.class;            // 类
            Class comparableClass = Comparable.class;    // 接口
            Class aClass = String[].class;               // 一维数组
            Class aClass1 = int[][].class;               // 二维数组
            Class overrideClass = Override.class;        // 注解
            Class elementTypeClass = ElementType.class;  // 枚举
            Class integerClass = Integer.class;          // 基本数据类型
            Class voidClass = void.class;                // void
            Class classClass = Class.class;              // Class
    
            System.out.println(objectClass);
            System.out.println(comparableClass);
            System.out.println(aClass);
            System.out.println(aClass1);
            System.out.println(overrideClass);
            System.out.println(elementTypeClass);
            System.out.println(integerClass);
            System.out.println(voidClass);
            System.out.println(classClass);
        }
    }
    

    输出:
    在这里插入图片描述

    Java 内存分析

    在这里插入图片描述

    类的加载与 ClassLoader 的理解

    • 加载:将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象

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

      • 验证:确保加载的类信息符合 JVM 规范,没有安全方面的问题
      • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
      • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
    • 初始化:

      • 执行类构造器< clint>() 方法的过程,类构造器< clint>() 方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)
      • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
      • 虚拟机会保证一个类的< clint>() 方法在多线程环境中被正确加锁和同步
    package com.java.demo.reflect;
    
    /**
     * @author Woo_home
     * @create by 2020/8/1  20:12
     */
    public class Test01 {
        public static void main(String[] args) {
            /**
             * 1、加载到内存,会产生一个类对应的 Class 对象
             * 2、链接,链接结束后 m = 0
             * 3、初始化
                  <clint>() {
                        System.out.println("A 类静态代码块初始化");
                        m = 300;
                        m = 100;
                    }
                    m = 100;
             */
            A a = new A();
            System.out.println(A.m);
        }
    }
    
    class A {
        static {
            System.out.println("A 类静态代码块初始化");
            m = 300;
        }
    
        static int m = 100;
    
        public A() {
            System.out.println("A 类的无参构造函数初始化");
        }
    }
    

    输出:
    在这里插入图片描述

    获取运行时类的完整结构 & 动态创建对象执行方法

    Field、Method、Constructor、SuperClass、Interface、Annotation

    • 实现的全部接口
    • 所继承的父类
    • 全部的构造器
    • 全部的方法
    • 全部的 Field
    • 注解

    具体可以看下之前写的这篇文章,写得很详细 Java反射机制的简单使用

    性能对比分析

    setAccessible

    • Method 和 Field、Constructor 对象都有 setAccessible() 方法
    • setAccessible 作用是启动和禁用访问安全检查的开关
    • 参数值为 true 则提示反射的对象在使用时应该取消 Java 语言访问检查
      • 提高反射的效率,如果代码中必须用反射,而该语句需要频繁的被调用,那么请设置为 true
      • 使得原本无法访问的私有成员也可以访问
    • 参数值为 false 则指示反射的对象应该实施 Java 语言访问检查
    package com.java.demo.reflect;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * @author Woo_home
     * @create by 2020/8/1  20:12
     */
    public class Test01 {
    
        // 普通方式调用
        public static void test01() {
            User user = new User();
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
                user.getName();
            }
            long endTime = System.currentTimeMillis();
            System.out.println("普通方式执行 1 亿次:" + (endTime - startTime) + "ms");
        }
    
        // 反射方式调用
        public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            User user = new User();
            Class c = user.getClass();
            Method getName = c.getDeclaredMethod("getName", null);
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
                getName.invoke(user, null);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("反射方式执行 1 亿次:" + (endTime - startTime) + "ms");
        }
    
        // 反射方式调用,关闭检测
        public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            User user = new User();
            Class c = user.getClass();
            Method getName = c.getDeclaredMethod("getName", null);
            getName.setAccessible(true);
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
                getName.invoke(user, null);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("关闭检测方式执行 1 亿次:" + (endTime - startTime) + "ms");
        }
    
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
            test01();
            test02();
            test03();
        }
    }
    

    输出:
    在这里插入图片描述

    通过输出结果可以发现,普通方式执行是最快的,其次就是关闭检测的方式

    通过反射去操作泛型

    • Java 采用泛型擦除的机制来引入泛型,Java 中的泛型仅仅是给编译器 javac 使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除
    • 为了通过反射操作这些类型,Java 新增了 ParameterizedType,GenericArrayType,TypeVariable 和 WildcardType 几种类型来代表不能被归一到 Class 类中的类型但是又和原始类型齐名的类型
      • ParameterizedType:表示一种参数化类型,比如 Collection< String>
      • GenericArrayType:表示一种元素类型时参数化类型或者类型变量的数组类型
      • TypeVariable :是各种类型变量的公共父接口
      • WildcardType :代表一种通配符类型表达式
    package com.java.demo.reflect;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @author Woo_home
     * @create by 2020/8/1  20:12
     */
    public class Test01 {
    
        public void test01(Map<String, User> map, List<User> list) {
            System.out.println("test01");
        }
    
        public Map<String, User> test02() {
            System.out.println("test02");
            return null;
        }
    
        // 通过反射获取泛型
        public static void main(String[] args) throws NoSuchMethodException {
            Method test01 = Test01.class.getMethod("test01", Map.class, List.class);
            // 获取通用参数类型
            Type[] genericParameterTypes = test01.getGenericParameterTypes();
            // 迭代遍历
            for (Type genericParameterType : genericParameterTypes) {
                System.out.println("# " + genericParameterType);
                if (genericParameterType instanceof ParameterizedType) {
                    // 获取实际参数类型
                    Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                    for (Type actualTypeArgument : actualTypeArguments) {
                        System.out.println(actualTypeArgument);
                    }
                }
            }
    
            Method test02 = Test01.class.getMethod("test02", null);
            Type genericReturnType = test02.getGenericReturnType();
            if (genericReturnType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }
    }
    

    输出:
    在这里插入图片描述

    反射操作注解

    • getAnnotations
    • getAnnotation

    先定义两个注解,待会儿会用到

    // 类名注解
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Tables {
        String value();
    }
    
    // 属性的注解
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Fields {
        String columnName();
        String type();
        int length();
    }
    

    然后我们在实体类中使用我们定义的注解(是不是跟 JPA 有点像?)

    @Tables("db_student")
    public class Student {
    
        @Fields(columnName = "db_id", type = "int", length = 10)
        private int id;
    
        @Fields(columnName = "db_age", type = "int", length = 10)
        private int age;
    
        @Fields(columnName = "db_name", type = "varchar", length = 3)
        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 + '\'' +
                    '}';
        }
    }
    

    然后反射来获取注解信息

    public class Test01 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
            Class c1 = Class.forName("com.java.demo.reflect.Student");
    
            // 通过反射获得注解
            Annotation[] annotations = c1.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
    
            // 获得注解的 value 值
            Tables tables = (Tables) c1.getAnnotation(Tables.class);
            String value = tables.value();
            System.out.println(value);
    
            // 获得类指定的注解
            Field field = c1.getDeclaredField("name");
            Fields annotation = field.getAnnotation(Fields.class);
            System.out.println(annotation.columnName());
            System.out.println(annotation.type());
            System.out.println(annotation.length());
        }
    }
    

    输出:
    在这里插入图片描述

    扩展

    自定义注解的场景及实现

        登陆、权限拦截、日志处理,以及各种 Java 框架,如 Spring,Hibernate,Junit 提到注解就不能不说到反射,Java 自定义注解是通过运行时靠反射获取注解。

        实际开发中,例如我们要获取某个方法的调用日志,可以通过 AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志注解,就进行日志记|录

    展开全文
  • 关于Java你不知道的那些事之Java注解和反射

    千次阅读 多人点赞 2020-10-12 13:47:04
    Java注解和反射前言什么是注解内置注解元注解自定义注解反射机制动态语言与静态语言动态语言静态语言Java反射机制概述什么是反射反射的应用Java反射的优缺点反射相关的主要API理解Class类并获取Class实例Class类...

    前言

    我们在实际的工作中,项目使用的框架中会经常遇到注解和反射。但是我们大部分同学都仅仅限于知道这是注解和反射,却不太了解它们底层的原理实现。对这部分知识了解有点浅薄和片面,所以这边文章将会深入理解什么是注解和反射。让我们达到“知其然,知其所以然”的目的。

    什么是注解

    • Annotation是JDK5.0开始引入的新技术
    • Annotation的作用
      • 不是程序本身,可以对程序做出解释(这一点和注释没有什么区别)
      • 可以被其它程序,比如编译器读取
    • Annotation的格式
      • 注解以 @注释名 在代码中存在的,还可以添加一些参数值
      • 例如:@SuppressWarnings(value = "unchecked")
    • Annotation在那里使用?
      • 可以附加在package、class、method、field等上面,相当于给他们添加了额外的辅助信息
      • 通过反射机制变成实现对这些元数据的控制

    内置注解

    • @Override:定义在 java.lang.Override中,此注释只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明
    • @Deprecated:定义在java.lang.Deprecated中,此注释可以用于修饰方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险,或者存在更好的选择
    • @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息,与前面的两个注释不同,你需要额外添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了。
      • @SuppressWarnings(“all”)
      • @SuppressWarnings(“unchecked”)
      • @SuppressWarnings(value={“unchecked”, “deprecation”})

    元注解

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

    这些类型和它们所支持的类在 java.lang.annotation包可以找到 @Target@Retention@Documented@Inherited

    • @Target:用于描述注解的使用范围,即:被描述的注解可以在什么地方使用
    • @Retention:表示需要什么保存该注释信息,用于描述注解的生命周期
      • 级别范围:Source < Class < Runtime
    • @Document:说明该注解被包含在java doc中
    • @Inherited:说明子类可以集成父类中的注解

    示例

    /**
     * 元注解
     *
     * @author: 轻狂书生FS
     * @create: 2020-03-28-22:57
     */
    @MyAnnotation
    public class MateAnnotationDemo {
    
    }
    
    /**
     * 定义一个注解
     */
    @Target(value={ElementType.METHOD, ElementType.TYPE})  // target表示我们注解应用的范围,在方法上,和类上有效
    @Retention(RetentionPolicy.RUNTIME)   // Retention:表示我们的注解在什么时候还有效,运行时候有效
    @Documented   // 表示说我们的注解是否生成在java doc中
    @Inherited   // 表示子类可以继承父类的注解
    @interface MyAnnotation {
    
    }
    

    自定义注解

    使用 @interface自定义注解时,自动继承了 java.lang.annotation.Annotation接口

    • @interface 用来声明一个注解,格式:public @interface 注解名 {定义内容
    • 其中的每个方法实际上是申明了一个配置参数
    • 方法的名称j就是参数的类型
    • 返回值类型就是参数的类型(返回值只能是基本数据类型,Class,String,enum)
    • 通过default来申明参数的默认值
    • 如果只有一个参数成员,一般参数名为 value
    • 注解元素必须要有值,我们定义元素时,经常使用空字符串或者0作为默认值
    /**
     * 自定义注解
     *
     * @author: 轻狂书生FS
     * @create: 2020-03-28-22:57
     */
    public class MateAnnotationDemo {
    
        // 注解可以显示赋值,如果没有默认值,我们就必须给注解赋值
        @MyAnnotation(schools = {"大学"})
        public void test(){
    
        }
    
    }
    
    /**
     * 定义一个注解
     */
    @Target(value={ElementType.METHOD, ElementType.TYPE})  // target表示我们注解应用的范围,在方法上,和类上有效
    @Retention(RetentionPolicy.RUNTIME)   // Retention:表示我们的注解在什么时候还有效,运行时候有效
    @Documented   // 表示说我们的注解是否生成在java doc中
    @Inherited   // 表示子类可以继承父类的注解
    @interface MyAnnotation {
    
        // 注解的参数:参数类型 + 参数名()
        String name() default "";
    
        int age() default 0;
    
        // 如果默认值为-1,代表不存在
        int id() default -1;
    
        String[] schools();
    }
    

    反射机制

    动态语言与静态语言

    动态语言

    动态语言是一类在运行时可以改变其结构的语言:例如新的函数,对象,甚至代码可以被引进,已有的函数可以被删除或是其它结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构

    主要的动态语言有:Object-c、C#、JavaScript、PHP、Python等

    静态语言

    与动态语言相比,运行时结构不可变的语言就是静态语言。例如Java、C、C++

    Java不是动态语言,但是Java可以称为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制来获取类似于动态语言的 特性,Java的动态性让编程的时候更加灵活。

    Java反射机制概述

    什么是反射

    Java Reflection:Java反射是Java被视为动态语言的关键,反射机制运行程序在执行期借助于Reflection API 去的任何类内部的信息,并能直接操作任意对象的内部属性及方法。

    Class c = Class.forName("java.lang.String")
    

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

    在这里插入图片描述

    tip:反射可以获取到private修饰的成员变量和方法

    反射的应用

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

    Java反射的优缺点

    • 优点:可以实现动态创建对象和编译,体现出很大的灵活性
    • 缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求,这类操作总是慢于直接执行相同的操作。也就是说new创建和对象,比反射性能更高

    反射相关的主要API

    • java.lang.Class:代表一个类
    • java.lang.reflect.Method:代表类的方法
    • java.lang.reflect.Field:代表类的成员变量
    • java.lang.reflect.Constructor:代表类的构造器

    理解Class类并获取Class实例

    Class类

    我们下面通过Class.forName来获取一个实例对象

    /**
     * 反射Demo
     *
     * @author: 轻狂书生FS
     * @create: 2020-09-29-8:21
     */
    public class ReflectionDemo {
        public static void main(String[] args) throws ClassNotFoundException {
            // 通过反射获取类的Class对象
            Class c1 = Class.forName("com.moxi.interview.study.annotation.User");
            Class c2 = Class.forName("com.moxi.interview.study.annotation.User");
            Class c3 = Class.forName("com.moxi.interview.study.annotation.User");
            System.out.println(c1.hashCode());
            System.out.println(c2.hashCode());
            System.out.println(c3.hashCode());
    
    
        }
    }
    
    /**
     * 实体类:pojo,entity
     */
    class User {
        private String name;
        private int id;
        private int age;
    
        public User() {
    
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            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;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", id=" + id +
                    ", age=" + age +
                    '}';
        }
    }
    

    上面我们通过反射获取了三个对象,我们输出对应对象的hashcode码,会发现

    1173230247
    1173230247
    1173230247
    

    它们的hashcode码是一样的,这就说明了:

    • 一个类在内存中只有一个Class对象
    • 一个类被加载后,类的整体结构都会被封装在Class对象中

    在Object类中定义了以下的方法,此方法将被所有子类继承

    public final Class getClass()
    

    以上方法的返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。
    在这里插入图片描述

    也就是说,我们通过对象来获取到它的Class,相当于逆过程

    通过对照镜子我们可以得到的信息:某个类的属性,方法和构造器,某个类到底实现了那些接口。对于每个类而言,JRE都为其保留一个不变的Class类型对象,一个CLass对象包含了特定某个结构的有关信息

    • Class本身也是一个类
    • Class对象只能由系统建立对象
    • 一个加载的类在JVM中只会有一个Class实例
    • 一个Class对象对应的是一个加载到JVM中的一个.class文件
    • 每个类的实例都会记得自己是由哪个Class实例所生成
    • 通过Class可以完整地得到一个类中所有被加载的结构
    • Class类是Reflection的根源,针对任何你想动态加载、运行的类、唯有先获得相应的Class对象

    Class类常用的方法

    • ClassforName(String name):返回指定类name的Class对象
    • newInstance():调用缺省构造函数,返回Class对象的一个实例
    • getName():返回此Class对象所表示的实体(类,接口,数组或void)的名称
    • getSuperClass():返回当前Class对象的父类Class对象
    • getinterfaces():返回当前对象的接口
    • getClassLoader():返回该类的类加载器
    • getConstructors():返回一个包含某些Constructor对象的数组
    • getMethod(String name, Class… T):返回一个Method对象,此对象的形参类型为paramsType
    • getDeclaredFields():返回Field对象的一个数组

    获取对象实例的方法

    • 若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
      • Class clazz = Person.class;
    • 已知某个类的实例,调用该实例的getClass()方法获取Class对象
      • Class clazz = person.getClass()
    • 已经一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()获取,HIA可能抛出ClassNotFoundException
      • Class clazz = Class.forName(“demo01.Sutdent”)
    • 内置数据类型可以直接通过 类名.Type
    • 还可以利用ClassLoader
    /**
     * Class类创建的方式
     *
     * @author: 轻狂书生FS
     * @create: 2020-09-29-9:56
     */
    class Person {
        public String name;
        public Person() {
        }
        public Person(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    
    class Student extends Person{
        public Student() {
            this.name = "学生";
        }
    }
    
    class Teacher extends Person {
        public Teacher() {
            this.name = "老师";
        }
    }
    
    
    public class ClassCreateDemo {
        public static void main(String[] args) throws ClassNotFoundException {
    
            Person person = new Student();
            System.out.println("这个人是:" + person.name);
    
            // 方式1:通过对象获得
            Class c1 = person.getClass();
            System.out.println("c1:" + c1.hashCode());
    
            //方式2:通过forName获得
            Class c2 = Class.forName("com.moxi.interview.study.annotation.Student");
            System.out.println("c2:" + c2.hashCode());
    
            // 方式3:通过类名获取(最为高效)
            Class c3 = Student.class;
            System.out.println("c3:" + c3.hashCode());
    
            // 方式4:基本内置类型的包装类,都有一个Type属性
            Class c4 = Integer.TYPE;
            System.out.println(c4.getName());
    
            // 方式5:获取父类类型
            Class c5 = c1.getSuperclass();
        }
    }
    

    哪些类型可以有Class对象

    class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

    interface:接口

    []:数组

    enum:枚举

    annotation:注解@interface

    primitive type:基本数据类型

    void

    /**
     * 获取Class的方式
     *
     * @author: 轻狂书生FS
     * @create: 2020-09-29-10:16
     */
    public class GetClassDemo {
        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
    
            System.out.println(c1);
            System.out.println(c2);
            System.out.println(c3);
            System.out.println(c4);
            System.out.println(c5);
            System.out.println(c6);
            System.out.println(c7);
            System.out.println(c8);
            System.out.println(c9);
        }
    }
    

    最后运行结果为:

    class java.lang.Object
    interface java.lang.Comparable
    class [Ljava.lang.String;
    class [[I
    interface java.lang.Override
    class java.lang.annotation.ElementType
    class java.lang.Integer
    void
    class java.lang.Class
    

    同时需要注意,只要类型和维度一样,那就是同一个Class对象

    int [] a = new int[10];
    int [] b = new int[10];
    System.out.println(a.getClass().hashCode());
    System.out.println(b.getClass().hashCode());
    

    这两个的hashcode是一样的

    Java内存分析

    java内存分为以下三部分

      • 存放new的对象和数组
      • 可以被所有的线程共享,不会存放别的对象引用
      • 存放基本变量(会包含这个基本类型的具体数值)
      • 引用对象的变量(会存放这个引用在对堆里面的具体地址)
    • 方法区
      • 可以被所有线程共享
      • 包含了所有的class和static变量

    类的加载与ClassLoader的理解

    类加载过程

    当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤对该类进行初始化:

    在这里插入图片描述

    • 加载:将class文件字节码内容加载到内存,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象。
    • 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
      • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
      • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
      • 解析:虚拟机常量池的符号引用(常量名)替换为直接引用(地址)的过程
    • 初始化:
      • 执行类构造器方法的过程,类构造器 方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
      • 当初始化一个类的时候,如果发现其父类还没有初始化完成,则需要先触发其父类的初始化
      • 虚拟机会保证一个类的方法在多相差环境中被正确的加锁和同步

    下面一段代码,分别说明了static代码块,以及子类和父类构造方法的执行流程

    /**
     * 类加载流程
     *
     * @author: 轻狂书生FS
     * @create: 2020-09-29-11:02
     */
    class SuperA {
    
        static {
            System.out.println("父类静态代码块初始化");
        }
    
        public SuperA() {
            System.out.println("父类构造函数初始化");
        }
    }
    class A extends SuperA{
        static {
            System.out.println("静态代码块初始化");
            m = 300;
        }
    
        static int m = 100;
    
        public A() {
            System.out.println("A类的无参构造方法");
        }
    
    }
    public class ClassLoaderDemo {
    
        public static void main(String[] args) {
            A a = new A();
            System.out.println(a.m);
        }
    }
    

    最后的结果为:

    父类静态代码块初始化
    静态代码块初始化
    父类构造函数初始化
    A类的无参构造方法
    100
    

    说明静态代码块都是执行的,并且父类优先

    类加载步骤

    • 加载到内存,会产生一个类对应Class对象

    • 链接,链接结束 m = 0

    • 初始化:

      • <clinit>() {
        	syso("A类静态方法")
        	m = 300;
        	m = 100;
        }
        
        

    在这里插入图片描述

    什么时候发生类初始化

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

    • 当虚拟机启动,先初始化main方法所有在类
    • new 一个类的对象
    • 调用类的静态成员(除了 final常量)和静态方法
    • 使用 java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类

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

    • 当访问一个静态域时,只有真正的申明这个域的类才会被初始化,如:当通过子类引用父类的静态变量,不会导致子类初始化
    • 通过数组定义类引用,不会触发此类的初始化
    • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池了)

    类加载器的作用

    • 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成了一个代表这个类的 java.lang.Class对象,作为方法区中类数据的访问入口。
    • 类缓存:标准的JavaSE类加载器可以按要求查找类,但是一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象

    在这里插入图片描述

    类加载器作用是用来把类(Class)装载进内存的,JVM规范定义了如下类型的类的加载器

    在这里插入图片描述

    代码如下:

    /**
     * 类加载器的种类
     *
     * @author: 轻狂书生FS
     * @create: 2020-09-29-11:51
     */
    public class ClassLoaderTypeDemo {
        public static void main(String[] args) {
    
            //当前类是哪个加载器
            ClassLoader loader = ClassLoaderTypeDemo.class.getClassLoader();
            System.out.println(loader);
    
            // 获取系统类加载器
            ClassLoader classLoader = ClassLoader.getSystemClassLoader();
            System.out.println(classLoader);
    
            // 获取系统类加载器的父类加载器 -> 扩展类加载器
            ClassLoader parentClassLoader = classLoader.getParent();
            System.out.println(parentClassLoader);
    
            // 获取扩展类加载器的父类加载器 -> 根加载器(C、C++)
            ClassLoader superParentClassLoader = parentClassLoader.getParent();
            System.out.println(superParentClassLoader);
    
            // 测试JDK内置类是谁加载的
            ClassLoader loader2 = Object.class.getClassLoader();
            System.out.println(loader2);
        }
    }
    
    

    运行结果:我们发现,根加载器我们无法获取到

    sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$ExtClassLoader@45ee12a7
    null
    null
    

    获取类加载器能够加载的路径

    // 如何获取类加载器可以加载的路径
    System.out.println(System.getProperty("java.class.path"));
    

    最后输出结果为:

            // 如何获取类加载器可以加载的路径
            System.out.println(System.getProperty("java.class.path"));
    
            /*
            E:\Software\JDK1.8\Java\jre\lib\charsets.jar;
            E:\Software\JDK1.8\Java\jre\lib\deploy.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\access-bridge-64.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\cldrdata.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\dnsns.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\jaccess.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\jfxrt.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\localedata.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\nashorn.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\sunec.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\sunjce_provider.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\sunmscapi.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\sunpkcs11.jar;
            E:\Software\JDK1.8\Java\jre\lib\ext\zipfs.jar;
            E:\Software\JDK1.8\Java\jre\lib\javaws.jar;
            E:\Software\JDK1.8\Java\jre\lib\jce.jar;
            E:\Software\JDK1.8\Java\jre\lib\jfr.jar;
            E:\Software\JDK1.8\Java\jre\lib\jfxswt.jar;
            E:\Software\JDK1.8\Java\jre\lib\jsse.jar;
            E:\Software\JDK1.8\Java\jre\lib\management-agent.jar;
            E:\Software\JDK1.8\Java\jre\lib\plugin.jar;
            E:\Software\JDK1.8\Java\jre\lib\resources.jar;
            E:\Software\JDK1.8\Java\jre\lib\rt.jar;
            C:\Users\Administrator\Desktop\LearningNotes\校招面试\JUC\Code\target\classes;
            C:\Users\Administrator\.m2\repository\org\projectlombok\lombok\1.18.10\lombok-1.18.10.jar;
            C:\Users\Administrator\.m2\repository\cglib\cglib\3.3.0\cglib-3.3.0.jar;
            C:\Users\Administrator\.m2\repository\org\ow2\asm\asm\7.1\asm-7.1.jar;
            E:\Software\IntelliJ IDEA\IntelliJ IDEA 2019.1.2\lib\idea_rt.jar
             */
    

    我们能够发现,类在加载的时候,都是有自己的加载区域的,而不是任何地方的类都能够被加载

    获取运行时候类的完整结构

    通过反射能够获取运行时类的完整结构

    • 实现的全部接口
    • 所继承的父类
    • 全部的构造器
    • 全部的方法
    • 全部的Field
    • 注解
    /**
     * 获取运行时类信息
     * @author: 轻狂书生FS
     * @create: 2020-09-29-12:13
     */
    public class GetClassInfo {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
            Class clazz = Class.forName("com.moxi.interview.study.annotation.User");
    
            // 获取类名字
            System.out.println(clazz.getName()); // 包名 + 类名
            System.out.println(clazz.getSimpleName()); // 类名
    
            // 获取类属性
            System.out.println("================");
            // 只能找到public属性
            Field [] fields = clazz.getFields();
    
            // 找到全部的属性
            Field [] fieldAll = clazz.getDeclaredFields();
    
            for (int i = 0; i < fieldAll.length; i++) {
                System.out.println(fieldAll[i]);
            }
    
            // 获取指定属性的值
            Field name = clazz.getDeclaredField("name");
    
            // 获取方法
            Method [] methods = clazz.getDeclaredMethods(); // 获取本类和父类的所有public方法
            Method [] methods2 = clazz.getMethods(); // 获取本类所有方法
    
            // 获得指定方法
            Method method = clazz.getDeclaredMethod("getName", null);
    
            // 获取方法的时候,可以把参数也丢进去,这样因为避免方法重载,而造成不知道加载那个方法
            Method method2 = clazz.getDeclaredMethod("setName", String.class);
    
        }
    }
    

    双亲委派机制

    如果我们想定义一个:java.lang.string 包,我们会发现无法创建

    因为类在加载的时候,会逐级往上

    也就是说当前的系统加载器,不会马上的创建该类,而是将该类委派给 扩展类加载器,扩展类加载器在委派为根加载器,然后引导类加载器去看这个类在不在能访问的路径下,发现 sring包已经存在了,所以就无法进行,也就是我们无法使用自己自定义的string类,而是使用初始化的stirng类

    当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。

    采用双亲委派的一个好处是比如加载位于rt.jar 包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object 对象

    在这里插入图片描述

    有了Class对象,我们能够做什么?

    创建类的对象:通过调用Class对象的newInstance()方法

    • 类必须有一个无参数的构造器
    • 类的构造器的权限需要足够

    如果没有无参构造器就不能创建对象?

    只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。

    步骤如下:

    • 通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
    • 向构造器的形参中,传递一个对象数组进去,里面包含了构造器中所需的各个参数
    • 通过Constructor实例化对象

    调用指定方法

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

    • 通过Class类的getMethod方法取得一个Method对象,并设置此方法操作是所需要的参数类型
    • 之后使用Object invoke进行调用,并向方法中传递要设置的obj对象的参数信息

    Invoke方法

    • Object invoke(Object obj, Object … args)
    • Object对应原方法的返回值,若原方法无返回值,此时返回null
    • 若原方法为静态方法,此时形参Object 可以为null
    • 若原方法形参列表为空,则Object[] args 为 null
    • 若原方法声明private,则需要在调用此invoke() 方法前,显示调用方法对象的setAccessible(true)方法,将可访问private的方法

    setAccessible方法

    • Method和Field、Constructor对象都有setAccessible()方法
    • setAccessible作用是启动和禁用访问安全检查的开关
    • 参数值为true则指示反射对象再使用时应该取消Java语言访问检查
      • 提高反射效率,如果代码中必须使用反射,而这句代码需要频繁被嗲用,那么设置成true
      • 使得原本无法访问的私有成员也可以访问
    • 参数值为false则指示反射的对象应该实行Java语言访问检查

    在这里插入图片描述

    完整代码:

    /**
     * 通过反射获取对象
     *
     * @author: 轻狂书生FS
     * @create: 2020-09-29-12:43
     */
    public class GetObjectByReflectionDemo {
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
    
            // 获取Class
            Class clazz = Class.forName("com.moxi.interview.study.annotation.User");
    
            // 构造一个对象,newInstance调用的是无参构造器,如果没有无参构造器的话,本方法会出错
    //        User user = (User)clazz.newInstance();
    
            // 获取class的有参构造器
            Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class, int.class);
            User user2 = (User) constructor.newInstance("小溪", 10, 10);
            System.out.println(user2);
    
    
            // 通过反射调用普通构造方法
            User user3 = (User)clazz.newInstance();
            // 获取setName 方法
            Method setName = clazz.getDeclaredMethod("setName", String.class);
            // 执行setName方法,传入对象 和 参数
            setName.invoke(user3, "小白");
            System.out.println(user3);
    
            System.out.println("============");
            Field age = clazz.getDeclaredField("age");
            // 关闭权限检测,这样才能直接修改字段,因为 set方法不能直接操作私有变量
            age.setAccessible(true);
            age.set(user3, 10);
            System.out.println(user3);
    
        }
    }
    

    运行结果

    User{name='小溪', id=10, age=10}
    User{name='小白', id=0, age=0}
    ============
    User{name='小白', id=0, age=10}
    

    反射性能对比

    下面我们编写代码来具体试一试,使用反射的时候和不适用反射,在执行方法时的性能对比

    /**
     * 反射性能
     *
     * @author: 轻狂书生FS
     * @create: 2020-09-29-14:55
     */
    public class ReflectionPerformance {
    
        /**
         * 普通方式调用
         */
        public static void test01() {
            User user = new User();
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 1000000000; i++) {
                user.getName();
            }
            long endTime = System.currentTimeMillis();
    
            System.out.println("普通方式执行10亿次getName的时间:" + (endTime - startTime) + " ms");
        }
    
        /**
         * 反射方式调用
         */
        public static void test02() throws Exception {
            Class clazz = Class.forName("com.moxi.interview.study.annotation.User");
            Method getName = clazz.getDeclaredMethod("getName", null);
            User user = (User) clazz.newInstance();
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 1000000000; i++) {
                getName.invoke(user, null);
            }
            long endTime = System.currentTimeMillis();
    
            System.out.println("反射方式执行10亿次getName的时间:" + (endTime - startTime) + " ms");
        }
    
        /**
         * 反射方式调用,关闭权限检查
         */
        public static void test03() throws Exception {
            Class clazz = Class.forName("com.moxi.interview.study.annotation.User");
            Method getName = clazz.getDeclaredMethod("getName", null);
            User user = (User) clazz.newInstance();
            long startTime = System.currentTimeMillis();
            getName.setAccessible(true);
            for (int i = 0; i < 1000000000; i++) {
                getName.invoke(user, null);
            }
            long endTime = System.currentTimeMillis();
    
            System.out.println("反射方式执行10亿次getName的时间:" + (endTime - startTime) + " ms");
        }
        public static void main(String[] args) throws Exception {
            test01();
            test02();
            test03();
        }
    }
    

    运行结果:

    普通方式执行10亿次getName的时间:3 ms
    反射方式执行10亿次getName的时间:2554 ms
    反射方式执行10亿次getName的时间:1365 ms
    

    我们上面分别是执行了 10亿次 getName的方法,从里面可以看出,通过直接实例化对象后,调用getName耗时最短,同时关闭了 权限检查后的比不关闭能提高一倍的性能。

    反射操作泛型

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

    为了通过反射操作这些类型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是有何原始类型齐名的类型。

    • ParameterizedType:表示一种参数化类型,比如Collection
    • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
    • TypeVariable:是各种类型变量的公共父接口
    • WildcardType:代表一种通配符类型的表达式

    下面我们通过代码来获取方法上的泛型,包括参数泛型,以及返回值泛型

    /**
     * 通过反射获取泛型
     *
     * @author: 轻狂书生FS
     * @create: 2020-09-29-15:15
     */
    public class GenericityDemo {
    
        public void test01(Map<String, User> map, List<User> list) {
            System.out.println("test01");
        }
    
        public Map<String, User> test02() {
            System.out.println("test02");
            return null;
        }
    
        public static void main(String[] args) throws Exception{
    
            Method method = GenericityDemo.class.getMethod("test01", Map.class, List.class);
    
            // 获取所有的泛型,也就是参数泛型
            Type[] genericParameterTypes = method.getGenericParameterTypes();
    
            // 遍历打印全部泛型
            for (Type genericParameterType : genericParameterTypes) {
                System.out.println(" # " +genericParameterType);
                if(genericParameterType instanceof ParameterizedType) {
                    Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                    for (Type actualTypeArgument : actualTypeArguments) {
                        System.out.println(actualTypeArgument);
                    }
                }
            }
    
            // 获取返回值泛型
            Method method2 = GenericityDemo.class.getMethod("test02", null);
            Type returnGenericParameterTypes = method2.getGenericReturnType();
    
            // 遍历打印全部泛型
            if(returnGenericParameterTypes instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) returnGenericParameterTypes).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
    
        }
    }
    

    得到的结果

     # java.util.Map<java.lang.String, com.moxi.interview.study.annotation.User>
    class java.lang.String
    class com.moxi.interview.study.annotation.User
     # java.util.List<com.moxi.interview.study.annotation.User>
    class com.moxi.interview.study.annotation.User
    ###################
    class java.lang.String
    class com.moxi.interview.study.annotation.User
    

    反射操作注解

    通过反射能够获取到 类、方法、字段。。。等上的注解

    • getAnnotation
    • getAnnotations

    ORM对象关系映射

    ORM即为:Object relationship Mapping,对象关系映射

    • 类和表结构对应
    • 属性和字段对应
    • 对象和记录对应

    在这里插入图片描述

    下面使用代码,模拟ORM框架的简单使用

    /**
     * ORMDemo
     *
     * @author: 轻狂书生FS
     * @create: 2020-09-29-15:33
     */
    @TableKuang("db_student")
    class Student2 {
        @FieldKuang(columnName = "db_id", type="int", length = 10)
        private int id;
    
        @FieldKuang(columnName = "db_age", type="int", length = 10)
        private int age;
    
        @FieldKuang(columnName = "db_name", type="varchar", length = 10)
        private String name;
    
        public Student2() {
        }
    
        public Student2(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 "Student2{" +
                    "id=" + id +
                    ", age=" + age +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
    /**
     * 自定义注解:类名的注解
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface TableKuang {
        String value();
    }
    
    /**
     * 自定义注解:属性的注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface FieldKuang {
        String columnName();
        String type();
        int length() default 0;
    }
    public class ORMDemo {
    
        public static void main(String[] args) throws Exception{
            // 获取Student 的 Class对象
            Class c1 = Class.forName("com.moxi.interview.study.annotation.Student2");
    
            // 通过反射,获取到全部注解
            Annotation [] annotations = c1.getAnnotations();
    
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
    
            // 获取注解的value值
            TableKuang tableKuang = (TableKuang)c1.getAnnotation(TableKuang.class);
            String value = tableKuang.value();
            System.out.println(value);
    
            // 获得类指定的注解
            Field f = c1.getDeclaredField("name");
            FieldKuang fieldKuang = f.getAnnotation(FieldKuang.class);
            System.out.println(fieldKuang.columnName());
            System.out.println(fieldKuang.type());
            System.out.println(fieldKuang.length());
        }
    }
    

    总结

    点赞+关注,多谢。

    展开全文
  • Java及spring 注解(反射原理)

    万次阅读 2018-08-14 00:55:28
    java注解:  Java5.0以后引入了注解的概念  注解只在源码阶段保留(编译时忽略),不会影响程序内部的东西,决定运行级别 是一个标识  定义一个注解就可以在其他类中使用 1.元注解:注解上面的注解  (1)@...

    java注解:

           Java5.0以后引入了注解的概念

            注解只在源码阶段保留(编译时忽略),不会影响程序内部的东西,决定运行级别 是一个标识

            定义一个注解就可以在其他类中使用

    1.元注解:注解上面的注解

            (1)@ Retention(...)

                            

                           

        (2)@Documented

                          

    (3).@Target------注解运行的地方(构造函数/属性/方法上)

                            

    (4).@Inherited

                         

    (5)@Repeatable(...) 可多次重复使用--可以将多个值重复赋给容器

     

    注解的属性:也叫成员变量,注解只有成员变量,没有方法

     注解的成员变量在注解中以“无形参的方法”形式来声明,其方法名定义该成员变量的名字,其返回值定义了该成员变量的类型;

      给注解的属性赋值:

    当一个注解(RunAnnotation2)没有属性---它只是一个标识 ()可以省略

                      

                     

                      

     

    Java预用(本来就有)注解:

         @Deprecated:过时的方法或者类(还可以用 只是有了更好的升级)

         @Override:方法重载

         @SuppressWarnings(“要忽略的内容”):阻止警告

         @SafeVarargs:参数安全类型

         @FunctionalInterface:函数式接口注解

     

    注解与反射:

    注解通过反射获取(通过反射获取注解信息), 通过类 方法名名字获取

    mvc在运行时在handlemapping会读你的注解 拿到你的注解的值 拼成一个请求发送到DispaterServlet或者controller

    自定义注解:可以定义一个static 方法获取注解,,然后控制程序的走向

    package com.spring.annotation;
    
    @TestAnnotation(id="666",message = "wxx")
    public class Test {
    
        //只有当id=666时才访问到这个方法
        private void test(){
            System.out.println("test run");
        }
    
        public void runTest()
        {
            //获取类的注解
            TestAnnotation t=this.getClass().getAnnotation(TestAnnotation.class);
            if(t.id().equals("666"))
            {
                this.test();
            }
        }
    
    }
    

     

     只要实例化对象就判断是否加了该注解????

          使用注解--- 反射原理(需要把注解加到多个类或者多个方法)判断逻辑

                         

                

     

    spring 注解:

    自动注入:成员变量上

         @Autowired:自动注入 默认ByType (包/接口的实现类)找,如果找到有多个bean 再按ByName查找

                           (用你注入的类型去匹配你定义的bean 当匹配到多个时 根据 唯一的name再进行匹配  多个则异常)

                              

      @Qualifier("name"):名字注入
    

                                

          @Resource:成员变量

                                  

     @Singleton:单例类

     @RequestBody:可以放协议上的内容

                 

    @RequestParam:获取参数

                   

             

    @RequestMapping:请求映射

                     

     @ResponseBody:返回字符串 去掉后返回原先视图指定的页面

     @Controller:声明控制器  控制层

     @Serviceservice层

     @Repository相当于定义成bean 注入时便于分层

     @Component分层

                    

     

     ps:spring 的注解很强大,其应用机制主要就是反射原理  通过获取对象做判断逻辑;


     

    展开全文
  • 好程序员Java培训分享注解反射原理说明,首先注解若想发挥更大作用,还需借助反射机制之力。通过反射,可以取得一个方法上声明的注解的全部内容。  一般有两种需求:  1、取得方法中全部的注解,通过调用...

    好程序员Java培训分享注解与反射原理说明,首先注解若想发挥更大作用,还需借助反射机制之力。通过反射,可以取得一个方法上声明的注解的全部内容。
      一般有两种需求:
      1、取得方法中全部的注解,通过调用getAnnotations来实现。
      2、判断操作是否是指定注解,通过调用getAnnotation来实现。
      下面从源码角度来说明怎样获取这些注解信息。
      源码导读——取得方法中全部的注解

    public class AccessibleObject implements AnnotatedElement {

    //取得全部Annotation
    public Annotation[] getAnnotations() {
    return getDeclaredAnnotations();
    }

    }
    public final class Method extends Executable {

    public Annotation[] getDeclaredAnnotations() {
    //针对Method类,需要调用父类的getDeclaredAnnotations方法
    return super.getDeclaredAnnotations();
    }

    }
    //Method的父类Executable的getDeclaredAnnotations实现全部注解信息的获取
    public abstract class Executable extends AccessibleObject
    implements Member, GenericDeclaration {

    public Annotation[] getDeclaredAnnotations() {
    return AnnotationParser.toArray(declaredAnnotations());
    }

    }

    源码导读——判断操作是否是指定注解

    public final class Method extends Executable {

    取得指定Annotation
    public T getAnnotation(Class annotationClass) {
    return super.getAnnotation(annotationClass);
    }

    }
    public abstract class Executable extends AccessibleObject
    implements Member, GenericDeclaration {

    public T getAnnotation(Class annotationClass) {
    Objects.requireNonNull(annotationClass);
    //获得指定注解类的信息
    return annotationClass.cast(declaredAnnotations().get(annotationClass));
    }

    }

    展开全文
  • Java注解和反射(二)Calss对象

    万次阅读 2021-02-24 11:39:54
    Java让我们在运行时识别对象类的信息,主要有2种方式:一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型信息;另一种是反射机制,它允许我们在运行时发现使用类的信息。 二、反射的对象 1、通过类来...
  • 一文掌握Java注解和反射-你总该用过@Override吧?

    千次阅读 多人点赞 2021-03-19 10:41:08
    注解不是程序本身,可以对程序作出解释说明(这一点注释(comment)没什么区别)。 反射(Reflection)机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。 双亲...
  • Java注解和反射(三)类加载过程

    万次阅读 2021-03-01 10:36:57
    学习了类加载过程,可以更加方便我们理解反射的机制和原理。没兴趣的同学可直接跳过该篇。 一、类加载过程 1、加载(load) 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,...
  • java中有7种自带的注解,包括@Override, @Deprecated, @SuppressWornings 四种元注解(表示注解注解)。jdk7后,逐步增加为10种。 (1)按来源分 JDK 自带注解; 第三方注解,如Spring框架中的...
  • 注解: Annotation其实就是代码里的特殊标记, 它用于替代配置文件,也就是说,传统方式通过...它们都不会直接影响到程序的语义,只是作为注解(标识)存在,我们可以通过反射机制编程实现对这些元数据(用来描述数据的
  • 反射原理:每一个类都有一个Class对象。所有类都是在第一次使用时,动态加载到JVM中,当程序创建第一个对类的静态成员引用时,会加载这个类。一旦你一个类的Class对象被载入到内存,它便被用来创建这个类的所有对象...
  • 下面添上一段利用java反射技术阻止通过按钮关闭对话框的代码 package crazypebble.androidreflection; import java.lang.reflect.Field; import android.app.Activity; import android.app.AlertDialog; ...
  • java 1.5开始引入了注解和反射,正确的来说注解反射的一部分,没有反射注解无法正常使用,但离开注解反射依旧可以使用,因此来说,反射的定义应该包含注解才合理一些。 当然,这只是个人想法,至于java官方为...
  • 深入理解java注解的实现原理(转载)

    万次阅读 多人点赞 2018-10-23 14:22:11
    转自:深入理解java注解的实现原理 今天将从以下4个方面来系统的学习一下java注解 什么是注解 注解的用途 注解使用演示 注解的实现原理 1,什么是注解 注解也叫元数据,例如我们常见的@...
  • 实现基本原理就是通过java中的注解反射实现,本文主要介绍java中的反射机制和自定义注解原理和实例编写。2、反射机制 定义:反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于...
  • java注解的实现原理

    千次阅读 多人点赞 2020-06-10 09:37:44
    Java注解用于为Java代码提供元数据。 元数据是指用来描述数据的数据,通俗一点,就是描述代码间关系,或者代码与其它资源(例如数据库表)之间内在联系的数据。在一些技术框架中,如Struts、hibernate就不知不觉...
  • Java注解的实现原理

    2020-09-04 23:00:52
    Java注解实际上只是对包、类、方法、成员变量等java程序进行标注。其本身没有业务逻辑、要实现注解相应的业务逻辑功能必须由另外的处理类来实现。 其基本原理就是通过java反射机制,获取这些java程序的包、类、方法...
  • java注解——实现原理

    千次阅读 2018-03-31 21:45:49
    Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。 当然它也支持自定义Java标注。 内置注解 ...
  • 再说说ButterKnife吧,其实原理和Android Annotations差不多,只是一些写法和细节上有略微差别,最主要一点是没有Android Annotations的坑,Android Studio上有个ButterKnife的插件,这个插件提供的炫酷技能几乎是...
  • Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。
  • JAVA 注解的基本原理

    2018-08-08 14:27:48
    以前,『XML』是各大框架的青睐者,它以松耦合的方式完成了框架中几乎所有的配置,但是随着项目越来越庞大,『XML...关于『注解『XML』两种不同的配置模式,争论了好多年了,各有各的优劣,注解可以提供更大的便...
  • Java注解的基本原理

    2020-05-09 13:17:10
    什么是Java注解 在注解出现之前,xml配置以其松耦合的优势一度成为各大框架的主流配置方式,但是随着项目规模越来越大,xml配置也越来越复杂,维护成本逐渐变高。于是有人就提出一种标记式高耦合的配置方式—注解。注解...
  • 深入理解java注解的实现原理

    千次阅读 2019-11-13 16:58:08
    前言 在Spring 2.0及早期时代,Web项目开发是通过配置文件 xml来实现 Bean 的依赖注入,有多少个Bean,就在xml配置问价中加多少个,这样一来在 Bean 的数量越来越多的时候,xml的配置也就...深入理解Java注解原理
  • 前面讲解了如何自定义注解,但是所定义的注解并没有起到应有的作用,比如@SuppressWarnings自定义注解并不会取消警告,那么注解究竟如何工作的呢?...2.注解工作原理的实质是反射知识的应用。   代码2   ...
  • java注解工作原理

    2020-09-25 17:10:22
    java注解工作原理 1、什么是注解 注解是在jdk1.5版本引入的新特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量进行注解,注解也叫元数据,即是一种描述数据的数据,常见的注解有@override...
  • java中的注解我们已经见过非常多了,现在来深入了解一下。 什么是注解(Annotation) Annotation是从JDK5.0开始引入的新技术.(现在不算新了) Annotation的作用: 不是程序本身,可以对程序作出解释(这一点注释...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 38,078
精华内容 15,231
关键字:

java注解和反射原理

java 订阅