注解和反射
注解
一、元注解
元注解的作用就是注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型做说明。
4个元注解分别为:
-
@Target:用于描述注解的使用范围
-
ElementType.TYPE 针对类、接口
-
ElementType.FIELD 针对成员变量
-
ElementType.METHOD 针对成员方法
-
ElementType.PARAMETER 针对方法参数
-
ElementType.CONSTRUCTOR 针对构造器
-
ElementType.PACKAGE 针对包
-
ElementType.ANNOTATION_TYPE 针对注解
-
-
@Retention:用于表示需要在什么级别保存注解信息,用于描述注解的生命周期,(SOURCE<CLASS<RUNTIME)
-
RetentionPolicy.SOURCE 源代码级别,由编译器处理,处理之后就不再保留
-
RetentionPolicy.CLASS 注解信息保留到类对应的 class 文件中
-
RetentionPolicy.RUNTIME 由 JVM 读取,运行时使用
-
-
@Document:说明该注解将被包含在javadoc(文档)中
-
@Inherited:说明子类可以继承父类中的该注解
二、自定义注解
public class Test {
//如果注解的参数没有默认值,则必须给该参数赋值
@MyAnnotation(schools = {"北京大学","清华大学"})
public void test(){
}
//如果注解只有一个参数且参数名为value,则value可以省略不写(参数名不为value时不允许省略)
@MyAnnotation2("aa")
public void test2(){
}
}
//注解的写法
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//注解的参数:类型+参数名() [default 默认值];
String name() default "";
int age() default 0;
int id() default -1; //如果默认值为-1,代表不存在
String[] schools();
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//如果注解只有一个参数,一般参数名取为value
String[] value(); //不成文的一个规范
}
反射
一、反射概述
Java不是动态语言,但Java可以称为“准动态语言”。因为Java的反射机制可以让Java获得类似动态语言的特性,即运行时代码可以根据一些条件来改变自身的结构。
反射机制允许程序在运行期间借助于Reflection API获取任何类的内部信息(比如类名,类的接口,类的方法,字段,属性…),并且能够直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存中产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时获取泛型信息
- 在运行时处理注解
- 生成动态代理
- ……
二、Class对象
一个类在内存中只有一个Class对象,不可能有多个。
一个类被被加载后,类的整个结构都会被封装在Class对象中。
Class对象只能由系统创建,我们只负责使用。我们要想使用反射机制,首先得要获得相应的Class对象。
1. 获得Class对象的方式
(1)如果已知具体的类,通过类的class属性获取,是最为安全可靠且性能最高的方法。
Class clz = 类名.class;
(2)如果已知某个类的对象,调用此对象的getClass()方法获取Class对象。
Class clz = 对象名.getClass();
【注】getSuperClass()方法可以返回当前对象的父类Class对象。
(3)已知一个类的全限定类名且该类在类路径下,可以通过Class类的静态方法forName()获取,需要处理异常ClassNotFoundException。
Class clz = Class.forName("类的全限定类名")
(4)内置基本数据类型可以直接使用类名.TYPE,比如:
Class clz = Integer.TYPE; //基本内置类型的包装类都有一个 TYPE属性
(5)还可以用ClassLoader。
2. 通过Class对象获取运行时类的完整结构
(1)获取类的名字
- clz.getName() 获取包名+类名
- clz.getSimpleName 获取类名
(2)获取类的属性
-
clz.getFields() 获取本类public属性
-
clz.getDeclaredFields() 可以获取到本类所有属性,包括私有属性
-
clz.getDeclaredField("属性名") 获取指定的属性
(3)获取类的方法
-
clz.getMethods() 获取本类及其父类的所有public方法
-
clz.getDeclaredMethods() 获取本类的所有方法,包括私有方法,不能获取父类的方法
-
clz.getMethod("方法名", ...方法参数类型.class)
clz.getDeclaredMethod("方法名", ...方法参数类型.class)
获取指定方法,若方法无参,则传递null
(4)获取类的构造方法
- clz.getConstructors() 获取本类public构造方法
- clz.getDeclaredConstructors() 获取本类所有构造方法
- clz.getDeclaredConstructor(...构造方法参数类型.class) 获取指定构造方法
3. 获取到类的结构信息后能做什么
(1)通过反射创建类的对象
方式1:调用Class对象的newInstance()方法
前提:
必须要有一个无参构造器;
类的构造器的访问权限要够。
`User user = (User) clz.newInstance(); //本质上调用了类的无参构造器`
方式2:通过指定的构造器创建
首先通过Class对象获取指定的构造器,然后调用Constructor对象的newInstance(...构造方法参数值)方法。
Constructor constructor = clz.getDeclaredConstructor(String.class, int.class);
User user = (User) constructor.newInstance("小明", 18);
(2)通过反射调用方法
User user = (User) clz.newInstance();
//通过反射获取一个方法
Method setName = clz.getMethod("setName", String.class);
//invoke: 激活的意思 (对象, "方法需要的参数")
setName.invoke(user,"小张");
System.out.println(user.getName());//输出:小张
Object invoke(Object obj, Object ... args)
- invoke方法的返回值对应原方法的返回值,若原方法无返回值,则返回null。
- 若原方法为静态方法,此时形参Object obj可为null。
- 若原方法形参列表为空,则Object ... args为null。
- 若原方法声明为private,则需要在调用此invoke()方法之前,显示调用Method对象的setAccessible(true)方法,这样才可以访问私有方法。
(3)通过反射操作属性
User user= (User) clz.newInstance();
Field name = clz.getDeclaredField("name");
//不能直接操作私有属性,我们需要关闭程序的安全检查,Field或Method对象调用setAccessible(true)方法
//setAccessible 默认为false,如果没有关闭将会报没有访问private的权限
//can not access a member of class com.javacto.reflection.User with modifiers "private"
name.setAccessible(true);
name.set(user,"小红"); //修改属性值
System.out.println(user.getName());//输出:小红
【注】使用反射机制会影响程序的运行效率。关闭安全检查在一定程度上能改善一些反射的效率问题。
三、通过反射获取泛型信息
Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,目的是确保数据的安全性和免去强制类型转换的问题,但是,一旦编译完成,所有和泛型有关的类型将被全部擦除。
思考该怎么获取泛型信息呢? (之前有说过,类加载的时候就产生了Class对象,故class对象里面应该是有保留泛型信息的)
public class Test {
/**
* 通过泛型传参
* @param map
* @param list
*/
public void test01(Map<String,User> map, List <User> list){
System.out.println("test01");
}
/**
* 通过泛型返回值
* @return
*/
public Map<String,User> test02(){
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = Test.class.getMethod("test01", Map.class, List.class);
//获取参数类型 即Map 和 List
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("1:"+genericParameterType);
//判断genericParameterType参数类型 是否属于 ParameterizedType 参数化类型
if (genericParameterType instanceof ParameterizedType){
//如果属于参数化类型,获得他的真实类型 getActualTypeArguments
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
//再次输出真实的泛型信息
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("2:"+actualTypeArgument);
}
}
}
//获得返回值类型
method = Test.class.getMethod("test02",null);
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
//如果genericReturnType返回值类型属于参数化类型,获得他的真实类型 getActualTypeArguments
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
//再次输出真实的泛型信息
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("3:"+actualTypeArgument);
}
}
}
}
四、通过反射获取注解信息
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class clz = Class.forName("com.javacto.reflection.Student");
//通过反射获取注解
Annotation[] annotations = clz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解的value的值 获取指定注解值
MyTable myTable =(MyTable) clz.getAnnotation(MyTable.class);
String value = myTable.value();
System.out.println(value);
//获得类指定的注解
System.out.println("=====获得类指定的注解======");
Field field= clz.getDeclaredField("name");
MyField annotation = field.getAnnotation(MyField.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@MyTable("db_students")
class Student{
@MyField(columnName = "db_id",type = "int",length = 10)
private int id;
@MyField(columnName = "db_age",type = "int",length = 10)
private int age;
@MyField(columnName = "db_name",type = "varchar",length = 50)
private String name;
public Student() {
}
public Student(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", age=" + age +
", name=" + name +
'}';
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable{
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
String columnName(); //列名
String type(); //类型
int length(); //长度
}