JVM和類
同一個JVM的所有線程、所有變量都處於同一個進程里,它們都使用該JVM進程的內存區。
JVM進程終止條件:
1、程序運行到最后正常結束
2、程序運行到使用System.exit()或Runtime.getRuntime().exit()代碼處結束程序
3、程序執行過程中遇到未捕獲的異常或錯誤而結束
4、程序所在平台強制結束了JVM進程
由上可知,當Java程序運行結束時,JVM進程結束,該進程在內存中的狀態將會丟失。
類的加載
當程序主動使用某個類時,如果該類還未被加載到內存中,則系統會通過加載、連接、初始化三個步驟來對該類進行初始化。
類加載指的是將類的class文件讀入內存,並為之創建一個java.lang.Class對象,即當程序中使用任何類時,系統都會為之建立一個java.lang.Class對象
系統中所有的類實際上也是實例,都是java.lang.Class的實例
類的加載由類加載器完成,類加載通常由JVM提供。JVM提供的類加載器稱為系統類加載器。
開發者可以通過繼承ClassLoader基類來創建自己的類加載器
記載類的來源:
1、從本地文件系統加載class文件
2、從JAR包加載class文件
3、通過網絡加載class文件
4、把一個Java源文件動態編譯,並執行加載
Java虛擬機規范允許系統預先加載某些類
類的連接
類被加載之后,系統為之生成一個對應的Class對象,接着將會進入連接階段,連接階段負責把類的二進制數據合並到JRE中。類連接分為三個階段:
(1)驗證:驗證階段用於檢驗被加載的類是否有正確的內部結構,並和其他類協調一致
(2)准備:類准備階段負責為類的變量分配內存,並設置默認的初始值
(3)解析:將類的二進制數據中的符號引用替換成直接引用
類的初始化
虛擬機負責對類進行初始化,主要對類變量進行初始化。
聲明變量時指定初始值,靜態初始化塊都將被當成類的初始化語句,JVM會按這些語句在程序中的排列順序依次執行它們。
1 package com.edu.ynu.java.learn.nio; 2 3 public class Test 4 { 5 6 7 8 9 static 10 { 11 // 使用靜態初始化塊為變量b指定初始值 12 b = 6; 13 System.out.println("---------------------------------"); 14 } 15 static int a = 5; 16 static int b = 9; 17 static int c; 18 public static void main(String[] args) 19 { 20 System.out.println(Test.b); 21 } 22 }
按照初始化順序上述的代碼的執行結果為9;
JVM初始化一個類包含如下幾個步驟:
1、假如這個類還沒有被加載和連接,則程序先加載並連接該類
2、假如該類的直接父類還沒有被初始化,則先初始化其直接父類
3、假如類中有初始化語句,則系統依次執行這些初始化語句
當執行第2步驟時,系統對直接父類的初始化步驟也遵循此步驟1~3;如果該直接父類又有直接父類,則系統再次重復這三個步驟,依次類推。
類初始化的時機
當java程序首次通過6種方式類使用某個類或接口時,系統就會初始化該類或接口
1、創建類的實例。為某個類創建實例的方式包括:使用new操作符來創建實例,通過反射來創建實例,通過序列化的方式來創建實例
2、通過某個類的方法(靜態方法)
3、訪問某個類或接口的類變量,或為該類變量賦值
4、使用反射方式來強制創建某個類或接口對應的java.lang.Class對象。例如:Class.forName("Person")
5、初始化某個類的子類。當初始化某個類的子類時,該子類的所有父類都會被初始化
6、直接使用java.exe命令來運行某個主類。當運行某個主類時,程序會先初始化該主類
注意:對於final型的類變量,如果該類變量的值在編譯時就可以確定下來,那么這個變量相當於“宏變量”,Java編譯器會在編譯時直接把這個變量出現的地方替換成它的值,因此即使程序使用該靜態變量,也不會導致該類的初始化。
反之,如果final修飾的類變量的值不能在編譯時確定下來,則必須等到運行時才可以確定該類變量的值,如果通過該類來訪問它的類變量,則會導致該類被初始化。
1 //采用系統當前時間為static final類變量賦值
2 static final String compileConstant=System.currentTimeMillis()+"";
當使用ClassLoader類的loadClass()方法來加載某個類時,該方法只是加載該類,並不會執行該類的初始化。使用Class的forName()靜態方法才會導致強制初始化該類
1 package com.edu.ynu.java.learn.reflect; 2 3 class Tester 4 { 5 static 6 { 7 System.out.println("Tester類的靜態初始化塊..."); 8 } 9 } 10 11 public class ClassLoaderTest 12 { 13 public static void main(String[] args) 14 { 15 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 16 //下面語句僅僅是加載Tester類 17 try 18 { 19 classLoader.loadClass("com.edu.ynu.java.learn.reflect.Tester"); 20 } catch (ClassNotFoundException e) 21 { 22 e.printStackTrace(); 23 } finally 24 { 25 } 26 System.out.println("系統加載Tester類"); 27 // 下面語句才會初始化Tester類 28 try 29 { 30 Class.forName("com.edu.ynu.java.learn.reflect.Tester"); 31 } catch (ClassNotFoundException e) 32 { 33 e.printStackTrace(); 34 } 35 } 36 }
類加載器
類加載器負責將.class文件(可能在磁盤上,也可能在網絡上)加載到內存上,並為之生成對應的java.lang.Class對象。
一個載入JVM的類也有一個唯一的標識,其包括你全限定類名和其類加載器作為其唯一標識。其中全限定類名(包括包名和類名)
例如,包名為pg,一個名為Person的類,類加載器ClassLoader的實例k1負責加載,其在JVM中表示為(Person、pg、k1)
意味着不同的加載器,加載同一個的類,他們所加載的類也是完全不同、互補兼容的
當JVM啟動時,會形成有三個類加載器組成的初始類加載器層次結構
1、Bootstrap ClassLoader:根類加載器
2、Extension ClassLoader:拓展類加載器
3、System ClassLoader:系統類加載器
Bootstrap ClassLoader被稱為引導(也稱為原始或根)類加載器,它負責加載Java的核心類。在Sun的JVM中,當執行java.exe命令時,使用-Xbootclasspath選項或使用-D選項指定sun.boot.class.path系統屬性值可以指定加載附加的類
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.net.URL; 4 5 public class BootstrapTest 6 { 7 public static void main(String[] args) 8 { 9 URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); 10 11 for (int i = 0; i < urls.length; i++) 12 { 13 System.out.println(urls[i].toExternalForm()); 14 } 15 } 16 }
Extension Classloader被稱為拓展類加載器,負責加載JRE的拓展目錄(%JAVA_HOME%/jre/lib/ext或者由java.ext.dirs系統屬性指定的目錄)中JAR包的類。通過這種方式,就可以為Java拓展核心類以外的新功能,只要把自己開發的類打包成JAR文件然后放入JAVA_HOME/jre/lib/ext路徑即可。
System Classloader被稱為系統(也稱為應用)類加載器,它負責在JVM啟動時加載來自java命令的-classpath選項、java.class.path系統屬性,或CLASSPATH環境變量所指定的JAR包和類路徑。
程序可以通過ClassLoader的靜態方法getSystemClassLoader()來獲取系統類加載器。
如果沒有特別指定,用戶的自定義的類加載器都已此加載器為父類加載器。
類加載器的機制
1、全盤負責:當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負載載入,除非顯示使用另外一個類加載器來載入
2、父類委托。先讓parent(父)類加載器試圖加載該Class,只有在父類加載器無法加載該類時才嘗試從自己路徑中加載該類。
3、緩存機制:緩存機制將會保證所有的加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區中搜尋該Class,只有當緩存區中不存在該Class對象時,系統才會讀取該類對應的二進制數據,並將其轉換層Class對象,存入緩存區中。因此修改了Class后,必須重新啟動JVM,程序所做的修改才會生效。
除了可以使用Java提供的類加載器之外,開發者還可以實現自己的類加載器
1 package com.edu.ynu.java.learn; 2 3 import java.io.IOException; 4 import java.net.URL; 5 import java.util.Enumeration; 6 7 public class ClassLoaderPropTest 8 { 9 public static void main(String[] args) 10 { 11 ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); 12 System.out.println("系統類加載器:" + systemLoader); 13 /** 14 * 獲取系統類加載器的加載路徑---通常由CLASSPATH環境變量指定 15 * 如果操作系統沒有指定CLASSPATH環境變量,則默認以當前路徑作為系統類加載器路徑 16 */ 17 try 18 { 19 Enumeration<URL> em1 = systemLoader.getResources(""); 20 while (em1.hasMoreElements()) 21 { 22 System.out.println(em1.nextElement()); 23 } 24 ClassLoader extensionLader = systemLoader.getParent(); 25 System.out.println("拓展類加載器: " + extensionLader); 26 System.out.println("拓展類加載器的加載路徑:" + System.getProperty("java.ext.dirs")); 27 System.out.println("拓展類加載器parent:" + extensionLader.getParent()); 28 } catch (IOException e) 29 { 30 e.printStackTrace(); 31 } finally 32 { 33 34 } 35 36 } 37 }
程序運行的結果:
1 Connected to the target VM, address: '127.0.0.1:6420', transport: 'socket' 2 系統類加載器:sun.misc.Launcher$AppClassLoader@14dad5dc 3 file:/D:/development/developSpace/javaworkspace/JavaBase/java_learning/Excluded/production/java_learning/ 4 拓展類加載器: sun.misc.Launcher$ExtClassLoader@3fb4f649 5 拓展類加載器的加載路徑:D:\development\developTools\jdk1.8\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext 6 拓展類加載器parent:null 7 Disconnected from the target VM, address: '127.0.0.1:6420', transport: 'socket'
由上面的運行結果可以看到,拓展類加載器的父加載器是null,並不是返回根類加載器,這是因為根類加載器並沒有繼承ClassLoader抽象類,所以拓展類加載器的getParent()方法返回null,但實際上,拓展類加載器的父類加載器是根類加載器,只是根類加載器並不是Java實現的,程序通常無須訪問根類加載器,因此訪問拓展類加載器的父類加載器時返回null.
從運行結果可以看出,系統類加載器是AppClassLoader的實例,拓展類加載器是ExtClassLoader的實例,實際上這兩個類都是URLClassLoader類的實例
類加載器加載Class需要經過的步驟:
1、檢查此Classs是否載入過(即在緩存區中是否有此Class),如果有則直接進入第8步,否則接着執行第2步。
2、如果父類加載器不存在(如果沒有父類加載器,則要么parent一定是根類加載器,要么本身就是根類加載器),則跳到第4步執行,如果父類加載器存在,則接着執行第3步。
3、請求使用父類加載器載入目標類,如果成功載入則跳到第8步,否則接着執行第5步
4、請求使用根類加載器來載入目標類,如果成功則跳到第8步,否則跳到第7步
5、當前類加載器蠶食尋找Class文件(從與此ClassLoader相關的類路徑中尋找),如果找到則執行第6步,如果找不到則跳轉到第7步
6、從文件中載入Class,成功載入后跳到第8步
7、拋出ClassNotFoundException異常
8、返回對應的java.lang.Class對象
其中,第5、6步允許重寫ClassLoader的findClass()方法來實現自己的載入策略,甚至重寫loadClass()方法來實現自己的載入過程。
創建並使用自定義的類加載器
JVM中除根類加載器之外的所有類加載器都是ClassLoader子類的實例,開發者可以通過拓展ClassLoader的子類,並重寫該ClassLoader所包含的方法來實現自定義的類加載器。
ClassLoader類重要的方法
> loadClass(String name,boolean resolve):該方法為ClassLoader的入口點,根據指定名稱來加載類,系統就是調用ClassLoader的該方法來獲取指定類對應的Class對象
>findClasss(String name):根據指定名稱來查找類
需要實現自定義的ClassLoader,則可以通過重寫上述的方法來來實現,推薦使用findClass()方法,而不是重寫loadClass()方法。
loadClass()方法的執行步驟如下:
1、用findLoadedClass(String)來檢查是佛已經加載類,如果已經加載則直接返回
2、在父類加載器上調用loadClass()方法。如果父類加載器為null,則使用根類加載器來加載
3、調用findClass(String)方法查找類
因此,如果重寫loadClass()方法,則需要重寫父類委托、緩沖機制兩種策略,而重寫findClass()方法可以避免上述步驟
Class defineClass(String name,byte[] b,int off,int len),該方法負責將指定流淚的字節碼文件(即Class文件,如Hello.class)讀入自己數組byte[]中,並把它轉換為Class對象,該字節碼文件可以源於文件、網絡等。該方法是final的,並且管理JVM的許多復雜的實現,它負責將字節碼分析成運行時數據結構,並校驗有效性等。
自定義的ClassLoader案例:
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.IOException; 7 import java.lang.reflect.InvocationTargetException; 8 import java.lang.reflect.Method; 9 10 public class CompileClassLoader extends ClassLoader 11 { 12 public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException 13 { 14 if (args.length < 1) 15 { 16 System.out.println("缺少目標類,請按如下格式運行Java源文件:"); 17 System.out.println("java CompileClassLoader ClassName"); 18 } 19 String progClass = args[0]; 20 //剩下的參數將作為運行目標類時的參數 21 //將這些參數復制到一個新的數組中 22 String[] progArgs = new String[args.length - 1]; 23 System.arraycopy(args, 1, progArgs, 0, progArgs.length); 24 CompileClassLoader ccl = new CompileClassLoader(); 25 26 //加載需要的運行的類 27 Class<?> clazz = ccl.loadClass(progClass); 28 //獲取需要運行的類的主方法 29 Method main = clazz.getMethod("main", (new String[0]).getClass()); 30 Object argsArray[] = {progArgs}; 31 main.invoke(null, argsArray); 32 } 33 34 private byte[] getBytes(String filename) 35 { 36 File file = new File(filename); 37 long len = file.length(); 38 byte[] raw = new byte[(int) len]; 39 try (FileInputStream fin = new FileInputStream(file);) 40 { 41 // 一次讀取Class文件的全部二進制數 42 int r = fin.read(raw); 43 if (r != len) 44 { 45 throw new IOException("無法讀取全部文件:" + r + "!=" + len); 46 47 } 48 } catch (FileNotFoundException e) 49 { 50 e.printStackTrace(); 51 } catch (IOException e) 52 { 53 e.printStackTrace(); 54 } finally 55 { 56 57 } 58 return raw; 59 } 60 //重寫ClassLoader的findClass方法 61 62 // 定義編譯指定Java文件的方法 63 private boolean compile(String javaFile) throws IOException 64 { 65 System.out.println("CompileClassLoader:正在編譯 " + javaFile + "..."); 66 // 調用系統的javac命令 67 Process p = Runtime.getRuntime().exec("javac " + javaFile); 68 try 69 { 70 p.waitFor(); 71 } catch (InterruptedException e) 72 { 73 System.out.println(e); 74 e.printStackTrace(); 75 } 76 //獲取javac線程的退出值 77 int ret = p.exitValue(); 78 // 返回編譯是否成功 79 return ret == 0; 80 } 81 82 @Override 83 protected Class<?> findClass(String name) throws ClassNotFoundException 84 { 85 Class classs = null; 86 //將包路徑中的點(.)替換成斜線(/) 87 String fileStub = name.replace(".", "/"); 88 String javaFilename = fileStub + ".java"; 89 String classFilename = fileStub + ".class"; 90 File javaFile = new File(javaFilename); 91 File classFile = new File(classFilename); 92 if ((javaFile.exists() && (!classFile.exists())) || (javaFile.lastModified() > classFile.lastModified())) 93 { 94 try 95 { 96 if (!compile(javaFilename) || !classFile.exists()) 97 { 98 throw new ClassNotFoundException("ClassNotFoundException" + javaFilename); 99 } 100 } catch (IOException e) 101 { 102 e.printStackTrace(); 103 } 104 } 105 // 如果Class文件存在,系統負責將該文件轉換成Class對象 106 if (classFile.exists()) 107 { 108 byte[] raw = getBytes(classFilename); 109 classs = defineClass(name, raw, 0, raw.length); 110 } 111 if (classs == null) 112 { 113 throw new ClassNotFoundException(name); 114 } 115 return classs; 116 } 117 }
使用自定義的類加載器,可以實現如下常見的功能
1、執行代碼前自動驗證數字簽名
2、根據用戶提供的密碼解密代碼,從而可以實現代碼混淆器來避免反編譯*.class文件
3、根據用戶需求來動態的加載類
4、根據應用需求把其他數據以字節碼的形式加載到應用中
URLClassLoader類
Java為ClassLoader提供了一個URLClassLoader實現類,該類是系統系統類加載器和拓展類加載器的父類(此處的父類,就是指類與類之間的繼承關系)
URLClassLoader既可以從本地文件系統獲取二進制文件來加載類,也可以從遠程主機獲取二進制文件來加載類
在應用程序中可以直接使用URLClassLoader加載類,URLClassLoader類提供的構造器如下:
>URLClassLoader(URL[] urls):使用默認的父類加載器創建一個ClassLoader對象,該對象將從urls所指定的系列路徑來查詢並加載類
>URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父類加載器創建一個ClassLoader對象,其他功能與前一個構造器相同
一旦得到了URLClassLoader對象之后,就可以調用該對象的loadClass()方法來加載指定類。
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.net.MalformedURLException; 4 import java.net.URL; 5 import java.net.URLClassLoader; 6 import java.sql.Connection; 7 import java.sql.Driver; 8 import java.sql.SQLException; 9 import java.util.Properties; 10 11 public class URLClassLoaderTest 12 { 13 private static Connection conn; 14 15 public static Connection getConn(String url, String user, String pass) 16 { 17 if (conn == null) 18 { 19 try 20 { 21 URL[] urls = {new URL("file:D:\\development\\developData\\ali-repository\\mysql\\mysql-connector-java\\5.1.39\\mysql-connector-java-5.1.39.jar")}; 22 URLClassLoader myurlClassLoader = new URLClassLoader(urls); 23 Driver driver = (Driver) myurlClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance(); 24 Properties properties = new Properties(); 25 properties.setProperty("user", user); 26 properties.setProperty("password", pass); 27 conn = driver.connect(url, properties); 28 29 } catch (MalformedURLException e) 30 { 31 e.printStackTrace(); 32 } catch (IllegalAccessException e) 33 { 34 e.printStackTrace(); 35 } catch (InstantiationException e) 36 { 37 e.printStackTrace(); 38 } catch (ClassNotFoundException e) 39 { 40 e.printStackTrace(); 41 } catch (SQLException e) 42 { 43 e.printStackTrace(); 44 } 45 } 46 return conn; 47 } 48 49 public static void main(String[] args) 50 { 51 System.out.println(getConn("jdbc:mysql://localhost:3306/mysql", "root", "root")); 52 } 53 }
此處的URL可以以file:為前綴,表明從本地文件系統加載;可以以http:為前綴,表明從互聯網通過HTTP 訪問來加載;也可以以ftp:為前綴,表明從互聯網通過FTP訪問來加載
通過反射查看類信息
JAVA程序中很多對象都會出現兩種類型:編譯時類型和運行時類型,例如:Person p=new Student();代碼將會生成一個p變量該變量在編譯時類型為Person,運行時類型為Student。另外還會存在程序在運行時接收到外部傳入的一個對象,該對象的編譯時類型是Object,但程序又需要調用該對象的運行時的類型的方法。
解決該問題的方法如下:
1、第一種做法是假設在編譯時和運行時都知道類型的具體給信息。在這種情況下,可以先使用instanceof運算符進行判斷,再利用強制類型轉換將其轉換成運行時類型的變量。
2、采用反射依靠運行時信息來發現該對象和類的真實信息。
獲得Class對象
每個類被加載之后,系統就會為該類生成一個對應的Class對象,通過該Class對象接可以訪問JVM中的這個類。Java程序中或的Class對象的方式:
》使用Class類的forName(String className)靜態方法。該方法需要將傳入字符串參數,該字符串參數為類的全限定類名(完整的報名)
》調用某個類的class屬性類獲取該類對應的Class對象。例如,Person.class將會返回Person類對應的Class對象
》調用某個對象額getClass()方法。
相比於第一種方法,第二種方式如下兩種優勢:
1、代碼更安全。程序在編譯階段就可以檢查需要訪問的Class對象是否存在
2、程序性能更好。因為這種方式無須調用方法費,所以性能更好
因此,盡可能的使用第二種方式來獲取指定類的Class對象
從Class中獲取信息
Class類提供額大量的實例方法類獲取該Class對象所對應的類的構造函數,方法,屬性,類上包含的Annotation,內部類,實現的接口,繼承的父類,修飾符、所在包、類名,也可以判斷該類是否為接口,枚舉,注解等
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.annotation.Annotation; 4 import java.lang.annotation.Repeatable; 5 import java.lang.annotation.Retention; 6 import java.lang.annotation.RetentionPolicy; 7 import java.lang.reflect.Constructor; 8 import java.lang.reflect.Method; 9 import java.util.Arrays; 10 11 12 // 定義可重復注解 13 @Repeatable(Annos.class) 14 @interface Anno 15 { 16 } 17 18 @Retention(value = RetentionPolicy.RUNTIME) 19 @interface Annos 20 { 21 Anno[] value(); 22 } 23 24 // 使用4個注解修改該類 25 @SuppressWarnings(value = "unchecked") 26 @Deprecated 27 // 使用重復注解修改該類 28 @Anno 29 @Anno 30 public class ClassTest 31 { 32 private ClassTest() 33 { 34 35 } 36 37 38 public ClassTest(String name) 39 { 40 System.out.println("執行有參宿的構造器"); 41 } 42 43 public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException 44 { 45 Class<ClassTest> clazz = ClassTest.class; 46 //獲取該class對象所對應的類的全部構造器 47 48 Constructor[] constructors = clazz.getDeclaredConstructors(); 49 System.out.println("ClassTest的全部構造器如下:"); 50 for (Constructor c : constructors) 51 { 52 System.out.println(c); 53 } 54 // 獲取該Class對象所對應類的全部public方法 55 Method[] mtds = clazz.getMethods(); 56 System.out.println("ClassTest的全部public方法如下:"); 57 for (Method md : mtds) 58 { 59 System.out.println(md); 60 } 61 // 獲取該Class對象對應類的指定方法 62 System.out.println("ClassTest里帶一個字符串參數的info方法為:" + clazz.getMethod("info", String.class)); 63 // 獲取該Class對象所對應類的全部注解 64 Annotation[] annotations = clazz.getAnnotations(); 65 System.out.println("ClassTest的全部Annotation如下:"); 66 for (Annotation annotation : annotations) 67 { 68 System.out.println(annotation); 69 } 70 System.out.println("該Class元素上的@SuppressWarnings注解為:" + Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class))); 71 System.out.println("該Class元素上的@Anno注解為:" + Arrays.toString(clazz.getAnnotationsByType(Anno.class))); 72 //獲取該Class對象所對應的類的全部內部類 73 Class<?>[] inners = clazz.getDeclaredClasses(); 74 System.out.println("ClassTest的全部內部類如下:"); 75 for (Class c : inners) 76 { 77 System.out.println(c); 78 } 79 // 使用Class.forName()方法加載ClassTest的Inner內部類 80 Class inClazz = Class.forName("com.edu.ynu.java.learn.reflect.ClassTest$Inner"); 81 // 通過getDeclaringClass()訪問該類所在的外部類 82 System.out.println("inClazz對應的外部類為:" + inClazz.getDeclaringClass()); 83 System.out.println("ClassTest的包為: " + clazz.getPackage()); 84 System.out.println("ClassTest的父類為: " + clazz.getSuperclass()); 85 } 86 87 // 定義一個無參數的info方法 88 public void info() 89 { 90 System.out.println("執行無參數的info方法"); 91 } 92 93 // 定義一個有一個參數的info方法 94 public void info(String str) 95 { 96 System.out.println("執行有參數的info方法" + ",其str參數值:" + str); 97 } 98 99 class Inner 100 { 101 102 } 103 }
值得指出的是:雖然定義的ClassTest類時使用了@SuppressWarning注解,但程序運行時無法分析出該類里包含的該注解,這是因為@SuppressWarnings使用了@Retention(value=SOURCE)修改,這表明@SupressWarings只能保存在源代碼級別上,而通過ClassTest.class獲取該類的運行時Class對象,所以程序無法訪問到@SuppressWarning注解
對於只能在源代碼上保留的注解,使用運行時獲得的Class對象無法訪問該注解
Java8新增的方法參數反射
Java 8在java.lang.reflect包下新增了一個Executable抽象基類,該對象代表可執行的類成員,該類派生了Constructor,Method兩個子類
該類提供了如下方法:
1、isVarArgs()方法判斷該方法或構造器是否包含數量可變的形參
2、getModifiers()方法獲取該方法后構造器的修飾符
3、int getParameterCount():獲取該構造器或方法的形參個數
4、Parameter[] getParameters():獲取該構造器或方法的所有形參
Parameter是Java8中新加入的API,該類提供了大量方法來獲取參數信息
需要指出的是,使用javac命令編譯Java源文件時,默認生成的class文件並不包含方法的形參名信息,因此調用isNamePresenet()方法將會返回false,調用getName()方法也不能得到該參數的形參名。如果希望javac命名編譯Java源文件時可以保留形參信息,則需要為該命令指定-parameters選項
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Method; 4 import java.lang.reflect.Parameter; 5 import java.util.List; 6 7 class Test 8 { 9 public void replace(String str, List<String> list) 10 { 11 12 } 13 } 14 15 public class MethodParameterTest 16 { 17 public static void main(String[] args) throws NoSuchMethodException 18 { 19 // 獲取String類 20 Class<Test> clazz = Test.class; 21 // 獲取String類的帶兩個參數的replace()方法 22 Method replace = clazz.getMethod("replace", String.class, List.class); 23 System.out.println("replace方法參數個數: " + replace.getParameterCount()); 24 Parameter[] parameters = replace.getParameters(); 25 int index = 1; 26 for (Parameter p : parameters) 27 { 28 if (p.isNamePresent()) 29 { 30 System.out.println("---第" + index + "個參數信息---"); 31 System.out.println("參數名:" + p.getName()); 32 System.out.println("形參類型:" + p.getType()); 33 System.out.println("泛型類型:" + p.getParameterizedType()); 34 } 35 } 36 } 37 }
需要使用如下命令來編譯該程序:
1 javac -parameters -d . MethodParameterTest.java
使用反射生成並操作對象
創建對象
1、使用class對象的newInstance()方法來創建Classs對象對應類的實例,執行newInstance()方法實際上是利用默認構造器來創建該類的實例
2、先使用Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建該Class對象對應的類的實例。
示例程序
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.IOException; 6 import java.util.HashMap; 7 import java.util.Map; 8 import java.util.Properties; 9 10 public class ObjectPoolFactory 11 { 12 //定義一個對象池,前面是對象名,后面是實際對象 13 private Map<String, Object> objectPool = new HashMap<>(); 14 15 public static void main(String[] args) 16 { 17 ObjectPoolFactory opf = new ObjectPoolFactory(); 18 opf.initPool("a.txt"); 19 System.out.println(opf.getObject("Date")); 20 System.out.println(opf.getObject("JFrame")); 21 } 22 23 // 定義一個創建對象的方法,該方法只要傳入一個字符串類名,程序可以根據該類名生成Java對象 24 private Object createObject(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException 25 { 26 Class<?> clazz = Class.forName(className); 27 return clazz.newInstance(); 28 } 29 30 /** 31 * 該方法根據指定來初始化對象池,它會根據配置文件來創建對象 32 * 33 * @param fileName 34 */ 35 public void initPool(String fileName) 36 { 37 try (FileInputStream fis = new FileInputStream(fileName);) 38 { 39 Properties properties = new Properties(); 40 properties.load(fis); 41 for (String name : properties.stringPropertyNames()) 42 { 43 // 每取出一個key-value對,就根據value創建一個對象,調用createObject()創建對象,並將對象添加到對象池中 44 objectPool.put(name, createObject(properties.getProperty(name))); 45 } 46 } catch (FileNotFoundException e) 47 { 48 e.printStackTrace(); 49 } catch (IOException e) 50 { 51 System.out.println("讀取" + fileName + "異常"); 52 e.printStackTrace(); 53 } catch (IllegalAccessException e) 54 { 55 e.printStackTrace(); 56 } catch (InstantiationException e) 57 { 58 e.printStackTrace(); 59 } catch (ClassNotFoundException e) 60 { 61 e.printStackTrace(); 62 } 63 } 64 65 public Object getObject(String name) 66 { 67 // 從objectPool中取出指定name對應的對象 68 return objectPool.get(name); 69 } 70 }
其中a.txt文件配置如下:
1 Date=java.util.Date
2 JFrame=javax.swing.JFrame
Spring就是采用XML配置文件的方式,創建對象的
利用Constructor對象創建Java對象的步驟如下:
1、獲取該類的Class對象
2、利用Class對象的getConstructor()方法來獲取指定的構造器
3、調用Constructor的newInstance()方法來創建Java對象
程序案例:
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Constructor; 4 import java.lang.reflect.InvocationTargetException; 5 6 public class CreateJFrame 7 { 8 public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException 9 { 10 // 獲取JFrame對應的Class對象 11 Class<?> jframeClass = Class.forName("javax.swing.JFrame"); 12 // 獲取JFrame中帶一個字符串參數的構造器 13 Constructor constructor = jframeClass.getConstructor(String.class); 14 Object object = constructor.newInstance("反射創建的窗口"); 15 System.out.println(object); 16 } 17 }
調用方法
調用方法的步驟:
1、通過Class對象的getMethods()方法或者getMethod()方法來獲取全部方法方法或指定的方法
2、通過Method對象的invoke()方法,實現方法調用
invoke()方法的簽名如下:
》》》Object invoke(Object obj,Object...args):該方法中的obj是執行該方法的主調,后面的args是執行該方法的傳入方法的實參
1 package com.edu.ynu.java.learn.reflect; 2 3 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.IOException; 7 import java.lang.reflect.InvocationTargetException; 8 import java.lang.reflect.Method; 9 import java.util.HashMap; 10 import java.util.Map; 11 import java.util.Properties; 12 13 public class ExtendedObjectPoolFactory 14 { 15 // 定義一個對象池,前面是對象名,后面是實際對象 16 private Map<String, Object> objectPool = new HashMap<>(); 17 private Properties config = new Properties(); 18 19 public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, InstantiationException, NoSuchMethodException, InvocationTargetException 20 { 21 ExtendedObjectPoolFactory eopf = new ExtendedObjectPoolFactory(); 22 eopf.init("extObj.txt"); 23 eopf.initPool(); 24 eopf.initProperty(); 25 System.out.println(eopf.getObject("a")); 26 27 } 28 29 // 從指定屬性文件中初始化Properties對象 30 public void init(String fileName) 31 { 32 try (FileInputStream fis = new FileInputStream(fileName)) 33 { 34 config.load(fis); 35 } catch (FileNotFoundException e) 36 { 37 e.printStackTrace(); 38 } catch (IOException e) 39 { 40 System.out.println("讀取" + fileName + "異常"); 41 e.printStackTrace(); 42 } 43 } 44 45 // 定義一個創建對象的方法,該方法只要傳入一個字符串類名,程序可以根據類名生成Java對象 46 private Object createObject(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException 47 { 48 // 根據字符串來獲取對應的Class對象 49 Class<?> classs = Class.forName(className); 50 // 使用clazz對應的類默認構造器創建實例 51 return classs.newInstance(); 52 } 53 54 public void initPool() throws IllegalAccessException, InstantiationException, ClassNotFoundException 55 { 56 for (String name : config.stringPropertyNames()) 57 { 58 // 每取出一個key-value對,如果key中不包含百分號(%),這就表明是根據value來創建一個對象 59 // 調用createObject創建對象,並將對象添加到對象池中 60 if (!name.contains("%")) 61 { 62 objectPool.put(name, createObject(config.getProperty(name))); 63 } 64 } 65 } 66 67 // 該方法將會根據屬性文件來調用指定對象的setter方法 68 public void initProperty() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException 69 { 70 for (String name : config.stringPropertyNames()) 71 { 72 //每取出一對key-value對,如果key中包含百分號(%)即可認為該key用於控制調用對象的setter方法設置值 73 // %前半為對象名,后半控制setter方法 74 if (name.contains("%")) 75 { 76 String[] objAndProp = name.split("%"); 77 // 取出調用setter方法的參數值 78 Object target = getObject(objAndProp[0]); 79 // 獲取setter方法名:set+"首字母大寫"+剩下部分 80 String mtdName = "set" + objAndProp[1].substring(0, 1).toUpperCase() + objAndProp[1].substring(1); 81 Class<?> targetClass = target.getClass(); 82 Method method = targetClass.getMethod(mtdName, String.class); 83 //通過Method的invoke方法執行setter方法,將config.getProperty(name)的值作為調用setter方法的參數 84 method.invoke(target, config.getProperty(name)); 85 } 86 } 87 } 88 89 public Object getObject(String name) 90 { 91 // 從objectPool中取出指定的name對應的對象 92 return objectPool.get(name); 93 } 94 }
其中extObj.txt內容如下:
1 a=javax.swing.JFrame
2 b=javax.swing.JLabel
3 a%title=Test_Title
Spring框架就是通過這種方式將成員變量的以及依賴對象等都放在配置文件中進行管理的,從而是實現了較好的解耦,這也是Spring框架的IoC的秘密
當通過Method的invoke()方法來調用對應的方法時,Java會要求程序必須有調用該方法的權限。如果程序確實需要調用某個對象的private方法,則可以先調用Method對象的setAccessible()方法
setAccessible()方法簽名如下:
1、setAccessible(boolean flag):將Method對象的accessible設置為指定的布爾值。
值為true,指示該Method在使用時應該取消Java語言的訪問權限檢查;
值為false,則指示該Method在使用時要實施Java語言的訪問權限檢查
實際上,setAccessible()方法並不屬於Method,而是屬於它的父類AccessibleObject。因此Method、Constructor、Field都可調用該方法,從而實現通過反射來調用private方法、private構造器和private成員變量。
訪問成員變值
通過Class對象的getFields()或getField()方法可以獲得該類所包含的全部成員變量或指定成員變量
1、getXxx(Object obj):獲取obj對象的該成員變量的值。此處的Xxx對應8中基本類型,如果該成員變量是引用類型,則取消get后面的Xxx。
2、setXxx(Object obj,Xxx val):將obj對象的該成員變量設置為val值。此處的Xxx對應8中基本類型,如果該成員變量是引用類型,則取消get后面的Xxx。
1 package com.edu.ynu.java.learn; 2 3 import java.lang.reflect.Field; 4 5 public class FieldTest 6 { 7 public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException 8 { 9 //創建一個Person對象 10 // Person p = new Person(); 11 // 獲取Person類對應的Class對象 通過下面兩種方法都可以創建的Person對象 12 Class<Person> personClass = Person.class; 13 Person p = personClass.newInstance(); 14 // 使用getDeeclaredField()方法表明可獲取各種訪問控制符的成員變量 15 Field nameField = personClass.getDeclaredField("name"); 16 // 設置通過反射訪問該成員變量的是取消權限檢查 17 nameField.setAccessible(true); 18 // 通過set()方法為p對象的name成員變量的設置值 19 nameField.set(p, "leo"); 20 21 Field ageField = personClass.getDeclaredField("age"); 22 ageField.setAccessible(true); 23 ageField.set(p, 123); 24 25 System.out.println(p); 26 27 } 28 } 29 30 class Person 31 { 32 private String name; 33 private int age; 34 35 @Override 36 public String toString() 37 { 38 return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; 39 } 40 41 42 }
操作數組
java.lang.reflect包下還提供了一個Array類,Array對象可以代表所有的數組。程序可以通過使用Array來動態的創建數組,操作數據元素等
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Array; 4 5 public class ArrayTest1 6 { 7 public static void main(String[] args) 8 { 9 10 Object array = Array.newInstance(String.class, 10); 11 // 設置索引為5,6的值 12 Array.set(array, 5, "java編程思想"); 13 Array.set(array, 7, "java並發編程的藝術"); 14 // 依次取出array數組中index為5、6的元素的值 15 Object book1 = Array.get(array, 5); 16 Object book2 = Array.get(array, 7); 17 18 // 輸出array數組中index為5,7的元素 19 System.out.println(book1); 20 System.out.println(book2); 21 } 22 }
稍微復雜的Array創建的三維數組的案例
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Array; 4 5 public class ArrayTest2 6 { 7 public static void main(String[] args) 8 { 9 Object array = Array.newInstance(String.class, 3, 4, 10); 10 // 獲取array數組中index為2的元素,該元素應該是二維數組 11 Object arrObj = Array.get(array, 2); 12 Array.set(arrObj, 2, new String[]{"Java編程思想", "Java並發編程的藝術", "Effective java", "簡潔之道"}); 13 // 獲取arrObj數組中index為3的元素,該元素應該是一維數組 14 Object anArray = Array.get(arrObj, 3); 15 Array.set(anArray, 9, "瘋狂的python講義"); 16 String[][][] castArray = (String[][][]) array; 17 System.out.println(castArray[2][3][9]); 18 System.out.println(castArray[2][2][0]); 19 System.out.println(castArray[2][2][1]); 20 System.out.println(castArray[2][2][2]); 21 System.out.println(castArray[2][2][3]); 22 23 } 24 }
使用反射生成JDK動態代理
Proxy提供了用於創建動態代理類和代理對象的靜態方法,它也是所有動態代理類的父類。如果在程序中為一個或多個接口動態地生成實現類,就可以使用Proxy來創建動態代理類;如果需要為一個或多個接口動態的創建實例。也可以使用Proxy來創建動態代理實例。
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 7 interface Person 8 { 9 void walk(); 10 11 void sayHello(String name); 12 } 13 14 class MyInvokationHandler implements InvocationHandler 15 { 16 /** 17 * 執行動態代理的所有方法時,都會白替換成執行如下的invoke方法 18 * 19 * @param proxy 代表動態代理對象 20 * @param method 代表正在執行的方法 21 * @param args 代表調用目標方法時傳入的實參 22 * @return 23 * @throws Throwable 24 */ 25 @Override 26 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 27 { 28 System.out.println("----正在執行的方法:" + method); 29 if (args != null) 30 { 31 System.out.println("下面是執行該方法時傳入的實參為:"); 32 for (Object val : args) 33 { 34 System.out.println(val); 35 } 36 } 37 else 38 { 39 System.out.println("調用該方法沒有實參!"); 40 } 41 return null; 42 } 43 } 44 45 public class ProxyTest 46 { 47 public static void main(String[] args) 48 { 49 // 創建一個InvocationHandler對象 50 InvocationHandler handler = new MyInvokationHandler(); 51 // 使用指定的InvocationHandler來生成一個動態代理對象 52 Person person = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler); 53 // 調用動態代理對象的walk()和sayHello()方法 54 person.walk(); 55 person.sayHello("飛科剃須刀"); 56 } 57 }
運行結果如下:
1 ----正在執行的方法:public abstract void com.edu.ynu.java.learn.reflect.Person.walk() 2 調用該方法沒有實參! 3 ----正在執行的方法:public abstract void com.edu.ynu.java.learn.reflect.Person.sayHello(java.lang.String) 4 下面是執行該方法時傳入的實參為: 5 飛科剃須刀
不管程序是執行代理對象的walk()方法,還是執行代理對象的sayHello()方法,實際上都是執行InvocationHandler對象的invoke()方法
動態代理和AOP
在實際軟件開發中,通常會存在相同的代碼段重復出現的情況,在這種情況下,通常有兩種做法
1、將相同的代碼段“復制”,"粘貼"方式貼到不同位置
2、將相同的代碼段獨立出來,然后在不同的地方調用的相同的代碼段
對於第一種方式,存在如果需要更改的代碼,則任何被粘貼的地方都需要修改
對於第二種方式,存在公共代碼塊脫離了,需要的代碼的地方與代碼公共的地方耦合了,
而最理想的效果是:方法既能執行公共代碼塊,同時又無須以硬編碼的方式直接調用公共代碼塊,對於JDK中的動態代理方法能夠達到如下效果
1 package com.edu.ynu.java.learn.Proxy; 2 3 public interface Dog 4 { 5 // info()方法聲明 6 void info(); 7 8 // run()方法聲明 9 void run(); 10 }
1 package com.edu.ynu.java.learn.Proxy; 2 3 public class GunDog implements Dog 4 { 5 //實現info()方法,僅僅打印一個字符串 6 @Override 7 public void info() 8 { 9 System.out.println("我是一個獵狗"); 10 } 11 12 // 實現run()方法 13 @Override 14 public void run() 15 { 16 System.out.println("我奔跑迅速"); 17 } 18 }
1 package com.edu.ynu.java.learn.Proxy; 2 3 public class DogUtil 4 { 5 public void method1() 6 { 7 System.out.println("======模擬第一個通用方法====="); 8 } 9 10 public void method2() 11 { 12 System.out.println("=====模擬第二個通用方法======"); 13 } 14 }
1 package com.edu.ynu.java.learn.Proxy; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 6 public class MyInvokationHandler implements InvocationHandler 7 { 8 private Object target; 9 10 public void setTarget(Object target) 11 { 12 this.target = target; 13 } 14 15 @Override 16 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 17 { 18 DogUtil du = new DogUtil(); 19 du.method1(); 20 System.out.println(proxy.getClass()); 21 Object result = method.invoke(target, args); 22 du.method2(); 23 return result; 24 } 25 }
1 package com.edu.ynu.java.learn.Proxy; 2 3 import java.lang.reflect.Proxy; 4 5 public class MyProxyFactory 6 { 7 public static Object getProxy(Object target) 8 { 9 MyInvokationHandler handler = new MyInvokationHandler(); 10 handler.setTarget(target); 11 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); 12 } 13 }
1 package com.edu.ynu.java.learn.Proxy; 2 3 4 5 public class Test 6 { 7 public static void main(String[] args) 8 { 9 Dog target=new GunDog(); 10 Dog dog= (Dog)MyProxyFactory.getProxy(target); 11 dog.info(); 12 dog.run(); 13 } 14 }
反射和泛型
從JDk 5以后,Java的Class類增加了泛型功能,從而允許使用泛型來限制Class類,例如,String.class的類型實際上是Class<String>。如果Class對應的類暫時未知,則使用Class<?>。通過在反射中使用泛型,可避免使用反射生成的對象需要強制類型轉換
使用Class<T>泛型可以避免使用強制類型你給轉換
1 ackage com.edu.ynu.java.learn.reflect; 2 3 public class CrazyitObjectFactory 4 { 5 public static Object getInstance(String clsName) 6 { 7 try 8 { 9 Class cls = Class.forName(clsName); 10 return cls.newInstance(); 11 } catch (ClassNotFoundException e) 12 { 13 e.printStackTrace(); 14 } catch (IllegalAccessException e) 15 { 16 e.printStackTrace(); 17 } catch (InstantiationException e) 18 { 19 e.printStackTrace(); 20 } 21 return null; 22 } 23 }
1 獲取實例后需要強制類型轉換
2 Date d=(Date)Crazyit.getInstance("java.util.Date");
甚至出現如下代碼:
1 JFrame f=(JFrame)Crazyit.getInstance("java.util.Date");
如果將上面的CrazyitObjectFactory工廠類改寫成泛型的Class,程序可以變為
1 package com.edu.ynu.java.learn.reflect; 2 3 import javax.swing.*; 4 import java.util.Date; 5 6 public class CrazyitObjectFactory2 7 { 8 public static <T> T getInstance(Class<T> cls) 9 { 10 try 11 { 12 return cls.newInstance(); 13 } catch (InstantiationException e) 14 { 15 e.printStackTrace(); 16 } catch (IllegalAccessException e) 17 { 18 e.printStackTrace(); 19 } 20 return null; 21 } 22 23 public static void main(String[] args) 24 { 25 //獲取實例后無需類型轉換 26 Date d = CrazyitObjectFactory2.getInstance(Date.class); 27 JFrame f = CrazyitObjectFactory2.getInstance(JFrame.class); 28 } 29 }
示范泛型的優勢,可以對Array的newInstance()方法進行包裝
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Array; 4 5 public class CrazyitArray 6 { 7 public static <T> T[] newInstance(Class<T> compponentType, int length) 8 { 9 return (T[]) Array.newInstance(compponentType, length); 10 } 11 12 public static void main(String[] args) 13 { 14 // 創建一維數組 15 String[] arr = CrazyitArray.newInstance(String.class, 10); 16 // 創建二維數組 17 int[][] intArr = CrazyitArray.newInstance(int[].class, 5); 18 arr[5] = "瘋狂Java講義"; 19 // 初始化二維數組 20 intArr[1] = new int[]{23, 12}; 21 System.out.println(arr[5]); 22 System.out.println(intArr[1][0]); 23 } 24 }
使用反射來獲取泛型信息
為了獲得指定成員變量的泛型類型,應先使用如下方法來獲取成員變量的泛型類型
1 //獲得成員變量f的泛型類型
2 Type gType=f.getGenericType();
然后將Type對象強制在類型轉換為ParameterizedType對象,ParameterType代表被參數化的類型
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Field; 4 import java.lang.reflect.ParameterizedType; 5 import java.lang.reflect.Type; 6 import java.util.Map; 7 8 public class GenericTest 9 { 10 private Map<String, Integer> score; 11 12 public static void main(String[] args) throws NoSuchFieldException 13 { 14 Class<GenericTest> classs = GenericTest.class; 15 Field field = classs.getDeclaredField("score"); 16 // 直接使用getType()取出類型只對普通類型的成員變量有效 17 Class<?> a = field.getType(); 18 // 下面將看到僅輸出java.util.Map 19 System.out.println("score的類型是:" + a); 20 // 獲取成員變量f的泛型類型 21 Type gType = field.getGenericType(); 22 // 如果gType類型是ParameterizedType對象 23 if (gType instanceof ParameterizedType) 24 { 25 // 強制類型轉換 26 ParameterizedType pType = (ParameterizedType) gType; 27 // 獲取原始類型 28 Type rType = pType.getRawType(); 29 System.out.println("原始類型是:" + rType); 30 // 取得泛型類型的泛型參數 31 Type[] tArgs = pType.getActualTypeArguments(); 32 System.out.println("泛型信息是:"); 33 for (int i = 0; i < tArgs.length; i++) 34 { 35 System.out.println("第" + i + "個泛型類型是:" + tArgs[i]); 36 } 37 } 38 else 39 { 40 System.out.println("獲取泛型類型出錯!"); 41 } 42 } 43 }
Type也是java.lang.reflect包下的一個接口,該接口代表所有類型的公共高級接口,Class是Type接口的實現類。Type包括原始類型,參數化類型、數組類型、類型變量和基本類型。