浅谈Java高级特性 · 注解
Java在网上都被玩烂了,在安卓开发中最蛋疼的事就是写一大堆代码,就只是为了拿到一个值(findViewByid() ,getIntent().getStringExtra(),setOnClickListener()诸如此类的)。为了解决这个问题,网上出现好多的注解框架。但是这些注解框架背后是什么实现原理呢?现在我们来看看是怎么实现的。
这里我们自己来写一个findViewByid的注解。
1.定义一个注解
New ->Annotation
public @interface findViewByid
{
}
这样就定义出来一个注解了。但是注解我们在平时使用中发现有的注解是写在类名(class)上面的(为类注解),有的注解是写在方法Methed上面的(为方法注解),还有的则是写在变量Field上面的(属性注解)。
很明显,我们这里定义的findViewByid
是属于变量的注解。因为我们需要为我们定义的一个Button(或者其他的)自动绑定到Layout.xml中的定义的id的一个控件上、
然后不知道大家有没有发现,在使用Eclipse开发的时候经常看见这样的代码,比如我们在重写父类一个方法,Eclipse会自动在我们重写的方法上面添加这么一句、如下:
@Override
public String toString()
{
// TODO Auto-generated method stub
return super.toString();
}
大家应该注意到了这个 @Override
然后我们发现,我们把这个@Override
删掉也并不影响我们程序的使用。对,这就涉及到了一个作用域(这个词是我瞎编的)的问题,说白了就是这个注解在什么时候产生作用。那我们的这个情况肯定是在运行的时候需要使用。那么我们的代码可以这么写:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface findViewByid
{
}
@Retention(RetentionPolicy.RUNTIME)
这个是定义我们的注解是在什么作用域下起作用的。我们详细应该注意的是参数里的这个类 RetentionPolicy 让我们来看看源码:
/**
* Annotation retention policy. The constants of this enumerated type
* describe the various policies for retaining annotations. They are used
* in conjunction with the {@link Retention} meta-annotation type to specify
* how long annotations are to be retained.
*
* @author Joshua Bloch
* @since 1.5
*/
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
我们发现这个类只是一个枚举类,其中只写了三个值在里面。其实这就代表了三个作用域。
SOURCE代表的是这个Annotation类型的信息只会保留在程序源码里,源码如果经过了编译之后,Annotation的数据就会消失,并不会保留在编译好的.class文件里面。
ClASS的意思是这个Annotation类型的信息保留在程序源码里,同时也会保留在编译好的.class文件里面,在执行的时候,并不会把这一些信息加载到虚拟机(JVM)中去.注意一下,当你没有设定一个Annotation类型的Retention值时,系统默认值是CLASS.
RUNTIME,表示在源码、编译好的.class文件中保留信息,在执行的时候会把这一些信息加载到JVM中去的.
@Target(ElementType.FIELD)
这个是定义我们的注解是为什么注解的。我们详细应该注意的是参数里的这个类 ElementType 让我们来看看源码:
/** @author Joshua Bloch
* @since 1.5
* @jls 9.6.4.1 @Target
* @jls 4.1 The Kinds of Types and Values
*/
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
TYPE,表示我们写的注解是为类class而设计的 然后我们这个注解就只能在类上使用。
FIELD,表示我们写的注解是为属性变量field而设计的 然后我们这个注解就只能在我们定义的属性变量上使用。
METHOD,表示我们写的注解是为方法method而设计的 然后我们这个注解就只能在我们定义的方法上使用。
PARAMETER,表示我们写的注解是为方法的参数param而设计的 然后我们这个注解就只能修饰方法内的参数。
这里讲的主要的就是这些。还有一些就自行百度。
到这里,我们的注解就可以在Field上使用了,就是我们定义的属性变量。如:
@findViewByid
Button button;
但是我们如何才能找到对应的Button呢?所以我们应该弄一个参数进去,把对应的id传进去。我们修改代码如下:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface findViewByid
{
public int id();
}
使用方法:
@findViewByid(id=R.id.button1)
Button button;
但是这样的话我们还要多写个 ‘id=’了,能不能不写‘id=’呢?JavaAPI为我们提供了一个默认值:value 当参数名为value的时候,那么则可以省略参数名,这次我们修改如下:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface findViewByid
{
public int value();
}
我们再试试:
@findViewByid(R.id.button1)
Button button;
OK到这一步我们前期工作就算完成了
2.使用注解
注解已经定义好了,那么怎么使用呢?这个时候就要利用Java另外一个特性:反射。
第一步:获取当前类的Class,
Class clazz = MainActivity.this.getClass();
第二步:获取当前类的所有的属性变量
java.lang.reflect.Field fields[] = clazz.getDeclaredFields();
第三步:找到被@findViewByid
注解过的变量。
for(Field field:fields)
{
if(field.isAnnotationPresent(findViewByid.class))//判断此方法是否被findViewByid所注解
{
}
}
第四步:找到注解内参数的R.id值
findViewByid fivd = field.getAnnotation(findViewByid.class);//通过方法获取到注解对象
int id = fivd.value();//通过注解对象获取参数value值 也就是R.id.button1
第五步:通过id拿到对应控件Object对象
Object obj = this.findViewById(id);
第六步:把对象注入到对应的变量中。
/**
*第一个参数是哪个对象?因为可能别的activity中也有一个名字叫button的field所以确定是哪个对象,第二个则是为这个field设置什么值进去?
*/
field.set(MainActivity.this, obj);
注意:这一步可能会报错,导致注入失败,因为当我们定义的变量为私有变量Private时,这时候就因为权限原因导致注入失败!
@findViewByid(123)
private Button button;//设置为私有变量
为了解决这个问题,JavaAPI为我们提供了一个解决方案:
field.setAccessible(true);//设置变量为可访问的
修改后完整的代码如下:
Class clazz = MainActivity.this.getClass();
java.lang.reflect.Field fields[] = clazz.getDeclaredFields();
for(Field field:fields)
{
if(field.isAnnotationPresent(findViewByid.class))//判断此方法是否被findViewByid所注解
{
findViewByid fivd = field.getAnnotation(findViewByid.class);//通过方法获取到注解对象
int id = fivd.value();//通过注解对象获取参数value值 也就是R.id.button1
Object obj = this.findViewById(id);
field.setAccessible(true);//设置变量为可访问的
field.set(MainActivity.this, obj);
}
}