一. 類的加載,連接,初始化
1.1. JVM和類
當調用Java命令運行某個Java程序時,該命令將會啟動一個Java虛擬機進程。不管Java程序多么復雜,啟動多少個線程,它們都處於該Java虛擬機進程里,都是使用同一個Java進程內存區。
JVM程序終止的方式:
- 程序運行到最后正常結束
- 程序運行到使用System.exit()或Runtime.getRuntime().exit()代碼處結束程序
- 程序執行過程中遇到未捕獲的異常或錯誤而結束
- 程序所在平台強制結束了JVM進程
JVM進程結束,該進程所在內存中的狀態將會丟失
1.2 類的加載
當程序主動使用某個類時,如果該類還未被加載到內存中,則系統會通過加載、連接、初始化三個步驟來對該類進行初始化。
類的加載時將該類的class文件讀入內存,並為之創建一個java.lang.Class對象,也就是說,當程序使用任何類時,系統都會為之建立一個java.lang.Class對象。
系統中所有的類實際上也是實例,它們都是java.lang.Class的實例
類的加載通過JVM提供的類加載器完成,類加載器時程序運行的基礎,JVM提供的類加載器被稱為系統類加載器。除此之外,開發者可以通過繼承ClassLoader基類來創建自己的類加載器。
通過使用不同的類加載器,可以從不同來源加載類的二進制數據,通常有如下幾種來源。
- 從本地文件系統加載class文件,這是前面絕大部分實例程序的類加載方式
- 從jar包加載class文件,這種方式也是很常見的,jdbc編程所用的驅動類就放在jar文件中,JVM可以直接從jar文件中加載該class文件。
- 通過網絡加載class文件
- 把一個Java源文件動態編譯,並執行加載
類加載器通常無需等到首次使用該類時才加載該類,Java虛擬機規范允許系統預先加載某些類。
1.3 類的連接
當類被加載后,系統會為之生成一個對應的Class對象,接着會進入連接階段,連接階段負責把類的二進制數據合並到JRE中。類的鏈接可分為如下三個階段。
- 驗證:驗證階段用於檢驗被加載的類是否有正確的內部結構,並和其他類協調一致
- 准備:類准備階段則負責為類的類變量分配內存,並設置默認初始值
- 解釋:將類的二進制數據中的變量進行符號引用替換成直接引用
1.4 類的初始化
再累舒適化階段,虛擬機負責對類進行初始化,主要就是對類變量進行初始化。在Java類中對類變量指定初始值有兩種方式:①聲明類變量時指定初始值;②使用靜態初始化塊為類變量指定初始值。
JVM初始化一個類包含如下步驟
- 加載並連接該類
- 先初始化其直接父類
- 依次執行初始化語句
當執行第2步時,系統對直接父類的初始化也遵循1~3,以此類推
1.5 類初始化時機
當Java程序首次通過下面6種方式使用某個類或接口時,系統會初始化該類或接口
- 創建類的實例。創建類的實例包括new操作符來創建實例,通過反射來創建實例,通過反射實例化創建實例
- 調用某個類的類方法(靜態方法)
- 訪問某個類或接口的類變量或為該類變量賦值
- 使用反射方式來強制來創建某個類或接口的java.lang.Class對象。例如代碼“Class.forname("Person")”,如果系統還未初始化Person類,則這行代碼會導致Person類被初始化,並返回person類的java.lang.Class對象
- 初始化某個類的子類
- 使用java.exe命令來運行某個主類。當運行某個主類時,程序會初始化該主類
二. 類加載器
2.1類加載器介紹
類加載器負責將.class文件加載到內存中,並為之生成對應的java.lang.Class對象。
一個載入JVM的類有一個唯一的標識。在Java中,一個類使用全限定類名(包括包名和類名)作為標識;但在JVM中,一個類使用全限定類名和其類加載器作為唯一標識。
當JVM啟動時,會形成由三個類加載器組成的初始類加載器層次結構
- Bootstrap ClassLoader:跟類加載器
- Extension ClassLoader:擴展類加載器
- System ClassLoader:系統類加載器
Bootrap ClassLoader被稱為引導(也稱為原始或跟)類加載器,它負責加載Java的核心類。跟類加載器不是java.lang.ClassLoader的子類,而是JVM自身實現的。
Extension ClassLoader負責加載JRE拓展目錄中的JAR包的類,它的父類加載器是跟類加載器
System ClassLoader,它負責在JVM啟動時加載來自Java命令的-classpath選項、java.class,path系統屬性,或CLASSPATH指定的jar包和類歷經。系統可通過ClassLoader的靜態方法或區該系統類加載器。如果沒有特別指定,則用戶自定義的類加載器都已類加載器作為父加載器
2.2 類加載機制
JVM類加載機制主要有三種
- 全盤負責。就是當類加載器負責加載某個Class時,該Class所依賴的和所引用的其他Class也將由該類加載器負責載入,除非顯式使用另外一個類加載器來載入
- 父類委托。所謂父類委托,就是先讓父類加載器試圖加載該Class。只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類
- 緩存機制。緩存機制將會保證所有加載過的Class都會被緩存,當程序需要使用時,先從緩存中搜索該Class,當緩存中不存在該Class,系統菜才讀取該類對應的二進制數據,並將其轉為Class對象,存入緩存區中。這就是為什么修改了Class后,必須重新啟動JVM,程序所做的修改才會生效的原因。
類加載器加載Class大致經過8個步驟
- 檢測此Class是否載入過(即緩存區中是否有此Class),如果有則直接進入第8步,否者接着第2步
- 如果父類加載器(父類 gt+ 加載器,要么Parent一定是跟類加載器,要么本身就是跟類加載器)不存在,則調到第4步執行
- 請求使用父類加載器載入目標類,如果成功載入調到第8步
- 請求使用跟類加載器來載入目標類
- 當前類加載器嘗試尋找Class文件(從與此ClassLoader相關的類路徑中尋找),如果找到則執行第6步,如果找不到執行第7步
- 從文件中載入Class,成功載入調到第8步
- 拋出ClassNotFoundException異常
- 返回對應的java.lang.Class對象
其中,第5、6步允許重寫ClassLoader的findClass()方法來實現自己的載入策略,甚至重寫loadClass()方法來實現自己的載入過程。
2.3 創建並使用自定義的類加載器
JVM除跟類加載器之外的所有類加載器都是ClassLoader子類的實例,開發者可以通過拓展ClassLoader的子類,並重寫該ClassLoader所包含的方法實現自定義的類加載器。ClassLoader有如下兩個關鍵方法。
- loadClass(String name,boolean resolve):該方法為ClassLoader的入口點,根據指定名稱來加載類,系統就是調用ClassLoader的該方法來獲取指定類的class對象
- findClass(String name):根據指定名稱來查找類
如果需要是實現自定義的ClassLoader,則可以通過重寫以上兩個方法來實現,通常推薦重寫findClass()方法而不是loadClass()方法。
classLoader()方法的執行步驟:
- findLoadedClass():來檢查是否加載類,如果加載直接返回。
- 父類加載器上調用loadClass()方法。如果父類加載器為null,則使用跟類加載器加載。
- 調用findClass(String)方法查找類
從上面看出,重寫findClass()方法可以避免覆蓋默認類加載器的父類委托,緩沖機制兩種策略;如果重寫loadClass()方法,則實現邏輯更為復雜。
ClassLoader的一些方法:
- Class defineClass(String name,byte[] b,int off,int len):負責將字節碼分析成運行時數據結構,並檢驗有效性
- findSystemClass(String name):從本地文件系統裝入文件。
- static getSystemClassLoader():返回系統類加載器
- getParent():獲取該類加載器的父類加載器
- resolveClass(Class<?> c):鏈接指定的類
- findClassLoader(String name):如果加載器加載了名為name的類,則返回該類對用的Class實例,否則返回null。該方法是類加載緩存機制的體現。
下面程序開發了一個自定義的ClassLoader。該classLoader通過重寫findClass()方法來實現自定義的類加載機制。這個ClassLoader可以在加載類之前先編譯該類的源文件,從而實現運行Java之前先編譯該程序的目標,這樣即可通過該classLoader運行Java源文件。
package com.gdut.basic; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; public class CompileClassLoader extends ClassLoader { private byte[] getBytes(String fileName) { File file = new File(fileName); Long len = file.length(); byte[] raw = new byte[(int)len]; FileInputStream fin = new FileInputStream(file); //一次讀取class文件的二進制數據 int r = fin.read(raw); if(r != len) { throw new IOException("無法讀取文件"+r+"!="+raw); return null; } } private boolean compile(String javaFile) throws IOException { System.out.println("正在編譯"+javaFile+"..."); Process p = Runtime.getRuntime().exec("javac"+javaFile); try { //其他線程都等待這線程完成 p.waitFor(); }catch(InterruptedException ie) { System.out.println(ie); } int ret = p.exitValue(); return ret == 0; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = null; String findStub = name.replace(".", "/"); String javaFileName = findStub+".java"; String classFileName = findStub+".class"; File javaFile = new File(javaFileName); File classFile = new File(classFileName); //但指定Java源文件存在,class文件不存在,或者Java源文件的修改時間比class文件修改的時間更晚時,重新編譯 if(javaFile.exists() && classFile.exists() || javaFile.lastModified() > classFile.lastModified()) { try { if(!compile(javaFileName)|| !classFile.exists()) { throw new ClassNotFoundException("ClassNotFoundExcetion"+javaFileName); } }catch(IOException ie) { ie.printStackTrace(); } } if(classFile.exists()) { byte[] raw = getBytes(classFileName); clazz = defineClass(name,raw,0,raw.length); } //如果clazz為null,表明加載失敗,則拋出異常 if(clazz == null) { throw new ClassNotFoundException(name); } return clazz; } public static void main(String[] args) throws Exception { //如果運行該程序時沒有參數,即沒有目標類 if (args.length<1) { System.out.println("缺少目標類,請按如下格式運行Java源文件:"); System.out.println("java CompileClassLoader ClassName"); } //第一個參數是需要運行的類 String progClass = args[0]; //剩下的參數將作為運行目標類時的參數,將這些參數復制到一個新數組中 String[] progArgs = new String[args.length - 1]; System.arraycopy(args, 1,progArgs,0, progArgs.length); CompileClassLoader ccl = new CompileClassLoader(); //加載需要運行的類 Class<?> clazz = ccl.loadClass(progClass); //獲取運行時的類的主方法 Method main = clazz.getMethod("main", (new String[0]).getClass()); Object argsArray[] = {progArgs}; main.invoke(null, argsArray); } }
接下來可以提供任意一個簡單的主類,該主類無需編譯就可以使用上面的CompileClassLoader來運行他
package com.gdut.basic; public class Hello { public static void main(String[] args) { for(String arg:args) { System.out.println("運行Hello的參數:"+arg); } } }
無需編譯該Hello.java,可以直接運行下面命令來運行該Hello.java程序
java CompileClassLoader hello 瘋狂Java講義
運行結果如下:
CompileClassLoader:正常編譯 Hello.java...
運行hello的參數:瘋狂Java講義
使用自定義的類加載器,可以實現如下功能
- 執行代碼前自動驗證數字簽名
- 根據用戶提供的密碼解密代碼,從而可以實現代碼混淆器來避免反編譯*.class文件
- 根據應用需求把其他數據以字節碼的形式加載到應用中。
2.4 URLClassLoader類
該類時系統類加載器和拓展類加載器的父類(此處的父類,是指類與類之間的的繼承關系)。URLClassLoader功能比較強大,它可以從本地文件系統獲取二進制文件來加載類,也可以從遠程主機獲取二進制文件加載類。
該類提供兩個構造器
- URLClassLoader(URL[] urls):使用默認的父類加載器創建一個ClassLoader對象,該對象將從urls所指定的路徑來查詢並加載類
- URLClassLoader(URL[] urls,ClassLoader prarent):使用指定的父類加載器創建一個ClassLoader對象,該對象將從urls所指定的路徑來查詢並加載類。
下面程序示范了如何從文件系統中加載MySQL驅動,並使用該驅動獲取數據庫連接。通過這種方式來獲取數據庫連接,無需將MySQL驅動添加到CLASSPATH中。
package java.gdut; import java.net.URL; import java.net.URLClassLoader; import java.sql.Connection; import java.sql.Driver; import java.util.Properties; public class URLClassLoaderTest { private static Connection conn; public static Connection getConn(String url,String user,String pass)throws Exception{ if(conn == null){ URL[] urls = {new URL("file:mysql-connection-java-5.1.46-bin.jar")}; URLClassLoader myClassLoader = new URLClassLoader(urls); //加載MySQL,並創建實例 Driver driver = (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driveer").newInstance(); Properties properties = new Properties(); properties.setProperty("user",user); properties.setProperty("pass",pass); //調用driver的connect方法來取得數據庫連接 conn = driver.connect(url,properties); } return conn; } public static void main(String[] args) throws Exception { System.out.println(getConn("jdbc:mysql://localhost:3306/tb_test","sherman","a123")); } }
本程序類加載器的加載路徑是當前路徑下的mysql-connection-java-5.1.46-bin.jar文件,將MySQL驅動復制到該路徑下,這樣保證ClassLoader可以正常加載到驅動類
三. 通過反射查看類信息
Java程序中的許多對象在運行時都會出現收到外部傳入的一個對象,該對象編譯時類型是Object,但程序又需要調用該對象運行時的方法。
- 第一種做法是假設編譯時和運行時都知道該對象的的類型的具體信息,這種情況下,可以先用instanceof()運算符進行判斷,再利用強制類型轉換將其轉換成運行時類型的變量即可
- 第二種做法是編譯時根本無法知道該對象和類可能屬於那些類,程序只依靠運行時信息來發現該對象和類的真實信息,這就必須使用反射
3.1 獲得class對象
每個類被加載后,系統會為該類生成一個對應的Class對象,通過該Class對象可以訪問到JVM中的這個類。獲得Class對象通常三種方式
- 使用Class類的forName(String clazz)靜態方法。字符串參數傳入全限定類名(必須添加包名),可能會拋出ClassNotFoundexception異常。
- 調用某個類的class屬性來獲取該類的的Class對象。
- 調用某個對象的getClass()方法,該方法是Object類的一個方法。
對於第一種方式,第二種的優勢:
- 代碼更安全。程序在編譯階段就可以檢查需要訪問的Class對象是否存在。
- 程序性能更好。這的種方式無需調用方法,所以性能更好。
3.2 從Class中獲取信息
Class類提供了大量的實例方法獲取該Class對象所對應類的詳細信息
下面4個方法用於獲取Class對象對應類的構造器
- ConStructor<T> getConStructor(Class<?> parameterTypes):返回Class對象對應類的,帶指定參數列表的public構造器
- ConStructor<?>[] getConStructor():返回此Class對象對應類的所有public構造器
- ConStructor<T> getDeclaredConStructor(Class<?>... parameterTypes):返回此Class對象對應類的、帶指定參數列表的構造器,與構造器的訪問權限無關
- ConStructor<?>[] getDeclaredConStructor():返回此Class對象對應類的所有構造器,與構造器的訪問權限無關
下面四個方法獲取Class對象對應類所包含方法。
- Method getMethod(String name,Class<?> parameterTypes):返回Class對象對應類的,帶指定形參列表的public方法
- Method[] getMethods():返回Class對象對應類的所有public方法
- Method getDeclaredMethod(String name,Class<?> parameterTypes):返回Class對象對應類的,帶指定形參列表的方法,與訪問權限無關
- Method[] getDeclaredMethods():返回Class對象對應類的所有全部方法,與方法的訪問權限無關
下面四個方法獲取Class對象對應類所包含的成員變量。
- Field getField(String name):返回Class對象對應類的,指定名稱的public成員變量
- Field[] getFIelds():返回Class對象對應類的所有public成員變量
- Field getDeclaredField(String name):返回Class對象對應類的,指定名稱的成員變量,與成員的訪問權限無關
- Field[] getFIelds():返回Class對象對應類的所有成員變量,與成員的訪問權限無關
如下幾個方法用於訪問Class對應類的上所包含的Annotation.
- <A extends Annotation>A getAnnotation(Class<A> annotationClass):嘗試獲取該Class對象對應類存在的,指定類型的Annotation;如果該類型的注解不存在,則返回null。
- <A extends Annotation>A getDeclaredAnnotation(Class<A> annotationClass):Java 8新增方法,嘗試獲取直接修飾該Class對象對應類存在的,指定類型的Annotation;如果該類型的注解不存在,則返回null。
- Annotation[] getAnnotations():獲取該Class對象對應類存在的所有Annotation
- Annotation[] getDiclaredAnnotations():獲取直接修飾該Class對象對應類存在的所有Annotation
- <A extends Annotation>A[] getAnnotationByType(Class<A> annotationClass):由於Java 8的新增了重復注解功能,因此需要使用該方法獲取修飾該Class對象對應類,指定類型的多個Annotation
- <A extends Annotation>A[] getDeclaredAnnotationByType(Class<A> annotationClass):由於Java 8的新增了重復注解功能,因此需要使用該方法獲取直接修飾該類的,指定類型的多個Annotation
如下方法用於訪問Class對應類的內部類
- Class<?>[] getDeclaredClass():返回該Class對象對應類里包含的內部類
如下方法用於訪問Class對應類的所在的外部類
- Class<?>[] getDeclaringClass():返回該Class對象對應類所在的外部類
如下方法用於訪問Class對應類的所實現的接口
- Class<?>[] getInterfaces():返回該Class對象對應類的所實現的接口
如下方法用於訪問Class對應類的所繼承的父類
- Class<? super T> getSuperClass():返回該Class對象對應類的超類的Class對象
如下方法用於訪問Class對應類的修飾符,所在包,類名等基本信息
- int getModifiers():返回此類或接口的所有修飾符對應的常量,返回的整數需要Modifier工具類的方法來解碼,才可以獲取真正的修飾符
- Package getPackage():獲取此類的包
- String getName():以字符串的形式返回該Class對象對應類的類名
- String getSimpleName():以字符串的形式返回該Class對象對應類的簡稱
以下幾個方法來判斷該類是否為接口、枚舉、注解類型
- boolean isAnnotation():返回此Class對象是否表示一個注解類型(有@interface定義)
- boolean isAnnotationPresent(Class<? extends Annotation>annotationClass):判斷此Class對象是否使用了注解修飾
- boolean isAnonymousClass():返回此Class對象是否為匿名類
- boolean isArray():返回此Class對象是否為數組類
- boolean isEnum():返回此Class對象是否為枚舉類
- boolean isInterface():返回此Class對象是否為接口
- boolean isInstance(Object obj):判斷obj是否為該Class對象的實例,該方法可以替代instanceof操作符
以上getMethod()方法和getConStructor()方法中,都需要傳入多個類型為Class<?>的參數,用於獲取指定的方法和構造器。要確定一個方法應該由方法名和形參列表確定。例如下面代碼獲取clazz對應類的帶一個String參數的info方法:
clazz.getMethods("info",String.class)
若要獲取clazz對應類的帶一個String參數,一個Integer參數的info方法
clazz.getMethods("info",String.class,Integer.class)
3.3 Java 8新增加的方法參數反射
Java 8新增了一個Executable抽象基類,該對象代表可執行的類成員,該類派生了Constructor和Method兩個子類。
Executable抽象基類提供了大量方法來獲取修飾該方法或構造器的注解信息;還提供了is VarArgs()方法用於判斷該方法或構造器是否包含數量可變的形參,以及通過getModifiers()方法獲取該方法或構造器的修飾符。除此之外,還提供如下兩個方法
- int getParameterCount():獲取該構造器或方法的形參個數
- Parameter[] getParameters():獲取該構造器或方法的所有形參
Parameter類是Java 8新增的api,提供了大量方法來獲取聲明該方法或參數個數的泛型信息,還提供了如下方法獲取參數信息
- getModifiers():獲取修飾該形參的修飾符
- String getName():獲取形參名
- Type getParameterizedType():獲取帶泛型的形參類型
- Class<?> getType():獲取形參類型
- boolean isNamePresent():該方法返回該類的class文件中是否包含了方法的形參名信息
- boolean isVarArgs():判斷該參數是否為個數可變的形參
需要指出的是,使用javac命令編譯Java源文件時,默認生成的class文件並不包含方法的形參名信息,因此調用isNamePresent()將返回false,調用getName()也不能得到該參數的形參名。需要編譯時保留形參信息,則需要該命令指定-parameter選項。
下面示范了Java 8的參數反射功能
public class MethodParameterTest { public static void main(String[] args) throws Exception { Class<Test> clazz = Test.class; Method replace = clazz.getMethod("replace",String.class,List.class); System.out.println("replace方法的參數個數為:"+replace.getParameterCount()); Parameter[] parameters = replace.getParameters(); int index = 1; for(Parameter parameter:parameters){ if(!parameter.isNamePresent()){ System.out.println("-----第"+index+"行的參數信息-----"); System.out.println("參數名:"+parameter.getName()); System.out.println("形參類型:"+parameter.getType()); System.out.println("泛型類型:"+parameter.getParameterizedType()); } } } }
3.4 利用反射生成並操作對象
Class對象可以獲得該類的方法,構造器,成員變量。程序可以通過Method對象來執行對應的方法,通過ConStructor對象調用對應的構造器創建實例,能通過Field對象直接訪問並修改對象的成員變量值。
3.4.1 創建對象
通過反射生成對象有兩種方式。
- 使用Class對象的newInstance()方法來創建該Class對象對應類的實例,這種方式要求該Class對象的對應類有默認構造器。
- 先使用Class對象獲取指定的Constructor對象,在調用Constructor對象的newInstance()方法來創建該Class對象對應類的實例。
3.4.2 調用方法
可以通過Class對象的getMethods()方法和getMethod()方法來獲取全部方法和指定方法。
每個Method對象對應一個方法,可以通過它調用對應的方法,在Method里包含一個invoke()方法,該方法的簽名如下。
- Object invoke(Object obj,Object... args):該方法中的obj是執行該方法的主調,后面的args是執行該方法時傳入該方法的實參。
下面程序是對象池工廠加強版,它允許在配置文件中增加配置對象的成員變量的值,對象池工廠會讀取為該對象配置的成員變量值,並利用該對象的Setter方法設置成員變量的值。
package com.gdut.test0516; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Properties; public class ExtendedObjectPoolFactory { //定義一個對象池,前面是對象名,后面是實際對象 private Map<String,Object> objectPool = new HashMap<>(); private Properties config = new Properties(); public void init(String fileName) { try(FileInputStream fis = new FileInputStream(fileName)) { config.load(fis); }catch(IOException ex){ System.out.println("讀取"+fileName+"異常"); } } private Object createObject(String clazzName)throws ClassNotFoundException, InstantiationException,IllegalAccessException{ Class<?> clazz = Class.forName(clazzName); //使用clazz默認構造器創建實例 return clazz.newInstance(); } public void initPool()throws ClassNotFoundException, InstantiationException,IllegalAccessException{ for (String name:config.stringPropertyNames()) { //沒取出一個key-value對。如果key中不包含百分號(%),即可認為該key用於 // 控制調用對象的setter方法設置值,%前半為對象名字,后半控制setter方法名 if( !name.contains("%")){ objectPool.put(name,createObject(config.getProperty(name))); } } } public Object getObject(String name){ return objectPool.get(name); } public void initProperty()throws NoSuchMethodException, IllegalAccessException,InvocationTargetException { for (String name:config.stringPropertyNames()) { if(name.contains("%")){ String[] objAndProp = name.split("%"); Object target = getObject(objAndProp[0]); String mtdName = "set"+objAndProp[1].substring(1); Class<?> targetClass = target.getClass(); Method mtd = targetClass.getMethod(mtdName); mtd.invoke(target,config.getProperty(name)); } } } public static void main(String[] args)throws Exception { ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory(); epf.init("com/gdut/test0516/extObj.txt"); epf.initPool(); epf.initProperty(); System.out.println(epf.getObject("a")); } }
3.4.3 訪問成員變量
通過Class對象的getFields()方法和getField()方法可以獲取該類包含的所有成員變量和指定成員變量。Field提供如下方法讀取或設置成員變量值
- getXxx(Object obj):獲取Object對象的成員變量值。此處的Xxx對應8種基本類型,如果該成員變量類型時引用類型,則取消get后面的Xxx。
- setXxx(Object obj,Xxx val):將obj對象的該成員變量設置成val值。此處的Xxx對應8種基本類型,如果該成員變量類型時引用類型,則取消set后面的Xxx。
3.4.4 操作數組
在java.lang.reflect包下還提供了一個Array類,Array對象可以代表所有的數組。程序可以通過使用該類來創建數組,操作數組元素等。
Array提供如下方法
- static Object newInstance(Class<?>ComponentType,int... length):創建一個具有指定的元素類型,指定維度的新數組
- static xxx getXxx(Object array,int index):返回數組array的第index個元素。此處的xxx對應8種基本類型,如果數組元素是引用類型,則該方法變為get(Object array,int index)。
- static void setXxx(Object array,int index,Object val):將數組array的第index個元素設置為val。此處的xxx對應8種基本類型,如果數組元素是引用類型,則該方法變為set(Object array,int index,Object val)。