8.集合、泛型、列舉、註解、反射

可乐爱兑姜汁發表於2024-04-14

第八章【集合、泛型、列舉、註解、反射】

一、集合

1、概述

集合是JavaAPI中提供的一種容器工具,可以用來儲存多個資料。

集合框架中主要有三個要素組成:

  • 介面

  • 實現類

  • 資料結構

集合中不可以存放基本型別

集合按照其儲存結構可以分為兩大類:

  • java.util.Collection 單值存放
  • java.util.Map 鍵值存放
image-20210124094720058
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實現類

  1. ArrayList

    增刪慢,查詢快

  2. LinkedList

    增刪快,查詢慢;

    同時它還是一個雙向連結串列,

  3. Vector

    內部也是採用了陣列來儲存資料,大多數方法都是執行緒安全的方法;

    檢視 Vector 中方法的定義,可以看到多大數方法都使用了 synchronized 關鍵字,來給當前方法加鎖。

7、Set

特點:無序,不帶下標索引,不存放重複資料。

Set實現類:

  1. HashSet

    HashSet中儲存元素是無序的,主要因為它是靠物件的雜湊值來確定元素在集合中的儲存位置。

    HashSet中元素不可重複,主要是靠物件的hashCode和equals方法來判斷物件是否重複。

    ​ 如果倆個物件的hashCode值相等,那麼再使用equals判斷是否倆物件是否相同;

    ​ 如果倆個物件的hashCode值不同等,那麼就不再使用equals進行判斷了。

  2. 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實現類:

  1. HashMap :儲存資料採用的雜湊表結構,元素的存取順序不能保證一致。由於要保證鍵的唯一、不重複,需

    要重寫鍵的hashCode()方法、equals()方法,HashMap的鍵和值可以為空。(重要,最常用)

  2. HashTable :和之前List集合中的 Vector 的功能類似,可以在多執行緒環境中,保證集合中的資料的操作安全,類中的方法大多數使用了 synchronized 修飾符進行加鎖,HashTable的鍵和值都不能為空。(執行緒安全)

  3. TreeMap :該類是 Map 介面的子介面 SortedMap 下面的實現類,和 TreeSet 類似,它可以對key值進行排序,同時構造器也可以接收一個比較器物件作為引數。支援key值的自然排序和比較器排序倆種方式。(支援key排序)

  4. 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、泛型的邊界

如果在泛型中使用extendssuper關鍵字,就可以對泛型的型別進行限制。

即:規定泛型的上限下限

7、型別擦除

泛型資訊被擦除後,所有的泛型型別都會統一變為原始型別:Object

三、列舉

1、介紹

列舉類是一種特殊的類,它和普通類一樣可以使用構造器、定義成員變數和方法,也能實現一個或多個介面。

列舉類不能繼承其他類

注意,列舉型別中的第一行程式碼,要求一定是指定列舉物件的個數和名字,同時最後面加分號(;)

在這行程式碼下, 才可以定義列舉型別的屬性和方法

  1. 列舉其實也是一種類,同時還是一個final修飾的類

  2. 列舉型別都會預設繼承一個父型別:java.lang.Enum,這還是一個抽象的泛型類

  3. 列舉中所定義的物件,其實就是類裡面的public static final修飾的常量,並且這些常量會在靜態程式碼塊中做初始化

  4. 列舉型別中還一個預設的私有構造器,說明我們在外面並不能自己去建立列舉型別的物件

  5. 列舉型別中還有預設新增進來的方法

    values()可以返回這個列舉型別的所有物件,返回型別是陣列

    valueOf(String str)透過一個字串可以返回列舉物件,這個字串引數就是列舉物件

  6. 列舉型別會從父類中繼承過來一些方法(具體可以檢視其固定的父型別)

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、自定義
  1. 格式
[元註解];
[修飾符]  @interface 註解名稱{  
    資料型別 屬性名() [default  預設值];
}

public @interface MyAnno{}
  1. 本質

    自定義的註解本質是一個介面,而該介面會預設繼承java.lang.annotation.Annotation,因此在註解中我們可以定義自己抽象方法。

  2. 屬性

    屬性的返回型別必須為以下資料型別,否則定義的屬性為非法屬性。

    1. 基本資料型別
    2. String型別
    3. 列舉
    4. 註解
    5. 以上資料型別對應的陣列型別

    注意:屬性宣告成功後在使用的時候需要給屬性賦值

    語法:

    @註解名稱(屬性名=屬性值,屬性名=屬性值,屬性名={陣列型別的屬性值})

    注意事項

    1. 如果定義屬性時,使用了default關鍵字給屬性進行了初始化,那麼在使用時可以不用給屬性進行顯示賦值。
    2. 如果定義屬性時只有一個屬性需要賦值,且該屬性名為value,那麼value可以省略不寫,直接書寫屬性值,如果需要給多個屬性賦值則不能省略屬性名。
    3. 如果定義的屬性型別為陣列型別,值需要使用{}包裹,屬性值為單個值時,設定屬性名={屬性值}時{}可以省略。
  3. 元註解

    常用的元註解

    註解名稱 註解作用
    @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型別物件與之對應!並且在類載入的時候自動生成!

  1. 獲取基本型別的Class物件

    Class clazz = int.class;

  2. 獲取介面型別的Class物件(兩種方式)

    Class clazz1 = One.class;

    Class clazz2 = Class.forName(com.sxu.day14.One);

  3. 獲取陣列型別的Class物件(兩種方式)

    Class clazz1 = int[].class;

    int[] arr = {1,2,3}; Class clazz2 = arr.class;

  4. 獲取類型別的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);
	}

相關文章