2021-10-12 JAVA注解与反射详解

 目录

注解

一、注解入门

二、内置注解

三、元注解

四、自定义注解

反射

一、反射概述

二、Class 类

三、获取Class类的实例

四、所有类型的class对象

五、类加载内存分析

六、如何在运行时获得类的完整结构

七、动态创建对象执行方法

八、反射操作泛型

九、反射获取注解信息

注解

一、注解入门

作用

  • 不是程序本身,可以对程序作出解释

  • 可以被其它程序(编译器)读取

  • 有检查和约束作用

格式:@注释名(参数可加)

使用在package class method field上,相当于给他们添加了额外的辅助信息

我们可以通过反射机制编程实现对这些元数据的访问

二、内置注解

  • @Override:定义在java.lang.Override中,此注释只用于修辞方法,表示一个方法声明打算重写超类的另一个方法声明

  • @Deprecated:定义在java.lang.Deprecated,可用于修辞方法、属性、类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择

  • @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息,需加参数

public class Test01 extends Object{
   @Override
   public String toString(){
       return super.toString();
  }
   @Deprecated
   public static void test(){
       System.out.println("Deprecated");
  }
   @SuppressWarnings("all")
   public void test02(){
       List list=new ArrayList();
  }
   public static void main(String[] args) {
       test();
  }
}

三、元注解

作用:负责注解其他注解,对其它annotation类型进行说明

在java.long.annotation中@Target @Retention @Documented @Inherited

//定义一个注解
//Target 表示我们的注解使用范围   可以用在方法上 或者其他位置,继续加就是
@Target(value =ElementType.METHOD,...); 
//Retention 表示需要什么级别保存该注释信息,描述注解的生命周期 (Retention >class >Source)
@Retention 
//Document 表示是否将我们的注解生成在JAVAdoc中
@Document
//Inherited 说明子类可以继承父类的注解
@Inherited
​

四、自定义注解

格式:public @interface A{...}

pubic class Test01{
   //注解可以显示赋值 如果没有赋值,我们就必须给注解赋值
   @MyAnnotation(name = "MakerLu",age = 18)     //自定义注解参数名字没顺序 随便你怎么写
   publice void test(){}
}
@interface MyAnnotation{         //用interface来定义注解
   //注解的参数:参数类型 + 变量名();
   String name()   default "";         //default这里可以不用加 表示的是 不写注解名字的话 默认为什么什么... 
   int age() default 0;
   int id() default -1;             //如果默认值为-1 则表示不存在
   String[] schools() default {"武汉大学"};
​

反射

一、反射概述

静态语言:运行不可以改变结构 c,c++,java(反射可赋予其动态性)

动态:运行时结构可变,如JavaScript

function f(){
 var x="var a=3;var b=5;alert(a+b)";
 eval(x);//运行时改变了x
}

反射实际上就是java视为动态语言的关键,借助反射,我们可以在代码执行期间,通过Class类操纵任何类的内部信息,比如内部属性或方法,甚至可以去操作私有属性!(可读取private)

(但是对性能有影响!)

加载完类之后,在堆内存的方法区就产生了一个class类型的对象(一个类只有一个class对象),这个对象就包含了完整的结构信息,我们可以通过这个对象看到类的结构。

  • 正常方式:引入需要的包——通过new实例化——获取实例化对象

  • 反射方式:实例化对象——getClass()方法——得到完整的“包类”名称

Class类 的概念下面讲!

Java反射机制提供的功能:

  • 在运行时判断任意一个对象所属的类

  • 在运行时构造任意一个类的对象

  • 在运行时判断任意一个类所具有的成员变量和方法

  • 在运行时调用任意一个对象的成员变量和方法

  • 在运行时获取泛型信息

  • 在运行时处理注解

  • 生成动态代理

二、Class 类

注意Class 的首字母大写,代表一种类!(小写class 有其它作用)

首先呢,本身也是一个对象!由系统创建,在加载器将你写的代码定义的类加载到堆内存中的时候,就创建了这个对象。而它呢,是用来描述你写的代码中类的结构的 (比如:属性,构造器,方法,接口,枚举,注解….),你写的类不论创建(new)多少对象,这些对象都只拥有一个Class 对象!而我们呢,就可以通过对象的反射指向这个Class对象,得到这个Class对象,从而在执行期间去改变或者去获取关于你写的这个类的信息

  • Class类对应一个 JVM中的一个Class实例

  • 一个Class对象对应的是一个加载到JVM中的一个.class文件

三、获取Class类的实例

public class A {...}
A a = new A()
  • Class c1 = A.class;//已知具体类,通过类的class属性

  • Class c2 = a.getClass();//已知类的某个实例,调用getClass获得class对象

  • Classs c3 = Class.forName(“xxx.xx.A”);//已知一个类的全类名,在该类路径下,通过forname,注意抛出异常

  • 基础类型的 Class c4 = Integer.TYPE;

  • 通过ClassLoader获得

public class Test03 {
   public static void main(String[] args) throws ClassNotFoundException{
       Person person=new Student();
       System.out.println("是"+person.name);
       //通过对象获得
       Class c1=person.getClass();
       System.out.println(c1.hashCode());
       //forname获得
       Class c2=Class.forName("com.ic.reflection.Student");
       System.out.println(c2.hashCode());
       //通过类名.class获得
       Class c3=Student.class;
       System.out.println(c3.hashCode());
       //基本内置类型的包装类都有一个TYPE属性
       Class c4=Integer.TYPE;
       System.out.println(c4);
       //获得父类类型
       Class c5=c1.getSuperclass();
       System.out.println(c5);
  }
}

四、所有类型的class对象

只要元素类型与维度一样,就是同一个class

img

五、类加载内存分析

主要分为三个:

  1. 栈内存 : 存放基本变量类型 ,以及对象的指针

  2. 堆内存: 主要存放new的对象和数组,可以被所有线程共享

  3. 方法区: 包含了所有class和static变量的数据结构,可以被所有进程共享

img

1、关于一个类的加载过程

主要分三步:

加载阶段

  • 这个阶段主要由加载器来工作,也就是ClassLoader, 加载器的概念等下再说

  • 完成的工作:

    • 1、将class文件的字节码存到内存当中去,把静态数据变成方法区的运行时数据结构,如上图方法区的样子

    • 2、在堆内存中生成java.lang.Class对象

链接阶段

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

  • 完成的工作:

    • 1、为类变量(static) 分配内存以及赋予上默认初始值!比如 static int a = 0; static String b = “”;这样子。

    • 2、为类的常量值进行赋值 先这么理解吧,也就是说,在这个阶段,常量值是可以使用的!

初始化阶段

  • 这个阶段调用类构造器<clinit>,完成的工作,好比上图

    • 1、将类变量进行真正的赋值,也就是你写的代码这个变量到底等于多少!

    • 2、将静态代码块中的语句进行合并!

    • 3、初始一个类的时候,父类没初始化,则先触发父类的初始化

public class Test04 {
   public static void main(String[] args) {
       A a=new A();
       System.out.println(A.m);
     /*
     1.加载到内存,产生一个类对应class对象
     2.链接,结束后m=0
     3.初始化
       <clinit>(){
            sout("");
            m=300; m=100;
       }
       m=100;
     */
  }
}
class A{
   //先运行
   static{
       System.out.println("A类静态代码块初始化");
       m=300;
  }
   static int m=100;
   public A(){
       System.out.println("A类的无参构造初始化");
  }
}

2、首先来说加载器的概念

类加载器的作用:也就是在上面加载阶段所完成的事

类缓存:一旦类被加载到内存中,则会存在一段时间,然后再被回收!

加载器分为三种:

       //   获取系统类加载器
       ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
       System.out.println(systemClassLoader);
       // 获取系统类加载器的父类加载器>>扩展加载器
       ClassLoader sysParent = systemClassLoader.getParent();
       System.out.println(sysParent);
       //获取扩展类加载器的父类加载器>>根加载器 但是获取的值为null
       ClassLoader extParent = sysParent.getParent();
       System.out.println(extParent);
       //测试当前类是哪个加载器加载的
       ClassLoader classLoader=Class.forName("com.ic.reflection.Test06").getClassLoader();
       System.out.println(classLoader);
       //测试JDK内置的类是谁加载的
       classLoader=Class.forName("java.lang.Object").getClassLoader();
       System.out.println(classLoader);
       //如何获得系统加载器可以加载的路径
       System.out.println(System.getProperty("java.class.path"));

双亲委派机制:会从更高级选,根加载器优先

  1. 系统加载器 : 你写的用户自定义类的加载器,也是我们用的最多的加载器,父加载器是扩展加载器,可以获得引用

    • //   获取系统类加载器
      ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
  2. 扩展加载器: 存放在 jre/lib/ext目录下jar包里面类的加载器,父加载器就是根加载器,可以获得引用

    • // 获取系统类加载器的父类加载器>>扩展加载器 
      ClassLoader sysParent = systemClassLoader.getParent();
  3. 根加载器 : Java的核心类,是通过这个加载器获得,我们并能获取它的引用, 为null

    • //获取扩展类加载器的父类加载器>>根加载器 但是获取的值为null 
      ClassLoader extParent = sysParent.getParent();

3、再来说下初始化阶段的发生阶段

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

    • 虚拟机启动,执行main方法所在的类 ,这个类会到初始化阶段

    • new A() 也会发生初始化

    • 调用类的静态方法和静态属性(除了final 常量)

    • 使用java.lang.reflect包的方法,也就是获取Class的方法,也会将该类进行初始化

    • 初始化子类的时候,父类如果没有初始化,则会先初始化父类

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

    • 子类引用父类的静态变量,子类不会初始化

    • 数组定义分配空间,也不会引起该类的初始化 Son[] son1 = new Son[5]

    • 引用常量也不会触发此类的初始化,因为再类加载的过程的第二阶段链接阶段,常量就已经可以用了

public class Test05 {
   static {
       System.out.println("main类被加载");
  }
​
   public static void main(String[] args)throws ClassNotFoundException {
       //1.主动调用
       Son son=new Son();
       //2.反射也会产生主动引用
       Class.forName("com.ic.reflection.Son");
       //不会产生类的引用方法
       System.out.println(Son.b);
       //不加载类,只是一个数组空间
       Son[] array=new Son[5];
  }
}
class Father{
   static int b=2;
   static{
       System.out.println("父类被加载");
  }
}
class Son extends Father{
   static {
       System.out.println("子类被加载");
       m=300;
  }
   static int m=100;
   static final int M=1;
}

六、如何在运行时获得类的完整结构

实际上我们就是通过反射,也就是拿到Class类,然后执行Class类的方法,就可以获得运行时你自己写的类的完整的数据结构了,包括:接口、所继承的父类、全部构造器、全部方法、全部属性、注解….

public class Test07 {
   public static void main(String[] args)throws ClassNotFoundException,NoSuchFieldException ,NoSuchMethodException{
       Class c1=Class.forName("com.ic.reflection.User");
       /*
       User user=new User();
       c1=user.getClass();
       */
       System.out.println(c1.getName());
       System.out.println(c1.getSimpleName());
       //只能找到public属性
       Field[] fields=c1.getFields();
       //可以找到全部属性
       fields=c1.getDeclaredFields();
       for(Field field:fields){
           System.out.println(field);
      }
       //获得指定属性值
       Field name=c1.getDeclaredField("name");
       System.out.println(name);
       //获得本类及父类public方法
       Method[] methods=c1.getMethods();
       for(Method method:methods){
           System.out.println(method);
      }
       //获得本类所有方法
       methods=c1.getDeclaredMethods();
       for(Method method:methods){
           System.out.println(method);
      }
       //获得指定构造器
       Constructor[] constructors=c1.getDeclaredConstructors();
       for(Constructor constructor:constructors){
           System.out.println(constructor);
      }
       Constructor declaredConstor=c1.getDeclaredConstructor(String.class,int.class);
       System.out.println(declaredConstor);
  }
}

七、动态创建对象执行方法

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

创建类的对象,也就是实例,平常我们通过new A()的方式创建的实例,我们现在可以通过反射创建

  1. // 利用 Class类的newInstance()方法,调用了无参构造器创建实例  
    Class aClass=Class.forName("com.ClassLoader.Demo.User"); 
    User user1=(User) aClass.newInstance();
  2. // 非只能用无参构造器创建对象,用有参构造器也可以
    // 先通过Class类的getDeclaredConstructor(对应的参数)方法获得有参构造器
    // 然后调用有参构造器的newInstance()方法,就可以创建实例了 
    Constructor constructor=aClass.getDeclaredConstructor(String.class,int.class,double.class);
    User user2=(User) constructor.newInstance("xxx",xx,xx);//参数数量要一致否则出现异常

通过反射,去操作实例的方法和属性

  1. //       通过反射调用普通方法        
    User user= (User) aClass.newInstance();
    //       通过反射获取一个方法        
    Method setName=aClass.getDeclaredMethod("setName", String.class);
    //       inoke激活,将实例作为第一个参数传入,修改的数据作为第二个参数       setName.invoke(user,"xxx");        
    System.out.println(user.getName());
  2. //       通过反射操作属性,不能直接操作私有属性,可以通过关闭程序的安全检测关掉        
    User user4=(User) aClass.newInstance();        
    Field name=aClass.getDeclaredField("name");        
    name.setAccessible(true);       //可通过的意思。private修饰的变量有安全保护机制,       name.set(user4,"xxx");        
    System.out.println(user4.getName());   }
//通过反射,动态创建对象
public class Test08 {
   public static void main(String[] args)throwsClassNotFoundException,InstantiationException,IllegalAccessException,NoSuchMethodException, InvocationTargetException,NoSuchFieldException{
       Class c1 = Class.forName("com.ic.reflection.User");
       //本质调用了无参构造器
       //User user=(User) c1.newInstance();
       //通过构造器创建对象,可以没有无参构造器
       Constructor constructor=c1.getDeclaredConstructor(String.class,int.class);
       User user2=(User)constructor.newInstance("ic",18);
       System.out.println(user2);
       //通过反射调用普通方法
       User user3=(User) c1.newInstance();
       //1、通过反射获取一个方法
       Method setName=c1.getDeclaredMethod("setName",String.class);
       //2、invoke激活,(对象,"方法的值")调用
       setName.invoke(user3,"ic");
       System.out.println(user3.getName());
       //通过反射操作属性
       User user4=(User) c1.newInstance();
       Field name=c1.getDeclaredField("name");
       name.setAccessible(true);//不能直接操作私有属性,需要关掉安全检测,属性和方法setAccessible(true)
       name.set(user4,"ic");
       System.out.println(user4.getName());
  }
}

反射调用关闭检测可以提高效率

八、反射操作泛型

java采用泛型擦除机制引入泛型,给编译器javac使用,确保数据的安全性和免去强制类型转换问题,编译✅会擦除

public class Test09 {
   public void test01(Map<String,User>map, List<User>list){
​
  }
   public Map<String,User> test02(){
       System.out.println("t02");
       return null;
  }
​
   public static void main(String[] args)throws NoSuchMethodException{
       Method method=Test09.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);
              }
          }
      }
  }
}

九、反射获取注解信息

public class Test10 {
   public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
       Class c1=Class.forName("com.ic.reflection.Student1");
//   通过反射获得注解
       Annotation[] annotations=c1.getAnnotations();
       for (Annotation annotation : annotations) {
           System.out.println(annotation);
      }
//   获得类的注解value参数的值
       TableIc tableY=(TableIc)c1.getAnnotation(TableIc.class);
       String value=tableY.value();
       System.out.println(value);
//   获得类具体指定属性的注解
       Field field=c1.getDeclaredField("name");
       FieldIc annotation=field.getAnnotation(FieldIc.class);
       System.out.println(annotation.columnName());
       System.out.println(annotation.type());
       System.out.println(annotation.length());
  }
}
@TableIc("db_student")
class Student1{
   @FieldIc(columnName = "db_id",type="int",length = 10)
   private int id;
   @FieldIc(columnName = "db_name",type="varchar",length = 10)
   private String  name;
   @FieldIc(columnName = "db_age",type="int",length = 10)
   private int age;
   public Student1(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;
  }
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableIc{
   String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldIc{
   String columnName();
   String type();
   int length();
}
​
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值