也是重新溫習了下泛型與反射,反射基本就是一些api理解即可,不過需要注意類加載器原理,而泛型則需要理解其設計思想,可以代替Object,更加靈活,可讀性強。
泛型
泛型如果指定后,編譯階段就會檢查,不讓亂輸其他類型,必須是引用類型; 如果不指定就默認Object
// 如果指定泛型, 就必須存指定的類型 Iterator<String> iterator = arrayList.iterator(); List<String> arrayList = new ArrayList<>(); ? /*** new 集合 如果沒有指定泛型* 存放的類型是為Object類型*/ ArrayList arrayList = new ArrayList(); // Iterator iterator = arrayList.iterator(); //如果想要精確獲取,則String str = (String)iterator.next; 需要強轉,不過因為存放的不是一種類型,所以還要判斷(instanceof),否則可能轉換異常
1.Java 泛型 (generics) 是 JDK 5 中引入的一個新特性,泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。 2. 早期的時候,使用 Object 來代表任意類型。但是這樣在向上轉型的是沒有問題的,但是在向下轉型的時候存在類型轉換的問題,這樣的程序其實是不安全的。所以 Java 在 JDK5 之后提供了泛型來解決這個問題 3. 泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。 4. 泛型是一種把類型的明確工作推遲到創建對象或者調用方法的時候才去明確的特殊類型。 注意:類型參數只能代表引用型類型,不能是原始類型 (像 int,double,char 的等)。 泛型可以使用在 方法、接口、類 分別稱作為:泛型類、泛型方法、泛型接口。
泛型類
泛型類定義的格式: 格式:修飾符 class 類名 <類型>{} 范例:public class Student<T>{} 此處 T 可以隨便寫為任意標識,T、E、K、V 等型式的參數常用于表示泛型;
/*** 泛型類優化*/ public class Student<T> {private T number;public T show(T t) {return t;} }
缺點就是: 如果想要傳遞不同類型的方法則需要創建指明多個對象; (Object強轉就不考慮了,因為需要先instanceof判斷類型)
泛型方法
可以優化掉方法重載
格式:修飾符 <類型> 返回值類型 方法名 (類型 變量名){...} 范例:public<T> void show(T t){...}
public class Student {public <T> T show(T t) {return t;} }
優點: 可以只new一個對象,然后直接傳遞不同類型的方法,此時會正常獲取,而且我們也不用手動<指定類型>,ctrl alt v 可以正常解析獲取
泛型接口
格式:修飾符 interface 接口名 <類型>{...} 范例:public interface MayiktInterface <T>{...}
public interface Student<T> {T show(T t) {return t;} // 默認public } // 如果直接實現,還是Object public class StudentImpl<T> implements Student<T> {@Overridepublic T show(T t) {return t; // 而且此時可以發現,創建后傳參,前方會隱式顯示參數的名字,t} }
public class Test01 {public static void main(String[] args) {Student<String> stringMayikt = new StudentImpl<>();String show = stringMayikt.show("36");System.out.println(show);} }
方法:
public interface Mayikt<T> {<M> T show(T t, M m); // 如果寫成<T> T show (T t); 則這個T對應泛型方法,而不是類上的T,所以下方如果實現則變成T1好區分 } public class MayiktImpl<T> implements MayiktInterface<T> {@Override public <M> T show(T t, M m) {System.out.println(m);return t;} }
泛型通配符
類型通配符 一般用于接受使用,不能夠做添加。
List : 表示元素類型未知的 list,它的元素可以匹配任何類型。
帶通配符的 List 僅表示它是各種泛型 List 的父類,并不能把元素添加到其中。 (而且也是需要判斷類型的,否則此時獲得的是Object)
也是可以遍歷的,但是不能添加,比如 .add方法
類型通配符上限:<? extends 類型> List<? extends MayiktParent>: 它表示的類型是 MayiktParent 或者子類型。
類型通配符下限:<? super 類型> List<? super MayiktParent>: 它表示的類型是 MayiktParent 或者其父類型
/*** 定義的 printList方法 明確知道 接受具體 list泛型 是什么類型* List<?> 只能夠用于接受 ?------- 可以接受所有的泛型類型 不能夠用于添加* 是可以做get操作 獲取到類型是為Object類型** @param stringList*/ public static void printList(List<?> stringList) { // List<? extends People> stringList 也可以這樣傳參Object o = stringList.get(0); // 直接獲取到的是Object類型Iterator<?> iterator = stringList.iterator(); // 拿到的也是?while (iterator.hasNext()){System.out.println(iterator.next()); // 如果想要強轉iterator.next()為指定的對象,則instanceof判斷一下} }
可變參數
可變參數 又稱 參數個數可變,用作方法的形參出現,那么方法參數個數就是 可變 的了。 (底層基于數組實現)
書寫格式: 2.1 格式:修飾符 返回值類型 方法名 (數據類型... 量名){} 2.2 范例:public static int sum (int... a) {}
可變參數 注意事項: 這里的 可變參數變量 其實是一個數組。 如果一個方法 有多個參數,包含可變參數,可變參數要放在最后
public static int sum(int... a) {int sum = 0;for (int i = 0; i < a.length; i++) {sum+=a[i];}return sum; }
ArrayList中的asList就用到了可變參數 T...
public class Test04 {public static void main(String[] args) {/*** 使用 Arrays.asList 定義好的 元素個數 是不能夠發生變化的*/List<String> strings = Arrays.asList("mayikt", "meite", "wangmazi");// 注意 如果使用Arrays.asList 方法 創建的 集合 不能夠添加和刪除strings.set(0, "6666"); // 可以修改,不能add和removeSystem.out.println(strings);} }
擦除機制(底層)
說明:將一個 List 集合 泛型賦值給一個沒有使用到泛型 List 集合 直接去除泛型 --- 擦除機制
反編譯后可以發現運行階段沒有泛型
List<String> strs = new ArrayList<String>(); strs.add("mayikt"); //說明:將一個List集合 泛型賦值給一個未使用泛型的集合會直接去除掉 List list = strs; // 此時仍然可以添加Object類型變量
反射
1.Java 反射機制的核心是在程序運行時動態加載類并獲取類的詳細信息,從而操作類或對象的屬性和方法。本質是 JVM 得到 class 對象之后,再通過 class 對象進行反編譯,從而獲取對象的各種信息。
2.Java 屬于先編譯再運行的語言,程序中對象的類型在編譯期就確定下來了,而當程序在運行時可能需要動態加載某些類,這些類因為之前用不到,所以沒有被加載到 JVM。通過反射,可以在運行時動態地創建對象并調用其屬性,不需要提前在編譯期知道運行的對象是誰。
Java 反射機制可以動態方式獲取到 class 相關信息 class 中成員方法、屬性,反射技術靈活調用方法 或者給我們成員屬性賦值,class.forName 初始化對象(創建我們的對象)
類加載器
xxx.getClass().getClassLoader().loadClass(); 類加載器的主要作用: 類加載器(ClassLoader)是 Java 虛擬機中負責加載類的組件,它的核心職責是將類的字節碼文件(.class文件)加載到 JVM 中,并生成對應的Class對象,以便 JVM 可以使用這些類進行實例化、方法調用等操作。具體過程如下: ? 加載:在這一階段,類加載器會根據類的全限定名(包名 + 類名)找到對應的.class文件,通過 IO 操作讀取其字節碼數據,然后在 JVM 內存中創建一個對應的Class對象 。這就好比從磁盤這個 “倉庫” 中把類的 “藍圖” 搬運到 JVM 的 “工作區”,并整理成 JVM 能識別的格式。 ? 鏈接:包括驗證(確保字節碼符合 JVM 規范,如文件格式正確、字節碼指令合法等)、準備(為類的靜態變量分配內存并設置初始值,比如static int num = 10; 在準備階段num會被初始化為 0 )、解析(將符號引用轉換為直接引用,讓 JVM 能準確找到要訪問的類、方法、變量等)。 ? 初始化:對類的靜態變量進行賦值和執行靜態代碼塊,使類進入可使用狀態。 ? 對于同一個類,類加載器不一定只會執行一次,這要分情況來看: ? 同一個類加載器:在 Java 中,為了提高性能和避免重復加載,JVM 會維護一個已加載類的緩存。當使用同一個類加載器嘗試加載一個已經加載過的類時,類加載器會直接從緩存中返回已存在的Class對象,而不會再次執行完整的加載流程。 例如,在一個普通的 Java 應用中,自定義一個類加載器加載某個類,后續再次請求加載該類時,不會重復加載。 ? 不同類加載器:如果使用不同的類加載器去加載同一個類(比如自定義了多個不同的類加載器,或者應用程序中同時存在系統類加載器和自定義類加載器 ),由于類加載器的命名空間相互隔離,每個類加載器都有自己獨立的已加載類緩存,那么就會出現多次加載同一個類的情況,且不同類加載器加載出來的類,在 JVM 中被視為不同的類。例如,通過自定義類加載器加載一個User類,再通過系統類加載器加載User類,這兩個User類的Class對象在 JVM 中是不同的,它們之間不能進行類型轉換等操作。 ? 不過,對于 Java 核心類庫中的類(由啟動類加載器加載),在 JVM 啟動過程中會被加載一次,之后在整個應用運行期間,不會再重復加載 。但對于自定義類或其他非核心類,類加載器的加載次數取決于類加載器的使用方式和 JVM 的運行邏輯。
類加載器: 當我們new出自定義對象時,如果發現沒有加載到程序內存中,此時就會開始執行類加載器將該對象加載,而項目剛開始運行的時候應該底層依賴了其他類,所以其他先進行加載
class不管什么方式獲取到的,都是唯一的
public class Test13 {public static void main(String[] args) throws ClassNotFoundException {// 1.獲取class方式 直接類名稱.Class<MayiktUserEntity> mayiktUserEntityClass = MayiktUserEntity.class;// 2.new 對象 通過對象獲取class springioc容器 根據class獲取對象MayiktUserEntity mayiktUserEntity = new MayiktUserEntity();Class<? extends MayiktUserEntity> aClass = mayiktUserEntity.getClass();// 類的完整路徑地址 包的名稱+類名稱組合 第三種,企業使用最多,不過因為怕路徑寫錯,所以也要拋異常Class<?> aClass1 = Class.forName("com.mayikt.entity.MayiktUserEntity");} }
注意 : 上述三種獲取到的class都是完全相同的,因為運行期間,一個類只會產生一個class
反射應用的幾個常見場景: 1.JDBC 中 Class.forName ("com.mysql.jdbc.Driver")---- 反射技術加載 mysql 驅動 2.Spring 底層基于反射初始化對象 3.(寫一套自己)第三方框架擴展功能 代理設計模式
初始化對象
注意: 上邊倆個創建對象代碼都是只能獲取公有的,如果想獲取全部方法,那么就Declared即可
(1)批量獲取的方法: public Constructor[] getConstructors():所有"公有的"構造方法 public Constructor[] getDeclaredConstructors():獲取所有的構造方法(包括私有、受保護、默認、公有) (2)單個獲取的方法,并調用: public Constructor getConstructor(Class... parameterTypes):獲取單個的"公有的"構造方法: public Constructor getDeclaredConstructor(Class... parameterTypes):獲取"某個構造方法" (3) 調用構造方法: Constructor-->newInstance(Object... initargs) newInstance是 Constructor類的方法(管理構造函數的類) api的解釋為: newInstance(Object... initargs) ,使用此 Constructor 對象表示的構造方法來創建 它的返回值是T類型,所以newInstance是創建了一個構造方法的聲明類的新實例對象,并為之調用。
獲取成員屬性
獲取成員變量并調用:
批量的 1.1 Field[] getFields():獲取所有的"公有字段" 1.2 Field[] getDeclaredFields():獲取所有字段,包括: 私有、受保護、默認、公有;
獲取單個的: 2.1.public Field getField(String fieldName):獲取某個"公有的"字段; 2.2.public Field getDeclaredField(String fieldName):獲取某個字段(可以是私有的)
設置字段的值 需要注意權限問題: 3.1.Field --> public void set(Object obj,Object value): 3.2.參數說明: 3.3.obj:要設置的字段所在的對象;
public class Test15 {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {// 獲取class成員屬性(反射給成員屬性賦值、反射調用我們方法)//1.java反射技術 創建對象Class<?> aClass = Class.forName("com.mayikt.entity.MayiktUserEntity");//Field[] fields = aClass.getFields();) // 獲取所有的"公有字段"Field[] fields = aClass.getDeclaredFields(); // 獲取所有字段//2.獲取單個的:for (int i = 0; i < fields.length; i++) {System.out.println(fields[i]);}// 如何給成員屬性賦值呢?MayiktUserEntity mayiktUserEntity = (MayiktUserEntity) aClass.newInstance();Field userNameField = aClass.getDeclaredField("userName");// 反射技術給私有成員屬性賦值 參數1:傳遞對象 參數2:賦值的內容// 如果通過反射技術給私有成員屬性賦值的情況下 設置下訪問的權限userNameField.setAccessible(true); // 先設置權限才可賦值userNameField.set(mayiktUserEntity, "mayikt666"); // 這個也是獲取到對象,然后再修改System.out.println(mayiktUserEntity.getUserName());} }
調用方法
所有的方法: 1.1.public Method [] getMethods (): 獲取所有 "公有方法";(包含了父類的方法也包含 Object 類) 1.2.public Method [] getDeclaredMethods (): 獲取所有的成員方法,包括私有的 (不包括繼承的)
獲取單個的方法: 2.1.public Method getMethod (String name,Class... parameterTypes): 參數: name:方法名; Class ...:形參的Class類型對象 public Method getDeclaredMethod(String name,Class... parameterTypes)
調用方法: Method --> public Object invoke (Object obj,Object... args): 參數說明: obj: 要調用方法的對象;
注意 此時獲取到的公有方法還包含了Object,這是和其他不同的地方
Method addUserMethod = aClass.getDeclaredMethod("addUser", String.class, Integer.class); MayiktUserEntity mayiktUserEntity = (MayiktUserEntity) aClass.newInstance(); addUserMethod.setAccessible(true); String result = (String) addUserMethod.invoke(mayiktUserEntity, "mayikt", 22); System.out.println(result); //看自己編寫的方法有沒有返回值可獲取
也是調用修改方法或屬性強,如果是私有設置一下權限即可