Java学习笔记_11_注解和反射
注解Annotation
什么是注解
- 和注释comment一样,Annotation可以对程序做出解释,不过Annotation可以被其他程序(如compiler等)读取
- 注解的格式一般为:@注释名 当然还可以加上一点参数值(@)
内置注解
在JavaSE中内置了三个标准注解。下面就简单介绍下
- @Override:表示方法重载
- @Deprecated:表示方法已弃用(过时),不建议使用,可能会有更好的替代,但不是不能使用
- @SuppressWarnings:抑制警告,需要传递已定义好的参数
- @SuppressWarnings(“all”)
- @SuppressWarnings(“unchecked”)
- @SuppressWarnings(value={“unchecked”,”“deprecation”})
元注解
meta-annotation:元注解,负责注解其他注解,在Java中定于了标准的元注解(在java.lang.annotation包中可以找到)
- @Target:指示注释类型适用的上下文(即被描述的注解可以用在何处)
- @Retention:表示要保留带注释类型的注释多长时间(一般有三个级别:source、class、runtime,这三个级别的生命周期依次递增)
- @Inherited:表示一个注释类型是自动继承的
- @Documented:表明一个类型的注释被javadoc默认文件
自定义注解
格式:@interface 注解名{定义内容}
- 定义内容中的每一个方法实际上是申明了一个配置参数
- 方法的名称就是参数的名称,返回值类型就是参数的类型
- 注解元素必须有值,可以用default来申明参数的默认值
【代码示例:自定义注解】
package com.lee.Lesson01; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public class TestCustomizedAnnotation { @MyAnnotation(age = 18, name = "lee", id = 1) public void test() { } @Target(value = {ElementType.METHOD}) @Retention(value = RetentionPolicy.RUNTIME) @interface MyAnnotation { //注解的参数 参数类型+参数名 String name() default ""; int age() default 0; int id() default -1; } }
反射读取注解
显然通过反射机制读取注解前,我们需要 定义注解,并在类中使用注解(后面会详细讲述反射)
Class.forName("完整包名").getAnnotations(); //获取该类的注解当然还有其他其他方法,详情可见官方API(Class)
反射Reflection
说在前面
提及反射机制的话,不可避免地会提及到静态语言和动态语言
- 动态语言
- 运行时可以改变其结构的语言
- 常见的动态语言:C#、JavaScript、PHP、Python
- 静态语言
- 运行时结构不可变的语言
- Java不是动态语言,但由于反射机制的存在,让Java获得类似动态语言的特性,使得Java编程更加灵活
什么是反射
- 在类加载完毕后,会在堆内存中生成一个Class类型的对象(一个类只有一个对象),这个对象包含了类的全部的结构信息。通过这个对象我们可以看到类的结构,这就是反射机制
- 就像是一面镜子,我们每个人(类)都会在镜子中生成一个镜像(Class对象),我们想获取一个人的信息(结构信息),正常的话可以通过渠道问本人(正常方式),也可以透过镜子中该人的反射镜像获取信息(反射方式)
- 正常方式:通过渠道(引入需要的包类名,实例化)找到人在那,就可以取得想要的信息(获得实例化对象)
- 反射方式:先想办法得到镜像(对象实例化,然后使用方法获得Class模板),在镜像中找想要的信息(获得完整的包类名称)
反射机制的功能
前面提到过,我们通过反射可以获取任何类的内部信息,那么得到这些信息后我们可以做点啥了?
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的Field和Method
- 在运行时获取泛型信息
- 在运行时处理注解
- 生成动态代理
- 在运行时调用任意一个对象的Field和Method
显然反射机制的优点在于可以动态的创建对象和编译,但是这类操作有点迂回的意思,所以在性能上会有一定的影响
如何用反射:当然是官方文档了,java.lang.reflect罗列了我们所用到的Java反射类
Class类
上面我们提到过每个类都有一个Class对象,这个Class对象保存了类的全部结构信息,那么什么是Class类了?
- 解释有点套娃的意思,Class类是一个描述类的类,且Class对象只能由系统建立对象(一个加载的类在JVM中只会一个Class实例)
为什么要讨论Class类了?
- 因为通过Class类可以完整地得到一个类中地所有被加载的结构,它是Reflection的根源,只有得到相对应的Class对象,我们才能动态加载运行的类
如何获取Class对象?
【代码示例:获取Class对象的方法】
package com.lee.Lesson01; public class TestReflect02 { public static void main(String[] args) throws ClassNotFoundException { Person person = new Student(); System.out.println("我是谁:" + person.name); //获得Class办法一:通过对象获取 Class aClass = person.getClass(); //获得Class办法二:通过字符串(包名+类名)获取 Class<?> aClass1 = Class.forName("com.lee.Lesson01.Student"); //获得Class办法三:通过类.class获得 Class<Person> aClass2 = Person.class; //获得Class办法四:只针对内置的基本数据类型 Class<Integer> aClass3 = Integer.TYPE; //获得父类类型 Class<?> aClass4 = aClass1.getSuperclass(); } } class Person { public String name; @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } public Person(String name) { this.name = name; } public Person() { } } class Student extends Person { public Student() { this.name = "学生"; } } class Teacher extends Person { public Teacher() { this.name = "老师"; } }
上面提供了几种获取Class对象的方法,那么问题来了,那些类型可以由Class对象了?
答案很简单,类型能.class的,就有Class对象,分别有
- 类:class(外部类、成员(成员内部类、静态内部类)、局部内部类、匿名内部类)
- 接口:interface
- 数组(元素类型和维度一样的话,是同一个Class)
- 枚举:enum
- 注解:@interface
- 基本数据类型
- void
【代码示例:那些类型有Class对象】
package com.lee.Lesson01; import java.lang.annotation.ElementType; //看看那些类型可以有Class对象 public class TestReflect03 { public static void main(String[] args) { Class<Object> aClass = Object.class; Class<Comparable> aClass1 = Comparable.class; Class<String> aClass2 = String.class; Class<Integer> aClass3 = int.class; Class<int[][]> aClass4 = int[][].class; Class<ElementType> aClass5 = ElementType.class; Class<Override> aClass6 = Override.class; Class<Void> aClass7 = void.class; Class<Class> aClass8 = Class.class; //只要元素类型和维度一样 就是同一个Class int[] a = new int[10]; int[] b = new int[12]; System.out.println(a.getClass() == b.getClass()); } }
类的初始化(补充知识)
当程序主动使用某个类时,如果该类还未加载到内存中,系统就会通过以下步骤将类初始化
- 类的加载:将class文件字节码内容加载到内存中,并将这些数据转换成方法区的运行时数据,然后生成代表该类的Class对象(有ClassLoader负责,可以区官方文档查看详细解释)
- 类的链接:将Java类的二进制代码合并到JVM的运行状态之中的一个过程
- 类的初始化:执行类加载器(构造类信息)方法的过程
什么时候会发生类的初始化了?
类的主动引用(一定会)
- main方法肯定会被初始化
- new一个对象的时候
- 调用类的静态成员和静态方法(final常量除外)
- 反射调用类
- 初始化一个类时,若父类没被初始化,优先初始化父类(先父后子)
有主动引用,那么一定就有被动引用咯
类的被动引用(不会初始化)
- 通过入组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存在常量池中了)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化(如:通过子类引用父类的静态变量时,不会导致子类初始化)
Class对象能为我们做点啥
即反射机制能给我们做点啥,上面其实已经简答说到过,这里就说下对应的方法
创建类的对象(找对象是有条件的)
有车有房(类必须有一个无参的构造器)
户名归她(类的构造器访问权限得足够)
记得有后(要想构造器中传递参数)
clazz.getDeclaredConstructor().newInstance();clazz:Class对象
调用指定的方法()
clazz.getMethod(“指定方法名”)获得指定方法
若方法为private,需要调用setAccessible(true)方法,取消语言访问检查
invoke激活方法
操作泛型
泛型回顾:Java采用泛型擦除的机制(一旦编译完成,和泛型有关的类型将全部擦除)引入泛型(Java中泛型仅给编译器使用),用来确保数据的安全性以及避免强转问题
为了能够通过反射操作这些类型,Java新增了ParameterizedType、GenericArrayType、TypeVariable、WildcardType(详细见官方文档)
操作注解
- getAnnotation:如果存在指定类型的元素注解,则返回该元素的注解,否则为空
- getAnnotations:返回该元素上的注解。如果该元素上没有注解,返回值是一个长度为0的数组