今日分享開始啦,請大家多多指教~
今天給大家分享一下裝箱和拆箱以及Java反射機制。
Java為每種基本資料型別都提供了對應的包裝型,而且還提供了包裝類和基本資料型別之間的相互轉化機制,也就是所謂的“裝箱”和“拆箱”。Java反射機制其實是動態地獲取資訊以及動態呼叫物件的功能。今天將從多方面進行總結,以便大家更好地認識及應用。
一、什麼是裝箱?什麼是拆箱?
在Java SE5之前,如果要生成一個數值為10的Integer物件,必須這樣進行:
Integer i = new Integer(10);
而在從Java SE5開始就提供了自動裝箱的特性,如果要生成一個數值為10的Integer物件,只需要這樣就可以了:
Integer i = 10;
這個過程中會自動根據數值建立對應的 Integer物件,這就是裝箱。
那什麼是拆箱呢?顧名思義,跟裝箱對應,就是自動將引用型別轉換為基本資料型別:
Integer i = 10; //裝箱
int n = i; //拆箱
簡單一點說,裝箱就是 自動將基本資料型別轉換為引用型別;拆箱就是自動將引用型別轉換為基本資料型別。
下表是基本資料型別對應的引用型別:
二、裝箱和拆箱是如何實現的
上一小節瞭解裝箱的基本概念之後,這一小節來了解一下裝箱和拆箱是如何實現的。
我們就以Interger類為例,下面看一段程式碼:
public class Main {
public static void main(String[] args) {
Integer i = 10;
int n = i;
}
}
反編譯class檔案之後得到如下內容:
從反編譯的位元組碼中可以看出,在裝箱的時候自動呼叫的是interger的valueOf(int)方法。而在拆箱的時候自動呼叫的是interger的intValue方法
因此用一句話總結裝箱和拆箱的實現過程:
裝箱過程是透過呼叫包裝器的valueOf方法實現的,而拆箱過程是透過引用型別呼叫xxxValue實現的。
三、面試中的相關問題
雖然大多數人對裝箱和拆箱的概念都清楚,但是在面試和筆試中遇到了與裝箱和拆箱的問題卻不一定會答得上來。下面列舉一些常見的與裝箱/拆箱有關的面試題。
1、下面這段程式碼的輸出結果是什麼?
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System。out。println(i1==i2);
System。out。println(i3==i4);
}
}
也許有些朋友會說都會輸出false,或者也有朋友會說都會輸出true。但是事實上輸出結果是:
true
false
為什麼會出現這樣的結果?輸出結果表明i1和i2指向的是同一個物件,而i3和i4指向的是不同的物件。此時只需一看原始碼便知究竟,下面這段程式碼是Integer的valueOf方法的具體實現:
public static Interger valueOf(int i){
if(i>=-128&&i<=IntergerCache。high){
return IntergerCache。cache[i+128];
}else{
return new Interger(i);
}
}
透過valueOf方法建立Integer物件的時候,如果數值在[-128,127]之間,便返回指向IntegerCache。cache中已經存在的物件的引用;否則建立一個新的Integer物件。
上面的程式碼中i1和i2的數值為100,因此會直接從cache中取已經存在的物件,所以i1和i2指向的是同一個物件,而i3和i4則是分別指向不同的物件。
其它的引用型別,可以去檢視valueOf的實現。
弦外音:八種資料型別取值範圍
整型:
byte:-2^7 ~ 2^7-1,即-128 ~ 127。1位元組。Byte。末尾加B
short:-2^15 ~ 2^15-1,即-32768 ~ 32767。2位元組。Short。末尾加S
int:-2^31 ~ 2^31-1,即-2147483648 ~ 2147483647。4位元組。Integer。
long:-2^63 ~ 2^63-1,即-9223372036854774808 ~ 9223372036854774807。8位元組。Long。末尾加L。(也可以不加L)
浮點型:
float:4位元組。Float。
double:8位元組。Double。
字元型:
char:2位元組。Character。
布林型:
boolean:Boolean。
型別轉換:
boolean型別與其他基本型別不能進行型別的轉換(既不能進行自動型別的提升,也不能強制型別轉換), 否則,將編譯出錯
。
byte型不能自動型別提升到char,char和short直接也不會發生自動型別提升(因為負數的問題),同時,byte當然可以直接提升到short型。
當對小於int的資料型別(byte, char, short)進行運算時,首先會把這些型別的變數值強制轉為int型別進行計算,最後會得到int型別的值。因此,如果把2個short型別的值相加,最後得到的結果是int型別,如果需要得到short型別的結果,就必須顯示地運算結果轉為short型別。
下面程式的輸出結果是什麼?
當 “==”運算子的兩個運算元都是 包裝器型別的引用,則是比較指向的是否是同一個物件,而如果其中有一個運算元是表示式(即包含算術運算)則比較的是數值(即會觸發自動拆箱的過程)。另外,對於包裝器型別,equals方法並不會進行型別轉換。
第一個和第二個輸出結果沒有什麼疑問。第三句由於 a+b包含了算術運算,因此會觸發自動拆箱過程(會呼叫intValue方法),因此它們比較的是數值是否相等。而對於c。equals(a+b)會先觸發自動拆箱過程,再觸發自動裝箱過程,也就是說a+b,會先各自呼叫intValue方法,得到了加法運算後的數值之後,便呼叫Integer。valueOf方法,再進行equals比較。同理對於後面的也是這樣,不過要注意倒數第二個和最後一個輸出的結果(如果數值是int型別的,裝箱過程呼叫的是Integer。valueOf;如果是long型別的,裝箱呼叫的Long。valueOf方法)。
一、類的載入與ClassLoader的理解
1、載入
將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後生成一個代表這個類的java。lang。class物件。
2、連結
將Java類的二進位制程式碼合併到JVM的執行狀態之中的過程。
驗證:確保載入的類資訊符合JVM規範,沒有安全方面的問題;
準備:正式為類變數分配記憶體並設定類變數預設初始值的階段,這些記憶體都將在方法區內進行分配;
解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程。
3、初始化
執行類構造器
當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
虛擬機器會保證一個類的
二、什麼時候會發生類初始化
1、類的主動引用(一定會發生類的初始化)
當虛擬機器啟動,先初始化main方法所在的類;
new一個類的物件;
呼叫類的靜態成員(除了final常量)和靜態方法;
使用java。lang。reflect包的方法對類進行反射呼叫;
當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類;
2、類的被動呼叫(不會發生類的初始化)
當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化。如:當透過子類引用父類的靜態變數,不會導致子類初始化;
透過陣列定義類引用,不會觸發此類的初始化;
引用常量不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池中了);
三、類載入器的作用
將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後在堆中生成一個代表這個類的java。lang。Class物件,作為方法區中類資料的訪問入口。
四、動態建立物件執行方法
package com。reflection;
import java。lang。reflect。Constructor;
import java。lang。reflect。Field;
import java。lang。reflect。Method;
public class Test03 {
public static void main(String[] args) throws Exception {
//獲得class物件
Class c1 = Class。forName(“com。reflection。User”);
//1、構造一個物件,本質是無參構造器
User user1 = (User) c1。newInstance();
System。out。println(user1);
//2、透過構造器建立物件
Constructor constructor = c1。getDeclaredConstructor(int。class, String。class, int。class);
User user2 = (User) constructor。newInstance(1,“郭一諾”,1);
System。out。println(user2);
//3、透過反射呼叫普通方法
User user3 = (User) c1。newInstance();
Method setName = c1。getDeclaredMethod(“setName”, String。class);
//invoke啟用
setName。invoke(user3,“素小暖”);
System。out。println(user3。getName());
//4、透過反射操作屬性
User user4 = (User) c1。newInstance();
Field name = c1。getDeclaredField(“name”);
//true:取消Java語言訪問檢查
name。setAccessible(true);
name。set(user4,“素小暖2”);
System。out。println(user4。getName());
}
}
五、透過反射獲取泛型資訊
1、程式碼例項
package com。reflection;
import java。lang。reflect。Method;
import java。lang。reflect。ParameterizedType;
import java。lang。reflect。Type;
import java。util。List;
import java。util。Map;
public class Test04 {
public void test01(Map
System。out。println(“test01”);
}
public Map
System。out。println(“test02”);
return null;
}
//透過反射獲取泛型資訊
public static void main(String[] args) throws Exception {
Method method = Test04。class。getMethod(“test01”, Map。class, List。class);
Type[] genericParameterTypes = method。getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System。out。println(“***”+genericParameterType);
if(genericParameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType)。getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System。out。println(actualTypeArgument);
}
}
}
method = Test04。class。getMethod(“test02”, null);
Type genericReturnType = method。getGenericReturnType();
if(genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType)。getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System。out。println(“test02,”+actualTypeArgument);
}
}
}
}
2、控制檯輸出
3、反射解決泛型問題
六、透過反射獲取註解資訊
1、程式碼例項
package com。reflection;
import java。lang。annotation。*;
import java。lang。reflect。Field;
public class Test05 {
public static void main(String[] args) throws Exception {
Class c1 = Class。forName(“com。reflection。Student”);
//透過反射獲取註解
Annotation[] annotations = c1。getAnnotations();
for (Annotation annotation : annotations) {
System。out。println(annotation);
}
//獲得註解value的值
TableSu tableSu = (TableSu) c1。getAnnotation(TableSu。class);
String value = tableSu。value();
System。out。println(value);
//獲得類指定的註解
Field field = c1。getDeclaredField(“name”);
FieldSu annotation = field。getAnnotation(FieldSu。class);
System。out。println(annotation。columnName());
System。out。println(annotation。type());
System。out。println(annotation。length());
}
}
@TableSu(“db_student”)
class Student{
@FieldSu(columnName = “db_id”,type = “int”,length = 10)
private int id;
@FieldSu(columnName = “db_name”,type = “varchar2”,length = 10)
private String name;
@FieldSu(columnName = “db_age”,type = “int”,length = 10)
private int age;
public Student() {
}
public Student(int id, String name, int age) {
this。id = id;
this。name = name;
this。age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this。id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this。name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this。age = age;
}
@Override
public String toString() {
return “Student{” +
“id=” + id +
“, name=‘” + name + ’\‘’ +
“, age=” + age +
‘}’;
}
}
//類名的註解
@Target(ElementType。TYPE)
@Retention(RetentionPolicy。RUNTIME)
@interface TableSu{
String value();
}
//屬性的註解
@Target(ElementType。FIELD)
@Retention(RetentionPolicy。RUNTIME)
@interface FieldSu{
String columnName();
String type();
int length();
}
2、控制檯輸出
七、透過配置檔案動態呼叫方法
1、在專案根目錄建Class.txt檔案,內容如下
classname=com。guor。reflect。Person
methodname=getPerson
2、建一個Person類
3、透過反射獲取配置檔案中內容呼叫類中方法
4、控制檯輸出
小結:
Java反射機制其實是動態地獲取資訊以及動態呼叫物件的功能;而所謂動態是指,對於任意一個
執行狀態
的類,都能夠知道這個類的所有屬性和方法;並且對於任意一個物件,都能呼叫他的任意一個方法。
今日份分享已結束,請大家多多包涵和指點!