注解和反射
注解Annotation
注解的作用
- 不是程序本身,可以对程序作出解释
- 可以被其他程序读取(如编译器)
注解的格式
注解以“@注解名”在代码中存在,可以添加一些参数;可以通过反射机制实现对这些元数据的访问
内置注解
- @Override:定义在java.lang.Override中,该注解只适用于修饰方法,表明重写
- @Deprecated:定义在java.lang.Deprecated中,可以修饰方法,属性,类,标记api状态,表示不鼓励使用这样的元素
- @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息,必须添加参数才能使用如@SuppressWarnings("all")
元注解
- 元注解的作用是负责注解其他注解,java定义了4个标准的元注解meta-annotation来对其他annotation进行说明
- 定义在java.lang.annotation包中
- @Target:描述注解的使用范围
- @Retention:描述注解的生命周期,定义被他所注解的注解保留多久
- SOURCE:注解只保留在源文件,当Java文件被编译成class文件时就会被遗弃(在源文件中存在,即编译器会忽略该注解)
- CLASS:注解被保留在class文件中,JVM加载class文件时被遗弃,这是默认的生命周期(在class文件中存在)
- RUNTIME:注解不仅保留在class文件中,JVM加载class文件后,仍然存在(在字节码中仍然存在)
- @Document:说明该注解将被包含在javadoc中
- @Inherited:说明子类可以继承父类中的该注解
定义一个注解
@Target(value = ElementType.METHOD) @Retention(value = RetentionPolicy.RUNTIME) @Inherited @Documented public @interface MyAnnotation { }
自定义注解
- 使用@interface自定义注解时,自动继承java.lang.annotation.Annotation接口
- @interface用来声明一个注解,格式 public @interface 注解名{定义内容}
- 定义内容中的每一个方法实际上就是声明了一个配置参数
- 方法的名称就是参数名称
- 返回值类型就是参数类型
- 可以通过default来声明参数默认值
- 如果只有一个参数,一般用参数名value
- 注解元素必须要有值,一般用空字符串或0来做默认值
@Target(value = ElementType.METHOD) @Retention(value = RetentionPolicy.RUNTIME) @Inherited @Documented public @interface MyAnnotation { //参数类型 参数名 默认值 String name() default ""; int age() default 0; }
反射读取注解
public class JavaTest { public static void main(String[] args) throws Exception { //通过反射获取类的全部信息 Class class1=Class.forName("com.deng.jd.b"); //获取类上的注解 Annotation[] annotations=class1.getAnnotations(); System.out.println(annotations); //获取指定注解 MyAnnotation myAnnotation= (MyAnnotation) class1.getAnnotation(MyAnnotation.class); //获取注解的参数 System.out.println(myAnnotation.name()); //获取类指定属性上的注解 Field name=class1.getDeclaredField("name"); MyAnnotation myAnnotation1=name.getAnnotation(MyAnnotation.class); System.out.println(myAnnotation1); } }
反射Reflection
运行时可以改变其结构的语言就是动态语言,如PHP,Python等,Java不是动态语言,单可以被称为准动态语言,可以使用反射机制获取类似动态语言的特性。
反射机制允许程序在执行期间借助ReflectionAPI获取任何类的内部信息,并能直接操作任意对象的内部属性及方法。
在加载玩类后,在堆内存的方法区中产生了一个Class类型的对象,该对象包含了完整的类的结构信息,可以通过该对象看到类的结构这就是反射
Class类
在Object类中定义了public final Class getClass()方法,被所有子类继承,其返回类型是一个Class类,此类是反射的源头
获取Class类对象的方式
public class JavaTest { public static void main(String[] args) throws Exception { Student xm=new Student(); //方法1:通过实例对象获得 Class class1=xm.getClass(); //方法2:通过类名获得(包名+类名) Class class2=Class.forName("com.deng.jd.Student"); //方法3:通过类的静态成员变量class获得 Class class3=Student.class; //对于基本数据类型的包装类,通过TYPE可以获得 Class class4=Integer.TYPE; //获得父类类型 Class class5= class1.getSuperclass(); System.out.println(class1); System.out.println(class2); System.out.println(class3); System.out.println(class4); System.out.println(class5); } }
Class类常用方法
public class JavaTest { public static void main(String[] args) throws Exception { //静态方法,获取指定名称的Class对象 Class class2=Class.forName("com.deng.jd.Student"); //创建实例对象 Student student1=(Student) class2.newInstance(); //获取类名 System.out.println(class2.getName()); //获取父类 System.out.println(class2.getSuperclass()); //获取当前类的接口 Class[] classes=class2.getInterfaces(); for(Class class3:classes){ System.out.println(class3.getName()); } //获取该类的类加载器 System.out.println(class2.getClassLoader()); //获取该类的构造器 Constructor[] constructors=class2.getConstructors(); System.out.println(constructors); //获取类的所有属性 Field[] fields=class2.getDeclaredFields(); for (Field f:fields) { System.out.println(f); } } }
Java内存分析
类的加载过程
当程序主动使用某一类时,如果该类还没被加载到内存中,系统会通过3个步骤对类进行初始化
- 类的加载Load:将类的class文件读入内存,并创建一个java.lang.Class对象,该过程由类加载器完成
- 类的链接Link:将类的二进制数据合并到JRE中
- 类的初始化Initialize:JVM负责对类进行初始化
类的加载理解
- 加载:
- 将class文件字节码内容加载到内存中,并将静态数据转换成方法区中的运行时数据结构,然后生成一个代表该类的java.lang.Class对象
- 链接:将java类的二进制码合并到java运行状态之中的过程
- 验证:确保加载的类信息符合JVM规范
- 准备:为类变量分配内存并设置默认初始值,都是在方法区进行分配
- 解析:将JVM常量池中的符号引用替换成直接引用
- 初始化:
- 执行类构造器,类构造器是编译器自动收集的类中所有类变量赋值语句和静态代码块语句合并产生(不是类的对象的构造器)
- 在初始化时,如果类的父类还没有初始化,先触发其父类的初始化
什么时候发生类的初始化
- 类的主动引用(一定会发生类的初始化)
- 虚拟机启动时,先初始化main方法所在的类
- new一个类对象
- 调用类的静态成员和静态方法(不包括final常量)
- 对类进行反射调用
- 初始化一个类时,其父类未被初始化,先初始化其父类
- 类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化,如子类引用父类的静态域,子类不会被初始化
- 通过数组定义类的引用,不会触发此类的初始化,此时类还没有被主动使用
- 引用常量不会触发类的初始化
类加载器的作用
- 将class文件字节码加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,然后在堆中生成一个java.lang.Class对象作为方法区中类数据的入口
有了Class对象能做什么
通过反射可以获取运行时类的完整结构:
- Interface:全部接口
- SuperClass:所继承的分类
- Constructor:全部构造器
- Annotation:全部注解
- Method:全部方法
- Field:全部属性成员
可以通过构造器getDeclaredConstructor(Class … parameterTypes)或者newInstance方法创建类对象
可以通过getMethod(String name,Class…parameterTypes)获取方法,通过Object invoke(Object obj, Object[] args)调用方法
setAccessible
- Method,Field,Constructor对象都有setAccessible()方法
- setAccessible()作用是启动或禁用访问安全检查的开关,设置为true表禁用,false表示关闭,当在一个类的外部访问其私有成员时,必须setAccessible(true),否则会抛出异常