天天看點

Java 枚舉&注解&反射

4-9 枚舉&注解&反射

枚舉

  • 引入的目的:用于定義有限數量的一組同類常量,限定了使用者在使用時常量的取值範圍。
  • Java中枚舉的演變:
    • java1.5前使用

      類+final修飾變量+私有構造方法

      來實作,外部無法使用構造方法建立對象,隻能使用類名來使用定義好的常量。
    • java1.5引入了枚舉類型,在定義的時候,簡化了文法:
      • 第一種方法(簡化内部調用構造方法):
        public enum Level {
            //使用Enum方法比較大小時根據傳入的數字比較
            LOW(30), MEDIUM(15), HIGH(7), URGENT(1);
            
            private int levelValue;
            private Level(int levelValue) {
                this.levelValue = levelValue;
            }
            public int getLevelValue() {
                return levelValue;
            }
        }
                   
      • 第二種方法(直接寫出枚舉變量不傳參)
        public enum Level {
            //使用Enum方法比較大小時根據先後順序比較
            LOW, MEDIUM, HIGH, URGENT;
        }
                   
  • 枚舉類型繼承的是抽象類

    Enum

    ,對枚舉的變量不能直接使用運算符操作,需要使用該抽象類的方法:
    Java 枚舉&注解&反射
    • 若使用沒有指定數字的枚舉變量,則其預設的起始序号為0,它們之間使用

      compareTo

      方法進行比較,其比較的值也是根據序号值進行運算的。
  • 每個枚舉對象,都可以實作自己的抽象方法:
    • 自定義一個接口,然後讓該枚舉類型繼承該接口:
      interface LShow{
      	void show();
      }
      public enum Level implements LShow{}
                 
    • 可以在枚舉中實作公共接口(沒有單獨實作的将調用公共方法),也可以對每個枚舉對象單獨實作接口:
      interface LShow{
          void show();
      }
      public enum Level implements LShow {
          //比較大小時根據傳入的數字比較
          LOW{
              @Override
              public void show() {
                  System.out.println("LOW 實作方法");
              }
          }, MEDIUM(){
              @Override
              public void show() {
                  System.out.println("MEDIUM 實作方法");
              }
          }, HIGH;
          
          @Override
          public void show() {
              System.out.println("公共實作方法");
          }
      }
                 
  • 枚舉注意事項:
    • 一旦定義了枚舉,最好不要妄圖修改裡面的值,除非修改是必要的。
    • 枚舉類預設繼承的是

      java.lang.Enum類

      而不是

      Object類

    • 枚舉類不能有子類,因為其枚舉類預設被final修飾
    • 隻能有

      private

      構造方法
    • switch中使用枚舉時,直接使用常量名,不用攜帶類名,因為類名在條件語句中已經獲得。
    • 不能定義

      name

      屬性,因為自帶

      name

      屬性
    • 不要為枚舉類中的屬性提供

      set

      方法,不符合枚舉最初設計初衷。

注解

  • Java 注解(

    Annotation

    )又稱 Java 标注,是 JDK5.0 引入的一種注釋機制,但不是注釋!。
    • 注釋是不會進入編譯階段的,在class檔案中不會出現,因為注釋是給人看的。
    • 預設注解是會進入位元組碼中的。是以注解是給機器看的。
    • 在Java架構中經常會使用注解。
  • 注解的應用場景:
    • 編譯格式檢查
    • 反射中解析
    • 生成幫助文檔
    • 跟蹤代碼依賴等
  • 常用的内置注解:
    • @Override

      : 重寫,定義在

      java.lang.Override

    • @Deprecated

      :廢棄 ,定義在

      java.lang.Deprecated

    • @FunctionalInterface

      : 函數式接口。
      • 辨別一個匿名函數或函數式接口。
      • 函數式接口是具有一個方法的接口,用作

        lambda

        表達式的類型。
    • @SuppressWarnings

      :抑制編譯時的警告資訊。
      • 定義在

        java.lang.SuppressWarnings

      • @SuppressWarnings("unchecked")

        抑制單類型的警告
      • @SuppressWarnings("unchecked","rawtypes")

        抑制多類型的警告
      • @SuppressWarnings("all")

        抑制所有類型的警告
      • 還有其他參數,例如裝箱警告

        boxing

        ,空操作警告

        null

        等等,具體可以查表。
      • 生效的範圍與編寫的位置有關,可以針對變量、方法、類等進行注解。
  • 元注解(用來配置注解):
    • @Retention

      - 辨別這個注解怎麼儲存,是隻在代碼中,還是編入class檔案中,或者是在運作時可以通過反射通路。
    • @Documented

      - 标記這些注解是否包含在使用者文檔中

      javadoc

    • @Target

      - 标記這個注解應該是哪種 Java 成員。
    • @Inherited

      - 标記這個注解是自動繼承的:
      • 子類會繼承父類使用的注解中被

        @Inherited

        修飾的注解
      1. 接口繼承關系中,子接口不會繼承父接口中的任何注解,不管父接口中使用的注解有沒有

        @Inherited

        修飾
      2. 類實作接口時不會繼承任何接口中定義的注解.
  • 注解的整體架構:
    Java 枚舉&注解&反射
  • 注解少于兩個參數
    • 注解參數無參時,可以不傳參數
    • 注解參數為1個參數時,推薦定義預設的value屬性,這樣可以省略書寫時

      vaule = "xxxx"

      中的前面部分,直接寫

      "xxxx"

      即可。
    • 注解參數傳入超過1個時,不能省略,必須寫全,例如:

      vaule = "xxxx",num=100

    • 可以定義一個預設的參數,使用

      default xxx

      ,當不傳入該參數,則預設使用定義的參數取值。
    • 出入數組參數且長度大于1個時,需要添加大括号,例如:

      value = {"x","y"}

      @MyAnnotation("param")
      public class Demo {
          public static void main(String[] args) {
      
          }
      }
      
      @Documented
      //限定MyAnnotation用于在類/接口/枚舉、方法中進行注解
      @Target({ElementType.TYPE,ElementType.METHOD})
      //MyAnnotation注解 儲存的政策 儲存在class中且被JVM可讀
      @Retention(RetentionPolicy.RUNTIME)
      //可以被子類繼續繼承MyAnnotation注解
      @Inherited
      @interface MyAnnotation{
          String value();
          int num() default 100;
      }
                 

反射

  • JAVA反射機制是在運作狀态中,擷取任意一個類的結構 , 建立對象 , 得到方法,執行方法 , 屬性。這種在運作狀态動态擷取資訊以及動态調用對象方法的功能被稱為java語言的反射機制。
    • 強調與正常的流程不一樣,正常流程下必須在編譯階段就确定好類的結構,然後其他代碼根據權限去建立和通路該類,而反射機制使得不必在編譯階段就确定類結構,而且不用考慮權限問題,這有利于在代碼不停機不重新開機的情況下更新部分代碼的應用場景。
    • 總結就是三個點:無權限問題、動态應用場景、反封裝機制。
  • 類加載器:
    • 引導啟動類加載器(

      BootstrapClassLoader

      • C++語言

        寫内嵌在

        JVM

        中的加載器,主要加載

        JAVA_HOME/lib

        下類庫,無法被程式員直接使用。
    • 擴充類加載器(

      ExtensionClassLoader

      • Java語言編寫,父加載器是

        BootstrapClassLoader

        ,是由

        sun.misc.Launcher$ExtClassLoader

        實作的,主要加載

        JAVA_HOME/lib/ext

        目錄中的類庫。
    • 應用類加載器(

      App ClassLoader

      • 負責加載應用程式

        classpath

        目錄下的所有jar和class檔案。它的父加載器為

        ExtensionClassLoader

    • 類加載的Java委派概念:
      • 如果一個類加載器收到加載請求,它會把請求轉交給父類加載器,如果父類加載器已經加載或者響應加載則子類加載器才會嘗試自己去加載。
      • 按需加載,第一次使用時才進行加載,且隻加載一次。
      • 于有了類加載器,Java運作時系統不需要知道檔案與檔案系統。
  • 加載配置檔案:
    • 注意,在未設定

      resource root目錄

      前,預設從

      src目錄

      進行加載,否則從

      resource root

      目錄下加載配置檔案,加載代碼如下:
      public class Test {
          public static void main(String[] args) throws IOException {
              InputStream is = Test.class.getClassLoader().
                      getResourceAsStream("config.txt");
              BufferedReader br = new BufferedReader(new InputStreamReader(is));
              System.out.println(br.readLine());
          }
      }
                 
    • 配置

      resource root目錄

      方法:IDEA中右鍵選擇某個目錄,

      Mark Directory as Resources Root

      即可。
      Java 枚舉&注解&反射
  • 加載類的三種方法,注意差別第三種和前兩種的差別:
    public static void main(String[] args) throws ClassNotFoundException {
        //方法1:通過類名來加載類
        Class<Person> c1 = Person.class;//class com.forwardxiang.Person
        System.out.println(c1);
        //方法2:通過類的對象來加載類
        Person p = new Person();
        Class<Person> c2 = (Class<Person>)p.getClass();
        System.out.println(c1 == c2);//true,說明是同一塊記憶體
        //方法3:通過類的全名來加載類,無需通過建立類或對象來加載類
        Class<Person> c3 = (Class<Person>)Class.forName("com.forwardxiang.Person");
        System.out.println(c1 == c3);//true,說明是同一塊記憶體
        //方法3的另一個不強轉的情況下,可以在沒有Person類的情況下編譯通過,
        // 隻需要在運作到這行之前能夠生成Person類即可,
        // 也就是說可以單獨編譯該行代碼無需Person參與
        Class c4 = Class.forName("com.forwardxiang.Person");
    }
               
  • 反射中的構造方法:注意

    getDeclaredConstructor

    c3.setAccessible(true)

    的配合使用
    Class pClass = Class.forName("com.forwardxiang.Person");
    
    //使用擷取的類的無參構造方法
    Constructor c1  = pClass.getConstructor();
    Object p = c1.newInstance();
    System.out.println(p);//類中有override toString方法
    
    //使用擷取的類的全參構造方法
    Constructor c2  = pClass.getConstructor(String.class,int.class);
    Object p2 = c2.newInstance("xiangwei",18);
    System.out.println(p2);//類中有override toString方法
    
    //使用擷取的類的私有構造方法 c3.setAccessible(true);
    Constructor c3  = pClass.getDeclaredConstructor(String.class,int.class);
    c3.setAccessible(true);
    Object p3 = c3.newInstance("xiangwei",18);
    System.out.println(p3);//類中有override toString方法
               
  • 反射中的方法:注意

    getDeclaredMethod

    ageSet.setAccessible(true)

    的配合使用
    Class pClass = Class.forName("com.forwardxiang.Person");
    //使用擷取的類的無參構造方法
    Constructor constructor  = pClass.getConstructor();
    Object o = constructor.newInstance();
    System.out.println(o);
    //擷取類的方法
    Method nameSet = pClass.getMethod("setName", String.class);
    nameSet.invoke(o,"xiangwei");
    System.out.println(o);
    //擷取類的私有方法
    Method ageSet = pClass.getDeclaredMethod("setAge", int.class);
    ageSet.setAccessible(true);
    ageSet.invoke(o,22);
    System.out.println(o);
               
  • 反射中的屬性:注意

    getDeclaredField

    namer.setAccessible(true)

    的配合使用
    //擷取類屬性
    Field number = pClass.getField("num");
    number.set(o,10086);
    System.out.println(o);
    //擷取類的私有屬性
    Field namer = pClass.getDeclaredField("name");
    namer.setAccessible(true);
    namer.set(o,"xiangwei");
    System.out.println(o);
               
  • 反射與注解:
    • 設定注解并傳入值,以資料庫表名稱和表中的字段做注解為例:
      @TableAnnotation("test_Book")
      public class Book {
          @ColumnAnnotation(columnName = "name",type = "varchar",length = "11")
          private String name;
          @ColumnAnnotation(columnName = "info",type = "varchar",length = "50")
          private String info;
          @ColumnAnnotation(columnName = "id",type = "int",length = "11")
          private int id;
          //......此處省略
      }
                 
      @Documented
      @Target(ElementType.FIELD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface ColumnAnnotation {
          String columnName();//描述列名
          String type();//描述類型
          String length();//描述長度
      }
                 
      @Documented
      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface TableAnnotation {
          String value();//用于标注類對應的表格名稱
      }
                 
    • 擷取注解中傳入的值:
      Class pClass = Class.forName("com.forwardxiang.Book");
      TableAnnotation ta = (TableAnnotation) pClass.getAnnotation(TableAnnotation.class);
      System.out.println(ta.value());//test_Book
      Field[] fs = pClass.getDeclaredFields();
      for (Field f:fs) {
          ColumnAnnotation ca = f.getAnnotation(ColumnAnnotation.class);
          System.out.println(f.getName()+"屬性,對應資料庫中字段:"+ca.columnName()+" "+ca.type()+" "+ca.length());
          //name屬性,對應資料庫中字段:name varchar 11
          //info屬性,對應資料庫中字段:info varchar 50
          //id屬性,對應資料庫中字段:id int 11
      }
                 
  • 内省:基于反射,Java提供一套應用到JavaBean的API,對于反射的操作,進行了封裝 。
    • 由于應用場景是bean對象,要求至少有:
      • 擁有無參構造器
      • 所有屬性私有
      • 所有屬性提供get/set方法,注意:

        boolean類型

        對應的

        get方法

        is屬性名

      • 實作了序列化接口
    • 方法調用層次(内省使用起來比直接用反射要更容易):
      Introspector--->getBeanInfo--->BeanInfo--->getPropertyDescriptors()--->
      					--->getReadMethod()--->Method(屬性的get方法)
      MethodDescriptor[]兩個分支
      					--->getWriterMethod()--->Method(屬性的set方法)
                 
    • 代碼舉例:
      Class c = Express.class;
      Constructor constructor = c.getConstructor();
      Object o = constructor.newInstance();
      
      BeanInfo bi = Introspector.getBeanInfo(c);
      PropertyDescriptor[] pds = bi.getPropertyDescriptors();
      
      for (PropertyDescriptor pd:pds) {
          System.out.println("屬性名稱:" + pd.getName() + "\t  屬性類型:" + pd.getPropertyType());
          //由于屬性排列順序并非定義時的順序 是以不要想當然直接用pds[]下标操作
          if (Objects.equals(pd.getName(),"address")){
              Method get = pd.getReadMethod();
              Method set = pd.getWriteMethod();
              set.invoke(o, "shanghai");
              System.out.println(get.invoke(o));
          }
      }