注解与反射
Posted arno-vc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了注解与反射相关的知识,希望对你有一定的参考价值。
一、什么是注解
英文:annotation
注解与注释:注解与注释都是"注",但是注释是给人看的,而注解除了可以给人看,还可以给机器看的,被机器(编译器)读取
重要性:几乎是所有java框架的底层
二、内置注解:
1.@Override:表示一个方法打算重写超类(也就是父类)中的另一个方法声明
2.@Deprecated:表示一个方法很危险或者存在更好的选择,也就是常说的已废弃,通常是由于版本的更新迭代
3.@SuppressWarnings:通常用于抑制警告信息
三、元注解
作用:负责注解其他注解的注解
meta-annotation
四个元注解:
(1).@Target:用于描述注解的使用范围(通常为ElementType的静态字段值)
(2).@Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE<CLASS<RUNTIME)
(3).@Document:说明该注解将被包含在javadoc(API帮助文档)中
(4).@Inherited:说明子类可以继承父类中的该注解
四、自定义注解
import java.lang.annotation.*;
public class MyTest {
@myAnnotation(value=123,age=123)
public void test(){
}
@myAnnotation(123)
public void test1(){
}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface myAnnotation{
String name() default "123";
int value();
int age() default 123;
}
注意:
(1).使用@interface继承java.lang.annotation.Annotation接口
(2).注解的参数定义:类型 属性名() default 参数,其中默认的参数是可选的
(3).以value命名的属性:在使用注解并写入参数时可以不用写属性名
(4).注解的行为:通过反射实现
五、什么是反射
java Reflectioin
重要性:是Java被视为动态语言的关键,反射机制允许程序借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
原理:
Class c= Class.forName("java.lang.String")
加载完类之后,在堆内存的方法区中就产生了一个CLass类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息.我们可以通过这个对象看到类的结构.这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为:反射正常方式:引入需要的"包类"名称 → 通过new实例化 →取得实例化对象
反射格式: 实例化对象 → getClass()方法 → 得到完整的"包类"名称
作用:
(1).在运行时判断任意一个对象所属的类
(2).在运行时构造任意一个类的对象
(3).在运行时判断任意一个类所具有的成员变量和方法
(4).在运行时获取泛型信息
(5).在运行时调用任意一个对象的成员变量和方法
(6).在运行时处理注解
(7).生产动态代理(AOP相关)
…
关键:使用Class.forName(全限定类名)创建一个"Class对象",通过这个对象的方法可以获得实例
注意:
(1).一个类只有一个Class对象
(2).一个类被加载后,类的整个结构都会被封装在Class对象
相关的api:
(1).java.lang.Class:代表一个类
(2).java.lang.reflect.Method:代表类的方法
(3).java.lang.reflect.Field:代表类的成员变量
(4).java.lang.reflect.Constructor:代表类的构造器
Class与Object的关系:两者没有直接关系
(1).Object是一切java类的父类
(2).Class类是用于java反射机制的,一切java类都有一个对应的Class对象,他是一个Final类.Class类的实例表示,正在运行的Java应用程序中的类和接口
六、Class对象
获得:通过对象"照镜子"获得
注:
(1).Class本身也是一个类
(2).Class对象只能由系统建立对象
(3).一个加载的类在JVM中只会有一个Class实例
(4).一个Class对象对应的是一个加载到JVM中的一个**.class文件**
(5).每个类的实例都会记得自己是由哪个Class实例所生成
(6).通过Class可以完整地得到一个类中的所有被加载的结构
(7).Class类是Reflection的根源,针对任何你想动态加载,运行的类,唯有先获得相应的Class对象
获得Class类的实例
(1).已知具体的类:
Class<Student> studentClass = Student.class;
(2).已知某个类的实例:
Class aClass = student.getClass();
(3).已知类的全类名:
Class<?> student1 = Class.forName("Student");
(4).内置基本数据类型可以直接用基本包装类型的类名.Type:
Class<Integer> integerClass = Integer.class; //class Integer Class<Integer> type = Integer.TYPE; //int Class<Integer> integerClass = int.class;
(5).之后还可以利用ClassLoader
哪些类型可以有Class对象?
(1).class:各种class
Class c1 = Object.class; //class java.lang.Object
(2).interface
Class c2 = Comparable.class; //interface java.lang.Comparable
(3).[]
Class c3 = String[].class; //class [Ljava.lang.String;
注:维度相同的数组属于同一个class
(4).enum
Class c4 = ElementType.class; //class java.lang.annotation.ElementType
(5).annotation
Class c5 = Override.class; //interface java.lang.Override
(6).Void
Class c6 = Void.class; //Void
七、从内存分析反射机制(韩顺平 JVM)
测试代码
public class MyTest {
public static void main(String[] args) throws ClassNotFoundException {
A a = new A();
System.out.println(A.m);
}
}
class A {
static{
System.out.println("静态初始化");
m = 100;
}
static int m =300;
static int n;
A(){
System.out.println("A的无参构造初始化!");
}
}
/*
静态初始化完成
A的无参构造初始化!
100
*/
加载到内存时会产生一个类对于class对象
链接,链接结束后m=0
初始化
<clinit>(){ System.out.println("静态初始化"); m =300; }
八、什么时候回发生类初始化
类的主动引用(会发生类的初始化)
(1).当虚拟机启动,先初始化main方法所在的类
(2).new一个类的对象
(3).调用类的静态方法(除了final常量)和静态方法
(4).使用java.lang.reflect包的方法对类进行反射调用
(5).当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
类的被动引用(不会发生类的初始化)
(1).当访问一个静态域时,只有真正声明这个域的类才会被初始化.如:当通过子类引用父类的静态变量,不会导致子类初始化
(2).通过数组定义类引用,不会触发此类的初始化,因为这实际上仅仅只是开辟了一个内存空间
(3).引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
测试
(1).环境搭建:
class B{ static { System.out.println("B类初始化完成"); } static int b = 100; } class A extends B{ static{ m = 100; System.out.println("A类初始化完成"); } static int m =300; static final int n=300; }
(2).编写测试类
public class MyTest { static{ System.out.println("MyTest类被加载!"); } public MyTest(){ System.out.println("MyTest初始化!"); } public static void main(String[] args) throws ClassNotFoundException { // A a = new A(); /* * 先加载main方法所在类 * 加载父类B * 加载子类A * */ // System.out.println(A.b); /* * 先加载main方法所在类 * 由于调用的A类的父类B,所以这里只加载父类B * */ // System.out.println(A.n); /* * 调用的静态常量,这里只加载了main方法所在类 * */ A[] a = new A[5]; //这里初始化数组的大小需要数值说明 /* * 类数组的初始化,直发生main方法所在类的加载 * */ } }
九、类加载器的作用
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行数据,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载中,它将维持加载(缓存)一段时间.不过JVM垃圾回收机制可以回收这些Class对象
分类:
(1).引导类加载器:C++编写,是JVM自带的类加载器,负责Java平台核心库(如java.lang包对应的rt.jar),用来装载核心类库,该加载器无法直接获取
(2).扩展类加载器:负责jre/lib/ext目录下的jar包,
(3).系统类加载器:负责java-classpath目录下的类加载器,是最常用的加载器
注:七、八、九三块内容都是对于类的内存分析
十、获取运行时类的完整结构
可以获得的结构:Field,Method,COnstructor,Superclass,Interface,Annotation
相关函数:
- getName():全类名(包名+类名)
- getSimpleName():获得类名
- getFields():找到所有public属性
- getDeclaredFields():找到本类所有属性
- getField(String name):找到所有public属性中指定的属性
- getDeclaredField(String name):找到本类所有属性中指定的属性
- getMethods():获得所有public方法
- getDeclaredMethods():获得本类的所有方法
- getMethod(String name,para…):获得所有public方法中指定的方法
- getDeclaredMethod(String name,para…):获得本类中指定的方法
- getConstructors():获得所有public构造方法
- getDeclaredConstructors():获得所有构造方法
- getDeclaredConstructor(para…):获得指定的构造器
注意:这里的"所有"的含义,有时指包括子类和父类,有时指包括public和private(对于带有Declared的内容)
测试:
(1).环境搭建
class T{
public String T1;
private String T2;
public T(){
}
private T(String a){
}
public void method3(){
}
public void method4(){
}
}
class TestClass extends T{
public String field1;
private String field2;
public TestClass(){
}
private TestClass(String a){
}
private void method1(){
}
public void method2(){
}
}
(2).测试
public class MyTest {
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
TestClass testClass = new TestClass();
Class aClass = testClass.getClass();
System.out.println(aClass.getName());
System.out.println("================");
System.out.println(aClass.getSimpleName());
System.out.println("================");
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("================");
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
System.out.println("================");
System.out.println(aClass.getField("field1"));
System.out.println("================");
System.out.println(aClass.getDeclaredField("field2"));
System.out.println("================");
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("================");
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
System.out.println("================");
System.out.println(aClass.getMethod("method2"));
System.out.println("================");
System.out.println(aClass.getDeclaredMethod("method1"));
System.out.println("================");
Constructor[] constructors = aClass.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("================");
Constructor[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
System.out.println("================");
System.out.println(aClass.getDeclaredConstructor());
}
}
注:在实际开发中,我们用的并不多
十一、通过反射动态创建对象(间接)
环境
class T{
public String T1;
private String T2;
public T(){
}
public T(String t1, String t2) {
T1 = t1;
T2 = t2;
}
public void method3(){
}
public void method4(){
}
}
class TestClass extends T{
private String field1;
public String field2;
public TestClass(){
}
public TestClass(String field1, String field2) {
this.field1 = field1;
this.field2 = field2;
}
public TestClass(String t1, String t2, String field1, String field2) {
super(t1, t2);
this.field1 = field1;
this.field2 = field2;
}
public String method1(){
return field1;
}
public String method2(){
return field2;
}
@Override
public String toString() {
return "TestClass{" +
"T1=‘" + T1 + ‘‘‘ +
", field1=‘" + field1 + ‘‘‘ +
", field2=‘" + field2 + ‘‘‘ +
‘}‘;
}
}
通过newInstance构造
//直接调用newInstance构造 TestClass testClass = aClass.newInstance(); System.out.println(testClass);
通过构造器构造
//调用构造器构造 Constructor constructor = aClass.getDeclaredConstructor(String.class, String.class); TestClass o = (TestClass) constructor.newInstance("123", "456"); //注意参数仍然要间接调用newInstance System.out.println(o);
操作方法
//调用private方法 Method method1 = aClass.getDeclaredMethod("method1"); method1.setAccessible(true); //设定可以访问 System.out.println(method1.invoke(o));
操作属性
//调用private字段 Field field1 = aClass.getDeclaredField("field1"); field1.setAccessible(true); field1.set(o,"变了"); System.out.println(o.method1());
注意:
(1).通过反射构造下一切都是间接的调用
(2).构造时需要使用newInstance(),如果要使用有参构造,可以先获得constructor再调用newInstance(),其参数需要放在new Instance中
(3).调用函数需要使用invoke()激活
(4).参数的设置和获取使用set/get方法
(5).对于访问控制修饰符的限制,可以使用setAccessible()修改,当打开的情况下可以提高反射的效率
十二、性能分析
测试环境搭建
class TestClass extends T{ public String field2; public String method2(){ return field2; } }
测试类
public class MyTest { public static void test1(){ TestClass testClass = new TestClass(); long startTime = System.currentTimeMillis(); for(int i=1;i<=100000000;i++){ testClass.method2(); } long endTime = System.currentTimeMillis(); System.out.println("直接调用:" + (endTime-startTime) + "ms"); } public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { TestClass testClass = new TestClass(); long startTime = System.currentTimeMillis(); Class aClass = testClass.getClass(); Method method2 = aClass.getDeclaredMethod("method2"); for(int i=1;i<=100000000;i++){ method2.invoke(testClass,null); } long endTime = System.currentTimeMillis(); System.out.println("直接调用:" + (endTime-startTime) + "ms"); } public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { TestClass testClass = new TestClass(); long startTime = System.currentTimeMillis(); Class aClass = testClass.getClass(); Method method2 = aClass.getDeclaredMethod("method2"); method2.setAccessible(true); for(int i=1;i<=100000000;i++){ method2.invoke(testClass,null); } long endTime = System.currentTimeMillis(); System.out.println("直接调用:" + (endTime-startTime) + "ms"); } public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { test1(); test2(); test3(); } }
输出结果
直接调用:4ms 反射调用:438ms 反射调用(关闭检测):249ms
结论
(1).反射调用远远慢于直接调用,且这个差距是数量级的(约100倍)
(2).反射调用可以通过关闭检测来提高效率(约2倍)
十三、反射操作泛型
泛型:Java采用泛型擦除的机制来引入泛型,Java中的泛型不仅仅是给编译器javac使用,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有合泛型有关的类型全部擦除
具体 也是使用先关的函数:看视频我人傻了(可能也和没有特意学泛型有关)
重要的参数:
- ParameterizedType:表示一种参数化类型
- GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
- TYpeVariable:各种类型变量的公共父接口
- WildcardType:代表一种通配符类型表达式
十四、获得注解信息
测试:
环境搭建
@tableAnnotation("table") class Table{ @fieldAnnotation(name="name",type="String",length=10) public int field; } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface tableAnnotation{ String value(); } @Target({ElementType.TYPE,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @interface fieldAnnotation{ String name(); String type(); int length(); }
测试
public class User { public static void main(String[] args) throws NoSuchFieldException { Class aClass = Table.class; //获得类注解的参数 tableAnnotation annotation = (tableAnnotation) aClass.getAnnotation(tableAnnotation.class); System.out.println(annotation.value()); //获得字段注解的参数 Field field = aClass.getField("field"); // System.out.println(field); fieldAnnotation annotation1 = field.getAnnotation(fieldAnnotation.class); System.out.println(annotation1.length()); } }
以上是关于注解与反射的主要内容,如果未能解决你的问题,请参考以下文章