Java從入門到實戰總結-3.8、Java枚舉、注解、反射、内省
文章目錄
- Java從入門到實戰總結-3.8、Java枚舉、注解、反射、内省
- 1. 枚舉
- (1). 枚舉概述
- (2). 定義格式
- (3). 枚舉類的主要方法
- (4). 實作接口的枚舉類
- (5). 枚舉注意事項
- 2. 注解
- (1). 簡介
- (2). 重點
- (3). 内置注解
- (4). 元注解
- (5). 自定義注解
- 3. 反射
- (1). 概述
- (2). 類加載器
- (3). 所有類型的Class對象
- (4). 得到Class的幾種方式
- (5). 擷取Constructor
- (6). 擷取Method
- (7). 擷取Field
- (8). 擷取注解資訊
- 4. 内省
- (1). JavaBean
- (2). Introspector
- (3). BeanInfo
- (4). MethodDescriptor
- (5). 示例
- 5. 最後
1. 枚舉
(1). 枚舉概述
JDK1.5引入了新的類型:枚舉。
在JDK1.5 之前,我們定義常量都是: public static fianl… 。很難管理。
枚舉,可以把相關的常量分組到一個枚舉類型裡,而且枚舉提供了比常量更多的方法。
用于定義有限數量的一組同類常量,例如:
錯誤級别: 低、中、高、急
一年的四季: 春、夏、秋、冬
商品的類型: 美妝、手機、電腦、男裝、女裝…
在枚舉類型中定義的常量是該枚舉類型的執行個體。
(2). 定義格式
權限修飾符 enum 枚舉名稱 {
執行個體1,執行個體2,執行個體3,執行個體4;
}
(3). 枚舉類的主要方法
方法名稱 | 描述 |
values() | 以數組形式傳回枚舉類型的所有成員 |
valueOf() | 将普通字元串轉換為枚舉執行個體 |
compareTo() | 比較兩個枚舉成員在定義時的順序 |
ordinal() | 擷取枚舉成員的索引位置 |
建立時選擇枚舉:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5CMyYzN3ETMjJWZ4QjYxQ2YyYzXyEzN1QTMyEzLcdDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
package com.xiaoyaoyou.day14;
public enum Level {
// LOW(1), MEDIUM(50), HIGH(100);
// private int levelValue;
//
// private Level(int levelValue) {
// this.levelValue = levelValue;
// }
//
// public int getLevelValue() {
// return levelValue;
// }
//
// public void setLevelValue(int levelValue) {
// this.levelValue = levelValue;
// }
LOW, MEDIUM, HIGH;
}
package com.xiaoyaoyou.day14;
public class EnumTest {
public static void main(String[] args) {
System.out.println(Level.LOW.compareTo(Level.HIGH));
System.out.println(Level.LOW.compareTo(Level.MEDIUM));
System.out.println(Level.HIGH.compareTo(Level.LOW));
System.out.println(Level.LOW.name());
System.out.println(Level.LOW.toString());
System.out.println(Level.LOW.ordinal());
Level x = Enum.valueOf(Level.class, "LOW");
System.out.println(x);
}
}
結果:
-2
-1
2
LOW
LOW
0
LOW
(4). 實作接口的枚舉類
所有的枚舉都繼承自java.lang.Enum類。由于Java 不支援多繼承,是以枚舉對象不能再繼承其他類。
每個枚舉對象,都可以實作自己的抽象方法。
如下圖:
package com.xiaoyaoyou.day14;
public interface LShow {
void show();
}
package com.xiaoyaoyou.day14;
public enum Level implements LShow {
// LOW(1), MEDIUM(50), HIGH(100);
// private int levelValue;
//
// private Level(int levelValue) {
// this.levelValue = levelValue;
// }
//
// public int getLevelValue() {
// return levelValue;
// }
//
// public void setLevelValue(int levelValue) {
// this.levelValue = levelValue;
// }
LOW{
@Override
public void show() {
System.out.println("低級别");
}
},
MEDIUM{
@Override
public void show() {
System.out.println("中級别");
}
},
HIGH{
@Override
public void show() {
System.out.println("進階别");
}
};
}
package com.xiaoyaoyou.day14;
public class EnumTest {
public static void main(String[] args) {
System.out.println(Level.LOW.compareTo(Level.HIGH));
System.out.println(Level.LOW.compareTo(Level.MEDIUM));
System.out.println(Level.HIGH.compareTo(Level.LOW));
System.out.println(Level.LOW.name());
System.out.println(Level.LOW.toString());
System.out.println(Level.LOW.ordinal());
Level x = Enum.valueOf(Level.class, "LOW");
System.out.println(x);
Level.LOW.show();
Level.MEDIUM.show();
Level.HIGH.show();
}
}
結果:
-2
-1
2
LOW
LOW
0
LOW
低級别
中級别
進階别
(5). 枚舉注意事項
- 一旦定義了枚舉,最好不要妄圖修改裡面的值,除非修改是必要的。
- 枚舉類預設繼承的是java.lang.Enum類而不是Object類
- 枚舉類不能有子類,因為其枚舉類預設被final修飾
- 隻能有private構造方法
- switch中使用枚舉時,直接使用常量名,不用攜帶類名
- 不能定義name屬性,因為自帶name屬性
- 不要為枚舉類中的屬性提供set方法,不符合枚舉最初設計初衷。
2. 注解
(1). 簡介
Java 注解(Annotation)又稱 Java 标注,是 JDK5.0 引入的一種注釋機制。
Java語言中的類、方法、變量、參數和包等都可以被标注。和注釋不同,Java标注可以通過反射擷取标注内容。在編譯器生成類檔案時,标注可以被嵌入到位元組碼中。Java 虛拟機可以保留标注内容,在運作時可以擷取到标注内容。當然它也支援自定義 Java 标注。
主要用于:
- 編譯格式檢查
- 反射中解析
- 生成幫助文檔
- 跟蹤代碼依賴等
(2). 重點
- 概念
- 怎麼使用内置注解
- 怎麼定義注解
- 反射中怎麼擷取注解内容
(3). 内置注解
- @Override : 重寫 *
- 定義在java.lang.Override
- @Deprecated:廢棄 *
- 定義在java.lang.Deprecated
- @SafeVarargs
- Java 7 開始支援,忽略任何使用參數為泛型變量的方法或構造函數調用産生的警告。
- @FunctionalInterface: 函數式接口 *
- Java 8 開始支援,辨別一個匿名函數或函數式接口。
- @Repeatable:辨別某注解可以在同一個聲明上使用多次
- Java 8 開始支援,辨別某注解可以在同一個聲明上使用多次。
- @SuppressWarnings:抑制編譯時的警告資訊。 *
- 定義在java.lang.SuppressWarnings
- 三種使用方式
1. @SuppressWarnings("unchecked") [^ 抑制單類型的警告]
2. @SuppressWarnings("unchecked","rawtypes") [^ 抑制多類型的警告]
3. @SuppressWarnings("all") [^ 抑制所有類型的警告]
- 參數清單
我們剛學習了proto buf,發現proto的設計和注解有很多類似之處,也可以看出來這些語言文法的設計都是為了解決問題而産生的,是以如果你發現了一個問題,就可以嘗試看下是否有解決該問題的方案,如果沒有,我們能否自己解決該問題那,解決後是否可開源出來?
(4). 元注解
含義:作用在其他注解的注解(需要結合自定義注解了解)
- @Retention - 辨別這個注解怎麼儲存,是隻在代碼中,還是編入class檔案中,或者是在運作時可以通過反射通路。
- @Documented - 标記這些注解是否包含在使用者文檔中 javadoc。
- @Target - 标記這個注解應該是哪種 Java 成員。
- @Inherited - 标記這個注解是自動繼承的
1. 子類會繼承父類使用的注解中被@Inherited修飾的注解
2. 接口繼承關系中,子接口不會繼承父接口中的任何注解,不管父接口中使用的注解有沒有被@Inherited修飾
3. 類實作接口時不會繼承任何接口中定義的注解
(5). 自定義注解
- 注解架構
-
(1) Annotation與RetentionPolicy與ElementType
每1個 Annotation 對象,都會有唯一的 RetentionPolicy 屬性;至于 ElementType 屬性,則有 1~n個。
-
(2) ElementType(注解的用途類型)
“每 1 個 Annotation” 都與 “1~n 個 ElementType” 關聯。當 Annotation 與某個ElementType關聯時,就意味着:Annotation有了某種用途。例如,若一個 Annotation 對象是 METHOD 類型,則該Annotation 隻能用來修飾方法。
package java.lang.annotation;
public enum ElementType {
TYPE, /* 類、接口(包括注釋類型)或枚舉聲明 */
FIELD, /* 字段聲明(包括枚舉常量) */
METHOD, /* 方法聲明 */
PARAMETER, /* 參數聲明 */
CONSTRUCTOR, /* 構造方法聲明 */
LOCAL_VARIABLE, /* 局部變量聲明 */
ANNOTATION_TYPE, /* 注釋類型聲明 */
PACKAGE /* 包聲明 */
}
-
(3) RetentionPolicy(注解作用域政策)
“每 1 個 Annotation” 都與 “1 個 RetentionPolicy” 關聯。
a) 若 Annotation 的類型為 SOURCE,則意味着:Annotation 僅存在于編譯器處理期間,編譯器處理完之後,該 Annotation 就沒用了。 例如," @Override" 标志就是一個 Annotation。當它修飾一個方法的時候,就意味着該方法覆寫父類的方法;并且在編譯期間會進行文法檢查!編譯器處理完後,"@Override" 就沒有任何作用了。
b) 若 Annotation 的類型為 CLASS,則意味着:編譯器将 Annotation 存儲于類對應的 .class 檔案中,它是 Annotation 的預設行為。
c) 若 Annotation 的類型為 RUNTIME,則意味着:編譯器将 Annotation 存儲于 class 檔案中,并且可由JVM讀入。
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE, /* Annotation資訊僅存在于編譯器處理期間,編譯器處理完之後就沒有該 Annotation資訊了 */
CLASS, /* 編譯器将Annotation存儲于類對應的.class檔案中。預設行為 */
RUNTIME /* 編譯器将Annotation存儲于class檔案中,并且可由JVM讀入 */
}
- 定義格式
@interface 自定義注解名{}
- 注意事項
- 定義的注解,自動繼承了java.lang,annotation.Annotation接口
- 注解中的每一個方法,實際是聲明的注解配置參數
- 方法的名稱就是配置參數的名稱
- 方法的傳回值類型,就是配置參數的類型,隻能是:基本類型/Class/String/enum
- 可以通過default來聲明參數的預設值
- 如果隻有一個參數成員,一般參數名為value
- 注解元素必須要有值,我們定義注解元素時,經常使用空字元串、0作為預設值。
- 案例
package com.xiaoyaoyou.day14;
import java.lang.annotation.*;
@MyAnnotation("測試自定義注解")
public class InterfaceTest {
public static void main(String[] args) {
}
}
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface MyAnnotation {
String value() default "";
int num() default 0;
}
等到學完反射後看注解如何擷取注解内容。
3. 反射
(1). 概述
JAVA反射機制是在運作狀态中,擷取任意一個類的結構 , 建立對象 , 得到方法,執行方法 , 屬性 !
這種在運作狀态動态擷取資訊以及動态調用對象方法的功能被稱為java語言的反射機制,反射被視為動态語言的關鍵。
(2). 類加載器
Java類加載器(Java Classloader)是Java運作時環境(Java Runtime Environment)的一部分, 負責動态加載Java類到Java虛拟機的記憶體空間中。
java預設有三種類加載器,BootstrapClassLoader、ExtensionClassLoader、App ClassLoader。
BootstrapClassLoader(引導啟動類加載器):嵌在JVM核心中的加載器,該加載器是用C++語言寫的,主要負載加載JAVA_HOME/lib下的類庫,引導啟動類加載器無法被應用程式直接使用。
ExtensionClassLoader(擴充類加載器): ExtensionClassLoader是用JAVA編寫,且它的父類加載器是Bootstrap。
是由sun.misc.Launcher$ExtClassLoader實作的,主要加載JAVA_HOME/lib/ext目錄中的類庫。
它的父加載器是BootstrapClassLoader
App ClassLoader(應用類加載器):App ClassLoader是應用程式類加載器,負責加載應用程式classpath目錄下的所有jar和class檔案。它的父加載器為Ext ClassLoader
類通常是按需加載,即第一次使用該類時才加載。由于有了類加載器,Java運作時系統不需要知道檔案與 檔案系統。學習類加載器時,掌握Java的委派概念很重要。
雙親委派模型:如果一個類加載器收到了一個類加載請求,它不會自己去嘗試加載這個類,而是把這個請求 轉交給父類加載器去完成。每一個層次的類加載器都是如此。是以所有的類加載請求都應該傳遞到最頂層的 啟動類加載器中,隻有到父類加載器回報自己無法完成這個加載請求(在它的搜尋範圍沒有找到這個類)時,子類加載器才會嘗試自己去加載。委派的好處就是避免有些類被重複加載。
加載配置檔案,給項目添加resource root目錄:
漢化後:
代碼示例:
package com.xiaoyaoyou.day14;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class LoadResourceTest {
public static void main(String[] args) throws IOException {
InputStream inputStream = LoadResourceTest.class.getClassLoader().getResourceAsStream("config.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String text = bufferedReader.readLine();
System.out.println(text);
bufferedReader.close();
}
}
結果:
測試類加載器
(3). 所有類型的Class對象
要想了解一個類,必須先要擷取到該類的位元組碼檔案對象.
在Java中,每一個位元組碼檔案,被夾在到記憶體後,都存在一個對應的Class類型的對象
(4). 得到Class的幾種方式
- 如果在編寫代碼時, 知道類的名稱, 且類已經存在, 可以通過
得到一個類的類對象包名.類名.class
- 如果擁有類的對象, 可以通過
得到一個類的類對象Class 對象.getClass()
- 如果在編寫代碼時,知道類的名稱,可以通過
:得到一個類的類對象Class.forName(包名+類名)
上述的三種方式, 在調用時, 如果類在記憶體中不存在, 則會加載到記憶體!如果類已經在記憶體中存在, 不會重複加載, 而是重複利用!
(一個class檔案 在記憶體中不會存在兩個類對象 )
特殊的類對象
-
基本資料類型的類對象:
基本資料類型.clss
包裝類.type
-
基本資料類型包裝類對象:
包裝類.class
示例:
package com.xiaoyaoyou.day14;
public class GetClassTest {
public static void main(String[] args) throws ClassNotFoundException {
//第一種,通過包名.類名.class加載類
Class c1 = com.xiaoyaoyou.day14.LoadResourceTest.class;
System.out.println(c1);
//第二種,通過類的對象過去類的資訊
LoadResourceTest loadResourceTest = new LoadResourceTest();
Class c2 = loadResourceTest.getClass();
System.out.println(c2);
//第三種方式
Class c3 = Class.forName("com.xiaoyaoyou.day14.LoadResourceTest");
System.out.println(c3);
System.out.println(c1==c2 && c1==c3);
}
}
結果:
class com.xiaoyaoyou.day14.LoadResourceTest
class com.xiaoyaoyou.day14.LoadResourceTest
class com.xiaoyaoyou.day14.LoadResourceTest
true
(5). 擷取Constructor
- 通過擷取class對象擷取一個類的構造方法
-
(1). 通過指定的參數類型, 擷取指定的單個構造方法 getConstructor(參數類型的class對象數組)
例如:構造方法如下: Person(String name,int age)
得到這個構造方法的代碼如下:
Constructor c = p.getClass().getConstructor(String.class,int.class);
-
(2). 擷取構造方法數組
getConstructors();
-
(3). 擷取所有權限的單個構造方法
getDeclaredConstructor(參數類型的class對象數組)
- (4). 擷取所有權限的構造方法數組 getDeclaredConstructors();
- Constructor 建立對象
-
常用方法: newInstance(Object… para)
調用這個構造方法, 把對應的對象建立出來
參數: 是一個Object類型可變參數,傳遞的參數順序必須比對構造方法中形式參數清單的順序!
- setAccessible(boolean flag) 如果flag為true 則表示忽略通路權限檢查 !(可以通路任何權限的方法)
package com.xiaoyaoyou.day14;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class GetConstructorTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class c1 = Class.forName("com.xiaoyaoyou.day14.LoadResourceTest");
Constructor c2 = c1.getConstructor();
LoadResourceTest loadResourceTest = (LoadResourceTest) c2.newInstance();
System.out.println(loadResourceTest.toString());
}
}
結果:
LoadResourceTest{}
其它的方法和方式可以自己嘗試一下(忽略權限檢查的接口一定要試試)。
(6). 擷取Method
- 通過class對象 擷取一個類的方法
-
getMethod(String methodName , class… clss)
根據參數清單的類型和方法名, 得到一個方法(public修飾的)
-
getMethods();
得到一個類的所有方法 (public修飾的)
-
getDeclaredMethod(String methodName , class… clss)
根據參數清單的類型和方法名, 得到一個方法(除繼承以外所有的:包含私有, 共有, 保護, 預設)
-
getDeclaredMethods();
得到一個類的所有方法 (除繼承以外所有的:包含私有, 共有, 保護, 預設)
- Method 執行方法
-
invoke(Object o,Object… para):
調用方法,
參數1. 要調用方法的對象
參數2. 要傳遞的參數清單
-
getName()
擷取方法的方法名稱
-
setAccessible(boolean flag)
如果flag為true 則表示忽略通路權限檢查 !(可以通路任何權限的方法)
(7). 擷取Field
- 通過class對象 擷取一個類的屬性
-
getDeclaredField(String filedName)
根據屬性的名稱, 擷取一個屬性對象 (所有屬性)
-
getDeclaredFields()
擷取所有屬性(屬性私有時需要配合忽略權限檢查使用)
-
getField(String filedName)
根據屬性的名稱, 擷取一個屬性對象 (public屬性)
-
getFields()
擷取所有屬性 (public)
-
Field 屬性的對象類型
常用方法:
-
get(Object o );
參數: 要擷取屬性的對象 擷取指定對象的此屬性值
-
set(Object o , Object value);
參數1. 要設定屬性值的對象
參數2. 要設定的值
設定指定對象的屬性的值
-
getName()
擷取屬性的名稱
-
setAccessible(boolean flag)
如果flag為true則表示忽略通路權限檢查!(可以通路任何權限的屬性)
(8). 擷取注解資訊
- 擷取類/屬性/方法的全部注解對象
Annotation[] annotations01 = Class/Field/Method.getAnnotations();
for (Annotation annotation : annotations01) {
System.out.println(annotation);
}
- 根據類型擷取類/屬性/方法的注解對象
注解類型 對象名 = (注解類型) c.getAnnotation(注解類型.class);
示例:
package com.xiaoyaoyou.day14;
import java.lang.annotation.Annotation;
public class GetAnnotationTest {
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("com.xiaoyaoyou.day14.InterfaceTest");
Annotation[] annotations = c.getAnnotations();
for (Annotation a: annotations) {
System.out.println(a);
}
MyAnnotation myAnnotation = (MyAnnotation) c.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.value());
System.out.println(myAnnotation.num());
}
}
結果:
@com.xiaoyaoyou.day14.MyAnnotation(value=測試自定義注解, num=0)
還可以擴充對類的屬性、方法等增加注解,然後通過擷取屬性、擷取方法的注解來做一些關聯操作。
4. 内省
(1). JavaBean
基于反射,java所提供的一套應用到JavaBean的API
一個定義在包中的類:
- 擁有無參構造器
- 所有屬性私有
- 所有屬性提供get/set方法
- 實作了序列化接口
這種類, 我們稱其為bean類.
Java提供了一套java.beans包的api,對于反射的操作, 進行了封裝!
(2). Introspector
擷取Bean類資訊
方法:
BeanInfo getBeanInfo(Class cls)
通過傳入的類資訊, 得到這個Bean類的封裝對象
(3). BeanInfo
常用的方法:
MethodDescriptor[] getPropertyDescriptors():
擷取bean類的 get/set方法 數組
(4). MethodDescriptor
常用方法:
- Method getReadMethod();擷取一個get方法
- Method getWriteMethod(); 擷取一個set方法 有可能傳回null 注意加判斷
(5). 示例
我們之前寫快遞E棧項目的時候接觸過bean,後續的架構學習還會接觸,這裡我們也主要學會使用了解一些概念即可,随着接觸更多的架構和項目對bean的了解會逐漸加深。
package com.xiaoyaoyou.day14;
import java.io.Serializable;
public class Express implements Serializable {
private String name;
private String number;
private String phoneNumber;
private String address;
public Express() {
}
@Override
public String toString() {
return "Express{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
", address='" + address + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
package com.xiaoyaoyou.day14;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
public class BeanTest {
public static void main(String[] args) throws IntrospectionException {
Class c = Express.class;
BeanInfo beanInfo = Introspector.getBeanInfo(c);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor p:propertyDescriptors) {
Method get = p.getReadMethod();
Method set = p.getWriteMethod();
System.out.println(get);
System.out.println(set);
System.out.println(p.getName());
System.out.println(p.getPropertyType());
}
}
}
結果:
public java.lang.String com.xiaoyaoyou.day14.Express.getAddress()
public void com.xiaoyaoyou.day14.Express.setAddress(java.lang.String)
address
class java.lang.String
public final native java.lang.Class java.lang.Object.getClass()
null
class
class java.lang.Class
public java.lang.String com.xiaoyaoyou.day14.Express.getName()
public void com.xiaoyaoyou.day14.Express.setName(java.lang.String)
name
class java.lang.String
public java.lang.String com.xiaoyaoyou.day14.Express.getNumber()
public void com.xiaoyaoyou.day14.Express.setNumber(java.lang.String)
number
class java.lang.String
public java.lang.String com.xiaoyaoyou.day14.Express.getPhoneNumber()
public void com.xiaoyaoyou.day14.Express.setPhoneNumber(java.lang.String)