第八章【集合、泛型、列舉、註解、反射】
一、集合
1、概述
集合是JavaAPI中提供的一種容器工具,可以用來儲存多個資料。
集合框架中主要有三個要素組成:
-
介面
-
實現類
-
資料結構
集合中不可以存放基本型別
集合按照其儲存結構可以分為兩大類:
java.util.Collection
單值存放java.util.Map
鍵值存放
2、Collection介面
Collection是父介面,其中定義了單列集合(List和Set)通用的一些方法,可用於操作所有的單列集合物件。
3、迭代器
java.util.Iterator
介面中,主要定義倆個方法:
public interface Iterator {
boolean hasNext();//返回當前迭代器中是否還有下一個物件
Object next();//獲取迭代器中的下一個物件
}
{
//獲取c1集合的迭代器物件
Iterator iterator = c1.iterator();
//判斷迭代器中,是否還有下一個元素
while(iterator.hasNext()){
//如果有的話,就取出來
Object obj = iterator.next();
System.out.println(obj);
}
}
4、foreach迴圈
for(變數型別 變數名 : 集合){
//操作變數
}
5、資料結構
棧、佇列、陣列、連結串列、紅黑樹、雜湊表
6、List
特點:有序、帶索引、可以存放重複資料
List實現類
-
ArrayList
增刪慢,查詢快
-
LinkedList
增刪快,查詢慢;
同時它還是一個雙向連結串列,
-
Vector
內部也是採用了陣列來儲存資料,大多數方法都是執行緒安全的方法;
檢視 Vector 中方法的定義,可以看到多大數方法都使用了
synchronized
關鍵字,來給當前方法加鎖。
7、Set
特點:無序,不帶下標索引,不存放重複資料。
Set實現類:
-
HashSet
HashSet中儲存元素是無序的,主要因為它是靠物件的雜湊值來確定元素在集合中的儲存位置。
HashSet中元素不可重複,主要是靠物件的hashCode和equals方法來判斷物件是否重複。
如果倆個物件的hashCode值相等,那麼再使用equals判斷是否倆物件是否相同;
如果倆個物件的hashCode值不同等,那麼就不再使用equals進行判斷了。
-
TreeSet
TreeSet可以將我們存進去的資料進行排序,排序的方式有倆種
自然排序
比較器排序(也稱客戶化排序)
思考:如果放入TreeSet中的資料既實現類自然排序又實現了客戶端排序,哪個優先順序高?
客戶端排序優先順序高
8、Map
java.util.Map<K, V>
介面,就是專門處理這種對映關係資料的集合型別。
Map型別集合中,每次需要存一對資料,key-value(鍵值對)
- key值必須是唯一的,value值允許重複
- 鍵(key)和值(value)一一對映,一個key對應一個value
- 在Map中,透過key值(唯一的),可以快速的找到對應的value值
- Map不能使用迭代器遍歷,因為Map沒有繼承Iterable介面,而迭代器方法是Iterable介面的抽象方法
Map實現類:
-
HashMap
:儲存資料採用的雜湊表結構,元素的存取順序不能保證一致。由於要保證鍵的唯一、不重複,需要重寫鍵的hashCode()方法、equals()方法,HashMap的鍵和值可以為空。(重要,最常用)
-
HashTable
:和之前List集合中的 Vector 的功能類似,可以在多執行緒環境中,保證集合中的資料的操作安全,類中的方法大多數使用了 synchronized 修飾符進行加鎖,HashTable的鍵和值都不能為空。(執行緒安全) -
TreeMap
:該類是 Map 介面的子介面 SortedMap 下面的實現類,和 TreeSet 類似,它可以對key值進行排序,同時構造器也可以接收一個比較器物件作為引數。支援key值的自然排序和比較器排序倆種方式。(支援key排序) -
LinkedHashMap
:該類是 HashMap 的子類,儲存資料採用的雜湊表結構+連結串列結構。透過連結串列結構可以保證元素的存取順序一致;(存入順序就是取出順序)
二、泛型
1、概述
泛型也叫做 型別引數化(Parameterized by types)
給泛型參照傳的值,只能是引用型別,不能是基本型別
public class Point<T>{
T x;
T y;
}
2、集合的泛型
public interface Collection<E>{
boolean add(E e);
}
3、泛型的種類
-
泛型類
-
泛型介面
-
泛型方法
4、泛型的型別
宣告泛型的時候不牽扯繼承
5、萬用字元
萬用字元(?)表示泛型的父型別
6、泛型的邊界
如果在泛型中使用extends
和super
關鍵字,就可以對泛型的型別進行限制。
即:規定泛型的上限和下限。
7、型別擦除
泛型資訊被擦除後,所有的泛型型別都會統一變為原始型別:Object
三、列舉
1、介紹
列舉類是一種特殊的類,它和普通類一樣可以使用構造器、定義成員變數和方法,也能實現一個或多個介面。
列舉類不能繼承其他類
注意,列舉型別中的第一行程式碼,要求一定是指定列舉物件的個數和名字,同時最後面加分號(;)
在這行程式碼下, 才可以定義列舉型別的屬性和方法
-
列舉其實也是一種類,同時還是一個final修飾的類
-
列舉型別都會預設繼承一個父型別:
java.lang.Enum
,這還是一個抽象的泛型類 -
列舉中所定義的物件,其實就是類裡面的public static final修飾的常量,並且這些常量會在靜態程式碼塊中做初始化
-
列舉型別中還一個預設的私有構造器,說明我們在外面並不能自己去建立列舉型別的物件
-
列舉型別中還有預設新增進來的方法
values()
可以返回這個列舉型別的所有物件,返回型別是陣列valueOf(String str)
透過一個字串可以返回列舉物件,這個字串引數就是列舉物件 -
列舉型別會從父類中繼承過來一些方法(具體可以檢視其固定的父型別)
2、定義列舉類
public enum Color {
RED, GREEN, BLANK, YELLOW
}
public enum Color {
RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLO("黃色", 4);
private String name ;
private int index ;
private Color( String name , int index ){
this.name = name ;
this.index = index ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
3、獲取列舉物件
public void testEnum() {
//1.獲取列舉類物件
Gender MAN = Gender.MAN;
Gender WOMAN = Gender.WOMAN;
System.out.println(MAN);
System.out.println(WOMAN);
//2.呼叫方法獲取
Gender MAN1 = Gender.valueOf("MAN");
Gender WOMAN1 = Gender.valueOf("WOMAN");
System.out.println(MAN1);
System.out.println(WOMAN1);
//3.獲取全部的物件
Gender[] genders = Gender.values();
for (Gender gender : genders) {
System.out.println(gender);
gender.sex = "男;";
System.out.println(gender.sex);
}
}
四、註解
1、定義
- 註解(Annotation)也叫後設資料,是一種程式碼級別的說明。
- 註解是JDK1.5及以後版本引入的一個特性,與類、介面、列舉是在同一個層次。
- 註解可以宣告在包、類、欄位、方法、區域性變數、方法引數等的前面,用來對這些元素進行說明。
思考:註釋和註解的區別與聯絡
註解相當於一種標記,在程式中加了註解就等於為程式打上了某種標記。程式可以利用java的反射機制來了解你的類及各種元素上有無何種標記,針對不同的標記,就去做相應的事件。
註釋也是一種標記,不過這種標記是為了給程式設計師看。
2、作用
- 編寫文件:透過程式碼裡標識的後設資料生成文件【生成文件doc文件】
- 程式碼分析:透過程式碼裡標識的後設資料對程式碼進行分析【使用反射】
- 編譯檢查:透過程式碼裡標識的後設資料讓編譯器能夠實現基本的【編譯檢查】
3、內建
@Override
限定重寫父類的方法,檢查該方法是否為父類或者介面中重寫過後的方法,如果父類或者介面中不存在該方法,則使用該註解會報錯,該註解只能用於方法。
@Deprecated
用於表示某個程式元素(類,方法等)已過時
@FunctionalInterface
檢查介面是否為函式式介面(jdk1.8新特性)。
@SuppressWarnings
抑制編譯器警告,@SuppressWarnings
修飾的程式元素(以及該程式元素中的所有子元素)取消顯示指定的編譯器警告。
@SuppressWarnings
常見的取值:
-
unchecked
:執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics
) 來指定集合儲存的型別; 關閉編譯器警告 -
fallthrough
:壓制當 switch 程式塊中沒有新增break時警告; -
path
:在類路徑、原始檔路徑等中有不存在的路徑時的警告; -
serial
:當在可序列化的類上缺少 serialVersionUID 定義時的警告; -
finally
:任何 finally 子句不能正常完成時的警告; -
all
:關於以上所有情況的警告。
4、自定義
- 格式
[元註解];
[修飾符] @interface 註解名稱{
資料型別 屬性名() [default 預設值];
}
public @interface MyAnno{}
-
本質
自定義的註解本質是一個介面,而該介面會預設繼承
java.lang.annotation.Annotation
,因此在註解中我們可以定義自己抽象方法。 -
屬性
屬性的返回型別必須為以下資料型別,否則定義的屬性為非法屬性。
- 基本資料型別
- String型別
- 列舉
- 註解
- 以上資料型別對應的陣列型別
注意:屬性宣告成功後在使用的時候需要給屬性賦值
語法:
@註解名稱(屬性名=屬性值,屬性名=屬性值,屬性名={陣列型別的屬性值})
注意事項
- 如果定義屬性時,使用了
default
關鍵字給屬性進行了初始化,那麼在使用時可以不用給屬性進行顯示賦值。 - 如果定義屬性時只有一個屬性需要賦值,且該屬性名為
value
,那麼value
可以省略不寫,直接書寫屬性值,如果需要給多個屬性賦值則不能省略屬性名。 - 如果定義的屬性型別為陣列型別,值需要使用{}包裹,屬性值為單個值時,設定
屬性名={屬性值}
時{}可以省略。
-
元註解
常用的元註解
註解名稱 註解作用 @Retention
用於指定註解保留的階段 @Target
用於指定註解可以修飾程式中的哪些元素 @Documented
註解是否會被抽取到API文件中 @Inherited
註解是夠能夠被子類繼承 @Repeatable
(java 8新增)
@Retention
內部包含一個value
屬性,返回型別為RetentionPolicy
型別。
@Target
用於指定註解可以修飾程式中的哪些元素,該註解內部包含一個value
屬性,返回型別為ElementType[]
陣列型別,ElementType
用於表示註解在哪些元素上可以使用。
ElementType
常見的取值:
-
TYPE: 用於類,介面和列舉
-
FIELD: 用於屬性
-
METHOD: 用於方法
RetentionPolicy
為列舉型別,取值如下:
RetentionPolicy.SOURCE
:Annotation只保留在原始碼中,編譯器編譯時,直接丟棄這種Annotation,不記錄在.class檔案中。RetentionPolicy.CLASS
:編譯器把Annotation記錄在class檔案中。當執行Java程式時,JVM中不可獲取該Annotation資訊。這是預設值RetentionPolicy.RUNTIME
:編譯器把Annotation記錄在class檔案中。當執行Java程式時,JVM可獲取該Annotation資訊,程式可以透過反射獲取該Annotation的資訊。
5、解析
作用:Java中的註解可以用來替換專案中出現的配置檔案。我們使用【Java反射】機制從一個類中解析註解。 請記住,註解保持性策略應該是RUNTIME,否則它的資訊在執行期無效,我們也不能從中獲取任何資料。
解析註解的過程:
-
獲取註解新增類的Class物件。
-
透過Class物件,獲取該類上面新增的註解物件。
-
透過註解物件,獲取註解物件中的屬性對應的屬性值。
五、反射
1、概述
反射:自己封裝框架
Student類:
來源:總結了所有的學生物件的特徵,從中找到共同點用Java程式碼的形式提取出來,形成Student類。
package ……;
import ……;
[修飾符] class 類名{
//屬性
//方法
//構造器
}
共性:修飾符 類名 屬性 方法 構造器 ----->類 Class:可以描述Java任何一個類的特徵
2、Class型別
java.lang.Class
是API中提供的一個類,它可以表示java中所有的型別,包括基本型別和引用型別。
在之前的學習中,我們也接觸過這個型別,Object中的方法getClass方法:
public final native Class<?> getClass();
該方法的返回型別就是Class,所以obj.getClass();
這句程式碼的含義就是:返回obj引用在執行時所指向物件的實際型別。
3、獲取Class物件
在java中,每種型別(基本型別和引用型別)載入到記憶體之後,都會在記憶體中生成一個Class型別物件,這個物件就代表這個具體的java型別,並且儲存這個型別中的基本資訊。
注意,java中的每種型別,都有且只有唯一的一個Class型別物件與之對應!並且在類載入的時候自動生成!
-
獲取基本型別的Class物件
Class clazz = int.class;
-
獲取介面型別的Class物件(兩種方式)
Class clazz1 = One.class;
Class clazz2 = Class.forName(com.sxu.day14.One);
-
獲取陣列型別的Class物件(兩種方式)
Class clazz1 = int[].class;
int[] arr = {1,2,3}; Class clazz2 = arr.class;
-
獲取類型別的Class物件(三種方式)
Class c1 = Student.class;
Class c2 = Class.forName("com.briup.demo.Student");
Student stu = new Student(); Class c3 = stu.getClass();
4、獲取類的資訊
//獲取Student類的Class物件
Class<Student> clazz = Student.class;
//從Class物件中獲取Student類相關資訊
//1.檢視Student在哪個包下面
System.out.println(clazz.getPackage());
//2.檢視這個類被哪個類載入到記憶體中
System.out.println(clazz.getClassLoader());
//3.檢視Student使用了哪些修飾符 public(1)+final(16)
System.out.println(clazz.getModifiers());
//4.檢視Student使用了哪些修飾符,以字串型別返回
System.out.println(Modifier.toString(clazz.getModifiers()));
//5.檢視類名
System.out.println(clazz.getName());
System.out.println(clazz.getSimpleName());
//6.檢視實現的介面
Class[] interfaces = clazz.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
System.out.println(interfaces[i]);
}
//7.獲取類繼承的父類
System.out.println(clazz.getSuperclass());
5、反射訪問屬性
獲取類中的public修飾的屬性,也包含從父類中繼承過來的public屬性
-
Field[] getFields()
-
Field getField(String name)
獲取類中宣告的屬性(包含私有的),但是不能獲取從父類中繼承過來的屬性
-
Field[] getDeclaredFields()
-
Field getDeclaredField(String name)
Student stu = new Student();
Class class1 = stu.getClass();
//獲取類中的public修飾的屬性
Field field = class1.getField("id");
//1.獲取屬性名
System.out.println(field.getName());
//2.獲取屬性型別
System.out.println(field.getType());
//3.獲取屬性的修飾符
System.out.println(Modifier.toString(field.getModifiers()));
//4.獲取屬性值
Object object = field.get(stu);
System.out.println(object);
//5.設定屬性值
field.set(stu, "002");
System.out.println("**********");
//獲取公共的所有的屬性
// Field[] fields = class1.getFields();
//獲取類中宣告的屬性(包含私有的)
Field[] fields = class1.getDeclaredFields();
for (Field ff : fields) {
//預設私有的值取不到,獲取需要設定
ff.setAccessible(true);
System.out.println(ff.getName());
System.out.println(Modifier.toString(ff.getModifiers()));
System.out.println(ff.getType().getName());
System.out.println(ff.get(stu));
System.out.println("----------");
}
6、反射呼叫方法
獲取當前類中的public方法,包含從父類中繼承的public方法
-
Method[] getMethods()
-
Method getMethod(String name, Class<?>... parameterTypes)
獲取當前類中宣告的方法(包含私有的),但是不能獲取從父類中繼承過來的方法
-
Method[] getDeclaredMethods()
-
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
Student stu = new Student();
Class class1 = stu.getClass();
//獲取一個方法
Method mm = class1.getMethod("study",String.class);
System.out.println(mm.getName());
System.out.println(Modifier.toString(mm.getModifiers()));
System.out.println(mm.getReturnType().getName());
//獲取引數列表
Parameter[] parameters = mm.getParameters();
for (Parameter pp : parameters) {
System.out.println(pp.getName());
System.out.println(pp.getType());
}
//獲取方法丟擲異常的個數
int length = mm.getExceptionTypes().length;
System.out.println(length);
//呼叫方法
mm.invoke(stu, "zs");
//獲取所有的方法
Method[] methods = class1.getMethods();
7、反射建立物件
獲取當前類中的public構造器
-
public Constructor<?>[] getConstructors()
-
public Constructor<T> getConstructor(Class<?>... parameterTypes)
獲取當前類中的所有構造器,包含私有的
-
public Constructor<?>[] getDeclaredConstructors()
-
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
Class clazz = Student.class;
//獲取無參構造器
Constructor con1 = clazz.getConstructor();
System.out.println(con1.getModifiers());
System.out.println(con1.getName());
System.out.println(con1.getParameterCount());
//透過構造器構建物件
Object object = con1.newInstance();
System.out.println(object);
//透過無參構造器建立物件,獲取構造器和使用構造器可以合併為一步
Object object1 = clazz.newInstance();
System.out.println(object1);
//正常方式
Student student = new Student();
System.out.println(student);
//呼叫有參構造器
con1 = clazz.getConstructor(String.class,String.class,double.class,double.class);
Object object2 = con1.newInstance("001","ls",99.0,66.3);
System.out.println(object2);
8、反射獲取註解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotataion{
public String className();
public String methodName();
}
public class MyAnnotationTest {
@MyAnnotataion(className = "com.sxu.day15.MyAnnotationTest",methodName = "test")
public static void test() {
System.out.println("測試註解");
}
}
@Test
public void testAnnotation() throws Exception {
Class clazz = MyAnnotationTest.class;
Method mm = clazz.getMethod("test");
MyAnnotataion aa = mm.getAnnotation(MyAnnotataion.class);
String methodName = aa.methodName();
String className = aa.className();
System.out.println(methodName);
System.out.println(className);
System.out.println(mm.isAnnotationPresent(MyAnnotataion.class));
System.out.println("**********");
Class clazz1 = Class.forName(className);
Object oo = clazz1.newInstance();
clazz1.getMethod(methodName).invoke(oo);
}