深入理解Java框架机制(注解和反射)
版权声明:本文为博主ExcelMann的原创文章,未经博主允许不得转载。
Java基础——注解和反射
作者:ExcelMann,转载需注明。
该篇文章的内容来源于B站视频的总结(up主:遇见狂神说)。
内容目录:
- 什么是注解
- 内置注解
- 元注解
- 自定义注解
- 反射概述
- 获得反射对象
- 得到Class类的几种方式
- 所有类型的Class对象
- 类加载内存分析
- 分析类初始化
- 类加载器
- 获取类的运行时结构
- 动态创建对象执行方法
- 性能对比分析
- 获取泛型信息
- 获取注解信息
一、什么是注解
- Annotation是从JDK5.0开始引入的新技术;
- Annotation的作用:
1)不是程序的本身,但是可以对程序作出解释(与注释相同);
2)可以被其它的程序(比如编译器)读取,使得具有检错作用; - Annotation在哪里使用?
可以附加在package、class、method、field等上面,相当于给他们添加了额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问;
二、内置注解
- Java常用的三个内置注解:
三、元注解
- 元注解的作用:负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型做说明;
- 四个:
@MyAnnotation
public class Test {
}
//自定义一个注解,其中用到了四个元注解
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME) //一般自定义注解,都选择RUNTIME;
@Documented
@Inherited
@interface MyAnnotation{
}
四、自定义注解
-
如何自定义注解:使用@interface自定义注解,自动继承了java.lang.annotation.Annotation接口;
-
分析:
-
代码实例:
@MyAnnotation(name = "") //此时可以省略age参数的传参
@MyAnnotation2("value") //此时可以省略"value="
public class Test {
}
//自定义注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//注解的参数:参数类型+参数名()
//记住不是方法,是注解的参数
String name();
int age() default 0; //default定义默认值,此时使用注解的时候可以不用赋予该参数;
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
String value(); //特殊情况:当参数名为value时,此时使用注解的时候可以省略value;
}
五、反射概述
-
动态语言和静态语言:
动态语言是一类在运行的时候可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进或者删除已有函数等。
静态语言在运行时结构不可变,如Java、C和C++。
Java不是动态语言,不过称为“准动态语言”,因为可以利用反射机制获得类似动态语言的特性。 -
反射(Reflection):反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法;
-
在加载了类之后,在堆内存的方法区中,就产生了一个Class类型的对象(一个类只有一个Class对象),该对象包含了完整的类的结构信息。
我们可以通过该对象来看到类的完整的结构,这个对象就像是一个镜子一样,透过镜子看到类的结构,因此成为反射; -
Java反射机制提供的功能:
六、获得反射对象
- 代码如下:
public class ReflectionTest {
public static void main(String[] args) throws ClassNotFoundException {
//通过反射获取类的Class对象
Class c1 = Class.forName("AnnotationAndReflection.User");
System.out.println(c1); //printf class AnnotationAndReflection.User
//后面就可以通过该对象,来反射求出类
}
}
class User{
private String name;
private int id;
private int age;
public User() {
}
...
}
七、得到Class类的几种方式
-
Class类:对于每个类而言,JRE都为其保留了一个不变的Class类型的对象。一个Class对象包含了特定某个结构的有关信息;
-
Class类的常用方法:
-
获取Class类的多种方式:
1)若已知是具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高:
Class clazz = Person.class;
2)已知某个类的实例,调用该实例的getClass()方法获取Class对象:
Class clazz = person.getClass();
3)已知一个类的全名,且该类在类路径中,可通过Class类的静态方法forName()获取:
Class clazz = Class.forName(…);
4)内置基本数据类型的包装类可以直接用类名.Type;
5)还可以利用ClassLoader加载器。。。
八、所有类型的Class对象
-
哪些类型可以有Class对象:
class(外部类,成员,局部内部类,匿名内部类)、interface、数组、枚举、注解、基本数据类型包装器、void;
九、(★)类加载内存分析
-
Java内存划分(其中的方法区就是特殊的堆):
-
类的加载过程:当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过以下三个步骤来对该类进行初始化。
-
实例代码与解析:
public class Test{
public static void main(String[] args){
A a = new A();
System.out.println(A.m);
}
}
class A{
static{
System.out.println("A类静态代码块初始化");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A类的无参数构造初始化");
}
}
运行结果:
画内存图解析:
1.加载到内存,会产生一个类对应的Class对象
2.链接,链接结束后m=0
3.初始化,执行clinit方法,故最后m=100
<clinit>(){
//static代码块中的代码
System.out.println("A类静态代码块初始化");
m = 300;
//类变量初始化代码
m = 100;
}
十、分析类初始化
- (★)什么时候会发生类初始化:
十一、类加载器
-
java程序运行图:
-
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象;
-
类加载器的作用:
-
获取类加载器的代码:
//获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取系统类的加载器的父类加载器--->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@1540e19d
//获取扩展类加载器的父类加载器--->根加载器(c/c++)
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);//null
//测试当前类是哪个加载器加载的:系统类加载器
ClassLoader classLoader = Class.forName("AnnotationAndReflection.ClassLoaderTest").getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//测试JDK内置的类是谁加载的:根加载器
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);//null
//如何获得系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));//各种jdk/jre/jar包路径,以及E:\Java学习\Java核心技术\out\production\Java核心技术等
十二、获取类的运行时结构
- 通过反射获取运行时类的完整结构:Field,Method,Constructor,Superclass,Interface,Annotation;
- 代码如下:
Class aClass = Class.forName("AnnotationAndReflection.User");
//获得类的名字
System.out.println(aClass.getName());
System.out.println(aClass.getSimpleName());
System.out.println("==============================");
//获得类的属性
Field[] fields = aClass.getFields(); //只能找到类的public属性
fields = aClass.getDeclaredFields(); //找到全部属性
for(Field field:fields) {
System.out.println(field);
}
//获得类的指定属性
Field field = aClass.getDeclaredField("name");
System.out.println(field);
System.out.println("==============================");
//获得类的方法
Method[] methods = aClass.getMethods(); //获得本类以及父类的所有public方法
for(Method method:methods){
System.out.println("所有的:"+method);
}
methods = aClass.getDeclaredMethods(); //获得本类的所有方法(包含私有方法)
for(Method method:methods){
System.out.println("本类的:"+method);
}
//获取指定方法
Method getName = aClass.getDeclaredMethod("getName",null);
Method setName = aClass.getDeclaredMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
System.out.println("==============================");
//获取类的构造器
Constructor[] constructors = aClass.getConstructors(); //获得public的构造器
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
constructors = aClass.getDeclaredConstructors(); //获得所有构造器
for (Constructor constructor : constructors) {
System.out.println("#"+constructor);
}
//获得指定的构造器
Constructor constructor = aClass.getDeclaredConstructor(String.class, int.class, int.class);
System.out.println(constructor);
十三、动态创建对象执行方法
- 有了Class对象之后,能做什么:可以通过Class对象创建该类的对象,然后执行该类的方法。
- 如何利用反射机制动态创建对象:
1)调用Class对象的newInstance()方法,要求类必须有一个无参数的构造器,而且类的构造器的访问权限足够;
2)当类没有无参的构造器的时候,只要在操作的过程中明确指定类中的构造器即可创建对象,步骤如下:首先,通过Class对象获取本类的指定形参类型的构造器;第二步,向构造器的形参中传递一个对象数组进去,里面包含了构造器所需的各个参数;第三步,通过Constructor实例化对象; - 通过Object invoke(Object obj,Object[] args)调用指定的方法:
1)Object对应原方法的返回值,若原方法无返回值,此时返回null;
2)若原方法为静态方法,则obj参数可以为null;
3)若原方法形参列表为空,则args参数可以为null;
4)(★)若原方法声明为private,则在调用invoke方法之前,需要先调用setAccessible(true)方法,即可访问private的方法; - setAccessible:Method和Field、Constructor对象都有setAccessible()方法;
十四、性能对比分析
- 由实验结果得出,反射执行的速度比普通方式要慢很多。如果非得要用反射,而且是多次用的情况下,可以通过关闭检测来提高速度;
十五、获取泛型信息
-
注意点:对于泛型,Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的问题,但是,一旦编译成功,所有和泛型有关的类型全部擦除;
但是泛型的信息,还是会在加载的过程中存储在Class中; - 代码实例:
//通过反射获取方法参数泛型
//第一步:获取指定的方法
Method method = ReflectionTest.class.getDeclaredMethod("test01", Map.class, List.class);
//第二步:获取该方法的参数泛型
Type[] genericParameterTypes = method.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);
}
}
}
System.out.println("=============");
//通过反射获取方法返回值泛型
method = ReflectionTest.class.getDeclaredMethod("test02",null);
Type genericReturnType = method.getGenericReturnType();//通过方法获得返回值泛型
if(genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
十六、获取注解信息
该节中,通过一个练习,利用注解和反射完成类和表结构的映射关系。
-
了解什么是ORM:
- 通过反射获取类、方法或字段的注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface tabel{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface attribute{
String columnName();
String type();
int length();
}
@tabel("db_student")
class Student1{
@attribute(columnName = "id",type = "int",length = 10)
private int id;
@attribute(columnName = "age",type = "int",length = 10)
private int age;
@attribute(columnName = "name",type = "String",length = 10)
private String name;
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("AnnotationAndReflection.Student1");
//通过反射获取类的注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);//@AnnotationAndReflection.tabel(value=db_student)
}
//获得指定注解的value值
tabel tabelAnnotation = (tabel)c1.getAnnotation(tabel.class);
String value = tabelAnnotation.value();
System.out.println(value);//db_student
//获得类内部(方法或字段)的指定注解
Field name = c1.getDeclaredField("name");
attribute attributeAnnotation = name.getAnnotation(attribute.class);
System.out.println(attributeAnnotation.columnName());//name
System.out.println(attributeAnnotation.length());//10
System.out.println(attributeAnnotation.type());//String
}
本文地址:https://blog.csdn.net/a602389093/article/details/110244910