1. 程式人生 > 實用技巧 >漢孤臣の樹鏈剖分模板

漢孤臣の樹鏈剖分模板

註解(Java.Annotation)

什麼是註解?

  • 從JDK5.0開始引入的新技術。

  • 作用:不是程式本身,可以對程式作出解釋。可以被其他程式(比如:編譯器等)讀取。

  • 格式:@SuppressWarnings(value="unchecked").

  • 在哪裡使用?可以附加在package,class,method,fiekd等上面,相當於給他們添加了額外的輔助資訊,我們可以通過反射機制程式設計實現對這些元資料的訪問。

內建註解

  • @Override:定義在java.lang.Override中,只適用於修辭方法,表示一個方法宣告打算重寫超類中的另一個方法宣告。

  • Deprecated:定義在java.lang.Deprecated中,可以用於修辭方法,屬性,類,表示不鼓勵程式設計師使用這樣的元素,通常是因為它危險或者存在更好的選擇。

  • @SuppressWarnings:定義在Java.lang.SuppressWarnings中,用來抑制編譯時的警告資訊。這裡需要新增引數才能正確使用,引數都是已經定義好的。

    如:@SuppressWarnings("all"):抑制所有警告。

    @SuppressWarnings("unchecked"):抑制未經檢查的操作(比如強轉)的警告。

元註解

  • 作用:負責註解其他註解,Java定義了4個標準的meta-annotation型別,他們被用來提供對其他annotation型別作說明。

  • 這些型別和他們所支援的類在Java.lang.annotation包中可以找到(@Target,@Retention,@Documented,@inherited)

    @Target:用於描述註解的使用範圍(即:被描述的註解可以用在什麼地方)。

    @Retention:表示需要在什麼級別儲存該註釋資訊,用於描述註解的生命週期(SOURCE<CLASS<RUNTIME).

    @Documented:說明該註解包含在javadoc中。

    @inherited:說明子類可以繼承父類中的該註解。

自定義註解

  • 使用@interface自定義註解時,自動繼承了java.lang.annotation.Ammotation介面。

    @interface用來宣告一個註解,格式:public @Interface 註解名{定義內容}

    其中的每一個方法實際上是聲明瞭一個配置引數。

    方法名就是引數的名稱。

    返回值型別就是引數的型別(返回值只能是基本型別,class,string,enum).

    可以通過default來宣告引數的預設值。

    如果只有一個引數成員,一般引數名為value.

    註解元素必須有值,我們定義註解元素時,經常使用空字串,0作為預設值。

    package annnocation;
    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;

    public class TestAnnocation {
    //註解可以顯示賦值,如果沒有預設值,我們就必須給註解賦值
    @MyAnnocation(age=23)
    public void test() {
    }
    }
    //java內建四大元註解meta-annocation
    //Target指定註解的使用範圍:類,物件
    @Target(value = {ElementType.METHOD,ElementType.TYPE})
    //Retention指定註解的生命週期SOURCE(原始碼階段)<Class(類階段)<RUNTIME(程式執行階段,一般選擇)
    @Retention(value=RetentionPolicy.RUNTIME)
    //Documect指定該註解包含在javadoc中
    @Documented()
    //Inherited:指定父類的註解子類可以繼承
    @Inherited
    @interface MyAnnocation{
    //註解的引數:引數型別+註解名+()
    int age();
    String name() default "周杰倫";//註解可以設定預設值,default後面不用加括號
    String[] schools() default {"南開大學","清華大學"};
    }

反射(java.Reflection)

靜態語言vs動態語言

  • 動態語言:是一類在執行時可以改變其結構的語言:例如新的函式、物件、甚至程式碼可以被引進,已有的函式可以被刪除或是其他結構上的變化。通俗點說就是在執行時程式碼可以根據某些條件改變自身結構。

  • 靜態語言:與動態語言相對應的,執行時結構不可變的語言就是靜態語言。如java、C、C++。

    java不是動態語言,但java可以稱之為“準動態語言”。即java有一定的動態性,我們可以利用反射機制獲得類似動態語言的特性。javad的動態性讓程式設計的時候更加靈活。

反射概述

  • 反射是java被視為動態語言的關鍵,反射機制允許程式在執行期藉助於Reflction API取得任何內部類資訊,並能直接操作任意物件的內部屬性及方法。

    Class c=Class.forName("java.lang.String")

  • 載入完類之後,在堆記憶體的方法區中就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件就包含了完整的類的結構資訊。我們可以通過這個物件看到類的結構。這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之為:反射。

正常方式:引入需要的“包類名稱”——>通過new例項化——>取得例項化物件。

反射方式:例項化物件——>getClass()方法——>得到完整的“包類名稱”

獲得反射物件

java反射機制提供的功能

  • 在執行時判斷任意一個物件所屬的類

  • 在執行時構造任意一個類的物件

  • 在執行時判斷任意一個類所具有的成員變數和方法

  • 在執行時獲取泛型資訊

  • 在執行時呼叫任意一個物件的成員變數和方法

  • 在執行時處理註解

  • 生成動態代理(AOP)

  • ......

java反射優點和缺點

優點:可以實現動態建立物件和編譯,體現出很大的靈活性。

缺點:對效能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它滿足我們的要求。這類操作總是慢於直接執行相同的操作(new)差不多是幾十倍。

反射相關的主要API

  • java.lang.Class:代表一個類。

  • java.lang.reflect.Method:代表類的方法。

  • java.lang.reflect.Field:代表類的成員變數。

  • java.lang.reflect.Constructor:代表類的構造器。

package annnocation;

public class TestClass {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
Class c1=Class.forName("annnocation.user");
System.out.println(c1);
System.out.println(c1.getAnnotations());
System.out.println(c1.getClass());
System.out.println(c1.getConstructor());
System.out.println(c1.getDeclaredMethods());
System.out.println(c1.getDeclaredFields());
}
}
class user{
int age;
String name;
public user(int age, String name) {
super();
this.age = age;
this.name = name;
}
public user() {

}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}

}

輸出結果:class annnocation.user
[Ljava.lang.annotation.Annotation;@762efe5d
class java.lang.Class
public annnocation.user()
[Ljava.lang.reflect.Method;@490d6c15
[Ljava.lang.reflect.Field;@7d4793a8

Class類

在object類中定義了以下的方法,此方法將被所有的子類繼承。

public final Class getClass();

以上方法返回值的型別是一個Class類,此類事java反射的源頭,實際上所謂反射從程式的執行結果來看也很好理解,即:可以通過物件反射求出類的名稱。

物件照鏡子後可以得到的資訊:某個類的屬性、方法和構造器、某個類到底實現了哪些介面。對於每個類而言,JRE都為其保留一個不變的Class型別的物件。一個Class物件包含了特定的某個結構(class/interface/enum/annotation/primitive type(基本資料型別)/void[])的有關資訊。

只要元素型別與維度一樣,就是同一個Class.

  • Class本身也是一個類

  • Class物件只能有系統建立物件

  • 一個載入的類在JVM中只會有一個Classs例項

  • 一個Class物件對應的是一個載入到JVM中的一個.class檔案

  • 每個類的例項都會記得自己是由哪個Class例項所生成的

  • 通過Class可以完整的得到一個類中的所有被載入的結構

  • Class類是Reflection的根源,針對任何你想動態載入、執行的類,唯有先獲得相應的Class物件

獲得Class類的例項

  • 若已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程勳效能最高。

    Class c1=Person.class;//通過類名呼叫class

  • 已知某個類的例項,呼叫該例項的getClass()方法獲取Class物件。

    Class c2=person.getClass();//通過類的例項呼叫getClass()方法。

  • 已知一個類的全類名,且該類在類路徑下,可通過Class類的靜態方法forName()獲取,可能丟擲ClassNotException

    Class c3=Class.forName("全類名");//Class.靜態方法forName()

    注意:括號裡的全類名需要加引號。

  • 內建基本資料型別可以直接用類名.Type

  • 還可以利用ClassLoader。

  • 獲得父類的型別

    getSuperclass();

package annnocation;
public class TestgetClass {
public static void main(String[] args) throws ClassNotFoundException{
//1.已知類名,直接通過類名.class;
Class c1=Person.class;
System.out.println(c1);
//2.已知類的例項,通過類例項.getClass();
Person person=new Person();
Class c2=person.getClass();
System.out.println(c2);
//3.已知類的全類名,通過Class.靜態方法forName();
Class c3=Class.forName("annnocation.Person");
System.out.println(c3);
//4.內建基本型別.Type
Class c4=Integer.TYPE;
System.out.println(c4);
//獲得父類型別
Class c5=c1.getSuperclass();
System.out.println(c5);
}
}
class Person{
String name;
public Person(String name) {
super();
this.name = name;
}
public Person() {

}
@Override
public String toString() {
return "Person [name=" + name + "]";
}

}
class Student extends Person{
public Student() {
this.name="學生";
}
}
class Teacher extends Person{
public Teacher() {
this.name="教師";
}
}

類載入記憶體分析

java記憶體分為三個部分:

棧:

  • 存放基本資料型別(會包含這個基本型別的具體數值)。

  • 引用物件的變數(會存放這個引用在堆裡面的具體地址)。

堆:

  • 存放new的物件和陣列。

  • 可以被所有的執行緒共享,不會存放別的物件引用。

方法區:

  • 可以被所有的執行緒共享。

  • 包含了所有的class和static變數。

類的載入與ClassLoader的理解(三個階段)

  1. 載入(Class物件在這個階段產生):加class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後生成一個代表這個類的java.lang.Class物件。

  2. 連結:將java類的二進位制程式碼合併到JVM的執行狀態之中的過程。

    • 驗證:確保載入的類資訊符合JVM規範,沒有安全方面的問題。

    • 準備:正式為類變數(static)分配記憶體並設定類變數預設初始化的階段,這些記憶體都將在方法區中進行分配

    • 解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程。

  3. 初始化:

    • 執行類構造器<clinit>()方法的過程。類構造器<clinit>()方法是由編譯期自動收集類中所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的。(類構造器是構造類資訊的,不是構造該類物件的構造器)。

    • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。

    • 虛擬機器會保證一個類<clinit>()方法在多執行緒環境中被正確加鎖和同步。

分析類初始化

什麼時候回發生類初始化?

  • 類的主動引用(一定會發生類的初始化)

    • 當虛擬機器啟動時,先初始化main方法所在的類。(不在main方法中的類沒有被呼叫時,不會初始化)。

    • new一個類的物件。

    • 呼叫類的靜態成員(除了final常量)和靜態方法。

    • 使用java.lang.reflect包的方法對類進行反射呼叫。

    • 當初始化一個類,如果其父類沒有被初始化,則先回初始化它的父類。

  • 類的被動引用(不會發生類的初始化)

    • 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化。如:當通過子類引用父類的靜態變數,不會導致子類初始化。

    • 通過陣列定義類引用,不會觸發此類的初始化。

    • 引用常量不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池中了)

理解引用這個詞的概念

類載入器(把類裝載進記憶體中)

  • 類載入器的作用:將Class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後在堆中生成一個代表這個類的java.lang.Class物件,作為方法區中類資料的訪問入口。

  • 類快取:標準的javaSE類載入器可以按要求查詢類,但一旦某個類被載入到類載入器中,它將維持載入(快取)一段時間。不過JVM垃圾回收機制可以回收這些Class物件。

雙親委派機制:

逐層向上查詢,該類是否被載入過。

這種設計有個好處是,如果有人想替換系統級別的類:String.java。篡改它的實現,但是在這種機制下這些系統的類已經被Bootstrap classLoader載入過了,所以並不會再去載入,從一定程度上防止了危險程式碼的植入。

獲取類的執行時結構

通過反射獲取執行類的完整結構

Field、Method、Constructor、Superclass、Interface、Annotation

  • 實現的全部介面

  • 所繼承的父類

  • 全部的構造方法

  • 全部的方法

  • 全部的屬性Field

  • 註解

  • ......

動態建立物件執行方法

  • 建立類的物件:呼叫Class物件的newInstance()方法

    • 類必須有一個無引數的構造器

    • 類的構造器的訪問許可權需要足夠

    • Class.newInstance()

但是,如果沒有無參的構造器就不能創造物件了嗎?

通過反射呼叫類的構造器,將引數傳遞進去之後,就能例項化操作了。

步驟:

  • 通過Class類的getDeclaredConstructor(Class...parameterTypes)取得本類的指定引數型別的構造器。

  • 向構造器的形參中傳遞一個物件陣列進去,裡面包含了構造器中所需的各個引數。

  • 通過Constructor例項化物件(constructor.newInstance)

呼叫指定的方法

通過反射,呼叫類中的方法,通過Method類完成。

  • 通過Class類的getMethod(String name,Class...parameterTypes)方法取得一個Method物件,並設定此方法操作時所需要的引數型別。

  • 之後使用Object invoke(Object obj,Object[] args[])進行呼叫,並向方法中傳遞要設定的obj物件的引數資訊。

注意:在使用invoke方法時,如果原類的方法宣告為private,那就需要在invoke方法前面加一個setAccessible(true)方法,關閉程式的安全檢測,注意裡面的引數true是關閉。

package annnocation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestgetRunClass {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
//1.通過反射獲得一個Class物件。
Class class1=Person.class;
System.out.println(class1);
//2.通過獲得的Class物件呼叫newInstance()方法,建立一個類物件。
//本質是類的無參建構函式
Person person=(Person) class1.newInstance();
System.out.println(person);
//通過有參建構函式建立類的物件
//通過構造器建立物件
Constructor constructor=class1.getDeclaredConstructor(String.class);
Person person2=(Person) constructor.newInstance("周杰倫");
System.out.println(person2);
//通過反射獲取一個方法
Person person3=(Person) class1.newInstance();
Method setName=class1.getDeclaredMethod("setName", String.class);
//invoke:啟用的意思
//(物件,物件的值)
setName.invoke(person3, "林俊杰");
System.out.println(person3.getName());
//通過反射操作類的屬性
Person person4=(Person) class1.newInstance();
Field name=class1.getDeclaredField("name");
//不能直接操作類的私有屬性,需要通過屬性或方法的setAccessible(true);關閉程式的安全檢測。
name.setAccessible(true);
name.set(person4, "還有我");
System.out.println(person4.getName());

}
}

setAccessible:

  • 使得原本無法訪問的私有成員也可以訪問。

  • 提高反射的效率。如果程式碼中必須用反射,而該句程式碼需要頻繁的被呼叫,那麼請設定為true。

效能是不設定true的6倍左右。

獲取泛型資訊

  • java採用泛型擦除機制來引入泛型,java中的泛型僅僅是給編譯器javac使用的,確保資料的安全性和免去強制轉換問題,但是,一旦編譯完成,所有和泛型有關的型別全部擦除。

  • 為了通過反射操作這些型別,java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType幾種型別來代表不能被歸一到Class類中的型別但是又和原始型別齊名的型別。

    • ParameterizedType:表示一種引數化型別,比如Collection<String>

    • GenericArrayType:表示一種元素型別是引數化型別或者型別變數的陣列型別

    • TypeVariable:是各種型別變數的公共父介面

    • WildcardType:代表一種萬用字元型別表示式

public class TestString {
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
Method method=TestString.class.getMethod("test01", Map.class,List.class);
Type[] genericParameterTypes=(Type[]) method.getGenericParameterTypes();
for(Type genericParameterType:genericParameterTypes) {
System.out.println("#"+genericParameterType);
if(genericParameterType instanceof ParameterizedType) {
Type[] actualTypeArguments=((ParameterizedType) genericParameterType).getActualTypeArguments();
for(Type actualTypeArgument:actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
public void test01(Map<String, Person> map,List<Person> list) {
System.out.println("test01");
}