摘要:
# 本文對Java 原生型別與包裝器型別進行深度剖析,主要涉及以下四個面向:原生型別與包裝器型別基礎、字面值概念與種類、 基本型別的自動轉型與強制轉型與自動裝箱與拆箱機制。
重點:
原生類型與包裝器類型
數據類型:
特別要注意以下幾點:
#未帶有字元後綴標識的整數預設為int 類型,未帶有字元後綴標識的浮點數預設為double 類型;
byte,char,short,int 四個基本型別及其包裝類別(需要Java5.0/1.5以上版本支援)可以用於switch 語句(只有enum 類型和int類型可以應用到swith),其它類型編譯會報錯誤;
'良'#,' A';
以0B/0b開頭)、八進位(以0開頭的整數)、十進位、十六進位(以0x或0X開頭的整數)表示;
字串字面值也很常見可以算是一種,當然也可以把特殊的null當成字面值。字面值大體上可以分為 整數字面值(int型字面值、long型字面值和字元字面值)、整數字面值、 浮點型字面值(double型字面值和float類型字面值)、##字串字面值、特殊字面值 四種。
(1)從形式上看是整數的值歸類為整數字面值。例如: 10, 100000L,
‘B’‘B’、0XFF 這些都可以稱為整數字面值。使用時,需要注意以下幾點:
、十六進位( 以0X/0x開頭)、八進位(#以0開頭) 和二進位(以0B/0b開頭) 來表示。當然,基數不能超出進位的範圍,例如09是不合法的,八進位的基數只能到 7。
字面值建立的是int類型,但是int字面值可以賦值給 byte、short、char、long 和 int。 當然,前提是字面值在目標範圍以內(Java會自動完成轉換);如果試圖將超出範圍的字面值賦給某一型別(例如把128賦給byte型別),編譯通不過,需要強制型別轉換。
byte b0 = 128; // Type mismatch: cannot convert from int to bytebyte b1 = 12; // OKbyte b2 = (byte)300; // OK 需要强制转换,但表示的数字是截取后的数字(300二进制为100101100,截取后为00101100,即44)char c0 = 5; // OKchar c1 = char(-3); // 编译通过,但打印值为 ?(char 为无符号的)System.out.println(c1); // ?
如果想要建立一個 int 類型無法表示的 long 類型,則需要在字面值最後面加上L或l(通常建議使用容易區分的L)。所以,整數字面值包括int字面值和long字面值兩種。
Java中,字元字面值用單引號括起來(也屬於整數字面值),如'@','1'。所有的UTF-16字元集都包含在字元字面值中。無法直接輸入的字符,可以使用轉義字符,如‘\n’為換行字符。也可以使用八進位或十六進位表示字符,八進位使用反斜線加3位數字表示,例如’\141’表示字母a。十六進位使用\u加上4為十六進位的數表示,如’\u0061’表示字元a。也就是說,透過使用轉義字符,可以表示鍵盤上的有的或沒有的所有字符。常見的轉義字元序列有:
\ddd(八進位) 、 \uxxxx(十六進位Unicode字元)、\'(單引號)、\”(雙引號)、\ (反斜線)\r (回車符) \n(換行符號) \f(換頁符號) \t(製表符) \b(回格符號)
(2)浮點字面值
浮點字面值簡單的理解可以理解為小數,分為float字面值和double字面值。這是個double字面值。 @','1'。進位表示字符,八進制使用反斜杠加3位數字表示,例如'\141'表示字母a。字符a。 Unicode字元)、\'(單引號)、\”(雙引號)、\ (反斜線)\r(回車符) \n(換行符) \f(換頁符) \t(製表符) \b(回格符號)
字串字面值則使用雙引號,字串字面值中同樣可以包含字元字面值中的轉義字元序列
。(4) 特殊字面值
#null 是一種特殊的型別(type),可以將它賦給任何引用
型別,表示這個變數不引用任何東西。
如果一個引用類型變數為null,表示這個變數不可用。 還有一個特殊的 class literal,用 type name 加上 .class 表示,例如 String.class。
首先,String是類別Class(java.lang.Class)的一個實例(物件),而」This is a string」是類別String的一個對象。然後,class literal用來表示類別Class的一個對象,例如String.class用來表示類別Class的物件String。簡單來說,類別字面值(class literal)就是像String.class 、Integer.class這樣的字面值,它所表示的就是類別String、類別Integer。如果輸出Integer.class,你會得到class java.lang.Integer。 List.class的輸出為interface java.util.List。總之,class literal 用來表示類型本身。 特別需要注意:
在在數值類型字面值#中可以
(JDK7開始,可以在數值類型字面值(包括整數字面值和浮點字面值)插入一個或多個下劃線。但是下劃線只能用於分隔數字下劃線只能用於分隔數字,不能分隔字符與字符,也不能分隔字符與數字);double d1 = 10;
// OK,自动类型转换double d2 = 11.4;
// OKdouble d3 = 1.23E3;
// OK double d4 = 10D;
// OK double d5 = 0.4D;
// OK float f1 = 10;
// OK,自动类型转换float f2 = 11.1F;
// OKfloat f3 = 11.1;
// Type mismatch: cannot convert from double to float
三. 自動轉型與強制轉型1、 自動轉型
自动转型总原则:byte,short,char(同级)-> int -> long -> float -> double (由低精度到高精度)
(1) 由低精度到高精度的自动转换
具体可分为以下两种情形:
从位数低的类型向位数高的类型转换
byte b = 1; char c = 1; short s = 1; int i = 1; c = b; // Error,同级 c = s; // Error,同级 s = c; // Error,同级 i = c; // OK
从整型向浮点型的转换———-
从整型向浮点型的转换
int i = 1; long t = 1; float f = 1; double d = 1; f = i; // Ok f = t; // Ok d = f; // Ok
(2) 运算符对基本类型的影响
具体可分为以下两种情形:
1) 当使用 +、-、*、/、%、==、>、运算符对基本类型进行运算时,遵循如下规则:
两个操作数中,先考虑是否有一个是double类型的。如果有,另一个操作数和结果 将会被转换成double类型。再依次考虑float,long。除此之外,两个操作数(包括byte、short、int、char)都将会被转换成int类型。
byte b1 = 10 ; //OK,会检查发现10没有超过byte类型的最大值byte b2 = 12; //OK,会检查发现12没有超过byte类型的最大值byte b = b1 + b2; //Error,byte类型在计算时会自动提升为int类型,此时就会报错,因为b1+b2实际上是int类型,但是左侧的变量为byte类型。short s1=1; //OK,会检查发现1没有超过short类型的最大值s1=s1+1; //Error,因为s1+1 结果int,但左侧变量为 short,报错s1++; //OK,不会报错,与s1=s1+1不同!!!,会检查发现2没有超过short类型的最大值s1=1+1; //OK,1+1 是个编译时可以确定的常量,'+'运算在编译时就被执行了,而不是在程序执行的时候,这个语句的效果等同于s1=2
2) 当使用 +=、-=、*=、/=、%= 、 i++、 ++i 运算符对基本类型进行运算时,遵循如下规则:
运算符右边的数值将首先被强制转换成与运算符左边数值相同的类型,然后再执行运算,且运算结果与运算符左边数值类型相同。自增(减)运算也类似。
short s1=1; // OK,会检查发现1没有超过short类型的最大值short s2; s1+=1; // OK,正确,1首先被强制转换为short型,然后再参与运算,并且结果也是short类型s2 = ++s1; // OK,正确,s2的值为2
2、强制转型
强制转换的格式是在需要转型的数据前加上 “( )”, 然后在括号内加入需要转化的数据类型。主要发生于以下两种情形:
由高精度向低精度转换
一种类型到另一种类型转换,则必须使用强制类型转化(同级之间:byte,short,char)
byte b = 3; int i = 3; long t = 3; float f = 3; char c = 3; short s = 3; i = (int) f; // OK,由高精度向低精度转换 t = (long) f; // OK,由高精度向低精度转换 b = (byte) i; // OK,由高精度向低精度转换 i = b; // OK,由低精度向高精度转换,自动转型 System.out.println(c==s); // OK,true,c 和 s 自动转型为int,然后比较 b = (byte) s; // OK,一种类型到另一种类型转换 c = (char) b; // OK,一种类型到另一种类型转换 c = (char) s; // OK,一种类型到另一种类型转换
特别需要注意的是,强制转换常常会导致二进制位的截取,甚至会导致意想不到的结果:
int i = 128; byte b = (byte)i; System.out.println(b); // -128(即-0)
1、什么是装箱?什么是拆箱?
Java为每种基本数据类型都提供了对应的包装器类型。在 Java SE5 之前,如果要 创建一个数值为10的Integer对象,必须这样进行:
Integer i = new Integer(10);
而从 Java SE5 之后就提供了自动装箱的特性,如果要 创建一个数值为10的Integer对象,只需要这样就可以了:
Integer i = 10;
这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱。
那什么是拆箱呢?顾名思义,跟装箱对应,就是自动将包装器类型转换为基本数据类型:
Integer i = 10; //装箱int n = i; //拆箱
简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。
2、装箱和拆箱是如何实现的
上一小节了解装箱的基本概念之后,这一小节来了解一下装箱和拆箱是如何实现的。我们就以Interger类为例,下面看一段代码:
public class Main { public static void main(String[] args) { Integer i = 10; int n = i; } }
反编译class文件之后得到如下内容:
从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。
对于其他的包装器类,比如Double、Character,也同样适用。
因此,可以用一句话总结装箱和拆箱的实现过程:
装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的(xxx代表对应的基本数据类型)。
3、valueOf、xxxValue 方法在JDK中的实现
(1) 在 Byte,Character,Short,Integer,Long 中的实现(以 Integer 为例)
public static Integer valueOf(int i) : 类方法
public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); } private static class IntegerCache { static final int high; static final Integer cache[]; static { // 静态代码块 final int low = -128; // high value may be configured by property int h = 127; if (integerCacheHighPropValue != null) { // Use Long.decode here to avoid invoking methods that // require Integer's autoboxing cache to be initialized int i = Long.decode(integerCacheHighPropValue).intValue(); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - -low); } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) // 初始化 cache[k] = new Integer(j++); } private IntegerCache() {} }
在装箱时,valueOf方法会被自动调用:如果整型字面值在[-128,127]之间,便返回 IntegerCache.cache(在类加载时就自动创建) 中已经存在的对象的引用;否则,创建一个新的Integer对象并返回。
public int intValue():实例方法
public int intValue() { return value; }
从这段代码可以看出,在拆箱时,Integer对象会自动调用其 intValue 方法,返回该对象对应的 int 值。
下面代码可以很好说明这一点:
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); // true System.out.println(i3==i4); // false } }
(2) 在 Float, Double 中的实现(以 Double 为例)
public static Double valueOf(double d) : 类方法
public static Double valueOf(double d) { return new Double(d); }
在装箱时,valueOf方法会被自动调用,从而创建相应的Double对象并返回。
public int intValue():实例方法
public double doubleValue() { return value; }
从这段代码可以看出,在拆箱时,Double对象会自动调用其 doubleValue 方法,返回该对象对应的 double 值。
下面代码可以很好说明这一点:
public class Main { public static void main(String[] args) { Double i1 = 100.0; Double i2 = 100.0; Double i3 = 200.0; Double i4 = 200.0; System.out.println(i1==i2); // false System.out.println(i3==i4); // false } }
为什么Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现呢?原因很简单,在某个范围内的整型数值的个数是有限的,而浮点数却不是。
(3) 在 Boolean 中的实现
public static Boolean valueOf(boolean b) : 类方法
public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false); public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); }
在装箱时,valueOf方法会被自动调用,从而创建相应的Boolean对象并返回。
public boolean booleanValue():实例方法
public boolean booleanValue() { return value; } }
从这段代码可以看出,在拆箱时,Boolean对象会自动调用其 booleanValue 方法,返回该对象对应的 boolean 值。
下面代码可以很好说明这一点:
public class Main { public static void main(String[] args) { Boolean i1 = false; Boolean i2 = false; Boolean i3 = true; Boolean i4 = true; System.out.println(i1==i2); // true System.out.println(i3==i4); // true } }
总之,
Integer、Short、Byte、Character、Long 这几个类的valueOf方法的实现是类似的,有限可列举,共享[-128,127];
Double、Float的valueOf方法的实现是类似的,无限不可列举,不共享;
Boolean的valueOf方法的实现不同于以上的整型和浮点型,只有两个值,有限可列举,共享;
4、Integer i = new Integer(xxx) 和 Integer i =xxx;的区别
第一种方式不会触发自动装箱的过程,而第二种方式会触发;
在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下([-128,127])要优于第一种情况(注意这并不是绝对的)。
5、“==”运算符
当使用“==”运算符在基本类型和其包装类对象之间比较时,涉及到自动装箱、拆箱机制,遵循如下规则:
1). 只要两个操作数中有一个是基本类型或表达式(即包含算术运算符),就是比较它们的数值是否相等。
2). 否则,就是判断这两个对象的内存地址是否相等,即是否是同一个对象。
// 代码片段1public class Main { public static void main(String[] args) { Integer i01=59; int i02=59; Integer i03=Integer.valueOf(59); Integer i04=new Integer(59); System.out.println(i01==i02); // true,拆箱 System.out.println(i01==i03); // true,同一对象 System.out.println(i03==i04); // false,不同对象 System.out.println(i02==i04); // true,拆箱 } }
// 代码片段2public class Main { public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; Long h = 2L; System.out.println(c==d); // true System.out.println(e==f); // false System.out.println(c==(a+b)); // true System.out.println(c.equals(a+b)); // true System.out.println(g==(a+b)); // true System.out.println(g.equals(a+b)); // false System.out.println(g.equals(a+h)); // true } }
第一个和第二个输出结果没有什么疑问;
第三个打印语句由于 a+b 包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等;
对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较;
对于g==(a+b),会分别触发 Integer 和 Long 的自动拆箱过程,然后 int 自动转为 long,进行比较;
对于g.equals(a+b),最终会归结于 Long对象与Integer对象的比较,由于二者不为同一类型,直接返回 false ;
对于g.equals(a+h),最终会归结于 Long对象与Long对象的比较,由于 -128
6、小结
使用“==”运算符在基本类型和其包装类对象之间比较时,只要两个操作数中有一个是 基本类型 或 表达式(即包含算术运算符),就是比较它们的数值是否相等。否则,就是判断这两个对象的内存地址是否相等,即是否是同一个对象。
如果一个 方法中参数类型为原生数据类型 ,所传入的参数类型为其封装类对象,则会自动对其进行 拆箱 ;相应地,如果一个方法中 参数类型为封装类型对象 ,所传入的参数类型为其原始数据类型,则会自动对其进行 装箱 ,例如上述的equals方法。
至于什么时候装箱,什么时候拆箱主要取决于:在当前场景下,你需要的是引用类型还是原生类型。若需要引用类型,但传进来的值是原生类型,则自动装箱(例如,使用equals方法时传进来原生类型的值);若需要的是原生类型,但传进来的值是引用类型,则自动拆箱(例如,使用运算符进行运算时,操作数是包装类型)。
以上是深入理解Java原生類型與包裝器類型的詳細內容。更多資訊請關注PHP中文網其他相關文章!