【Java基礎】反射和註解

cryAllen發表於2016-07-22

前言

在Java中,反射機制和註解機制一直是一個很重要的概念,那麼他們其中的原理是怎麼樣呢,我們不僅僅需要會使用,更要知其然而之所以然。

目錄

  • 反射機制
  • 反射如何使用
  • 註解定義
  • 註解機制原理
  • 註解如何使用
  • 小結

反射機制

官網定義:

Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.

Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection.

Reflection is a language's ability to inspect and dynamically call classes, methods, attributes, etc. at runtime.

也就是說:

Java反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法,對於任意一個物件,都能夠呼叫它的任意一個方法和屬性,這種動態獲取的資訊以及動態呼叫物件的方法的功能。

主要提供了以下功能: 在執行時判斷任意一個物件所屬的類,在執行時構造任意一個類的物件,在執行時判斷任意一個類所具有的成員變數和方法,在執行時呼叫任意一個物件的方法,生成動態代理。

反射如何使用

通過一個物件獲得完整的包名和類名

package Reflect;
 
/**
 * 通過一個物件獲得完整的包名和類名
 * */
class Demo{
    //other codes...
}
 
class hello{
    public static void main(String[] args) {
        Demo demo=new Demo();
        System.out.println(demo.getClass().getName());
    }
}

我們可以發現,其實所有的類的物件都是Class的例項。

例項化Class類物件

package Reflect;
class Demo{
    //other codes...
}
 
class hello{
    public static void main(String[] args) {
        Class<?> demo1=null;
        Class<?> demo2=null;
        Class<?> demo3=null;
        try{
            //一般儘量採用這種形式
            demo1=Class.forName("Reflect.Demo");
        }catch(Exception e){
            e.printStackTrace();
        }
        demo2=new Demo().getClass();
        demo3=Demo.class;
         
        System.out.println("類名稱   "+demo1.getName());
        System.out.println("類名稱   "+demo2.getName());
        System.out.println("類名稱   "+demo3.getName());
         
    }
}

結果:

類名稱   Reflect.Demo
類名稱   Reflect.Demo
類名稱   Reflect.Demo

通過Class例項化其他類的物件

package Reflect;
 
class Person{
     
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString(){
        return "["+this.name+"  "+this.age+"]";
    }
    private String name;
    private int age;
}
 
class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.forName("Reflect.Person");
        }catch (Exception e) {
            e.printStackTrace();
        }
        Person per=null;
        try {
            per=(Person)demo.newInstance();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        per.setName("Rollen");
        per.setAge(20);
        System.out.println(per);
    }
}

結果:

[Rollen  20]

通過Class呼叫其他類中的建構函式

package Reflect;
 
import java.lang.reflect.Constructor;
 
class Person{
     
    public Person() {
         
    }
    public Person(String name){
        this.name=name;
    }
    public Person(int age){
        this.age=age;
    }
    public Person(String name, int age) {
        this.age=age;
        this.name=name;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    @Override
    public String toString(){
        return "["+this.name+"  "+this.age+"]";
    }
    private String name;
    private int age;
}
 
class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.forName("Reflect.Person");
        }catch (Exception e) {
            e.printStackTrace();
        }
        Person per1=null;
        Person per2=null;
        Person per3=null;
        Person per4=null;
        //取得全部的建構函式
        Constructor<?> cons[]=demo.getConstructors();
        try{
            per1=(Person)cons[0].newInstance();
            per2=(Person)cons[1].newInstance("Rollen");
            per3=(Person)cons[2].newInstance(20);
            per4=(Person)cons[3].newInstance("Rollen",20);
        }catch(Exception e){
            e.printStackTrace();
        }
        System.out.println(per1);
        System.out.println(per2);
        System.out.println(per3);
        System.out.println(per4);
    }
}

結果:

[null  0]

[Rollen  0]

[null  20]

[Rollen  20]

通過反射呼叫其他類中的方法

class hello {
    public static void main(String[] args) {
        Class<?> demo = null;
        try {
            demo = Class.forName("Reflect.Person");
        } catch (Exception e) {
            e.printStackTrace();
        }
        try{
            //呼叫Person類中的sayChina方法
            Method method=demo.getMethod("sayChina");
            method.invoke(demo.newInstance());
            //呼叫Person的sayHello方法
            method=demo.getMethod("sayHello", String.class,int.class);
            method.invoke(demo.newInstance(),"Rollen",20);
             
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

結果:

hello ,china

Rollen  20

註解定義

官網原話:

Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.

Annotations have a number of uses, among them:

  • Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings.
  • Compile-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth.
  • Runtime processing — Some annotations are available to be examined at runtime.

也就是說:一種程式碼級別的說明,它是JDK1.5及以後版本引入的一個特性,與類、介面、列舉是在同一個層次。它可以宣告在包、類、欄位、方法、區域性變數、方法引數等的前面,用來對這些元素進行說明,註釋。

作用:

  • 編寫文件:通過程式碼裡標識的後設資料生成文件【生成文件doc文件】

  • 程式碼分析:通過程式碼裡標識的後設資料對程式碼進行分析【使用反射】

  • 編譯檢查:通過程式碼裡標識的後設資料讓編譯器能夠實現基本的編譯檢查【Override】

註解機制原理

原理:

Annotation其實是一種介面。通過Java的反射機制相關的API來訪問annotation資訊。相關類(框架或工具中的類)根據這些資訊來決定如何使用該程式元素或改變它們的行為。

  • Annotation是不會影響程式程式碼的執行,無論annotation怎麼變化,程式碼都始終如一地執行。
    • Java語言直譯器在工作時會忽略這些annotation,因此在JVM中這些annotation是“不起作用”的,只能通過配套的工具才能對這些annontaion型別的資訊進行訪問和處理。

Annotation與interface的異同:

  • Annotation型別使用關鍵字@interface而不是interface。這個關鍵字宣告隱含了一個資訊:它是繼承了java.lang.annotation.Annotation介面,並非宣告瞭一個interface
  • Annotation型別、方法定義是獨特的、受限制的。Annotation 型別的方法必須宣告為無引數、無異常丟擲的。這些方法定義了annotation的成員:方法名成為了成員名,而方法返回值成為了成員的型別。而方法返回值型別必須為primitive型別、Class型別、列舉型別、annotation型別或者由前面型別之一作為元素的一維陣列。方法的後面可以使用 default和一個預設數值來宣告成員的預設值,null不能作為成員預設值,這與我們在非annotation型別中定義方法有很大不同。Annotation型別和它的方法不能使用annotation型別的引數、成員不能是generic。只有返回值型別是Class的方法可以在annotation型別中使用generic,因為此方法能夠用類轉換將各種型別轉換為Class。
  • Annotation型別又與介面有著近似之處。

  它們可以定義常量、靜態成員型別(比如列舉型別定義)。Annotation型別也可以如介面一般被實現或者繼承。

註解如何使用

What’s the use of Annotations?

1) Instructions to the compiler: There are three built-in annotations available in Java (@Deprecated, @Override & @SuppressWarnings) that can be used for giving certain instructions to the compiler. For example the @override annotation is used for instructing compiler that the annotated method is overriding the method. More about these built-in annotations with example is discussed in the next sections of this article.

2) Compile-time instructors: Annotations can provide compile-time instructions to the compiler that can be further used by sofware build tools for generating code, XML files etc.

3) Runtime instructions: We can define annotations to be available at runtime which we can access using java reflection and can be used to give instructions to the program at runtime. We will discuss this with the help of an example, later in this same post.

Annotations basics

An annotation always starts with the symbol @ followed by the annotation name. The symbol @ indicates to the compiler that this is an annotation.

For e.g. @Override

Here @ symbol represents that this is an annotation and the Override is the name of this annotation.

Where we can use annotations?

Annotations can be applied to the classes, interfaces, methods and fields. For example the below annotation is being applied to the method.

@Overridevoid myMethod(){//Do something }

What this annotation is exactly doing here is explained in the next section but to be brief it is instructing compiler that myMethod() is a overriding method which is overriding the method (myMethod()) of super class.

Built-in Annotations in Java

Java has three built-in annotations:

  • @Override
  • @Deprecated
  • @SuppressWarnings

1) @Override:

While overriding a method in the child class, we should use this annotation to mark that method. This makes code readable and avoid maintenance issues, such as: while changing the method signature of parent class, you must change the signature in child classes (where this annotation is being used) otherwise compiler would throw compilation error. This is difficult to trace when you haven’t used this annotation.

Example:

public class MyParentClass{
  public void justaMethod(){
    System.out.println("Parent class method");
  }
}
public class MyChildClass extends MyParentClass{
  @Override
  publicvoid justaMethod(){
    System.out.println("Child class method");
  }
}

I believe the example is self explanatory. To read more about this annotation, refer this article: @Override built-in annotation.

2) @Deprecated

@Deprecated annotation indicates that the marked element (class, method or field) is deprecated and should no longer be used. The compiler generates a warning whenever a program uses a method, class, or field that has already been marked with the @Deprecated annotation. When an element is deprecated, it should also be documented using the Javadoc @deprecated tag, as shown in the following example. Make a note of case difference with @Deprecated and @deprecated. @deprecated is used for documentation purpose.

Example:

/**
 * @deprecated
 * reason for why it was deprecated
 */
@Deprecatedpublicvoid 
anyMethodHere(){// Do something}

Now, whenever any program would use this method, the compiler would generate a warning. To read more about this annotation, refer this article: Java – @Deprecated annotation.

3) @SuppressWarnings

This annotation instructs compiler to ignore specific warnings. For example in the below code, I am calling a deprecated method (lets assume that the method deprecatedMethod() is marked with @Deprecated annotation) so the compiler should generate a warning, however I am using @@SuppressWarnings annotation that would suppress that deprecation warning.

@SuppressWarnings("deprecation")
void myMethod(){
    myObject.deprecatedMethod();
}

Creating Custom Annotations

  • Annotations are created by using @interface, followed by annotation name as shown in the below example.
  • An annotation can have elements as well. They look like methods. For example in the below code, we have four elements. We should not provide implementation for these elements.
  • All annotations extends java.lang.annotation.Annotation interface. Annotations cannot include any extends clause.

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    @Documented
    @Target(ElementType.METHOD)
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyCustomAnnotation{
    int studentAge() default18;
    String studentName();
    String stuAddress();
    String stuStream() default"CSE";
    }

Note: All the elements that have default values set while creating annotations can be skipped while using annotation. For example if I’m applying the above annotation to a class then I would do it like this:

@MyCustomAnnotation(
    studentName="Chaitanya",
    stuAddress="Agra, India")
public class MyClass{...}

As you can see, we have not given any value to the studentAge andstuStream elements as it is optional to set the values of these elements (default values already been set in Annotation definition, but if you want you can assign new value while using annotation just the same way as we did for other elements). However we have to provide the values of other elements (the elements that do not have default values set) while using annotation.

Note: We can also have array elements in an annotation. This is how we can use them:

Annotation definition:

@interface MyCustomAnnotation{
  int      count();
  String[] books();
}

Usage:

@MyCustomAnnotation(
    count=3,
    books={"C++","Java"}
)
public class MyClass{}

Lets back to the topic again: In the custom annotation example we have used these four annotations: @Documented, @Target,@Inherited & @Retention. Lets discuss them in detail.

@Documented

@Documented annotation indicates that elements using this annotation should be documented by JavaDoc. For example:

import java.lang.annotation.Documented
@Documented
public @interface MyCustomAnnotation{
  //Annotation body
}

@MyCustomAnnotation
public class MyClass{
  //Class body
}

While generating the javadoc for class MyClass, the annotation@MyCustomAnnotation would be included in that.

@Target

It specifies where we can use the annotation. For example: In the below code, we have defined the target type as METHOD which means the below annotation can only be used on methods.

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
public @interface MyCustomAnnotation{
  
}

public class MyClass{
  @MyCustomAnnotation 
  public void myMethod(){
    //Doing something
  }
}

Note: 1) If you do not define any Target type that means annotation can be applied to any element.

2) Apart from ElementType.METHOD, an annotation can have following possible Target values.

ElementType.METHOD

ElementType.PACKAGE

ElementType.PARAMETER

ElementType.TYPE

ElementType.ANNOTATION_TYPE

ElementType.CONSTRUCTOR

ElementType.LOCAL_VARIABLE

ElementType.FIELD

@Inherited

The @Inherited annotation signals that a custom annotation used in a class should be inherited by all of its sub classes. For example:

import java.lang.annotation.Inherited
  @Inheritedpublic
  @interface
  MyCustomAnnotation{}

@MyCustomAnnotation 
public class MyParentClass{...}

public class MyChildClass extends MyParentClass{...}

Here the class MyParentClass is using annotation@MyCustomAnnotation which is marked with @inherited annotation. It means the sub class MyChildClass inherits the@MyCustomAnnotation.

@Retention

It indicates how long annotations with the annotated type are to be retained.

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface MyCustomAnnotation{}

Here we have used RetentionPolicy.RUNTIME. There are two other options as well. Lets see what do they mean:

RetentionPolicy.RUNTIME: The annotation should be available at runtime, for inspection via java reflection.

RetentionPolicy.CLASS: The annotation would be in the .class file but it would not be available at runtime.

RetentionPolicy.SOURCE: The annotation would be available in the source code of the program, it would neither be in the .class file nor be available at the runtime.

That’s all for this topic “Java Annotation”. Should you have any questions, feel free to drop a line below.

小結

總之,反射和註解的機制在很多應用和開源庫上廣泛應用,通過了解背後的機制原理和使用方式,對於在開發中或者看一些比較優秀第三方庫的原始碼有很大的幫助,看看他們實現的思想,看看是否值得借鑑下,對於自我技術的收穫想必會有不一樣的衝擊。

參考地址:

1,http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html

相關文章