在写代码的过程中,经常会碰见注解,类似于重写的注解@Override,在架构的过程中,也常常需要自定义注解,因此掌握Java注解的使用,是非常有必要的。
1、Java注解
Java注解就是用来解释代码,对程序作出解释,其他的程序也可以获取该注解。注解使用的场景也很多,像package、method、class、field等,通常是通过反射来获取注解信息。
2、Java的注解类型
(1)内置注解
常见的内置注解有@Override
,关于重写的注解;
@SupressWraning
,用来抑制编译时的异常;
@SuppressWarnings("all")
public void text01(){
}
没有加SupressWraning
之前,方法名是灰色的,加了之后,就是编译之后的颜色。
@Deprecated
,过时的,程序员不鼓励使用的元素,但是还是可以使用的。
@Deprecated
public static void test(){
}
//这里没有体现,test是被画横线的,就是被移除的
public static void main(String[] args){
test();
}
(2)元注解
Java中定义了4种元注解,用来对注解进行说明,分别为@Target
、@Rentation
、@Document
、@Inherited
@Target
:用来描述注解的使用范围,该注解可以使用在什么地方。
@Rentation
:用来描述注解的生命周期(SOURCE < CLASS < RUNTIME)
@Documented
:该注解将被包含在javadoc中
@Inherited
:表示子类可以继承父类的注解
常用的注解就是前两种。
(3)自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
在自定义注解的时候,需要添加Target
和Retention
注解,Target
代表该注解只能使用在方法上,不能使用在其他地方,该注解的声明周期是在运行时才会有效。
3、自定义注解的使用
(1)注解参数
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
//参数类型 + 参数名 (); 如果没有默认值,需要赋值
String name() default "";
}
注意,在注解中写入的都是参数,不是方法!这些参数的返回值只能是基本数据类型、Class、String、枚举。
@MyAnnotation(name = "jiajia")
public static void main(String[] args){
test();
}
在注解中,也需要将这个参数写到注解上,这是在没有使用default赋值的时候,如果使用default赋初始值,那么就不需要再注解中赋值。
4、反射机制
自定义的注解,只能通过反射来获取注解信息,这样自定义的注解才有意义。
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student(int age){
this.age = age;
}
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
下面在使用反射时,就是利用上述实体类来完成。
(1)根据对象获取该类名。
Student student = new Student("lei",20);
Class<? extends Student> aClass = student.getClass();
String name = aClass.getName();
System.out.println("class=="+aClass+",name=="+name);
通过getClass
+ getName
方法可以获取该对象的类。
那么那些有class对象:类、Class、接口、注解、枚举、数组、void、基本数据类型…
(2)通过类对象获取构造方法(全部)
//获取构造方法
Constructor<?>[] constructors = aClass.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
//获取该构造方法的修饰符(private public protected)
int modifiers = constructors[i].getModifiers();
System.out.println(Modifier.toString(modifiers));
//构造方法成员类型
Class<?>[] parameterTypes = constructors[i].getParameterTypes();
for (int j = 0; j < parameterTypes.length; j++) {
System.out.println(parameterTypes[j].getName());
}
public
java.lang.String
int
public
int
通过使用某个类的getDeclaredConstructors
方法就可以获取到所有的构造方法,当然想要获取某个构造函数,比如说只有int形参的构造方法。
//获取只有int类型参数的构造方法
Class[] intType = {int.class};
Constructor<? extends Student> constructor = aClass.getDeclaredConstructor(intType);
(3)通过构造方法创建实例
//得到构造方法,创建实例
Student stu = constructor.newInstance(12);
int age = stu.getAge();
System.out.println(stu.toString()+",age==="+age);
Student{name='null', age=12},age===12
在得到某个构造方法后,调用newInstance
方法,就可以创建一个实例。
注意一点:当类使用newInstance
()动态创建一个对象的时候,它默认调用的是类的无参构造函数,因此在类中必须要实现一个无参的构造方法;当然也不是一定要存在无参构造,可以在调用newInstance
时,传入参数。
Class<?> c1 = Class.forName("com.example.eventbus.User");
System.out.println(c1);
User user = (User) c1.newInstance();
#User类中必须要有一个空参的构造方法,否则这样的方式运行就会报错
(4)使用类中的私有方法
//获取类中的私有方法
Class[] classType = {String.class,int.class};
Constructor<? extends Student> declaredConstructor = aClass.getDeclaredConstructor(classType);
Student student1 = declaredConstructor.newInstance("lay", 28);
//setName方法中的形参是String类型的
Class[] newtype = {String.class};
Method method = aClass.getDeclaredMethod("setName", newtype);
//private类型需要设置为可使用
method.setAccessible(true);
String[] strList = {"jia"};
//修改后的新实例
Object student2 = method.invoke(student1, strList);
System.out.println(student1.toString());
通过getDeclaredMethod
得到某个具体的方法,传入方法名和该方法的形参类型,调用invoke
执行该方法,然后传入要修改的对象,以及要修改的属性。
invoke
:激活该方法,该方法可以传入可变的参数列表,第一个参数往往是该类对象,后面的参数就是需要修改的参数,根据Paramtype来设置。
(5)获取类中的静态私有方法
//调用类中的私有静态方法
Method work = aClass.getDeclaredMethod("work");
work.setAccessible(true);
//静态方法可直接执行
work.invoke(null);
(6)获取类中某个私有字段
Student stu3 = new Student("lay",23);
Class<? extends Student> stu3Class = stu3.getClass();
//获取该字段属性
Field name= stu3Class.getDeclaredField("name");
name.setAccessible(true);
//得到该类中的字段值
//Object filedObject = name.get(stu3);
name.set(stu3,"jia");
通过getDeclaredField
方法,获取“name”这个字段属性,name字段拥有get和set方法,通过set方法可以设置该字段的值,传入的第一个参数为该类对象,第二个参数为修改的属性值。
(7)setAccessible的性能优化
当调用的属性或者方法是私有的时候,需要设置setAccessible为true,这样就可以访问该属性或者方法;当访问的属性和方法是共有属性的时候,设置setAccessible为true,则可以提高性能。
public static void testNormal(){
long startTime = System.currentTimeMillis();
User user = new User("lay",20,001);
for (int i = 0; i < 1000000000; i++) {
user.getUsername();
}
long endTime = System.currentTimeMillis();
System.out.println("正常的处理:"+(endTime - startTime));
}
public static void testReject() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
long startTime = System.currentTimeMillis();
User user = new User("lay",20,001);
Class<? extends User> c1 = user.getClass();
Method getUsername = c1.getDeclaredMethod("getUsername", null);
for (int i = 0; i < 1000000000; i++) {
getUsername.invoke(user,null);
}
long endTime = System.currentTimeMillis();
System.out.println("正常的处理:"+(endTime - startTime));
}
public static void testRejectNo() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
long startTime = System.currentTimeMillis();
User user = new User("lay",20,001);
Class<? extends User> c1 = user.getClass();
Method getUsername = c1.getDeclaredMethod("getUsername", null);
//关闭检测
getUsername.setAccessible(true);
for (int i = 0; i < 1000000000; i++) {
getUsername.invoke(user,null);
}
long endTime = System.currentTimeMillis();
System.out.println("正常的处理:"+(endTime - startTime));
}
运行结果:
正常的处理:2477
反射处理:3163
关闭检测处理:2315
(8)反射获取泛型
在JVM进行编译时,会将Java的泛型信息擦除,所有的对象都视为Object,因此Java中多出了几个方法,用反射来获取泛型信息。
public class Test11{
public void test01(Map<String,User> map, List<User> list){
System.out.println("test01");
}
public Map<String,User> getMap(){
System.out.println("getMap");
return null;
}
}
想要获取test01方法的参数泛型信息,可以使用该方法的getGenericParameterTypes
来获取。
Method test01 = Test11.class.getMethod("test01", Map.class, List.class);
Type[] parameterTypes = test01.getGenericParameterTypes();
for (Type types :
parameterTypes) {
System.out.println(types);
}
这样获取的是List和Map的信息:
java.util.Map<java.lang.String, com.example.eventbus.User>
java.util.List<com.example.eventbus.User>
如果想要获取Map中的String和User的信息,需要解析types 类型:
Method test01 = Test11.class.getMethod("test01", Map.class, List.class);
Type[] parameterTypes = test01.getGenericParameterTypes();
for (Type types :
parameterTypes) {
System.out.println(types);
//如果types 是参数化类型,就解析这个types
if(types instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) types).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
最终得到了全部的泛型信息:
java.util.Map<java.lang.String, com.example.eventbus.User>
class java.lang.String
class com.example.eventbus.User
java.util.List<com.example.eventbus.User>
class com.example.eventbus.User
如果想要获取getMap返回的泛型信息,就得使用getGenericReturnType
获取返回的泛型信息。
Method getMap = Test11.class.getMethod("getMap", null);
Type type = getMap.getGenericReturnType();
if(type instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
获取的泛型信息。
class java.lang.String
class com.example.eventbus.User
(9)反射获取注解信息
之间在JetPack组件中,使用过Room组件,其中就涉及到了很多注解,这里就写一些用到过的注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumInfo {
boolean PrimaryKey();
String column_name();
int length();
}
@Entity("db_user")
public class User {
@ColumInfo(PrimaryKey = true,column_name = "db_username",length = 10)
public String username;
@ColumInfo(PrimaryKey = false,column_name = "db_age",length = 10)
private int age;
@ColumInfo(PrimaryKey = false,column_name = "db_number",length = 10)
private int number;
如果通过反射得到这些注解信息,然后做相应的操作处理。
Class<?> c1 = Class.forName("com.example.eventbus.User");
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
通过getAnnotations
获取全部的注解,发现得到的是User类的全部信息。
@com.example.eventbus.annotation.Entity(value=db_user)
如果想要获取value的值,需要通过getAnnotation
获取特定的注解。
Entity entity = c1.getAnnotation(Entity.class);
String value = entity.value();
System.out.println(value);
对于属性的注解,获取的方式和类一致。
Field username = c1.getDeclaredField("username");
Annotation[] annotations1 = username.getAnnotations();
for (Annotation annotation : annotations1) {
System.out.println(annotation);
}
得到的结果:
@com.example.eventbus.annotation.ColumInfo(PrimaryKey=true, column_name=db_username, length=10)
如果想要获取其中的值,就需要单独解析。
ColumInfo annotation = username.getAnnotation(ColumInfo.class);
String name = annotation.column_name();
int length = annotation.length();
boolean isKey = annotation.PrimaryKey();
System.out.println(name);
System.out.println(length);
System.out.println(isKey);
最终的结果:
db_username
10
true