Java列舉類、註解和反射

GaoYuan206發表於2021-05-11

本文主要介紹的是列舉類,註解和反射。還有一些基礎知識:static,基本資料型別,運算子優先順序放在文中,以便查閱複習。
其中牽扯到泛型的部分,可參考本人的另一篇部落格:(Collection, List, 泛型)JAVA集合框架一

1. static關鍵字

static可以修飾的有:屬性,方法,程式碼塊,內部類。

1.1 static修飾屬性

按是否用static修飾分為靜態屬性和非靜態屬性(例項變數)。

非靜態屬性(例項變數):當建立了類的多個物件,每個物件都獨立擁有自己的非靜態屬性。當修改其中一個物件中的非靜態屬性時,不會改變其他物件中的非靜態屬性。

靜態屬性(靜態變數):當建立了類的多個物件,多個物件共享同一個靜態物件。通過任一個物件改變靜態屬性,所有物件的靜態屬性都會發生改變。

  • 靜態變數隨著類的載入而載入。可通過class.靜態變數進行呼叫。
  • 靜態變數的載入早於物件的建立,在建立物件的過程中,才實現例項屬性的載入。
  • 由於類只會載入一次,則靜態變數在記憶體中也只會存在一份。存在方法區的靜態域中。

1.2 static修飾方法

靜態方法:

  • 隨著類的載入而載入,可以直接通過類.靜態方法呼叫
  • 靜態方法中,只能呼叫靜態的方法或屬性,因為它們生命週期相同;非靜態方法中,既可以呼叫非靜態的方法或屬性,也可以呼叫靜態的方法或屬性。

注意點:

  • 在靜態的方法內,不能使用thissuper關鍵字,因為靜態方法不需要例項化物件,而沒有例項化物件this就沒有意義。一定要是同級的生命週期才能使用。

2. 列舉類

JDK5.0新增enum關鍵字

理解:

  • 類的物件只有有限個、確定的,稱此類為列舉類
  • 當需要定義一組常量時,建議使用列舉類

2.1 基本使用

常用方法:

重點掌握:toStringvaluesvalueOf

2.2 enum實現介面

  • 情況一:實現介面,在enum類中實現抽象方法
interface Info{
    void show();
}
enum Season implements Info{
@Override
    public void show() {
        System.out.println("hello,world");
    }
}
  • 情況二:讓列舉類的物件分別實現介面中的抽象方法

整體demo包含常用方法的使用:

public class EnumTest1 {
    public static void main(String[] args) {
        Season summer = Season.SUMMER;
        System.out.println(summer);  //SUMMER
        System.out.println(Season.class.getSuperclass()); //class java.lang.Enum
        System.out.println(summer.getSeasonFeel()); //hot
        Season[] values = Season.values();
        for(Object i : values){
            System.out.println(i); //SPRING,SUMMER,AUTUMN,WINTER
        }
        Season winter = Season.valueOf("WINTER"); //根據字串返回相應的enum,錯誤則出現異常
        System.out.println(winter); //WINTER
        winter.show();
    }
}

interface Info{
    void show();
}

enum Season implements Info{
    SPRING("spring","warm"){
        @Override
        public void show() {
            
        }
    },
    SUMMER("summer","hot"){
        @Override
        public void show() {

        }
    },
    AUTUMN("autumn","cool"){
        @Override
        public void show() {

        }
    },
    WINTER("winter","cold"){
        @Override
        public void show() {

        }
    };

    private final String SeasonName;
    private final String SeasonFeel;

    Season(String seasonName, String seasonFeel) {
        SeasonName = seasonName;
        SeasonFeel = seasonFeel;
    }

    public String getSeasonName() {
        return SeasonName;
    }

    public String getSeasonFeel() {
        return SeasonFeel;
    }
}

Thread中執行緒狀態利用的就是列舉類,可參考原始碼。

3. 註釋和註解

註解(Annotation)是程式碼裡的特殊標記,這些標記可以在編譯,類載入,執行時被讀取,並執行相應的處理。通過使用註解,程式設計師可以在不改變原有邏輯的情況下,在原始檔中嵌入一些補充資訊。程式碼分析工具、開發工具和部署工具可以通過這些補充資訊進行驗證或進行部署。JDK5.0新增

JavaSE中註解的使用目的比較簡單,比如標記過時功能,忽略警告等。在JavaEE中佔據了更重要的角色。後續部落格將會繼續JavaEE的內容。

在一定程度上,框架 = 註解 + 反射 + 設計模式

3.1 類註釋舉例

/**
   
*/

類註釋必須放在import語句之後,類定義之前。

3.2 方法註解

除了通用標記之外,還可以使用下面的標記:

@param變數描述,這個標記將對當前方法的引數部分新增一個條目。這個描述可以佔據多行,並可以使用HTML標記,一個方法的所有變數描述需要放在一起。

@return描述

@throws類描述,表示這個方法有可能丟擲異常。

3.3 通用註解

Java列舉類、註解和反射

注意,一定要用 # 分隔類名和方法名,或類名和變數名。

3.4 自定義註解

//1
public @interface MyAnnotation {
    String value();
}

@MyAnnotation(value =  "hello") //使用時
//2
public @interface MyAnnotation {
    String value() default "hello";
}

@MyAnnotation  //預設值"hello",可覆蓋
  • 註解宣告為:@interface
  • 內部定義成員,通常用value表示
  • 可以指定成員的預設值,使用default定義
  • 如果自定義註解裡沒有成員,表明是一個標識作用

具體用途在反射和框架中。自定義註解必須配上註解的資訊處理流程(使用反射)才有意義。

Java列舉類、註解和反射

3.5 JDK提供的4種元註解

元註解:用於修飾其他註解定義

例:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

JDK5.0提供了4個標準的元註解:

Retention, Target, Documented, Inherited

Java列舉類、註解和反射 Java列舉類、註解和反射

自定義註解通常都會使用以上兩個元註解Retention, Target。

Java列舉類、註解和反射

3.6 JDK8中註解的新特性

可重複註解

Repeatable()

@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
    String value() default "hello";
}

public @interface MyAnnotations {
    MyAnnotation[] value();
}

其中,MyAnnotation和MyAnnotations需要保持Retention和Target以及Inherited一致,在這部分只需要注意,在以後用到時再重點講解。

型別註解

Java列舉類、註解和反射

4. 反射

4.1 反射的基本概念

Java列舉類、註解和反射

反射機制提供的功能:

  • 執行時判斷任意一個物件所屬的類
  • 執行時構造任意一個類的物件
  • 執行時判斷任意一個類所具有的成員變數和方法
  • 執行時獲取泛型資訊
  • 執行時呼叫任意一個物件的成員變數和方法
  • 在執行時處理註解
  • 生成動態代理

關於java.lang.Class類的理解:

  • 類的載入過程:程式經過javac.exe命令後(編譯),會生成一個或多個位元組碼檔案(.class檔案)。接著使用java.exe命令對某個位元組碼檔案進行解釋執行。相當於將某個位元組碼檔案載入到記憶體中,此過程成為類的載入。載入到記憶體中的類,稱為執行時類,此執行時類,作為Class的一個例項。
  • Class的例項對應著一個執行時類
  • 載入到記憶體中的執行時類,會快取一定的時間。在此時間內,可以通過不同方式來獲取Class的例項。
  • Object是所有類的父類,而Class是類的類。

三種Class例項的獲取方式:

獲取當前物件的型別物件,型別類是指代表一個型別的類,獲取class物件的方法有:1.類名.class,所有的引用資料型別和基本資料型別都可以通過這個方式獲取。2.通過物件例項.getClass()來獲得class物件。3.呼叫Class的靜態方法 Class.forName(String classPath)

例:

//呼叫執行時類的屬性,   .class
Class<Person> clazz1 = Person.class;//泛型可省略
System.out.println(clazz1); //class demo1.Person
//通過執行時類的物件
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//呼叫Class的靜態方法  Class.forName(String classPath)
Class clazz3 = Class.forName("demo1.Person");
System.out.println(clazz3);

此外,還可以使用類的載入器ClassLoader,不再詳細贅述。

JDK8中對於自定義類的ClassLoader屬於系統類載入器,此外還有擴充套件類載入器和引導類載入器。

Java列舉類、註解和反射

簡述下方的Person類:內部成員變數為public String name, private int age, 有公有構造器和私有構造器,有公有方法show()和私有方法show1(), 重寫toString。

基本應用demo:

Class clazz = Person.class;  //public age,private name
//通過反射,建立Person類的物件
Constructor cons = clazz.getConstructor(String.class,int.class);
Object obj = cons.newInstance("Tom",12);
Person p = (Person) obj;
System.out.println(p.toString()); //Person{name='Tom', age=12} 呼叫了Person類中的toString方法
System.out.println(cons); //public demo1.Person(java.lang.String,int)
//通過反射,呼叫物件指定的屬性,方法
Field age = clazz.getDeclaredField("age");
age.set(p,10);
System.out.println(p.toString()); //Person{name='Tom', age=10}
Method show = clazz.getDeclaredMethod("show");
show.invoke(p);  //sout("hello")

需要注意的是Class物件並不能指明該型別的例項化,需要在FieldMethod這種將例項放入引數中。

4.2 反射呼叫私有結構

通過反射,可以呼叫Person類的私有結構,如:私有構造器,私有方法,私有屬性。

//呼叫私有構造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person p1 = (Person)cons1.newInstance("tim");
System.out.println(p1); //Person{name='tim', age=0}
//呼叫私有屬性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"Tim");
System.out.println(p1); //Person{name='Tim', age=0}
//呼叫私有方法
Method show1 = clazz.getDeclaredMethod("show1",String.class);
show1.setAccessible(true);
show1.invoke(p1,"H"); //world H,相當於p1.show1("H");
//.invoke返回的是object,強制轉換
String str = (String)show1.invoke(p1,"H");
System.out.println(str); //H,p1.show1(str)返回了str。

這裡和麵向物件概念中的封裝性可能有衝突,為什麼要利用反射呢?

舉個例子:反射具有動態性的特徵。後臺中,伺服器的程式一直執行,假如從前端傳來資訊,後臺就可以動態進行呼叫。動態過程中,可以利用反射進行應用。

4.3 反射的相關操作

通過反射建立執行時類的物件

Class<Person> clazz = Person.class;//使用泛型宣告後下方不用強制轉換,Person有public的空參構造器
Person obj = clazz.newInstance();//Person{name='null', age=0},內部其實呼叫了執行時類的空參構造器
System.out.println(obj);

獲取執行時類的完整結構

Class clazz = Person.class;
//獲取屬性結構
//.getFields()  獲取當前執行時類及其父類中宣告為Public訪問許可權的屬性
Field[] fields = clazz.getFields(); //public int demo1.Person.id, public double demo1.Creature.weight,只有public屬性
for(Field f : fields){
    System.out.println(f);
}

//.getDeclaredFields()  獲得當前執行類中宣告的所有屬性(不包含父類)
Field[] declaredfields = clazz.getDeclaredFields();//省略為:name,age,id
for(Field f : declaredfields){
    System.out.println(f);
}
Field[] declaredfields = clazz.getDeclaredFields();
for(Field f : declaredfields){
    //1.許可權修飾符
    int modifier = f.getModifiers();  //修飾符以int表示,Modifier類中有相關程式碼
    System.out.println(Modifier.toString(modifier)); //這樣的話就可以正常顯示public,private等了
    //資料型別
    Class type = f.getType();
    System.out.println(type.getName()); //也可直接用type
    //變數名
    System.out.println(f.getName());
}

獲得方法結構:

Class clazz = Person.class;
//獲取當前執行時類和父類的Public方法
Method[] methods = clazz.getMethods();
for(Method m : methods){
    System.out.println(m);
}

//獲取當前執行時類宣告的所有方法(不包含父類)
Method[] methods1 = clazz.getDeclaredMethods();
for(Method m : methods1){
    System.out.println(m);
}

獲取方法的內部結構:

Method[] methods = clazz.getDeclaredMethods();
for(Method m : methods){
    //獲取方法宣告的註解
    Annotation[] anno = m.getAnnotations();
    for(Annotation i : anno){
        System.out.println(i);
    }
    //得到每個方法的許可權修飾符
    System.out.println(Modifier.toString(m.getModifiers()));
    //返回值型別
    System.out.println(m.getReturnType().getName());
    //方法名
    System.out.println(m.getName());
    //形參列表
    Class[] paras = m.getParameterTypes();
    //丟擲的異常
    Class[] exs = m.getExceptionTypes();
}

構造器等都類似,不再贅述。構造器的.getConstructors和.getDeclaredConstructors不能獲取父類的結構,沒有意義。

此外還可獲取執行時類的父類和父類的泛型,執行時類的介面,包,註釋等,程式碼比較機械,不再贅述。

4.4 呼叫執行時類的指定結構

呼叫執行時類的屬性:

Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
//.getField只能獲取public,獲取其他的話需要用.getDeclaredField()
//此外,假如下方是獲取所有屬性,則需要繼續擴充套件許可權,呼叫id.setAccessible(true);
Field id = clazz.getField("id");
//設定當前屬性的值
id.set(p,12);
int i = (int)id.get(p); //返回object,需要強轉
System.out.println(i);

呼叫執行時類中指定的方法:

Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
//獲取指定的某個方法
Method m = clazz.getDeclaredMethod("show1",String.class,String.class);
m.setAccessible(true);
//注意: .getDeclaredMethod,.invoke均需要兩個引數
//需要注意的是如果方法有多個引數,需要全部標出來
m.invoke(p,"hello","hi"); //返回一個Object,可強轉方法的返回型別
//呼叫靜態方法
//private static void show1()
Method m = clazz.getDeclaredMethod("show1");
m.setAccessible(true);
m.invoke(Person.class);//寫null也可以,不影響

如果呼叫的執行時類的方法沒有返回值,則返回null

呼叫執行時類中指定的構造器:

Class<Person> clazz = Person.class;
Constructor cons = clazz.getDeclaredConstructor(String.class);
cons.setAccessible(true);
Person p = (Person) cons.newInstance("hi"); //Person{name='hi', age=0}
System.out.println(p);

需要注意的是即使Class加上泛型宣告,下方的Constructor.newInstance仍需強轉

接下來是最近刷題時總結了一些基礎知識,需要對這些資料保持敏感。

5. 基本資料型別

byte

  • 1個位元組,8位、有符號的,以二進位制補碼錶示的整數;
  • 最小值是 -128(-2^7)
  • 最大值是 127(2^7-1)
  • 預設值是 0
  • byte 型別用在大型陣列中節約空間,主要代替整數,因為 byte 變數佔用的空間只有 int 型別的四分之一;
  • 例子:byte a = 100,byte b = -50。

short

  • 2個位元組,16 位、有符號的以二進位制補碼錶示的整數
  • 最小值是 -32768(-2^15)
  • 最大值是 32767(2^15 - 1)
  • Short 資料型別也可以像 byte 那樣節省空間。一個short變數是int型變數所佔空間的二分之一;
  • 預設值是 0
  • 例子:short s = 1000,short r = -20000。

int:整數型,4個位元組32位,負數以補碼形式存在,取值範圍如下:

  • 最小值是 -2,147,483,648(-2^31)
  • 最大值是 2,147,483,647(2^31 - 1)
  • 預設值為0

long

  • 8個位元組, 64 位、有符號的以二進位制補碼錶示的整數;
  • 最小值是 -9,223,372,036,854,775,808(-2^63)
  • 最大值是 9,223,372,036,854,775,807(2^63 -1)
  • 這種型別主要使用在需要比較大整數的系統上;
  • 預設值是 0L
  • 例子: long a = 100000L,Long b = -200000L。

"L"理論上不分大小寫,但是若寫成"l"容易與數字"1"混淆,不容易分辯。所以最好大寫。

float

  • float 資料型別是單精度、32位、符合IEEE 754標準的浮點數;
  • float 在儲存大型浮點陣列的時候可節省記憶體空間;
  • 預設值是 0.0f
  • 浮點數不能用來表示精確的值,如貨幣;
  • 例子:float f1 = 234.5f。

double

  • double 資料型別是雙精度、64 位、符合 IEEE 754 標準的浮點數;
  • 浮點數的預設型別為 double 型別;
  • double型別同樣不能表示精確的值,如貨幣;
  • 預設值是 0.0d

boolean

  • boolean資料型別表示一位的資訊;
  • 只有兩個取值:true 和 false;
  • 這種型別只作為一種標誌來記錄 true/false 情況;
  • 預設值是 false
  • 例子:boolean one = true。

char

  • char 型別是一個單一的 16 位 Unicode 字元;
  • 最小值是 \u0000(左方是16進製表示,十進位制等效值為 0);
  • 最大值是 \uffff(即為 65535);
  • char 資料型別可以儲存任何字元;
  • 例子:char letter = 'A';。

6. 運算子優先順序

相關文章