Java基础之注解与反射

什么是注解?


  • Annotation是从JDK5.0开始引入的新技术
  • Annotation的作用:
    • 不是程序本身,可以对程序作出解释
    • 不可被其他程序(比如:编译器)读取
  • Annotation在哪里使用?
    • 可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问!

元注解


  • 元注解的作用是负责注解其他注解,Java定义了4个标准的meta-annotation类型,它们被用来提供对其他annotation类型说明。
  • 这些类型和它们所支持的类在java.lang.annotation包中可以找到(@Target , @Retention , @Document , @Inherited
    • @Target :用于描述注解的使用范围(即:被描述的注解可以用在什么地方,如:类,方法,构造器上等)
    • @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE(源码)RUNTIME)

    @Retention自己写的注解一般是使用RUNTIME,即当运行时使用该注解。

    • @Document :说明该注解将被包含在Javadoc中,即是否将注解生成在Javadoc
    • @Inherited:说明子类可以继承父类中的该注解

自定义注解


  • 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
  • 分析:
    • @interface来声明一个注解,格式:public @interface 注解名 { 定义的内容}
    • 其中每一个方法实际上是声明了一个配置参数
    • 方法的名称就是参数的名称
    • 返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)
    • 可以通过default来声明参数的默认值
    • 如果只有一个参数成员,一般参数名为value,使用该注解写参数时value可以省略
  • 举例:
public class Test{
  @MyAnnotation(name="cwx")
  publice void test(){}
}

@Target(value={ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
  String name();//注意,这里的name()不是方法,就是参数!
  int age() default 18;//由于这里有默认值,所以注解可以不用写这个参数,如果没有就必须要写参数!
}

动态语言 VS 静态语言


  • 动态语言

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

  • 主要的动态语言:Object-C、C#、JavaScript、PHP、Python等。
  • 静态语言:

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

  • ★虽然Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,可以利用反射机制获得类似动态语言的特性。

Java Reflection (反射)

  • Reflection是Java被视为动态语言的关键,反射机制允许程序在执行时借助于Reflection API取得任何类内部的信息,并能直接操作对象的内部属性及方法. 但是由于反射相对于JVM是解释型操作,性能会比正常的访问慢!

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

  • 加载完类之后,在堆内存中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们称之为:反射。
  • 举例:
    正常方式:引入需要“包类”名称--->通过new实例化--->取得实例化对象
    反射方式:实例化对象--->getClass()方法--->得到完整的“包类”名称。

Java内存分析:

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

类的加载与ClassLoader的理解:

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

什么时候回发生类初始化?

  • 类的主动引用(一定会发生类的初始化):
    1、 当虚拟机启动,先初始化main方法所在的类
    2、new一个类的对象
    3、调用类的静态成员(除了final常量)和静态方法
    4、使用java.lang.reflect包的方法对类进行反射调用
    5、当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
  • 类的别动引用(不会发生类的初始化):
    1、当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量或静态方法,不会导致子类初始化
    2、通过数组定义类引用,不会触发此类的初始化,例如:

Test[ ] test = new Test[5];//Test是一个类,这里是通过数组的方式引用

3、引用常量不会触发此类的初始化(常量在链接阶段就存入了调用类的常量池中了)。

  • 类加载器的作用:
    • 类加载器作用是用来把类(class)装载进内存的

有了Class对象,能做什么?

  • 获得Class对象:通过反射,通过已经实例化的对象.getClass()等。
  • 创建类的对象:调用Class对象的newInstance()方法,需要强转类型.
    1)类必须有一个无参数的构造器。
    2)类的构造器的访问权限需要足够。

难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确调用类中的构造器,并将参数传递进去之后,才可以实例化操作,例如:

主类:
public class Test{
  public static void main[ String[] (args) throw ClassNotFoundException, IllegalAccessException{
    Class c1 = Class.forName("com.cn.cwx.User");  
    //构造一个对象
    User user = (User) c1.newInstance();//本质是调用了类的无参构造器
    //通过构造器创建对象
    Constructor constructor = c1.getDeclaredConstructor(String.class,int.class,int.class);
    User user = (User)constructor.newInstance("cwx",001,18);
    //通过反射调用普通方法
    User user3 = (User)c1.newInstance();
    //通过反射获取一个方法,(方法名,方法参数的类类型)若方法没有参数,则写null
    Method setName = c1.getDeclaredMethod("setName",String.class);/*
    为什么要加String.class?
    因为程序需要通过该方法的参数类型去寻找方法,
    在方法中有可能会出现重载,如果不加String.class会让程序不知道找哪个方法*/

    //invoke:激活的意思
    //invoke(对象,方法的参数)若方法没有参数,则写null
    setName.invoke(user3,"cwx");
      //通过反射操作属性
      User user4 = (User)c1.Instance();
      //获得属性的名称获得属性
      Filed name = c1.getDeclaredFiled("name");
      //不能直接操作私有属性或__私有方法__,必须关闭程序的安全检测
      name.setAccessible(true);//利用`setAccessible(true)`保证安全检测关闭
      name.set("cwx2");
      //通过`user4.getName()`获得操作后的属性
}
}
User类:
public class User{
  private String name;
  private int id;
  private int age;
   有参构造;
   无参构造;
   get和set方法;

}

Tips: 如果程序里面用了很多的反射,效率很低,可以利用setAccessible(true)方法提高效率!

了解什么是ORM?

  • Object Relationship Mapping--->对象关系映射
    • 类(类名)和表结构(表名)对应
    • 属性和字段对应
    • 对象和记录对应
  • 举例:利用注解和反射完成类和表结构的映射关系
public class Test1{
  public static void main[ String[] (args) throw ClassNotFoundException, IllegalAccessException{
    
  Class c1 = Class.forName("com.cwx.student");
  //通过反射获得注解
  Annotation[] annotation = c1.getAnnotations();
  //获得注解的value的值
  Table table = (Table)c1.getAnnotation(Table.class);
    String value = table.value();

}
@Table("db_student")
class Student{
  @FieldTest(columnName="db_id",type="int",length=10)
  private int id;
  @FieldTest(columnName="db_age",type="int",length=20)
  private int age;
    get和set方法;
    有参和无参构造;
}
//自定义类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
  String value();//数据库名称
}
//自定义属性的注解
@Target(ElementType.Field)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldTest{
  String columnName();//列名
  String type();
  int length();
}

Tips: 这个例子中可以看出,其实注解是和数据库打交道的,由于可以通过反射方法获得注解的值,也就是数据库中的内容,这其实就现了类和表结构的映射关系,很多框架里面的注解都类似于这种。

你可能感兴趣的:(Java基础之注解与反射)