语法话题
本期的话题内容为:
byte b = 1为什么是合理的?
也许大家会感到惊讶,这样的语句不是天经地义的吗?可是,字面值“1”是int类型,b是byte类型,int类型向byte类型转换,这是一种收缩类型转换,难道不应该使用类型转换运算符吗?
程序示例
我们来看如下的程序:
package test;
public class Test {
public static void main(String[] args) {
int i = 1;
// 编译错误,需要使用类型转换。
// byte b = i;
// short s = i;
// char c = i;
// 可以,没有编译错误。
byte b = 1;
short s = 1;
char c = 1;
}
}
在本例中,将int类型字面常量“1”分别赋值给byte,char,short类型的变量(第11 ~ 13行),都没有编译错误。然而,如果将int类型的变量i(值还是1)对以上三种类型的变量赋值时,就会产生编译错误(第7 ~ 9行)。
语法解析
上面的情况可以看做是一种特例。对于编译时的常量表达式S,执行如下的赋值操作:
T t = S;
如果满足以下条件:
- S为byte,short,char或int类型。
- T为byte,short或char类型。
- 表达式S的值,在类型T的取值范围之内。
则在赋值时,编译器能够自动完成收缩类型转换(如果需要的话)。
在刚才的程序中,使用int类型的变量i执行赋值时(第7 ~ 9行),变量i不是常量表达式,因此不符合这种特例,所以需要我们显式使用类型转换运算符,否则会产生错误。而对于第11 ~ 13行,字面值“1”为int类型的常量表达式,目标类型也满足T类型的要求(byte,short或char类型),另外,数值1也均在byte,short或char的取值范围之内,因此,编译器就能够执行隐式的收缩类型转换。
这种特例完全是为了方便,因为类似第11 ~ 13行的代码非常常见,如果没有这种特例,就需要程序员显式执行收缩类型转换,如:
byte b = (byte)1;
这样不仅在操作上非常繁琐,而且代码看起来也很别扭。
数值丢失
也许大家还有一种疑惑,因为收缩转换可能会造成数值量纲(数量级)的丢失,那这种隐式的收缩转换会不会不安全呢?
实际上是不会的,因为编译器会对常量表达式(S)的值进行检测,如果超过了目标类型(T)所能表示的范围,就会产生编译错误,例如:
byte b = 128; //编译错误!
并且,这种特例也有严格的上下文环境限制。其只适用于对变量赋值,而并不适用于方法(构造器)调用时的参数传递。下例将给予说明。
package test;
public class Test {
public void methodA(byte b) {
}
// public void methodA(long lo) {
//
// }
public static void main(String[] args) {
// 产生编译错误。
new Test().methodA(1);
}
}
这个程序是无法通过编译的,因为隐式的收缩转换发生在方法调用的上下文环境中,而不是之前介绍的赋值上下文环境。因此,隐式的收缩转换并不适用。同时,这也是必要的,如果没有这种限制,当出现两个重载方法的时候(程序中注释的方法),就不知道该如何选择,是将int类型(1)收缩转换成short,还是扩展转换成long呢?