一扳抽、使用反射獲取類的信息
現(xiàn)在有兩個用于測試的類湖员,代碼很簡單屑迂,我們直接貼出來:
- FatherClass.java
public class FatherClass {
public String mFatherName;
public int mFatherAge;
public void printFatherMsg(){}
}
- SonClass.java
public class SonClass extends FatherClass{
private String mSonName;
protected int mSonAge;
public String mSonBirthday;
public void printSonMsg(){
System.out.println("Son Msg - name : "
+ mSonName + "; age : " + mSonAge);
}
private void setSonName(String name){
mSonName = name;
}
private void setSonAge(int age){
mSonAge = age;
}
private int getSonAge(){
return mSonAge;
}
private String getSonName(){
return mSonName;
}
}
1、獲取類的所有變量信息
/**
* 通過反射獲取類的所有變量
*/
private static void printFields(){
//1.獲取并輸出類的名稱
Class mClass = SonClass.class;
System.out.println("類的名稱:" + mClass.getName());
//2.1 獲取所有 public 訪問權(quán)限的變量
// 包括本類聲明的和從父類繼承的
Field[] fields = mClass.getFields();
//2.2 獲取所有本類聲明的變量(不問訪問權(quán)限)
//Field[] fields = mClass.getDeclaredFields();
//3. 遍歷變量并輸出變量信息
for (Field field :
fields) {
//獲取訪問權(quán)限并輸出
int modifiers = field.getModifiers();
System.out.print(Modifier.toString(modifiers) + " ");
//輸出變量的類型及變量名
System.out.println(field.getType().getName()
+ " " + field.getName());
}
}
以上代碼注釋很詳細栅隐,就不再解釋了塔嬉。需要注意的是注釋中 2.1 的 getFields() 與 2.2的 getDeclaredFields() 之間的區(qū)別,下面分別看一下兩種情況下的輸出租悄〗骶浚看之前強調(diào)一下:SonClass extends FatherClass extends Object
- 調(diào)用 getFields() 方法,輸出 SonClass 類以及其所繼承的父類( 包括 FatherClass 和 Object ) 的 public 方法泣棋。注:Object 類中沒有成員變量胶哲,所以沒有輸出
類的名稱:obj.SonClass
public java.lang.String mSonBirthday
public java.lang.String mFatherName
public int mFatherAge
- 調(diào)用 getDeclaredFields() , 輸出 SonClass 類的所有成員變量潭辈,不問訪問權(quán)限
類的名稱:obj.SonClass
private java.lang.String mSonName
protected int mSonAge
public java.lang.String mSonBirthday
2鸯屿、獲取類的所有方法信息
/**
* 通過反射獲取類的所有方法
*/
private static void printMethods(){
//1.獲取并輸出類的名稱
Class mClass = SonClass.class;
System.out.println("類的名稱:" + mClass.getName());
//2.1 獲取所有 public 訪問權(quán)限的方法
//包括自己聲明和從父類繼承的
Method[] mMethods = mClass.getMethods();
//2.2 獲取所有本類的的方法(不問訪問權(quán)限)
//Method[] mMethods = mClass.getDeclaredMethods();
//3.遍歷所有方法
for (Method method :
mMethods) {
//獲取并輸出方法的訪問權(quán)限(Modifiers:修飾符)
int modifiers = method.getModifiers();
System.out.print(Modifier.toString(modifiers) + " ");
//獲取并輸出方法的返回值類型
Class returnType = method.getReturnType();
System.out.print(returnType.getName() + " "
+ method.getName() + "( ");
//獲取并輸出方法的所有參數(shù)
Parameter[] parameters = method.getParameters();
for (Parameter parameter:
parameters) {
System.out.print(parameter.getType().getName()
+ " " + parameter.getName() + ",");
}
//獲取并輸出方法拋出的異常
Class[] exceptionTypes = method.getExceptionTypes();
if (exceptionTypes.length == 0){
System.out.println(" )");
}
else {
for (Class c : exceptionTypes) {
System.out.println(" ) throws "
+ c.getName());
}
}
}
}
- 調(diào)用 getMethods() 方法 獲取 SonClass 類所有 public 訪問權(quán)限的方法,包括從父類繼承的把敢。打印信息中寄摆,printSonMsg() 方法來自 SonClass 類, printFatherMsg() 來自 FatherClass 類修赞,其余方法來自 Object 類
類的名稱:obj.SonClass
public void printSonMsg( )
public void printFatherMsg( )
public final void wait( ) throws java.lang.InterruptedException
public final void wait( long arg0,int arg1, ) throws java.lang.InterruptedException
public final native void wait( long arg0, ) throws java.lang.InterruptedException
public boolean equals( java.lang.Object arg0, )
public java.lang.String toString( )
public native int hashCode( )
public final native java.lang.Class getClass( )
public final native void notify( )
public final native void notifyAll( )
- 調(diào)用 getDeclaredMethods() 方法
類的名稱:obj.SonClass
private int getSonAge( )
private void setSonAge( int arg0, )
public void printSonMsg( )
private void setSonName( java.lang.String arg0, )
private java.lang.String getSonName( )
3婶恼、訪問或操作類的私有變量和方法
都知道,對象是無法訪問或操作類的私有變量和方法的柏副,但是勾邦,通過反射,我們就可以做到割择。沒錯眷篇,反射可以做到!下面荔泳,讓我們一起探討如何利用反射訪問 類對象的私有方法 以及修改 私有變量或常量蕉饼。
- TestClass.java
public class TestClass {
private String MSG = "Original";
private void privateMethod(String head , int tail){
System.out.print(head + tail);
}
public String getMsg(){
return MSG;
}
}
1 訪問私有方法
以訪問 TestClass 類中的私有方法 privateMethod(...) 為例
/**
* 訪問對象的私有方法
* 為簡潔代碼虐杯,在方法上拋出總的異常,實際開發(fā)別這樣
*/
private static void getPrivateMethod() throws Exception{
//1. 獲取 Class 類實例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 獲取私有方法
//第一個參數(shù)為要獲取的私有方法的名稱
//第二個為要獲取方法的參數(shù)的類型昧港,參數(shù)為 Class...厦幅,沒有參數(shù)就是null
//方法參數(shù)也可這么寫 :new Class[]{String.class , int.class}
Method privateMethod =
mClass.getDeclaredMethod("privateMethod", String.class, int.class);
//3. 開始操作方法
if (privateMethod != null) {
//獲取私有方法的訪問權(quán)
//只是獲取訪問權(quán),并不是修改實際權(quán)限
privateMethod.setAccessible(true);
//使用 invoke 反射調(diào)用私有方法
//privateMethod 是獲取到的私有方法
//testClass 要操作的對象
//后面兩個參數(shù)傳實參
privateMethod.invoke(testClass, "Java Reflect ", 666);
}
}
需要注意的是慨飘,第3步中的 setAccessible(true) 方法确憨,是獲取私有方法的訪問權(quán)限,如果不加會報異常 IllegalAccessException瓤的,因為當(dāng)前方法訪問權(quán)限是“private”的
正常運行后休弃,打印如下,調(diào)用私有方法成功:
Java Reflect 666
2圈膏、修改私有變量
以修改 TestClass 類中的私有變量 MSG 為例塔猾,其初始值為 "Original" ,我們要修改為 "Modified"稽坤。老規(guī)矩丈甸,先上代碼看注釋
/**
* 修改對象私有變量的值
* 為簡潔代碼,在方法上拋出總的異常
*/
private static void modifyPrivateFiled() throws Exception {
//1. 獲取 Class 類實例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 獲取私有變量
Field privateField = mClass.getDeclaredField("MSG");
//3. 操作私有變量
if (privateField != null) {
//獲取私有變量的訪問權(quán)
privateField.setAccessible(true);
//修改私有變量尿褪,并輸出以測試
System.out.println("Before Modify:MSG = " + testClass.getMsg());
//調(diào)用 set(object , value) 修改變量的值
//privateField 是獲取到的私有變量
//testClass 要操作的對象
//"Modified" 為要修改成的值
privateField.set(testClass, "Modified");
System.out.println("After Modify:MSG = " + testClass.getMsg());
}
}
此處代碼和訪問私有方法的邏輯差不多睦擂,就不再贅述,從輸出信息看出 修改私有變量 成功:
Before Modify:MSG = Original
After Modify:MSG = Modified
二杖玲、注解
1顿仇、自定義注解
舉個栗子, 結(jié)合例子講解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TestAnnotation {
String value();
String[] value2() default "value2";
}
元注解的的意義參考上面的講解, 不再重復(fù), 這里看注解值的寫法,其中默認值是可選的, 可以定義, 也可以不定義:
類型 參數(shù)名() default 默認值;
2、處理運行時注解
Retention的值為RUNTIME時, 注解會保留到運行時, 因此使用反射來解析注解.
使用的注解就是上一步的@TestAnnotation, 解析示例如下:
public class Demo {
@TestAnnotation("Hello Annotation!")
private String testAnnotation;
public static void main(String[] args) {
try {
// 獲取要解析的類
Class cls = Class.forName("myAnnotation.Demo");
// 拿到所有Field
Field[] declaredFields = cls.getDeclaredFields();
for(Field field : declaredFields){
// 獲取Field上的注解
TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
if(annotation != null){
// 獲取注解值
String value = annotation.value();
System.out.println(value);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
此處只演示了解析成員變量上的注解, 其他類型與此類似
2摆马、解析編譯時注解
解析編譯時注解需要繼承AbstractProcessor類, 實現(xiàn)其抽象方法
public boolean process(Set annotations, RoundEnvironment roundEnv)
該方法返回ture表示該注解已經(jīng)被處理, 后續(xù)不會再有其他處理器處理; 返回false表示仍可被其他處理器處理.
舉個例子:
// 指定要解析的注解
@SupportedAnnotationTypes("myAnnotation.TestAnnotation")
// 指定JDK版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcesser extends AbstractProcessor {
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
for (TypeElement te : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
TestAnnotation testAnnotation = element.getAnnotation(TestAnnotation.class);
// do something
}
}
return true;
}
}
三臼闻、Android中使用編譯時注解
- 1、自定義編譯時注解
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface TestAnnotation {
String value() default "Hello Annotation";
}
- 2囤采、解析編譯時注解
// 支持的注解類型, 此處要填寫全類名
@SupportedAnnotationTypes("myannotation.TestAnnotation")
// JDK版本,一般推薦java8
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
// 類名的前綴后綴
public static final String SUFFIX = "AutoGenerate";
public static final String PREFIX = "My_";
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
for (TypeElement te : annotations) {
for (Element e : roundEnv.getElementsAnnotatedWith(te)) {
// 準備在gradle的控制臺打印信息
Messager messager = processingEnv.getMessager();
// 打印
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.toString());
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.getSimpleName());
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.getEnclosingElement().toString());
// 獲取注解
TestAnnotation annotation = e.getAnnotation(TestAnnotation.class);
// 獲取元素名并將其首字母大寫
String name = e.getSimpleName().toString();
char c = Character.toUpperCase(name.charAt(0));
name = String.valueOf(c+name.substring(1));
// 包裹注解元素的元素, 也就是其父元素, 比如注解了成員變量或者成員函數(shù), 其上層就是該類
Element enclosingElement = e.getEnclosingElement();
// 獲取父元素的全類名, 用來生成包名
String enclosingQualifiedName;
if(enclosingElement instanceof PackageElement){
enclosingQualifiedName = ((PackageElement)enclosingElement).getQualifiedName().toString();
}else {
enclosingQualifiedName = ((TypeElement)enclosingElement).getQualifiedName().toString();
}
try {
// 生成的包名
String genaratePackageName = enclosingQualifiedName.substring(0, enclosingQualifiedName.lastIndexOf('.'));
// 生成的類名
String genarateClassName = PREFIX + enclosingElement.getSimpleName() + SUFFIX;
// 創(chuàng)建Java文件
JavaFileObject f = processingEnv.getFiler().createSourceFile(genarateClassName);
// 在控制臺輸出文件路徑
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + f.toUri());
Writer w = f.openWriter();
try {
PrintWriter pw = new PrintWriter(w);
pw.println("package " + genaratePackageName + ";");
pw.println("\npublic class " + genarateClassName + " { ");
pw.println("\n /** 打印值 */");
pw.println(" public static void print" + name + "() {");
pw.println(" // 注解的父元素: " + enclosingElement.toString());
pw.println(" System.out.println(\"代碼生成的路徑: "+f.toUri()+"\");");
pw.println(" System.out.println(\"注解的元素: "+e.toString()+"\");");
pw.println(" System.out.println(\"注解的值: "+annotation.value()+"\");");
pw.println(" }");
pw.println("}");
pw.flush();
} finally {
w.close();
}
} catch (IOException x) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
x.toString());
}
}
}
return true;
}
}
看似代碼很長, 其實很好理解. 只做了兩件事, 1.解析注解并獲取需要的值 2.使用JavaFileObject類生成java代碼.
- 3述呐、向JVM聲明解析器
在java的同級目錄新建resources目錄, 新建META-INF/services/javax.annotation.processing.Processor文件, 文件中填寫你自定義的Processor全類名
然后打出jar包以待使用
Android中使用
- 使用apt插件
項目根目錄gradle中buildscript的dependencies添加
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
module目錄的gradle中, 添加
apply plugin: 'android-apt'
- 代碼中調(diào)用
將之前打出的jar包導(dǎo)入項目中, 在MainActivity中寫個測試方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test();
}
@TestAnnotation("hehe")
public void test(){
}
運行一遍項目之后, 代碼就會自動生成.
以下是生成的代碼, 在路徑y(tǒng)ourmodule/build/generated/source/apt/debug/yourpackagename中:
public class My_MainActivityAutoGenerate {
/** 打印值 */
public static void printTest() {
// 注解的父元素: com.example.pan.androidtestdemo.MainActivity
System.out.println("代碼生成的路徑: file:/Users/Pan/AndroidStudioProjects/AndroidTestDemo/app/build/generated/source/apt/debug/My_MainActivityAutoGenerate.java");
System.out.println("注解的元素: test()");
System.out.println("注解的值: hehe");
}
}
然后在test方法中調(diào)用自動生成的方法
@TestAnnotation("hehe")
public void test(){
My_MainActivityAutoGenerate.printTest();
}
會看到以下打印結(jié)果:
代碼生成的路徑: file:/Users/Pan/AndroidStudioProjects/AndroidTestDemo/app/build/generated/source/apt/debug/com/example/pan/androidtestdemo/MainActivityAutoGenerate.java
注解的元素: test()
注解的值: hehe