Android アノテーションとリフレクションの理解
Butterknife を使い始めたとき、とてもクールだと思いましたが、@bindview を追加して面倒な findViewById を記述する必要はありません。次に、EventBus、Retrofit、そして多くのライブラリがアノテーションを使用しているので、ここでそれらを整理しましょう。実行時のアノテーションを処理するリフレクションも一緒に書いてみましょう。
1. 注釈
1. 基本的な考え方
アノテーションは、コード内の特別なタグとして単純に理解でき、コンパイル、クラスのロード、および実行時にそれに応じて読み取られて処理できます。たとえば、最も一般的なオーバーライドです。
- アノテーションはメタデータ(データを記述するデータ)の一種です。
- アノテーションは、情報やメタデータを関連付けるために Java がソース プログラム内の要素を提供する方法および方法です。
- 注釈は受動的なメタデータであり、注釈には能動的な動作はありません。
注釈の分類:
-
標準アノテーション
標準アノテーションには主に以下が含まれます。
@Override: スーパークラス内のメソッドをオーバーライドするマーク。
@Deprecated: 廃止されたメソッドまたは非推奨のメソッドをマークし、注釈を付けます。
@SuppressWarnings: 特定のコード内の警告を選択的に抑制します。
@SafeVarargs: 可変長パラメーターを使用するメソッドを宣言するために使用され、ジェネリック クラスで使用しても型安全性の問題は発生しません。 -
メタアノテーション
メタアノテーションは、他のアノテーションに注釈を付けるために使用されるアノテーションであり、新しいアノテーションを作成します。主なものは次のとおりです。
@Retention: 注釈保持のライフサイクル。
@Target: 注釈オブジェクトのスコープ。
@Inherited: アノテーションを継承できることを示します。
@Documented: このアノテーションが JavaDoc ツールによって記録される必要があることを示します。
@Target と @Retention の値と説明を次の表に示します。
@保持値のタイプ | 説明する |
---|---|
RetentionPolicy.SOURCE | ソースコードのコメント。注釈はソース コード内にのみ保持され、注釈情報はコンパイル後に破棄されます。 |
RetentionPolicy.CLASS | コンパイル時の注釈。注釈情報はソース コードとコンパイル時の .class に保持されます。実行時に破棄され、JVM には保持されません |
RetentionPolicy.RUNTIME | 実行時の注釈。アノテーション情報を実行時まで保持、リフレクションによりアノテーション情報を取得可能 |
@対象値の型 | 説明する |
---|---|
要素タイプ.TYPE | インターフェイス、クラス、列挙を変更できます |
要素タイプ.フィールド | メンバー変数を変更できる |
要素タイプ.メソッド | メソッドを変更できる |
要素タイプ.PARAMETER | パラメータを変更できる |
要素タイプ.CONSTRUCTOR | コンストラクターを変更できる |
ElementType.LOCAL_VARIABLE | ローカル変数を変更できる |
ElementType.ANNOTATION_TYPE | 注釈を変更できる |
要素タイプ.パッケージ | バッグを変更できます |
ElementType.TYPE_PARAMETER | 型パラメータの宣言 |
要素タイプ.TYPE_USE | 使用タイプ |
2. カスタム注釈
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAnnotation {
String name();
}
上記のコード: カスタム アノテーションは @interface キーワードを通じて宣言され、2 つのメタ アノテーション @Retention と @Target で装飾されています。上の表によると、このアノテーションのライフサイクルは実行時 (RUNTIME) まで予約されており、スコープはメンバー変数 (FIELD) であることがわかります。
注: 「String name()」はメソッドではなく、メンバー変数はアノテーション内で「仮パラメータのないメソッド」として宣言されています。つまり、アノテーションは「name」という名前の String 型のメンバー変数を宣言します。
また、@Targer に複数の値がある場合は、@Target({type1, type2, type3,...}) と記述することもできます。
name 変数は MyAnnotation で宣言されており、使用時に値を割り当てる必要があります。
@MyAnnotation(name = "李磊")
private String userName;
または、次のように、default キーワードを使用して、アノテーションを定義するときにデフォルト値を指定できます。
public @interface MyAnnotation {
String name() default "李磊";
}
こうすることで、name 変数の値を指定する必要がなくなります。
@MyAnnotation
private String userName;
これまでに、アノテーションをカスタマイズし、デフォルト値を追加し、それらを userName に追加しました。ただし、この時点では、userName の出力値はまだ null です。これは、前述したように、注釈は受動的なメタデータであり、能動的な動作は存在しないためです。したがって、注釈の値を取得するには、注釈を処理する必要があります。
针对运行时注解和编译时注解分别会使用反射机制来处理和AbstractProcessor(apt)来处理。
2. 反省
1. 基本的な考え方
JAVA リフレクション メカニズムは実行状態にあり、どのクラスについても、このクラスのすべてのプロパティとメソッドを知ることができます。どのオブジェクトについても、そのメソッドやプロパティを呼び出すことができます。この種の動的に取得された情報と動的呼び出しは、オブジェクトのメソッドの機能をJava言語のリフレクション機構といいます。
2. 一般的な方法
クラスを取得するには 3 つの方法があります。ここでは例として String クラスを取り上げます。
Class a=String.class; //1
//2
String s= "";
Class b=s.getClass();
//3
Class c=Class.forName("java.lang.String");//这里要填写完整的包名
クラスを取得した後、次の一般的に使用されるメソッドを使用して、クラスのメンバー変数、メソッド、変数の型、メソッドの戻り値などのデータを取得できます。
-
Annotation[] getAnnotations () //このクラス内のすべてのアノテーションを取得します
-
getClassLoader() //このクラスをロードするクラスローダーを取得します
-
getDeclaredMethods() //このクラスで宣言されたメソッドを取得します
-
getDeclaredFields() //このクラスで宣言された変数を取得します
-
getReturnType() //メソッドの戻り値の型を取得します
-
getParameterTypes() // メソッドの受信パラメータのタイプを取得します
-
isAnnotation() //このクラスがアノテーション クラスであるかどうかをテストします
-
getDeclaredConstructors() //すべてのコンストラクターを取得します
-
getDeclaredMethod(String name, Class...parameterTypes) // 指定されたコンストラクターを取得します (パラメーター: パラメーター type.class)
-
getSuperclass() //このクラスの親クラスを取得します
-
getInterfaces() // このクラスによって実装されたすべてのインターフェースを取得します
-
getFields() //このクラス内のすべてのパブリックで変更されたメンバー変数を取得します
-
getField(String name) //指定された名前を持つ変更されたパブリックメンバー変数を取得します
-
isAnnotationPresent(Class<? extends Annotation> annotationType) //変数またはメソッドがアノテーションによって変更されているかどうかを判断します
-
getModifiers() // public、static などの属性修飾子を取得します。
-
newInstance() //この Class によって表されるクラス、つまりデフォルト (つまりパラメーターなし) コンストラクターを呼び出すことによって作成された新しいインスタンスを返します。
3. 例
ユーザー クラスを定義します。
public class User {
private String userName;
private int age;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
リフレクションは、User クラスのメンバー変数とメソッドを取得します。
private void getUserClass() {
Class c = User.class;
Field[] fields = c.getDeclaredFields();
Method[] methods = c.getDeclaredMethods();
StringBuilder sb = new StringBuilder();
sb.append(Modifier.toString(c.getModifiers()) + " class "); // 获取属性的修饰符,例如public,static等等
sb.append(c.getSimpleName() + " { \n"); //获取类名
for (Field field : fields) {
//遍历类中的变量
sb.append("\t");// 空格
sb.append(Modifier.toString(field.getModifiers()) + " ");
sb.append(field.getType().getSimpleName() + " ");// 属性的类型的名字 如String 、 int
sb.append(field.getName() + ";\n");
}
for (Method method : methods) {
//遍历类中的方法
sb.append("\t");
sb.append(Modifier.toString(method.getModifiers()) + " ");// 获取属性的修饰符
sb.append(method.getReturnType().getSimpleName() + " "); // 获取返回类型
sb.append(method.getName() + " "); //获取方法名
Parameter[] parameters = method.getParameters(); //获取方法所有参数
String params = "";
if (parameters.length > 0) {
//遍历参数
StringBuffer pSb = new StringBuffer();
for (Parameter parameter : parameters) {
//获取参数类型以及参数名称
pSb.append(parameter.getType().getSimpleName() + " " + parameter.getName() + ",");
}
//去掉最后一个逗号
params = pSb.substring(0, pSb.length() - 1);
}
sb.append("(" + params + ");\n");
}
sb.append("}");
System.out.println(sb);
}
メソッドのロジックは比較的単純で、まず User クラスとそのクラス内で宣言されているすべてのメソッドとメンバー変数を取得します。次に、メソッドとメンバー変数を個別に調べて出力します。詳細なコメントは上にありますので、一つ一つは紹介しませんが、 getUserClass() を呼び出して、User クラスのメソッドとメンバー変数の情報を確認します。
3. 実行時のアノテーション情報を取得するためのリフレクション
リフレクションとアノテーションの基本原理を理解すると、アノテーション情報を取得するのは比較的簡単です。
User の userName に注釈を追加します。
public class User {
@MyAnnotation
private String userName;
....
注釈情報を取得して userName に割り当てます。
private void getAnnotation() {
Class c = User.class;
User user = null;
try {
user = (User) c.newInstance();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
//判断该变量是否被MyAnnotation注解修饰
if (field.isAnnotationPresent(MyAnnotation.class)) {
//获取注解
MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
try {
//如果变量声明为private时,为了确保可以访问可将Accessible置为true
field.setAccessible(true);
//将当前属性的值修改为注解中成员变量的值
field.set(user, annotation.name());
System.out.println(user.getUserName());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
上記のコードは変数を取得した後、まず変数がカスタム MyAnnotation によって変更されているかどうかを判断し、変更されている場合はアノテーションを取得します。次に、アノテーションの値を変数に代入します。次の点に注意してください。
- field.setAccessible(true): このメソッドは、private によって変更された変数にアクセスして変更できることを保証します。
- field.set(user, annotation.name()); 変更操作、パラメーター 1 は、変更された変数のクラス (User クラス) です。
このようにして、userName がすでにアノテーションのデフォルト値であることがわかります。
原則として、バターナイフにも @bindview を通じてアノテーションが付けられ、最後に findviewByid を呼び出してコントロールを初期化します。しかし実際には、Butterknife はapt コンパイル時のアノテーション処理を使用しており、コードを自動的に生成する方法についてはここでは説明しません。