Java语法特性隐藏在了代码中的每一个角落,最多见的就是自动拆装箱和类型提高了。这些特性在带来编码便利性的同时也在代码中藏下了些不易察觉定时炸弹,好比对
null
拆箱时引起的空指针异常NPE。本文就将JLS中关于数值提高的机制译述出来,便于更深入地理解代码后面的东西。web
如下几段代码为何是这样的运行结果:数组
Object k = true ? null : 1; System.out.println(k); // 输出: null
Integer a = null; Object k = true ? a : 1; System.out.println(k); // 运行时报NPE错误
byte a = 2; byte k = true ? a : 128; System.out.println(k); // 编译时报错。 不兼容的类型: 从int转换到byte可能会有损失
数字类型提高机制被用于算术运算符上,一般使用场景为:svg
r.intValue()
。数值提高用于将算术运算中的操做数转化为一个相同的类型以便于运算,具体分为两种状况:一元数值提高和二元数值提高。oop
某些运算符将一元数值提高用在了单操做数运算中,其一定能获得一个数字类型的值,规则以下:编码
Byte
、Short
、Character
或Integer
,那么它会先拆箱为对应的原始类型,而后拓宽为int
类型。Long
、Float
或Double
,那么就直接拆箱为对应的原始类型。byte
、short
、char
或int
,那么就拓宽为int
类型。一元数值提高还用在如下情境的表达式中(提高为int
):指针
long
类型,A并不会被提高到long
。注意:自增和自减单目运算符一样也会进行类型提高,但运算后会自动进行强制类型转换,如
byte a = 127; a++; // a在运算后为int类型,转为byte截断后变成-128
等价于
byte a = (byte)128;
code
例:xml
class Test { public static void main(String[] args) { byte b = 2; int a[] = new int[b]; // 维度表达式提高 char c = '\u0001'; a[c] = 1; // 索引表达式提高 a[0] = -c; // 负号 提高 System.out.println("a: " + a[0] + "," + a[1]); b = -1; int i = ~b; // 按位补提高 System.out.println("~0x" + Integer.toHexString(b) + "==0x" + Integer.toHexString(i)); i = b << 4L; // 移位提高(左操做数) System.out.println("0x" + Integer.toHexString(b) + "<<4L==0x" + Integer.toHexString(i)); } } 输出: a: -1,1 ~0xffffffff==0x0 0xffffffff<<4L==0xfffffff0
当二元运算符的操做数皆可转化为数字类型时,那么将采用以下二元数值提高规则:索引
double
类型,那么另外一个也转为double
float
类型,那么另外一个也转为float
long
类型,那么另外一个也转为long
int
二元数值提高应用于如下运算符上:编译器
注意:混合赋值运算符一样也会自动进行强制类型转换,如
byte a = 127; a += 1; // a在运算后为int类型,转为byte截断后变成-128
例:
class Test { public static void main(String[] args) { int i = 0; float f = 1.0f; double d = 2.0; // int*float 先是被提高为 float*float,而后 // float==double 被提高为 double==double: if (i * f == d) System.out.println("oops"); // char&byte 被提高为 int&int: byte b = 0x1f; char c = 'G'; int control = c & b; System.out.println(Integer.toHexString(control)); // 此处 int:float 被提高为 float:float: f = (b==0) ? i : 4.0f; System.out.println(1.0/f); } } 输出: 7 0.25
三元运算符的数值提高机制较为复杂,这儿详细介绍分析一下。
条件运算符? : 在语法上是右结合的(从右到左结合)。所以, a?b:c?d:e?f:g
等价于 a?b:(c?d:(e?f:g))
。
条件运算符由三个表达式构成,第一个表达式的值类型必须为 boolean
或 Boolean
,不然将产生编译时错误。
当第二个或第三个表达式为void
方法时,也将抛出编译时错误。
条件表达式最终产生的类型取决于下述状况:
null
),那么它就是条件表达式的类型。null
类型,另外一个为引用类型,那么条件表达式的类型就是该引用类型。byte
或 Byte
,另外一个类型为short
或 Short
,那么条件表达式类型为short。byte
、short
或 char
,另外一个类型为int类型的常量表达式,且可用T来表达(在T可表示的范围内),那么条件表达式类型为T。Byte
、Short
或 Character
,另外一个类型为 int类型的常量表达式,且可用U来表达(U为T的拆箱类型),那么条件表达式类型为U。以上操做仅用于编译器判断条件表达式的最终类型T,只有在最终选择的操做数(第二个表达式的或第三个表达式的)与T不符时才会进行自动拆箱/类型提高操做。
总结一下,运算中的类型提高一般都是将低于int
位数的类型提高为int
,高于int
的拆箱后保持不变,两边操做数位数不一样则升为高精度的那一个类型。
最后咱们分析下开头的几个问题:
Object k = true ? null : 1; System.out.println(k); // 输出: null
注意上面的null
为编译时类型,1此时会自动装箱为Integer
类型,此时按照上述规则条件表达式的类型为Integer
类型。由于条件表达式的结果为操做数null
,因此k
的实际类型为Integer
,值为null
。
Integer a = null; Object k = true ? a : 1; System.out.println(k); // 运行时报NPE错误
上面条件表达式中的a
在编译时被识别为Integer
类型,而非null
类型,按照上述规则条件表达式的最终类型为int
类型。由于条件表达式的结果操做数a
与最终类型不符,因此此时将对a
进行自动拆箱操做(a.intValue()
),因为a
运行时为null
,所以将报NPE错误。
byte a = 2; byte k = true ? a : 128; System.out.println(k); // 编译时报错。 不兼容的类型: 从int转换到byte可能会有损失
因为128超出了byte
的范围,所以返回值为int
,a
将转化为int
返回,而接收方k
为byte
类型,向低精度转化时须要显示强制转换才行。