注解与反射
导言:
Java是一种静态语言,所谓静态语言就是强类型语言。
我们一般创建对象会直接使用new
来创建,但是我们想要动态的创建对象,不知道类名,或者方法名这些的时候怎么办?反射机制可以帮助到我们,注解与反射完美结合,帮助我们把Java语言从静态语言变成准动态语言。
注解
注解很简单,也很常见,例如在一个类继承另一个类的时候,会重写另一个类中的方法,在该方法的上面会出现一行@Override
的注解。
元注解
在idea中我们可以按住CTRL左键单击@Override
看到它的源码
package java.lang;
import ...
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Target:定义注解可以用在哪些地方,有参数
@Retention:表示注解在什么地方还有效,也就是生命周期,有参数(SOURCE<CLASS<RUNTIME,一般使用RUNTIME)
@Document:显示在JavaDoc中
@Inherited:定义注解可以被继承
@Target中有12个枚举类型的参数
TYPE,//类,接口
FIELD,//属性
METHOD,//方法
PARAMETER,//参数
CONSTRUCTOR,//构造器
LOCAL_VARIABLE,//局部变量
ANNOTATION_TYPE,
PACKAGE,//包
TYPE_PARAMETER,
TYPE_USE,
MODULE;
//没有注释的我基本没有用过
@Retention有三个参数SOURCE<CLASS<RUNTIME,一般使用RUNTIME就可以了。
自定义注解
使用@interface就可以定义注解了
注解的参数: 参数类型+参数名(),如果想要定义的注解只有一个参数的时候建议使用value()
当参数名,这样可以在使用注解的时候直接传如参数例如MyAnnotation("参数")
import java.lang.annotation.*;
@Target(value = {ElementType.METHOD,ElementType.TYPE})//定义注解可以用在哪些地方
@Retention(value = RetentionPolicy.RUNTIME)//表示注解在什么地方还有效
@Documented//显示在JavaDoc中
@Inherited//定义注解可以被继承
@interface MyAnnotation{
//注解的参数: 参数类型+参数名()
String value() default "默认名称";//用default规定参数的默认值
int id() default -1;//有了默认值后再使用注解的时候该参数可以选择不传入
int age();
}
测试自定义的注解:
@MyAnnotation(age = 18)
public class Test02 {
@MyAnnotation(value = "ffd",id = 1,age = 20)
public void test(){}
}
反射
创建需要使用的类
class Person{
private int id;
private String name;
private int age;
//这就是三个私有属性,下面就是构造方法,toString,get,set可以直接跳过,不浪费时间
public Person() {}
public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
//继承Person类
class Teacher extends Person{
public Teacher() {
System.out.println("老师");
}
}
class Student extends Person{
public Student(){
System.out.println("学生");
}
}
获得Class类的方法
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Person person = new Student();
System.out.println("这个人是"+person.getName());
//方式1:通过对象获得,在有实例化对象的时候使用
Class c1 = person.getClass();
System.out.println(c1.hashCode());
//方式2:forName获得,在已经知道包名的类名的情况下使用
Class c2 = Class.forName("com.tonyffd.reflection.Student");
System.out.println(c2.hashCode());
//方式3:使用该类名.Class获得,在已经知道类名的情况下使用
Class c3 = Student.class;
System.out.println(c3.hashCode());
}
}
我们可以输出比较他们的哈希值,发现它们都是一样的,下面的代码是输出结果,也就是说我们的到的是同一个类:
这个人是null
2129789493
2129789493
2129789493
接下来就要开启正餐了,使用反射的到类的实例化对象,类的方法,类的属性。(重点)
把这段代码放在上一段的main
方法中测试就可以了
//通过反射去创建一个对象
Person person1 = (Person) c1.newInstance();
System.out.println(person1);
//通过反射执行类中的方法,要使用invoke激活方法,如果方法是类私有的,可以调用方法对象的setAccessible(true)安全检查的开关是否开启
Method setId = c1.getMethod("setId", int.class);
setId.invoke(person1,10);
System.out.println(person1);
//通过反射操作类中的属性
Field age = Person.class.getDeclaredField("age");//如果调用的是getField获取不到该类的私有属性
age.setAccessible(true);//关闭安全检查
age.set(person1,18);
System.out.println(person1);
测试结果如下所示:
Person{id=0, name='null', age=0}
Person{id=10, name='null', age=0}
Person{id=10, name='null', age=18}
使用反射会影响到我们程序执行效率,比起new
一个对象来,效率大打折扣。所以在大量的操作反射时,我们可以使用setAccessible(true)
关闭安全检查可以优化程序的执行效率。
反射得到泛型,这个相对比较麻烦,可以选择性的看,仅供参考:
public class Test02 {
public static void main(String[] args) throws NoSuchMethodException {
Method test01 = Test02.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = test01.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("test01#"+genericParameterType);
if (genericParameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();//获得真实参数类型
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(" test01-"+actualTypeArgument);
}
}
}
Method test02 = Test02.class.getMethod("test02");
Type genericReturnType = test02.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("test02======Return====="+actualTypeArgument);
}
}
}
public void test01(Map<String,Person> map,List<Person> list){
System.out.println("test01");
}
public Map<String,Person> test02(){
System.out.println("test02");
return null;
}
}
运行结果:
test01#java.util.Map<java.lang.String, com.tonyffd.reflection.Person>
test01-class java.lang.String
test01-class com.tonyffd.reflection.Person
test01#java.util.List<com.tonyffd.reflection.Person>
test01-class com.tonyffd.reflection.Person
test02======Return=====class java.lang.String
test02======Return=====class com.tonyffd.reflection.Person
练习使用反射和注解(结合使用)
先定义注解
//定义一个注解把实体类转换为数据库
@Target(ElementType.TYPE)//使用在描述类
@Retention(RetentionPolicy.RUNTIME)
@interface EntityConvertData{
String value();
}
//定义一个描述类属性的注解
@Target(ElementType.FIELD)//使用在类的属性上
@Retention(RetentionPolicy.RUNTIME)
@interface FiledAnnotation{
String name();
String type() default "varchar";
int length() default 0;
}
定义一个实体类,使用上面的注解
@EntityConvertData("db_user")
class User{
@FiledAnnotation(name = "user_id",type = "int",length = 10)
int id;
@FiledAnnotation(name = "user_name",type = "varchar",length = 20)
String name;
@FiledAnnotation(name = "user_age",type = "int",length = 3)
int age;
}
测试
public class Test03 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = User.class;
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获取value值
EntityConvertData annotation = (EntityConvertData)c1.getAnnotation(EntityConvertData.class);
System.out.println(annotation.value());
//获取类的属性或者方法的注解的值
//先获取该方法或属性
Field id = c1.getDeclaredField("id");
FiledAnnotation idAnnotation = id.getAnnotation(FiledAnnotation.class);
System.out.println(idAnnotation.name());
System.out.println(idAnnotation.type());
System.out.println(idAnnotation.length());
}
}
总结:
通过测试我们虽然并没有真正意义上的得到一个实体类转换为数据库的功能,但是思想就是这样的,我们可以通过注解去动态获取未知类的方法,属性,类名等。动态的到信息后再使用这些信息写一些逻辑代码实现功能就可以了。这就已经接近框架的思想了,注解与反射配合。
例如spring,springMVC等框架,使用注解动态得到信息,把得到的信息交给框架的逻辑代码处理,就可以帮我们省去很多繁琐的实现代码。
鄙人愚见,感谢客官赏脸看完,正在努力的学生小白,有不对的地方望大佬指正。