Java注解和反射是很基础的Java知识了,为什么还要讲它呢?由于我在面试应聘者的过程当中,发现很多面试者不多使用过注解和反射,甚至有人只能说出@Override
这一个注解。我建议你们仍是尽可能能在开发中使用注解和反射,有时候使用它们能让你事半功倍,简化代码提升编码的效率。不少优秀的框架都基本使用了注解和反射,在Spring AOP中,就把注解和反射用得淋漓尽致。java
Java注解(Annotation)亦叫Java标注,是JDK5.0开始引入的一种注释机制。 注解能够用在类、接口,方法、变量、参数以及包等之上。注解能够设置存在于不一样的生命周期中,例如SOURCE(源码中),CLASS(Class文件中,默认是此保留级别),RUNTIME(运行期中)。程序员
注解以@注解名
的形式存在于代码中,Java中内置了一些注解,例如@Override
,固然咱们也能够自定义注解。注解也能够有参数,例如@MyAnnotation(value = "陈皮")。面试
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
那注解有什么做用呢?其一是做为一种辅助信息,能够对程序作出一些解释,例如@Override注解做用于方法上,表示此方法是重写了父类的方法。其二,注解能够被其余程序读取,例如编译器,例如编译器会对被@Override注解的方法检测判断方法名和参数等是否与父类相同,不然会编译报错;并且在运行期能够经过反射机制访问某些注解信息。数据库
Java中有10个内置注解,其中6个注解是做用在代码上的,4个注解是负责注解其余注解的(即元注解),元注解提供对其余注解的类型说明。编程
注解 | 做用 | 做用范围 |
---|---|---|
@Override | 检查该方法是不是重写方法。若是其继承的父类或者实现的接口中并无该方法时,会报编译错误。 | 做用在代码上 |
@Deprecated | 标记表示过期的,不推荐使用。能够用于修饰方法,属性,类。若是使用被此注解修饰的方法,属性或类,会报编译警告。 | 做用在代码上 |
@SuppressWarnings | 告诉编译器忽略注解中声明的警告。 | 做用在代码上 |
@SafeVarargs | Java 7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。 | 做用在代码上 |
@FunctionalInterface | Java 8开始支持,标识一个匿名函数或函数式接口。 | 做用在代码上 |
@Repeatable | Java 8开始支持,标识某注解能够在同一个声明上使用屡次。 | 做用在代码上 |
@Retention | 标识这个注解的保存级别,是只在代码中,仍是编入class文件中,或者是在运行时能够经过反射访问。包含关系runtime>class>source。 | 做用在其余注解上,即元注解 |
@Documented | 标记这些注解是否包含在用户文档中javadoc。 | 做用在其余注解上,即元注解 |
@Target | 标记某个注解的使用范围,例如做用方法上,类上,属性上等等。若是注解未使用@Target,则注解能够用于任何元素上。 | 做用在其余注解上,即元注解 |
@Inherited | 说明子类能够继承父类中的此注解,但这不是真的继承,而是可让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解 | 做用在其余注解上,即元注解 |
使用@interface关键字自定义注解,其实底层就是定义了一个接口,并且自动继承java.lang.annotation.Annotation
接口。数组
咱们自定义一个注解以下:安全
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface MyAnnotation { String value(); }
咱们使用命令javap反编译咱们定义的MyAnnotation注解的class文件,结果显示以下。虽然注解隐式继承了Annotation接口,可是Java不容许咱们显示经过extends关键字继承Annotation接口甚至其余接口,不然编译报错。数据结构
D:\>javap MyAnnotation.class Compiled from "MyAnnotation.java" public interface com.nobody.MyAnnotation extends java.lang.annotation.Annotation { public abstract java.lang.String value(); }
注解的定义内容以下:框架
import java.lang.annotation.*; /** * @Description 自定义注解 * @Author Mr.nobody * @Date 2021/3/30 * @Version 1.0 */ @Target(ElementType.METHOD) // 此注解只能用在方法上。 @Retention(RetentionPolicy.RUNTIME) // 此注解保存在运行时期,能够经过反射访问。 @Inherited // 说明子类能够继承此类的此注解。 @Documented // 此注解包含在用户文档中。 public @interface CustomAnnotation { String value(); // 使用时须要显示赋值 int id() default 0; // 有默认值,使用时能够不赋值 }
/** * @Description 测试注解 * @Author Mr.nobody * @Date 2021/3/30 * @Version 1.0 */ public class TestAnnotation { // @CustomAnnotation(value = "test") 只能注解在方法上,这里会报错 private String str = "Hello World!"; @CustomAnnotation(value = "test") public static void main(String[] args) { System.out.println(str); } }
在这里讲解下Java8以后的几个注解和新特性,其中一个注解是@FunctionalInterface,它做用在接口上,标识是一个函数式接口,即只有有一个抽象方法,可是能够有默认方法。ide
@FunctionalInterface public interface Callback<P,R> { public R call(P param); }
还有一个注解是@Repeatable,它容许在同一个位置使用多个相同的注解,而在Java8以前是不容许的。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Repeatable(OperTypes.class) public @interface OperType { String[] value(); }
// 能够理解@OperTypes注解做为接收同一个类型上重复@OperType注解的容器 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface OperTypes { OperType[] value(); }
@OperType("add") @OperType("update") public class MyClass { }
注意,对于重复注解,不能再经过clz.getAnnotation(Class<A> annotationClass)方法来获取重复注解,Java8以后,提供了新的方法来获取重复注解,即clz.getAnnotationsByType(Class<A> annotationClass)方法。
package com.nobody; import java.lang.annotation.Annotation; /** * @Description * @Author Mr.nobody * @Date 2021/3/31 * @Version 1.0 */ @OperType("add") @OperType("update") public class MyClass { public static void main(String[] args) { Class<MyClass> clz = MyClass.class; Annotation[] annotations = clz.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation.toString()); } OperType operType = clz.getAnnotation(OperType.class); System.out.println(operType); OperType[] operTypes = clz.getAnnotationsByType(OperType.class); for (OperType type : operTypes) { System.out.println(type.toString()); } } } // 输出结果为 @com.nobody.OperTypes(value=[@com.nobody.OperType(value=[add]), @com.nobody.OperType(value=[update])]) null @com.nobody.OperType(value=[add]) @com.nobody.OperType(value=[update])
在Java8中,ElementType枚举新增了两个枚举成员,分别为TYPE_PARAMETER和TYPE_USE,TYPE_PARAMETER标识注解能够做用于类型参数,TYPE_USE标识注解能够做用于标注任意类型(除了Class)。
咱们先了解下什么是静态语言和动态语言。动态语言是指在运行时能够改变其自身结构的语言。例如新的函数,对象,甚至代码能够被引进,已有的函数能够被删除或者结构上的一些变化。简单说便是在运行时代码能够根据某些条件改变自身结构。动态语言主要有C#,Object-C,JavaScript,PHP,Python等。静态语言是指运行时结构不可改变的语言,例如Java,C,C++等。
Java不是动态语言,可是它能够称为准动态语言,由于Java能够利用反射机制得到相似动态语言的特性,Java的动态性让它在编程时更加灵活。
反射机制容许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操做任意对象的内部属性以及方法等。类在被加载完以后,会在堆内存的方法区中生成一个Class类型的对象,一个类只有一个Class对象,这个对象包含了类的结构信息。咱们能够经过这个对象看到类的结构。
好比咱们能够经过Class clz = Class.forName("java.lang.String");
得到String类的Class对象。咱们知道每一个类都隐式继承Object类,Object类有个getClass()
方法也能获取Class对象。
Java反射机制提供的功能
Java反射机制的优缺点
Java反射相关的主要API
咱们知道在运行时经过反射能够准确获取到注解信息,其实以上类(Class,Method,Field,Constructor等)都直接或间接实现了AnnotatedElement接口,并实现了它定义的方法,AnnotatedElement接口的做用主要用于表示正在JVM中运行的程序中已使用注解的元素,经过该接口提供的方法能够获取到注解信息。
在Java反射中,最重要的是Class这个类了。Class自己也是一个类。当程序想要使用某个类时,若是此类还未被加载到内存中,首先会将类的class文件字节码加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,而后生成一个Class类型的对象(Class对象只能由系统建立),一个类只有一个Class对象,这个对象包含了类的结构信息。咱们能够经过这个对象看到类的结构。每一个类的实例都会记得本身是由哪一个Class实例所生成的。
经过Class对象能够知道某个类的属性,方法,构造器,注解,以及实现了哪些接口等信息。注意,只有class,interface,enum,annotation,primitive type,void,[] 等才有Class对象。
package com.nobody; import java.lang.annotation.ElementType; import java.util.Map; public class TestClass { public static void main(String[] args) { // 类 Class<MyClass> myClassClass = MyClass.class; // 接口 Class<Map> mapClass = Map.class; // 枚举 Class<ElementType> elementTypeClass = ElementType.class; // 注解 Class<Override> overrideClass = Override.class; // 原生类型 Class<Integer> integerClass = Integer.class; // 空类型 Class<Void> voidClass = void.class; // 一维数组 Class<String[]> aClass = String[].class; // 二维数组 Class<String[][]> aClass1 = String[][].class; // Class类也有Class对象 Class<Class> classClass = Class.class; System.out.println(myClassClass); System.out.println(mapClass); System.out.println(elementTypeClass); System.out.println(overrideClass); System.out.println(integerClass); System.out.println(voidClass); System.out.println(aClass); System.out.println(aClass1); System.out.println(classClass); } } // 输出结果 class com.nobody.MyClass interface java.util.Map class java.lang.annotation.ElementType interface java.lang.Override class java.lang.Integer void class [Ljava.lang.String; class [[Ljava.lang.String; class java.lang.Class
获取Class对象的方法
Class clz = User.class;
Class clz = user.getClass();
Class clz = Class.forName("com.nobody.User");
Class<Integer> clz = Integer.TYPE;
Class类的经常使用方法
在反射中常常会使用到Method的invoke方法,即public Object invoke(Object obj, Object... args)
,咱们简单说明下:
泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操做的数据类型被指定为一个参数,在用到的时候再指定具体的类型。这种参数类型能够用在类、接口和方法的建立中,分别称为泛型类、泛型接口和泛型方法。
在Java中,采用泛型擦除的机制来引入泛型,泛型能编译器使用javac时确保数据的安全性和免去强制类型转换问题,泛型提供了编译时类型安全检测机制,该机制容许程序员在编译时检测到非法的类型。而且一旦编译完成,全部和泛型有关的类型会被所有擦除。
Java新增了ParameterizedType
,GenericArrayType
,TypeVariable
和WildcardType
等几种类型,能让咱们经过反射操做这些类型。
package com.nobody; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Map; public class TestReflectGenerics { public Map<String, Person> test(Map<String, Integer> map, Person person) { return null; } public static void main(String[] args) throws NoSuchMethodException { // 获取test方法对象 Method test = TestReflectGenerics.class.getDeclaredMethod("test", Map.class, Person.class); // 获取方法test的参数类型 Type[] genericParameterTypes = test.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); } } } // 获取方法test的返回值类型 Type genericReturnType = test.getGenericReturnType(); System.out.println("返回值类型:" + genericReturnType); // 若是参数类型等于参数化类型 if (genericReturnType instanceof ParameterizedType) { // 得到真实参数类型 Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(" " + actualTypeArgument); } } } } class Person {} // 输出结果 方法参数类型:java.util.Map<java.lang.String, java.lang.Integer> class java.lang.String class java.lang.Integer 方法参数类型:class com.nobody.Person 返回值类型:java.util.Map<java.lang.String, com.nobody.Person> class java.lang.String class com.nobody.Person
在Java运行时,经过反射获取代码中的注解是比较经常使用的手段了,获取到了注解以后,就能知道注解的全部信息了,而后根据信息进行相应的操做。下面经过一个例子,获取类和属性的注解,解析映射为数据库中的表信息。
package com.nobody; import java.lang.annotation.*; public class AnalysisAnnotation { public static void main(String[] args) throws Exception { Class<?> aClass = Class.forName("com.nobody.Book"); // 获取类的指定注解,而且获取注解的值 Table annotation = aClass.getAnnotation(Table.class); String value = annotation.value(); System.out.println("Book类映射的数据库表名:" + value); java.lang.reflect.Field bookName = aClass.getDeclaredField("bookName"); TableField annotation1 = bookName.getAnnotation(TableField.class); System.out.println("bookName属性映射的数据库字段属性 - 列名:" + annotation1.colName() + ",类型:" + annotation1.type() + ",长度:" + annotation1.length()); java.lang.reflect.Field price = aClass.getDeclaredField("price"); TableField annotation2 = price.getAnnotation(TableField.class); System.out.println("price属性映射的数据库字段属性 - 列名:" + annotation2.colName() + ",类型:" + annotation2.type() + ",长度:" + annotation2.length()); } } // 做用于类的注解,用于解析表数据 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Table { // 表名 String value(); } // 做用于字段,用于解析表列 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface TableField { // 列名 String colName(); // 列类型 String type(); // 长度 int length(); } @Table("t_book") class Book { @TableField(colName = "name", type = "varchar", length = 15) String bookName; @TableField(colName = "price", type = "int", length = 10) int price; } // 输出结果 Book类映射的数据库表名:t_book bookName属性映射的数据库字段属性 - 列名:name,类型:varchar,长度:15 price属性映射的数据库字段属性 - 列名:price,类型:int,长度:10
前面咱们说过,反射对性能有必定影响。由于反射是一种解释操做,它老是慢于直接执行相同的操做。并且Method,Field,Constructor都有setAccessible()方法,它的做用是开启或禁用访问安全检查。若是咱们程序代码中用到了反射,并且此代码被频繁调用,为了提升反射效率,则最好禁用访问安全检查,即设置为true。
package com.nobody; import java.lang.reflect.Method; public class TestReflectSpeed { // 10亿次 private static int times = 1000000000; public static void main(String[] args) throws Exception { test01(); test02(); test03(); } public static void test01() { Teacher t = new Teacher(); long start = System.currentTimeMillis(); for (int i = 0; i < times; i++) { t.getName(); } long end = System.currentTimeMillis(); System.out.println("普通方式执行10亿次消耗:" + (end - start) + "ms"); } public static void test02() throws Exception { Teacher teacher = new Teacher(); Class<?> aClass = Class.forName("com.nobody.Teacher"); Method getName = aClass.getDeclaredMethod("getName"); long start = System.currentTimeMillis(); for (int i = 0; i < times; i++) { getName.invoke(teacher); } long end = System.currentTimeMillis(); System.out.println("反射方式执行10亿次消耗:" + (end - start) + "ms"); } public static void test03() throws Exception { Teacher teacher = new Teacher(); Class<?> aClass = Class.forName("com.nobody.Teacher"); Method getName = aClass.getDeclaredMethod("getName"); getName.setAccessible(true); long start = System.currentTimeMillis(); for (int i = 0; i < times; i++) { getName.invoke(teacher); } long end = System.currentTimeMillis(); System.out.println("关闭安全检查反射方式执行10亿次消耗:" + (end - start) + "ms"); } } class Teacher { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } //输出结果 普通方式执行10亿次消耗:13ms 反射方式执行10亿次消耗:20141ms 关闭安全检查反射方式执行10亿次消耗:8233ms
经过实验可知,反射比直接执行相同的方法慢了不少,特别是当反射的操做被频繁调用时效果更明显,固然经过关闭安全检查能够提升一些速度。因此,放射也不该该泛滥成灾的,而是适度使用才能发挥最大做用。