Java注解与反射
2021/5/9 1:25:33
本文主要是介绍Java注解与反射,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
(一)注解
注解Annotation:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。
注解Annotation的作用:
- 不是程序本身,可以对程序作出解释;
- 可以被其他程序读取;
- 注解一般是以“@注释名”在代码中存在;
- 可以通过反射机制编程实现对这些元数据的访问;
内置注解
@Override:检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated:标记过时方法,不安全的,废弃的。如果使用该方法,会报编译警告。
@SuppressWarnings :指示编译器去忽略注解中声明的警告。该注解可以添加参数:
@SuppressWarnings(“all”)
@SuppressWarnings(“unchecked”)
@SuppressWarnings(value={“unchecked”,”deprecation”})
public class Test { public static void main(String[] args) { Test00 test00 = new Test00(); test00.test00(); //@Deprecated,使用了不建议的方法 } @SuppressWarnings("all") //指示编译器去忽略注解中声明的警告 private static class Test00 implements Runnable{ @Override //检查该方法是否是重写方法 public void run() { } @Deprecated public void test00(){ System.out.println("!"); } } }
元注解
元注解的作用是负责注解其他注解,如:@Retention,@Documented,@Target ,@Inherited
- @Target:用于描述注解的使用范围(即在什么情况下可以使用);
- @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期;
- @Documented:说明该注解被包含在javadoc中;
- @Inherited :说明子类可以继承父类中的该注解;
@Test01 //利用注解Target可以将接口作用在方法上 public void test02(){ } //表示是否将注解生成在JAVAdoc中 @Documented //子类可以继承父类的注解 @Inherited //描述注解的使用范围,在方法中,类中使用 @Target(value = {ElementType.METHOD,ElementType.TYPE}) //需要在运行保存该注释信息 (RUNTIME>CLASS>SOURCE) @Retention(value = RetentionPolicy.RUNTIME) @interface Test01{} //自定义注解,而非接口
注:如果注解只有一个值,建议使用value,value在赋值时可以省略。
自定义注解
自定义注解一般使用@interface
来定义。
@interface
用来声明一个注解,格式为:public @interface 注解名{定义内容}
- 注解中的每个方法实际上是声明了一个参数
- 方法名称是参数名称,返回值类型是基本类型的参数
- 可以使用
default
来设置参数的默认值,以致于在引用注解时,不用再去赋值(通常用0,“”) - 如果注解中的参数只有一个的话,一般使用
value
,引用时可以省略value
public class Test { //注解可以显示赋值, 如果没有设置默认值,就必须给注解赋值 // @MyAnnotation(name = "vxzx", id = 1, age = 12,school = {"xxxx"}) @MyAnnotation() //有默认值,可以省略 @MyAnnotaion1("VXZX") public static void main(String[] args) { } } //自定义注解@interface @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation{ //里面的数据如果有默认值,就可以省略 //注解的参数:参数类型 + 参数名() String name() default ""; int id() default -1; //-1不存在 int age() default 0; String[] school() default {"xxxx"}; } @interface MyAnnotaion1{ //如果注解只有一个值,建议使用value,value在赋值时可以省略,其他的则不行 String value(); }
(二)反射
反射(Reflection)时Java被视为动态语言的关键,反射机制运行程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法、注解等。
Java反射机制提供的功能:
-
运行时判断任意一个对象所属的类;
-
运行时构造任意一个类的对象;
-
运行时判断任意一个类所具有的成员变量和方法;
-
运行时获取泛型信息;
-
运行时调用任意一个对象的成员变量和方法;
-
运行时处理注解;
-
生成动态代理;
Class类
一个Class对象包含了特定的某个结构的有关信息,一个加载的类在JVM中只会有一个Class实例。
Class类的常用方法:
public class ClassP02 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { Class c1 = Class.forName("annotation.demo2.User"); //获取类的名字: System.out.println("包名 + 类名:" + c1.getName()); //包名 + 类名 System.out.println("类名:" + c1.getSimpleName()); //类名 System.out.println("================"); //获取类的属性: //获取该类中的public属性 // Field[] fields = c1.getFields(); // for (Field field : fields) { // System.out.println("获取该类中的public属性:" + field); // } //获取该类中的所有属性: Field[] fields1 = c1.getDeclaredFields(); for (Field field : fields1){ System.out.println("获取该类中的所有属性:" + field); } //获得指定属性的值: Field address = c1.getDeclaredField("address"); System.out.println("获得指定属性的值:" + address); System.out.println("+++++++++++++++"); //获得类的方法: //获得本类和父类的public方法: Method[] methods = c1.getMethods(); for (Method method : methods){ System.out.println("本类和父类的public方法:" + method); } //获得本类的所有方法: Method[] declaredMethods = c1.getDeclaredMethods(); for (Method method1 : declaredMethods){ System.out.println("本类的所有方法:" + method1); } //获得指定的方法: Method getName = c1.getMethod("getName", null); Method setName = c1.getMethod("setName", String.class); System.out.println("指定的方法:" + getName); System.out.println("指定的方法:" + setName); System.out.println("**********************"); //获得构造器: Constructor[] constructors = c1.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } constructors = c1.getDeclaredConstructors(); for (Constructor constructor : constructors) { System.out.println("获得所有构造器:" + constructor); } //获得指定构造器: Constructor constructor = c1.getConstructor(String.class, Integer.class, String.class); System.out.println("获得指定构造器:" + constructor); } }
获取Class类的实例:
1)若已知具体的类,通过类的class属性获取,该方法最安全,程序性能最高。
Class c1 = Person.class;
2)已知某个类的实例,调用该实例的getClass()方法获取对象:
Person person = new Person(); Class c2 = person.getClass();
3)已知一个类的全名,且该类在类的路径下,可通过Class类的静态方法forName()获取,可能抛出异常:
Class c3 = Class.forName(包名.类名.类);
4)内置基本数据类型可以直接使用类名.Type
:
Class c4 = Integer.TYPE; System.out.println(c4);
//一个类中只能有一个类对象可以获取: Class class2 = Class.forName("annotation.demo2.User"); Class class3 = Class.forName("annotation.demo2.User"); Class class4 = Class.forName("annotation.demo2.User"); System.out.println(class2.hashCode()); System.out.println(class3.hashCode()); System.out.println(class4.hashCode()); //所打印出来的哈希值都是一样的。
Class可以对以下属性进行使用:
public static void main(String[] args) { Class c1 = Object.class; //类 Class c2 = Comparable.class; //接口 Class c3 = String[].class; //数组 Class c4 = int[][].class; //二维数组 Class c5 = Override.class; //注解 Class c6 = ElementType.class; //枚举类型 Class c7 = Integer.class; //基本数据类型 Class c8 = void.class; //void Class c9 = Class.class; //Class }
类的加载与ClassLoader的理解
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时的数据结构,生成一个class对象。
链接:将Java类的二进制代码合并到JVM的运行状态中的过程。
- 验证:检查是否符合JVM规范和安全;
- 准备:为类变量(static)分配内存,设置默认值;
- 解析:常量池内的符号引用(常量名)替换为直接引用(地址);
初始化:
- 执行
()方法,将类变量和static代码块整合到一起; - 初始化类时,如果父类没初始化,则会先初始化父类;
- 保证类的
()方法在多线程环境中被正确加锁和同步;
public class Reflect04 { public static void main(String[] args) { Testa a = new Testa(); System.out.println(a.m); } } class Testa{ static { int m = 300; System.out.println("静态代码块"); System.out.println(m); } static int m = 100; public Testa(){ System.out.println("~~~~~~~"); } } /*输出结果: 静态代码块 300 ~~~~~~~ 100 */
JVM在执行程序时,始终第一个先调用static方法块,再依次执行其他。
类的初始化:
类的主动引用(一定会发生类的初始):
- JVM启动时,先初始化main()方法所在的类;
- new一个类的对象;
- 调用类的静态成员和静态方法;
- 使用反射对类进行操作;
- 初始化一个类,父类没被初始,则会先初始化该父类;
类的被动引用(不会发生类的初始化):
- 访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化;
- 通过数组定义类引用,不会触发该类的初始化;
- 引用常量不会触发该类的初始化;(常量已经在常量池中了)
/测试类的初始化: public class Reflect04_1 { static { //始终第一个被初始化 System.out.println("main()方法所在的类"); } public static void main(String[] args) throws ClassNotFoundException { //主动引用: //先加载父类初始化,在进行子类初始化: // Son son = new Son(); //反射的主动引用: // Class c1 = Class.forName("annotation.demo2.Son"); //子类访问父类的静态变量,不会主动引用:子类不会被加载 // System.out.println(Son.f); //通过定义数组,不会引起主动引用: // Son[] sons = new Son[5]; //通过引用常量,不会引起主动引用: System.out.println(Son.z); } } class Father{ static int f = 100; static { System.out.println("父类的引用"); } } class Son extends Father{ static { System.out.println("子类的引用"); int m = 300; } static int m = 100; static final int z = 1; }
类加载器:
类加载器的作用:将class文件字节码内容加载到内存中,作为方法区中类数据的访问入口。
类缓存:某个类被加载到加载器中,将会维持一段时间。最后可以用JVM垃圾回收这些对象。
类加载器的分类:
- 引导类加载器(Bootstap Classloader);
- 扩展类加载器(Extension Classloader);
- 系统类加载器(System Classloader);
检查类是否已加载:自底向上
尝试加载类:自顶向下
public static void main(String[] args) throws ClassNotFoundException { //获取系统类的加载器:getSystemClassLoader() ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); //获取系统类加载器的父类加载器-->扩展类加载器: ClassLoader parent = systemClassLoader.getParent(); System.out.println(parent); //获取扩展类加载器的父类加载器-->根加载器: ClassLoader parent1 = parent.getParent(); System.out.println(parent1); //测试当前类时哪个类加载器加载的: ClassLoader c1 = Class.forName("annotation.demo2.ClassP01").getClassLoader(); System.out.println(c1); //测试JDK内的类是哪个类加载的: c1 = Class.forName("java.lang.Object").getClassLoader(); System.out.println(c1); //如何获得系统类加载器可以加载的路径: String property = System.getProperty("java.class.path"); System.out.println(property); }
利用反射创建一个class对象
创建类的对象:
- 通过Class类的getDeclaredConstructor(Class....parameterTypes)取得本类的指定形参类型的结构器;再用newInstance(指定形参的值)再插入对象的值;
- 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数;
- 通过Constructor实例化对象;
public class Reflect05 { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { Class c1 = Class.forName("annotation.demo2.User"); //构造一个对象: User user = (User) c1.newInstance(); System.out.println(user); //通过构造器创建对象: User user1 = (User) c1.getDeclaredConstructor(String.class, Integer.class, String.class).newInstance("VXZX", 001, "广州"); System.out.println(user1); } }
利用反射调用指定的方法
通过反射,调用类中的方法,通过Method类完成。
- 通过Class类的getMethod(String name,Class....parameterTypes)方法取得一个Method对象,并设置该方法操作时所需要的参数类型。
- 然后,使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象参数信息。
注意:若原方法声明为private,则需要在调用invoke()之前,显式调用setAccessible(true)方法,才可以访问private方法的数据。
setAccessible:作用是启动和关闭访问安全检查的开关。
- Method、Field和Constructor对象都有setAccessable()方法
- true:表示反射对象在使用访问时,会取消Java语言访问检查
- false:表示反射对象在使用访问时,会开启Java语言访问检查
//通过反射调用普通方法: User user2 = (User) c1.newInstance(); //通过反射获取一个方法: Method setName = c1.getDeclaredMethod("setName", String.class); //invoke():激活对象 //(对象,"方法值") setName.invoke(user2, "VXLZX"); System.out.println(user2.getName()); //通过反射操作属性: User user3 = (User) c1.newInstance(); //获取的属性不能为private Field name = c1.getDeclaredField("name"); //如果想执行操作,需要关闭权限检测: name.setAccessible(true); name.set(user3,"User3的name"); System.out.println(user3.getName());
其中,在性能方面----- 普通方法性能 > 关闭安全检查的反射方法性能 > 反射方法性能
反射操作注解
反射的强大之处就在于,它可以跟注解结合起来一起使用,最大化简化我们的操作。
public class Reflect08 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { //通过反射获取注解: Class c1 = Class.forName("annotation.demo2.Student2"); Annotation[] annotations = c1.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); } //获取注解value的值: Table table = (Table) c1.getAnnotation(Table.class); String value = table.value(); System.out.println(value); //获取类指定的注解:(name) Field f = c1.getDeclaredField("name"); Field annotation = f.getAnnotation(Field.class); System.out.println(annotation.columnName()); System.out.println(annotation.type()); System.out.println(annotation.length()); } } @Table("db_student") class Student2{ private int id; @Field(columnName = "db_name", type = "varchar", length = 3) private String name; private int age; public Student2() { } public Student2(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } 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 "Student2{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } } //类名的注解: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) // @interface Table{ String value(); } //属性的注解: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) // @interface Field{ String columnName(); String type(); int length(); }
注解,反射基本就这些了。
这篇关于Java注解与反射的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-26Mybatis官方生成器资料详解与应用教程
- 2024-11-26Mybatis一级缓存资料详解与实战教程
- 2024-11-26Mybatis一级缓存资料详解:新手快速入门
- 2024-11-26SpringBoot3+JDK17搭建后端资料详尽教程
- 2024-11-26Springboot单体架构搭建资料:新手入门教程
- 2024-11-26Springboot单体架构搭建资料详解与实战教程
- 2024-11-26Springboot框架资料:新手入门教程
- 2024-11-26Springboot企业级开发资料入门教程
- 2024-11-26SpringBoot企业级开发资料详解与实战教程
- 2024-11-26Springboot微服务资料:新手入门全攻略