自我提升(基础技术篇)——反射和注解

前言,本来只是想研究一下注解的,不过发现,要懂注解先得懂反射,别问我为什么,你可以自己试试

JAVA反射

主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

反射机制是什么

反射机制就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

用一句话总结就是反射可以实现在运行时可以知道任意一个类属性和方法。(所以,其实我们使用private,并没有那么安全,因为有反射,但是,java'的同学还是用吧)

反射机制能做什么

在运行时判断任意一个对象所属的类;

在运行时构造任意一个类的对象;

在运行时判断任意一个类所具有的成员变量和方法;

在运行时调用任意一个对象的方法;

生成动态代理

这里不多说,后面详细说。

Java 反射机制的应用场景

逆向代码 ,例如反编译

与注解相结合的框架 例如Retrofit

单纯的反射机制应用框架 例如EventBus

动态生成类框架 例如Gson

此刻心中是不是有一句“卧槽,这么吊,这些东西都用到了反射”。没错就这么吊!后面,就会发现,他真的这么吊。

反射机制的优缺点

说优缺点,之前,先说两个概念:静态编译,动态编译

静态编译:在编译时确定类型,绑定对象。

动态编译:运行时确定类型,绑定对象。

而反射,属于动态编译。这样最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

优点

可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一

次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,

假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,

它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。

缺点

对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

理解Class类和类类型

想要了解反射首先理解一下Class类,它是反射实现的基础

类是java.lang.Class类的实例对象,而Class是所有类的类(There is a class named Class)

对于普通的对象,我们一般都会这样创建和表示:

Code code1 =new Code();

上面说了,所有的类都是Class的对象,那么如何表示呢,可不可以通过如下方式呢:

Class c =new Class();

很遗憾,不是这样写的,源码是:

private Class(ClassLoader loader){

classLoader = loader;

}

可以看到构造器是私有的,只有JVM可以创建Class的对象,因此不可以像普通类一样new一个Class对象,虽然我们不能new一个Class对象,但

是却可以通过已有的类得到一个Class对象,共有三种方式,如下:

Class c1 = Code.class; 这说明任何一个类都有一个隐含的静态成员变量class,这种方式是通过获取类的静态成员变量class得到的

Class c2= code1.getClass(); code1是Code的一个对象,这种方式是通过一个类的对象的getClass()方法获得的

Class c3 = Class.forName("com.trigl.reflect.Code"); 这种方法是Class类调用forName方法,通过一个类的全量限定名获得

这里,c1、c2、c3都是Class的对象,他们是完全一样的,而且有个学名,叫做Code的类 类型(class type)。

这里就让人奇怪了,前面不是说Code是Class的对象吗,而c1、c2、c3也是Class的对象,那么Code和c1、c2、c3不就一样了吗?为什么还叫

Code什么类 类型?这里不要纠结于它们是否相同,只要理解类 类型是干什么的就好了,顾名思义,类 类型就是类的类型,也就是描述一个类是什么,

都有哪些东西,所以我们可以通过类 类型知道一个类的属性和方法,并且可以调用一个类的属性和方法,这就是反射的基础

Java反射相关操作

在这里先看一下sun为我们提供了那些反射机制中的类:

java.lang.Class;

java.lang.reflect.Constructor; java.lang.reflect.Field;

java.lang.reflect.Method;

java.lang.reflect.Modifier;

ok,现在,你已经知道怎么获取这个Class了吧,那么,这玩意,可以用来干嘛呢?

获取成员方法Method

获取成员变量Field

获取构造函数Constructor

获取成员方法信息

两个参数分别是方法名和方法参数类的类类型列表。

public Method getDeclaredMethod(String name, Class... parameterTypes)// 得到该类所有的方法,不包括父类的

public Method getMethod(String name, Class... parameterTypes)// 得到该类所有的public方法,包括父类的

//具体使用

Method[] methods= class1.getDeclaredMethods();//获取class对象的所有声明方法

Method[] allMethods = class1.getMethods();//获取class对象的所有public方法 包括父类的方法

Method method = class1.getMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的public方法

Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的方法

举个例子来说明一下

public void fun(String name,int age){

System.out.println("我叫"+name+",今年"+age+"岁");

}

现在知道A有一个对象a,那么就可以通过:

Class c = Class.forName("com.tengj.reflect.Person");//先生成class

Object o = c.newInstance();//newInstance可以初始化一个实例

Method method = c.getMethod("fun", String.class,int.class);//获取方法

method.invoke(o,"tengj",10);

执行结果:

我叫tengj,今年10岁

是不是很神奇?没错就这么神奇,首先,获取Calss对象,然后用这个Class的newInstance方法,创造一个实例对象,然后这个实例去获取Class中的fun方法(注意,由于方法是可以重载的,所以这里要把参数带上,才能知道具体是哪一个方法),最后,invoke方法,就实现了调用。

这个时候,或许会问了,那如果有很多方法,我不是要累死呀,这样写。ok,接着看,有高招。

下面先来一个完整的类


获取全部方法的方法:

1.获取所有方法的数组:

Class c = Class.forName("com.tengj.reflect.Person");

Method[] methods = c.getDeclaredMethods();// 得到该类所有的方法,不包括父类的

或者:

Method[] methods = c.getMethods();// 得到该类所有的public方法,包括父类的

2.然后循环这个数组就得到每个方法了:

for(Method method : methods)

ok,最后,还是来个例子吧:




ok,就这么简单,就得到了说有的方法了。下面再来看看如何获取成员变量

获取成员变量信息

想一想成员变量中都包括什么:成员变量类型+成员变量名(String name)

类的成员变量也是一个对象(万物皆对象),它是java.lang.reflect.Field的一个对象,所以我们通过java.lang.reflect.Field里面封装的方法来获取这些信息。

单独获取某个成员变量,通过Class类的以下方法实现:

参数是成员变量的名字

还是举个例子吧


执行结果:hello wrold(看之前那个person类)

相信说到这里,聪明的同学已经想到,要获取说有的成员变量的方法了吧,没错,循环遍历


结果是:

name

age

msg

获取构造函数

首先,想想想,构造函数是什么样的?

构造函数是java.lang.reflect.Constructor的一个对象,所以我们通过java.lang.reflect.Constructor里面封装的方法来获取这些信息。

获取某个构造函数,通过Class类的以下方法实现:

public Constructor getDeclaredConstructor(Class... parameterTypes)//  获得该类所有的构造器,不包括其父类的构造器

public Constructor getConstructor(Class... parameterTypes)// 获得该类所以public构造器,包括父类

//具体

Constructor[] allConstructors= class1.getDeclaredConstructors();//获取class对象的所有声明构造函数

Constructor[] publicConstructors = class1.getConstructors();//获取class对象public构造函数

Constructor constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数

Constructor publicConstructor = class1.getConstructor(String.class);//获取指定声明的public构造函数

下面还是举个例子,演示一下


注意:Class的newInstance方法,只能创建只包含无参数的构造函数的类,所以文中用了Constructor来创建,打开Class类发现,里面只有一个无参的newInstance


如果某类只有带参数的构造函数,那么就要使用另外一种方式:

fromClass.getDeclaredConstructor(String.class).newInstance("hello");

获取所有的构造函数,可以通过以下步骤实现:

1.获取该类的所有构造函数,放在一个数组中:

Constructor[] constructors = c.getDeclaredConstructors();

2.遍历构造函数数组,获得某个构造函数constructor:

for(Constructor constructor : constructors)

看一个实例吧:


其他方法

上面讲的,都是一些反射的基础方法,下面讲一些常用的方法和应用。

注解需要用到的:(了解这些方法,是我们最后用注解的基础)

Annotation[] annotations = (Annotation[]) class1.getAnnotations();//获取class对象的所有注解

Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//获取class对象指定注解

Type genericSuperclass = class1.getGenericSuperclass();//获取class对象的直接超类的

Type Type[] interfaceTypes = class1.getGenericInterfaces();//获取class对象的所有接口的type集合

获取class对象的信息

boolean isPrimitive = class1.isPrimitive();//判断是否是基础类型

boolean isArray = class1.isArray();//判断是否是集合类

boolean isAnnotation = class1.isAnnotation();//判断是否是注解类

boolean isInterface = class1.isInterface();//判断是否是接口类

boolean isEnum = class1.isEnum();//判断是否是枚举类

boolean isAnonymousClass = class1.isAnonymousClass();//判断是否是匿名内部类

boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判断是否被某个注解类修饰

String className = class1.getName();//获取class名字 包含包名路径

Package aPackage = class1.getPackage();//获取class的包信息

String simpleName = class1.getSimpleName();//获取class类名

int modifiers = class1.getModifiers();//获取class访问权限

Class[] declaredClasses = class1.getDeclaredClasses();//内部类

Class declaringClass = class1.getDeclaringClass();//外部类

getSuperclass():获取某类的父类

getInterfaces():获取某类实现的接口

通过反射了解集合泛型的本质

上述,只是简单介绍了一些方法,具体怎么用,会在后面再说,这里,先来用一下这个反射(当然,是用反射去解释一个东西,反射是java的核心之一吧,不仅仅是技术层面,在思想层面,反射也是很强大的)

Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行期就无效了。

先提出这个一个观念,如果不相信,那么往后看:

看一下上面的演示代码,然后,再看一下运行结果:

现在,不需要多说了吧。泛型,只是在编译期,才有用的,到了运行时,就没有用了。

那么再稍微多走几步看看:现在,我们试着从这个集合里面拿出数据看看



看到结果了吧,抛异常了,int类型的数据不能放到string数据中。这就是为什么前面说:Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行期就无效了。这个防止错误输入,就是在这里体现了。如果没有泛型,我们可能会出现错误输入,导致jvm崩掉。但是,由于反射机制的存在,我们却可以绕过这个检查,强制输入错误数据。所以,反射虽好,但也要注意合理使用。

帮助理解

ok,java反射,基础的东西差不多就是这些了。下面来到我们的应用部分,java注解,说java注解是java反射的应用或者是升华都是可以的。

JAVA注解

概念及作用

概念

注解即元数据,就是源代码的元数据

注解在代码中添加信息提供了一种形式化的方法,可以在后续中更方便的 使用这些数据

Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。

作用

生成文档

跟踪代码依赖性,实现替代配置文件功能,减少配置。如Spring中的一些注解

在编译时进行格式检查,如@Override等

每当你创建描述符性质的类或者接口时,一旦其中包含重复性的工作,就可以考虑使用注解来简化与自动化该过程。

什么是java注解?

在java语法中,使用@符号作为开头,并在@后面紧跟注解名。被运用于类,接口,方法和字段之上,例如:

@Override

void myMethod(){

......

}

这其中@Override就是注解。这个注解的作用也就是告诉编译器,myMethod()方法覆写了父类中的myMethod()方法。

java中内置的注解

java中有三个内置的注解:

@Override:表示当前的方法定义将覆盖超类中的方法,如果出现错误,编译器就会报错。

@Deprecated:如果使用此注解,编译器会出现警告信息。

@SuppressWarnings:忽略编译器的警告信息。

元注解

自定义注解的时候用到的,简单说,就是注解的注解

元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。

@Target

@Retention

@Documented

@Inherited

不过java8新增了两个新注解(这里不讲,原因是:我现在还没用java8,还有就是,先学最基础的吧,新注解后面再学)

@Target

@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

取值(ElementType)有:


比如说这个注解表示只能在方法中使用:

先定义一个注解

然后再使用

@Retention

@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

取值(RetentionPoicy)有:


使用示例:



@Documented

@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

作用:将注解包含在javadoc中(这个就不举例了,没什么好说的)

@Inherited

是一个标记注解

阐述了某个被标注的类型是被继承的

使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类

@Inherited annotation类型是被标注过的class的子类所继承。类并不从实现的接口继承annotation,方法不从它所重载的方法继承annotation

当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

作用:允许子类继承父类中的注解

还是一个例子:



看了这几个例子,现在应该有点感觉了吧,注解,其实就是,先定义一个注解,然后,再使用。只是在定义注解的时候,要注意他的作用域和生命周期。

ok,下面重头戏,前面都是介绍基础,下面我们来自定义注解。

自定义注解

格式

public @interface 注解名{

定义体

}

注解参数的可支持数据类型:

所有基本数据类型(int,float,double,boolean,byte,char,long,short)

String 类型

Class类型

enum类型

Annotation类型

以上所有类型的数组

规则

修饰符只能是public 或默认(default)

参数成员只能用基本类型byte,short,int,long,float,double,boolean八种基本类型和String,Enum,Class,annotations及这些类型的数组

如果只有一个参数成员,最好将名称设为”value”

注解元素必须有确定的值,可以在注解中定义默认值,也可以使用注解时指定,非基本类型的值不可为null,常使用空字符串或0作默认值

在表现一个元素存在或缺失的状态时,定义一下特殊值来表示,如空字符串或负值

注解处理器类库(java.lang.reflect.AnnotatedElement)

Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:

Class:类定义

Constructor:构造器定义

Field:类的成员变量定义

Method:类的方法定义

Package:类的包定义

java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。

AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

方法1:T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。

方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。

方法3:boolean is AnnotationPresent(Class annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.

方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

下面来一个完整的demo解释一下:

这里,用一个水果的例子说明一下:一个水果,有名字,颜色,产地等信息,这里简单一点,就三个吧。

首先是自定义注解:



这个应该不用多解释了吧,就是定义了名字,颜色和生产商的注解

然后就是使用注解(其实我更多的认为是实现注解,因为注解感觉是一个接口,个人认为哈)


然后是注解处理(说白了,其实就是具体操作,这里是用的反射)


ok,最后一步了,当然,就是跑起来看结果:



看到了吧,就是这么神奇。

ok,反射和注解的基础,就先讲到这里,以后自己水平提高了,再来聊聊这个高级应用。

最后附上一张思维导图结束了


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容