注释与反射

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


注释和反射

注释

  1. 注释 : annotation

    注解:comment

  2. annotaion的作用:

    • 不是程序本身,但可以对程序做出解释(与comment 类似)
    • 可以被其他程序读取,如编译器。
  3. annotation 在那里使用?

    • 可以附加在package,class,method,field 等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制实现对这些元数据的访问。
  4. 内置注解

    • @Deprecated,自己写的任何方法都可以添加此注解,添加此注解后,在其他地方使用此方法时就会将此方法划去,但是仍然可以使用,知识不建议而已。
    • 咱们在平时开发的过程中如果遇到有问题的逻辑方法等需要重新写时,而此方法又无法直接删除,就可以直接在上面加上此注解。
    • @SuppressWarnings(“all”)镇压全部警告信息。
  5. 元注解(meta-annotation)

    • 负责注解其他注解

    • 四个:

      • @target:描述注解的适用范围,如class,method,field,

      • @Retention:表示在什么 级别需要保存该注释,用以描述注释的生命周期(source < class < runtime)

      • @Documented:是否将我们的注解生成在javadoc中

      • @Inherited:子类可以继承父类的注解

  6. 自定义注解

    • @interface 来声明一个注解
  • 其中每一个方法实际上是声明了一个配置参数
    • 方法的名称就是参数的名称
  • 返回值类型就是参数的类型,只能是基本类型,class,string,enum
    • 可以通过default声明默认参数,String name() default “”;
  • 如果只有一个参数,一般参数名字为value,value=xx,如果只有一个值,value也可以省略掉
    • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串或者0作为默认值
  • 如果int id() default -1,如果默认值为-1,代表不存在

接下来学习使用反射获取注解,这两个配合才是天衣无缝的

反射机制(Reflection)

  1. 静态语言和动态语言

    • 动态语言:在运行时可以改变其结构:object-c,c#,js
      var x=var a=3var b=4;alert(a+b)”;
      eval(x);
    
    • 静态语言:运行时不可以改变其结构:java,c,c++
    • java不是动态语言,但java可以称为准动态语言,即java具有一定的动态性,我们可以利用反射机制获取类似动态语言的特性。java 的动态性让其编程的时候更加灵活!
  2. 概念

    • 反射机制允许程序在执行期间借助Reflection API 获取任何类的内部信息,并能随意操作任意对象的内部属性和方法,private的类一般来说只有get set方法才能访问,但是反射可以直接访问。
    • Class c1 = Class.forName(“包名.类名”),这样即是通过反射获取了这个类的Class对象
    • jvm 加载完类之后,在堆内存的方法区中就会产生一个class类型的对象(一个类只有一个class对象),这个对象就包含了完整的类的结构信息,我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为反射。
    • 正常方式:引入需要的包类名称,通过new实例化,获取实例化对象
    • 反射方式:通过反射获取实例化对象,getClass()方法获取class对象,通过class对象获取包类名称等所有信息。
  3. java反射机制提供的功能

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

    • 优点:可以实现动态创建对象和编译,体现出很大的灵活性
    • 缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉jvm,我们可以告诉jvm,我们希望做什么并且他满足我们的要求。这类操作总是慢于直接执行相同的操作
    • new出来的和反射获取的对象速度差了几十倍这个数量级
  5. 反射相关的主要API

    • java.lang.class : class类,class本身就是一个类
  6. 学习:-

    • 实体类,pojo,entity,与数据库实体相对应,写toString主要是为了方便测试
    • 通过 hascode() 方法判断两个对象是否相等。
    • 一个类被加载后,整个类的所有结构都会被封装在Class对象中
    • 又由于所有类都继承Object类,所以Object类中的所有方法都可以使用,其中有一个getClass
    • 按住alt在选择区域可以按列进行复制
  7. Class 类

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

    • Class c = Person.class ,这个最高效,最安全可靠,因为提前可以知道返回的是什么类
    • Class c1 = person.getClass()
    • Class c2 = Class.forName(“类名的完整路径”)
    • Class c3 = Integer.TYPE
    • Class c5 = c1.getSuperClass(); //获取父类实例
  9. 哪些类型可以有class 对象

    • class
    • interface
    • [] 数组
    • enum
    • annotation
    • primitive type 基本数据类型
    • void
  10. java 内存分析

    • 堆 :
      • 存放new的对象和数组
      • 可以被所有线程共享,不会存放别的对象引用
      • 存放基本变量类型,会包含这个基本类型的具体数值
      • 存放引用对象(new出来的)的变量,会存放这个引用在堆里面的具体地址
    • 方法区
      • 相当于一个特殊的堆
      • 可以被所有线程共享
      • 包含了所有的class 和 static 变量,class中包含静态变量,静态方法,常量池和代码等
  11. 类的加载过程

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

    • 类的加载 load
      • 将类的class 文件读入内存,并为之创建一个class对象(反射的那个对象),此过程由类加载器完成。
      • 详细:将javac生成的字节码文件加载到内存中,并将这些静态数据转化成方法区的运行时数据结构
    • 类的链接 Link
      • 将类的二进制文件合并到JRE 中
      • 详细:
        • 验证:确保加载的类信息符合jvm规范
        • 准备:正式为类变量(static)分配内存并设置初始值,这些都将在方法区中进行分配。静态变量初始值int为0.代码里边给static赋值是在初始化的时候进行
        • 解析:jvm中常量池的符号引用更换为直接地址引用,常量
    • 类的初始化 Initialize
      • JVM对类进行初始化
      • jvm对类进行初始化时,就是通过() {} 方法会将静态方法和静态变量等静态代码合并
      • 初始化一个类时,如果发现其父类还未初始化,则需要先触发其父类的初始化。
  12. 什么时候会发生类的初始化

    • 类的主动引用
      • 虚拟机启动时,先初始化main方法所在的类
      • new一个类的对象
      • 调用类的静态成员和静态方法(除了final常量)
      • 使用reflect包的方法对类进行反射操作
      • 当初始化一个类时,如果其父类没有初始化,则会先初始化他的父类
    • 类的被动引用(不会发生类的初始化)
      • 引用常量不会出发类的初始化,应为常量在链接阶段就存在调用类的常量池中了,也就是方法区中(类信息加载在方法区中,每个类信息中有一个区域叫做常量池)
      • 通过数组定义类的引用,不会触发类的初始化
  13. 类加载器的作用:

    • 将class字节码文件加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后堆中生成一个代表这个类的java.lang.class对象,作为方法区中类数据的访问入口
    • 类缓存:标准的javase类加载器classloader可以按照要求查找类,但一旦某个类被加载到类加载器中,他将维持加载缓存一段时间,不过gc可以回收这些class对象。提高效率
    • jvm规范定义了如下类型的类的加载器:
      • 引导类加载器:用c++编写,负责加载java平台核心库rt.jar
      • 扩展类加载器:加载jre/lib/ext目录下的jar包
      • 系统类加载器:最常用的类加载器,咱们平时写的类同时通过此加载器加载
      • 系统类加载器的父加载器是扩展类加载器,扩展类加载器的父加载器是引导类加载器(根加载器,系统无法直接获取)
    • 双亲委派机制:用于自己定义了一个java.lang.String,jvm会一层一层的类加载器向上检查,先检查系统类加载器有没有这个类,在检查扩展类加载器有没有这个类,在检查引导类加载器有没有这个包类,如果有的话,则此类无法生效,使用原先定义的包类,这就是双亲委派。
  14. 例子:

    Class class = Class.forName("包名.类名")  //这样即是通过反射获取了这个类的Class对象
    Class c1 = Person.class //这个最高效,最安全可靠,因为提前可以知道返回的是什么类
    Class c2 = person.getClass()  //通过对象获取
    Class c3 = Class.forName("类名的完整路径")   
    Class c4 = Integer.TYPE   //基本内置类型都有一个Type属性,只有基本内置的可以
    Class c5 = c1.getSuperClass(); //获取父类实例
    
  15. 获得类得信息

    Class c1 = Class.forName("com.kuang.reflecttion.User");
    User user = new User();
    Class c2 = user.getClass();
    c1.getSimpleName();  //User
    c1.getName(); //com.kuang.refection.User
    /**
    private java.lang.String com.kaung.reflection.User.name
    private int com.kuang.reflection.User.id
    此方法可以找到所有属性,包含private
    */
    Field[] field = c1.getDeclareFields();  
    
    /*
    此方法可以找到public属性
    */
    Field[] field = c1.getFields();
    
    Field name = c1.getFields("name") //获取指定属性的值 Field,只能获取public的
        
    //获取类的方法
    c1.getMethods();   //获取本类即父类的所有public方法 , 不包含private
    c1.getDeclareMethods();   //获取本类的所有方法,包含private
    
    //获取指定方法,需要参数来判断重载的函数
    c1.getMethod("getName",null);
    c1.getMethod("setName",String.class);
    
    //获取指定的构造器
    c1.getConstructors(); // 获取所有的public的构造器
    c1.getDeclaredConstructors(); //获取本类所有的构造器,private 和 public
    
    //获取指定的构造器,传入构造器需要的参数来区分重载的函数
    c1.getDeclaredConstructions(String.class,int.class,int.class)
    
  16. 有了class对象有什么用?动态执行方法

    可以根据class 对象创建一个类对象

    Class c1 = Class.forName("com.kuang.reflection.User");
    User user = (User)c1.newInstance();   //本质是调用了无参构造器,User类必须有无参构造器,且构造器权限必须足够
    //if not exist non-para construction
    //build object through construction
    Constructor constructor = c1.getDeclaredConstructior(String.class,int.class,int.class);
    User user=(User)constructor.newInstance("11",1,1);
    
    //use method through reflection
    User user3 = (User)c1.newInstance();
    Method setName = c1.getDeclaredMethod("setName",String.class);
    setName.invoke(user3,"zly");  //  激活invoke(object,value)
    
    //user attribute through reflection
    Field name = c1.getDeclaredField("name");
    User user4 = (User)c1.newInstance();
    name.setAccessible(true); // we can access private attributes through reflection ,通过此方法关闭安全检测
    name.set(user4,"zly"); 
    
    • Method , Field , Constructor 对象都有setAccessible()方法,用来启动和禁用安全检查的开关
    • 参数值为true则指示反射的对象在使用时应该取消java语言访问检查
      • 提高反射的效率。如果代码中必须使用反射,而该句代码需要频繁的被调用,那么请设置为true,public的方法也是如此
      • 使得private的方法也可以访问
  17. 性能对比

    | 普通方式调用 | 反射方式调用 | 反射方式调用,关闭检测 |
    | ------------ | ------------ | ---------------------- |
    | 1            | 570          | 200                    |
    
    反射方式相比普通方式慢了几百倍数量级,关闭检测可以使得反射的速度提高个3倍左右。
    
  18. 获取泛型信息

    • 反射操作泛型 : java 采用泛型擦除的机制来引入泛型,java 中的泛型仅仅是给编译器 javac 使用的,确保数据的安全性和强制类型转换的问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除

    • 在泛型之前,通用程序(例如排序方法需要满足不同的数据类型,如整数,浮点数等)的设计是利用继承来实现的,例如ArrayList类只维护一个Object 引用的数组,Object为所有类的基类,这样就可以往其中传入任意Object的子类的数据。但是有两个问题

      • 我们获取值得时候,必须将Object强制转换为我们需要得类型,
      • 上述强制转换编译不会报错,但如果我们不小心添加了不同得类型在arrayList中,此时在做转换就会运行时报错 java.lang.classcastexception.
    • 如果程序有潜在得错误,我们更期望在编译时被告知错误,而不是在运行时报告异常。

    • /**
      泛型擦除,见下例
      虽然我们定义了两个不同类型得arraylist,但是在编译之后,泛型信息被擦除,都会变成List 类,比较他们得类信息返回true。
      */
      public class GenericType {  
          public static void main(String[] args) {    
              ArrayList<String> arrayString=new ArrayList<String>();     
              ArrayList<Integer> arrayInteger=new ArrayList<Integer>();     
              System.out.println(arrayString.getClass()==arrayInteger.getClass());  //true  
          }    
      } 
      
    • 通过反射获取泛型

      getGenericParameterTypes()  //获取泛型参数化类型
      generic //泛型
      genericParameterType instanceof ParameterizedType //获取得类型是不是一个参数化类型,即泛型类型,如Map《string,int》 map。
      getActualTypeArguments() //获取真实参数信息
          
      getGenericRetuenType()  //得到泛型返回值类型
      
  19. 反射操作注解

    • ORM

      • Object relationship Mapping --> 对象关系映射
      • 将java 里面得类映射称为数据库得表
        • 类和表结构对应
        • 属性和字段对应 :
        • 对象和字段对应 : java中每个对象就对应数据库中得一行(一条记录)
      • 利用注解和反射可以完成表结构得映射
    • getAnnotations

    • getAnnotation

    • Class c1 = Student.class;
      //获取类指定得注解
      Field f = c1.getDeclaredField("id"); // 通过反射得到id属性字段
      FieldKuang annotation = f.getAnotation(FieldKunag.class);
      annotation.columnName();   //注解里面得具体值
      annotation.type();    
      annotation.length();
      
      
      //获取注解得value
      TableKuang tablekaung = (TableKuang)c1.getAnnotation(TableKuang.class);
      String value = tablekuang.value();
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值