一、注解
1、什么是注解?
注解说白了就是对程序做出解释,与我们在方法、类上的注释没有区别,但是注解可以被其他程序所读取,进行信息处理,否则与注释没有太大的区别。
2.内置注解
内置注解就是我们的jdk所带的一些注解。常用的三个注解:
-
@Override 重写方法的注解(接口和继承中)
-
@Deprecated 废弃注解 。这个注释可以修辞方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为他很危险或有更好的选择。
public class TestDemo {
public static void main(String[] args) {
testDeprecated(); //中间会有横线
}
@Deprecated
public static void testDeprecated(){
System.out.println("这是一个过时的方法");
}
}
-
@SuperWarnings 这个注解主要是用来抑制警告信息的,我们在写程序时,可能会报很多黄线的警告,但是不影响运行,我们就可以用这个注解来抑制隐藏它。与前俩个注解不同的是我们必须给注解参数才能正确使用他。
参数 |
说明 |
deprecation |
使用了过时的类或方法的警告 |
unchecked |
执行了未检查的转换时的警告 如:使用集合时未指定泛型 |
fallthrough |
当在switch语句使用时发生case穿透 |
path |
在类路径、源文件路径中有不存在路径的警告 |
serial |
当在序列化的类上缺少serialVersionUID定义时的警告 |
finally |
任何finally子句不能完成时的警告 |
all |
关于以上所有的警告 |
上表中就是@SuperWarnings注解的一些参数,按需使用即可。
3.自定义注解
格式:public @interface 注解名 { 定义体 }
- 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 我们在使用注解元素时必须要有值,可以定义默认值,空字符串,0或者-1
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
//参数默认为空
String value() default "";
}
4.元注解
我们在自定义注解时,需要使用java提供的元注解,就是负责注解的其他注解。java定义了四个标准的meta-annotation类型,他们被用来提供对其他注解类型声明。
所修饰范围 |
取值ElementType |
package 包 |
PACKAGE |
类、接口、枚举、Annotation类型 |
TYPE |
构造器 |
CONSTRUCTOR |
类型成员(方法,构造方法,成员变量,枚举值) |
FIELD |
用于描述方法 |
METHOD |
描述局部变量 |
LOCAL_VARIABLE |
描述参数 |
PARAMETER |
在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zPPXDmDi-1669042421222)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1667313476813.png)]
我们在来测试一下这个注解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JakYxPV9-1669042421223)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1667313566473.png)]
结果显而易见,当我们将注解放在我们的变量时,编译器给我们报了一个错,翻译过来的意思就是这个注解不被允许放在这里。而放在方法上就可以安静的放在那里。
-
@Retention
这个注解的作用就是我们需要告诉编译器我们需要在什么级别保存该注释信息,用于描述注解的生命周期。
取值RetentionPolicy |
作用 |
SOURCE |
在源文件中有效(即源文件保留) |
CLASS |
在class文件中有效(即class保留) |
RUNTIME |
在运行时有效(即运行时保留)注:为RUNTIME时可以被反射机制所读取 |
在一般情况下我们使用RUNTIME即可。这样在程序运行时我们也可以通过反射机制来读取到该注解。
@Document //是java在生成文档,是否显示注解的开关
@Inherited //如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解
上面俩个注解我们使用的就不算很多,我们一会可以通过反射机制读取到我们的注解。
二、反射(Reflect)
1、什么是反射?
==反射指的是我们可以在运行期间加载、探知、使用编译期间完全未知的类==。是一个动态的机制,允许我们通过字符串来指挥程序实例化,操作属性、调用方法。使得代码提高了灵活性,但是同时也带来了更多的资源开销。
加载完类之后,在堆内存中,就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。 我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过 这个镜子看到类的结构,所以,我们形象的称之为:反射。
2、class类
我们在使用反射时,需要先获得我们需要的类,而java.lang.Class这个类必不可少,他十分特殊,用来表示java中类型 (class/interface/enum/annotation/primitive type/void)本身。
- Class类的对象包含了某个被加载类的结构。一个被加载的类对应一个 Class对象。
- 当一个class被加载,或当加载器(class loader)的defineClass()被 JVM调用,JVM 便自动产生一个Class 对象。
我们应该如何获取Class类的对象?
我们先创建一个普通的实体类。
public class Student {
private String name;
public Integer age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
①、 通过Class.forName()获取(最常用)
public class TestDemo {
public static void main(String[] args) {
try {
//获取Student的Class对象,参数为需要获取类对象的全类名
Class c1 = Class.forName("Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
②、 通过getClass()获取
public class TestDemo {
public static void main(String[] args) {
//new一个student对象
Student student = new Student();
//通过student对象来获取student类对象
Class c1 = student.getClass();
}
}
③、 通过.class获取
public class TestDemo {
public static void main(String[] args) {
//通过导包获取类名点class来获取类对象
Class c1 = Student.class;
}
}
3、反射的基本操作
我们获取到Class对象后,可以获取该类的某些信息。在这里我们来看一些常用的。
①获取类名
public class TestDemo {
public static void main(String[] args) {
try {
Class c1 = Class.forName("Student");
//获取全类名
String name = c1.getName();
//获取简单的类名
String simpleName = c1.getSimpleName();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
②、获取类的字段、某些变量
注意:我们仔细看注释,不带Declared的方法职能获取到public字段,且包括父类的,带Declared的方法可以获取到所有的自定义的字段!
public class TestDemo {
public static void main(String[] args) {
try {
Class c1 = Class.forName("Student");
//获取该类的所有public字段,包括父类的
Field[] fields = c1.getFields();
System.out.println(Arrays.toString(fields));
//根据字段名获取该类的public字段
Field age = c1.getField("age");
//获取该类的所有字段,不包括父类(仅自定义)
Field[] declaredFields = c1.getDeclaredFields();
//根据字段名获取该类的字段
Field name = c1.getDeclaredField("name");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
③、获取类的方法
public class TestDemo {
public static void main(String[] args) {
try {
Class c1 = Class.forName("Student");
//获取该类的所有public方法,包括父类的
Method[] methods = c1.getMethods();
//根据方法名获取该类的public方法
Method getName = c1.getMethod("getName");
//获取该类的所有方法,不包括父类(仅自定义)
Method[] declaredMethods = c1.getDeclaredMethods();
//根据方法名获取该类的方法
Method getAge = c1.getDeclaredMethod("getAge");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
注:获取方法的方式与获取字段的方法一样,在这里我们需要注意的是重写的方法,一个类中存在俩个或多个方法名是一样的,因此在根据方法名获取方法时,提供第二个参数,为可变参数。参数类型为我们获取方法的参数类型的类,如果不写默认为无参方法。
④、获取类的构造器
public class TestDemo {
public static void main(String[] args) {
try {
Class c1 = Class.forName("Student");
//获取该类的所有构造器,包括父类
Constructor[] constructors = c1.getConstructors();
//根据构造器的参数类型来获取指定构造器,不写为无参构造器
Constructor constructor = c1.getConstructor(String.class, Integer.class);
//获取该类的所有构造器,不包括父类
Constructor[] declaredConstructors = c1.getDeclaredConstructors();
//根据构造器的参数类型来获取指定的自定义构造器,不写为无参构造器
Constructor declaredConstructor = c1.getDeclaredConstructor();
Constructor declaredConstructor1 = c1.getDeclaredConstructor(String.class, Integer.class);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
注:在我们获取类构造器和类方法时涉及到可变参数的知识,大家可以自行百度一下,或者查阅官方文档,也不难,就是在我们不确定参数有几个时,就可以写成可变参数,我们在使用时可以传多个参数。注意我们写的参数都为类对象!
我们获取到构造器第一想法应该是实例化对象。
⑤、类的实例化
public class TestDemo {
public static void main(String[] args) {
try {
Class c1 = Class.forName("Student");
//通过class类直接实例化,使用的是User类的无参构造器
Student student = (Student) c1.newInstance();
//获取构造器来进行实例化,这里获取有参构造器
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, Integer.class);
//根据构造器进行实例化
Student student1 = (Student) declaredConstructor.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
注:我们在使用类对象直接实例化时,一定要确保需实例化的类中存在无参构造器,否则会报错。默认获取的是Object类型,因此最后需要进行下类型转化。
测试:我们在User类中重写toString方法打印下获取的对象看一下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GUvhgV9X-1669042421223)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1667315461519.png)]
我们获取到对象是不是该通过获取的对象调用方法,NO!我们通过反射来调用对象的方法。
⑥、方法的调用
测试:我们打印下该对象看一下方法执行了没有
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NkgkzwYM-1669042421224)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1667315793190.png)]
注:如果我们调用的方法为私有方法,虽然编译器通过,在运行时会报错的(java.lang.IllegalAccessException),这是因为java的安全检查。我们可以使用setAccessible(true)这个方法来跳过安全检查。
public class TestDemo {
public static void main(String[] args) {
try {
Class c1 = Class.forName("Student");
//通过class类直接实例化,使用的是User类的无参构造器
Student student = (Student) c1.newInstance();
//获取setName方法
Method setName = c1.getMethod("setName", String.class);
//跳过安全检查
setName.setAccessible(true);
setName.invoke(student,"xiaohu");
System.out.println(student);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
我们在写程序时一般通过get/set方法来操作字段,下面我们同样也是通过反射来操作字段。
⑦、字段的操作
public class TestDemo {
public static void main(String[] args) {
try {
Class c1 = Class.forName("Student");
Student student= (Student) c1.newInstance();
//获取name字段
Field name = c1.getDeclaredField("name");
//通过该字段的set方法来改变该字段的值,该字段有俩个参数
//第一个为应该修改其字段的参数
//第二个为被修改字段的新值
name.set(student,"小胡");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
测试:我们打印下该对象,看一下name字段改变没有。
这还不够,反射很强,我们说过反射的对象像一面镜子,我们能看到的东西都可以获取,我们下面来读取一下参数泛型,返回值泛型!
⑧、泛型的操作(Generic)
对于泛型我们应该不会陌生,java采用泛型擦除的机制来引入泛型。也就是说java的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是一旦编译完成,所有和泛型有关的数据全部擦除。
为了通过反射操作这些类型以迎合实际开发的需要,Java就新增了ParameterizedType, GenericArrayType,TypeVariable 和WildcardType几种类型来代表不能被归一到Class 类中的类型但是又和原始类型齐名的类型,这四种类型实现了Type接口。
类型 |
含义 |
ParameterizedType |
参数化类型,带有类型参数的类型,即常说的泛型,如:List《T》 |
TypeVariable |
类型变量,如参数化类型Map《E,Y》中的Y、K等类型变量,表示泛指任何类 |
GenericArrayType |
(泛型)数组类型,比如List《T》[],T[]这种。注意,这不是我们说的一般数组,而是表示一种【元素类型是参数化类型或者类型变量的】数组类型 |
WildcardType |
代表通配符表达式,或泛型表达式,比如【?】【? super T】【? extends T】。虽然WildcardType是Type的一个子接口,但并不是Java类型中的一种 |
我们演示一下反射读取ParameterizedType类型。
public class TestDemo10 {
public static void main(String[] args) {
try {
//先获取到该类
Class c1 = Class.forName("test.TestDemo10");
//获取到测试方法
Method genericityTest = c1.getDeclaredMethod("GenericityTest", List.class, Student.class);
//获取到类型参数数组,就是获取方法所有的参数类型
Type[] genericParameterTypes = genericityTest.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);
}
}
}
//获取方法所有的参数类型
Type[] genericParameterTypes1 = genericityTest.getGenericParameterTypes();
//获取返回值的参数类型,返回值只有一个,所有不是数组
Type genericReturnType = genericityTest.getGenericReturnType();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
//测试的方法 ,返回类型与参数都为泛型
public static Map<String, Student> GenericityTest(List<Student> list, Student student) {
return null;
}
}
我们看着可能会很复杂,我们来分析一下。
首先,genericityTest这个为我们获取的方法,我们通过genericityTest来获取该方法的参数,注意看,这里返回的是Type类型,也就是所有类型的父接口,我们打印下看,就是返回的List与User,也就是他的俩个参数List,student。
接下来我们遍历下类型Type参数数组,我们现在需要的是读取参数化类型,那么我们对每一个Type参数进行判断,如果该Type参数属于ParameterizedType参数化类型,那么我们在获取到该泛型的实际类型参数,也就是List中的Student,注意,只有List《Student》才属于参数化类型,可以查看上面的表。
注:我们在上面演示的只是获取方法的参数,那么我们如何获取返回值的类型?下面第二个方法
//获取方法所有的参数类型
Type[] genericParameterTypes = genericityTest.getGenericParameterTypes();
//获取返回值的参数类型,返回值只有一个,所有不是数组
Type genericReturnType = genericityTest.getGenericReturnType();
//获取方法所有的参数类型
Type[] genericParameterTypes = genericityTest.getGenericParameterTypes();
//获取返回值的参数类型,返回值只有一个,所有不是数组
Type genericReturnType = genericityTest.getGenericReturnType();
⑨、注解的操作
注解的操作相对就比较简单了,如果我们想读取类上、方法上或字段上的注解,我们仅需要获取到你需要读取的注解所修辞的类、方法或字段来获取就可以。
我们以获取方法上的注解来测试一下:
这里我们还是使用我们在文章首部创建的TestAnnotation注解,只能放在方法上,保留到运行时。
public class AnnotationTest {
public class TestDemo {
public static void main(String[] args) {
try {
Class c1 = Class.forName("Student");
//先获取方法
Method getName = c1.getDeclaredMethod("getName");
//根据我们的main方法获取getName方法上的注解
MyAnnotation declaredAnnotation = getName.getDeclaredAnnotation(MyAnnotation.class);
//获取到他的值
String value = declaredAnnotation.value();
System.out.println(value);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
注:如果我们需要获取类上的注解,只需要获取到类对象,然后.getDeclaredAnnotation()即可,其实不管是获取类上的注解还是字段上的注解都是一样的方法,关于有无Declared的方法,看到这里大家应该也有了解了。
ation = getName.getDeclaredAnnotation(MyAnnotation.class);
//获取到他的值
String value = declaredAnnotation.value();
System.out.println(value);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
注:如果我们需要获取类上的注解,只需要获取到类对象,然后.getDeclaredAnnotation()即可,其实不管是获取类上的注解还是字段上的注解都是一样的方法,关于有无Declared的方法,看到这里大家应该也有了解了。
如有侵权,请联系删除,谢谢。