Java8中byte、short、char、int等类型变量在作为操作数时的类型自动转换(含final修饰符)和其他延伸问题
最近在牛客网看到一道题,做错了,很是遗憾,所以在本贴上对相关知识点进行一个次总结。本次总结基于Java8
问题:
(一)分析当操作数只含有byte
,short
,char
,int
等类型时进行计算的过程中,类型被自动转换的问题(包含final
修饰符的情况)
(二)分析当操作数含有其他数据类型(如long
,float
,double
等类型时…)进行计算过程中时,类型被自动转换的问题
(三)分析表达式赋值过程的类型转换问题(包含final的情况)
(三)对byte
类型变量赋值在常量方面进行测试
(四)探讨a = a +1和a+=1的区别
代码测试
Java代码:
void statu1(){
/************************不存在final的情况下*********************/
byte byte_1 = 1,byte_2 = 2,byte_3 = 3;
char char_1 = '1',char_2 = '2',char_3 = '3';
short short_1 = 1,short_2 = 2,short_3 = 3;
int int_1 = 1,int_2 =2;
double double_1 = 1;
float float_1 =1;
long long_1 =1;
//操作数属于byte,short,char,int内的计算
//赋值语句左边不为int型
byte_3 = (byte)(byte_1 + byte_2);
short_3 = (short)(short_1 + short_2);
char_3 = (char)(byte_1 + short_2);
//赋值语句左边为int型
int_1 = byte_1 + byte_2;
int_1 = byte_1 + char_2;
int_1 = short_1 + int_2;
//操作数含long,float,double的计算
double_1 = double_1 + byte_1;
long_1 = short_1 + byte_1;
float_1 = long_1 + byte_1;
}
void statu2(){
/************************存在final的情况下*********************/
final byte bytef_1 = 1,bytef_2 = 2;
final char charf_1 = '1',charf_2 = '2';
final short shortf_1 = 1,shortf_2 = 2;
final int intf_1 = 1,intf_2 =2;
final double doublef_1 = 1,doublef_2 = 2;
final float floatf_1 = 1,floatf_2 = 2;
byte byte_1 = 3,byte_2 = 4;
char char_1 = '3',char_2 = 4;
short short_1 = 3,short_2 = 4;
int int_1 = 3,int_2 =4;
float float_1 = 1,float_2 = 2;
double double_1 = 1,double_2 = 2;
//操作数属于byte,short,char,int内的计算
//部分操作数被final修饰
byte_2 = (byte) (charf_1 + char_1);
int_2 = charf_1 + char_1;
char_2 = (char) (shortf_1 + byte_1);
int_2 = shortf_1 + byte_1;
//所有操作数被final修饰
byte_2 = charf_1 + bytef_2;
byte_1 = intf_1 + intf_2;
int_1 = bytef_1 + shortf_1;
float_1 = shortf_1 + charf_2;
double_1 = bytef_1 + intf_2;
//操作数存在高位类型(long ,float ,double)计算
//操作数类型相同
double_2 = doublef_1 + doublef_2;
double_2 = floatf_1 + floatf_2;
float_2 = floatf_1 + floatf_2;
//操作数类型不相同
float_2 = floatf_1 + intf_2;
double_2 = floatf_1 + intf_2;
double_2 = floatf_1 + bytef_2;
}
分析:
- 以上代码中,凡是能出现既是不出现编译错误。没有显式强制转换类型意味着已经进行了隐式类型转换。
结果分析
影响因素和原则:
这里存在多个可变因素:
- 赋值语句的变量类型是否属于int,byte,short,char等变量
- 赋值语句左边的数据类型是什么
- 赋值语句的数据类型是否被final修饰
遵守四个原则:
- Java中字面量整数值默认为Int类型(1 是 int) ,字面量浮点数值默认为double类型(1.0是double,不是float)
- Java计算表达式转换规则,由低向高转换,(double + float ,计算结果为double)(int < long < float < double)
- 窄化转换(narrowing conversion)需要显式写出了,扩展转换(widening conversion)由编译器自动完成
- final所修饰的变量不隐式转换,因为所修饰的变量不可变,编译器会把final修饰的变量用它的字面量值代替。
(被final修饰的变量为常量,编译器会在含有它的表达式中将其字面值替换变量符号)
等式的多个步骤:
首先我们得知道一个等式不是原子性的,是具有多个步骤的。
a = b + c; //简单的a = b + c
简单的一个等式a = b + c
实际上存在两个操作:
- 首先b + c 求和 (计算过程)
- 然后将和的值赋值给左边的a (赋值过程)
这其中隐含了两个计算操作和赋值操作两个步骤,每个步骤都会存在一些类型转换
分析过程
一、在没有final修饰的情况下:
-
操作数为byte,short,char,int类型范围计算:
- 表达式计算过程中当操作数为
byte
,short
,char
,int
型范围内,整数值都为int型,整数值在作为操作数时都将默认隐式转换为int
型,然后再进行计算,所以计算结果也为int
类型。我们可以简单的看成凡是在计算表达式中的byte
,short
,char
都是int类型,这也是其中的特别之处。
- 表达式计算过程中当操作数为
-
表达式赋值过程中 当等式左右两边类型不相等,需要分析是窄化转换 还是 扩展转换,窄化需要显式强制转换,扩展是隐式转换,所以将int类型的计算结果赋值给右边的byte变量,自然要强制转换。int -> byte (窄化)
例如:
byte = (byte)(short + char)
,同样分两步:- ⑴首先short 和 char提升了Int型进行计算,得到
Int
结果。 - ⑵ 将int型结果赋值给等式左边的byte变量,因为类型不等,同时int->byte,是窄化转换,必须显式强制转换
- ⑴首先short 和 char提升了Int型进行计算,得到
-
操作数存在高位类型(long ,float ,double)计算:
- ①表达式计算过程中如操作数的类型不同,则遵循Java计算表达式转型规则由低向高转换(int < long < float < double),计算结果转换成操作数中的最高位类型,如
byte+int
,计算结果为int
。int+long
,计算机结果为long
。double+long
,计算结果double
- ②表达式赋值过程中如果类型不同则需要看是窄化转换 还是 扩展转换,窄化需要显式强制转换,扩展是隐式转换
例如:
double= short + float
,同样分两步:- ⑴首先short提升类型为
Int
,然后和float进行计算,根据低向高转换**(Int < float)**,得到float
型的计算结果 - ⑵ 将float型结果赋值给等式左边的double变量,因为类型不等,所以需要转换,但是float -> double是扩展转换,由编译器自行完成,不需要我们显式操作。
- ①表达式计算过程中如操作数的类型不同,则遵循Java计算表达式转型规则由低向高转换(int < long < float < double),计算结果转换成操作数中的最高位类型,如
二、存在final修饰变量的情况下:
前提概念:
一、首先我们来区别一下符号常量和字面量常量,我们看一下这个定义
final int a = 10;
a
是一个符号常量,10
是一个字面量常量。这个过程中,我们将字面量常量10
赋值给一个符号常量a
。
这里不会出现任何问题,因为,符号常量a
是一个Int型常量,字面量常量时整数值,Java中的整数值默认为Int,所以可以赋值。如果是下面的情况:
final float b = 1.0; //error,Type mismatch: cannot convert from double to float
会提示我们不能将一个double类型赋值给一个float类型。因为字面量常量1.0
是一个浮点数,Java中的浮点数默认为Double类型。而符号常量b
是一个float类型,类型不等且double->float是缩窄转换,所以不能直接赋值。可以通过这样final float b = 1.0f
来指明字面量常量时一个float类型的字面量常量。
//源代码
final int a = 1;
int b = 2;
int c = a + b;
//反编译class文件得到的代码
int a = 1;
int b = 2;
int c = 1 + b; //在计算表达式中符号常量a被其字面量常量所代替
三、被final修饰的变量是符号常量,该符号常量的类型跟值一样都是不能改变的,所以final的byte类型的符号常量不会自动提升为Int类型。那计算时怎么办呢?不要紧,我们看下面
final byte a = 1;
float b = 2.0f;
System.out.println(a + b); //结果是3.0f
不是说好的符号变量a不会提升了int类型吗?那还怎么跟float类型计算?根据第二个概念,编译器会在计算表达式中替换符号常量为其字面常量,所以实际语句是System.out.println(1 + b);
,1
是一个字面量常量,在Java中默认为Int类型。所以Int类型和float类型计算过程中,根据表达式的低向高转换原则,自然得到的结果是float类型。所以符号变量类型的确也没有得到提升,只是实际运算的不是符号常量,而是它的字面量常量,这是两个不同的东西。
了解了上面三个概念,我们再看下面的分析:
-
部分操作数被final修饰:
- 表达式计算过程:
final byte + float
,这是计算过程,我想在前提概念三时已经说的很清楚,这里就简单说一下,final byte 被替换成其字面量整数值,所以是int 类型和 float做计算,因为类型不同,所以根据表达式转换原则,低向高转化,所以计算结果是float型 - 表达式赋值过程:中如果类型不同则需要看是窄化转换 还是 扩展转换,窄化需要显式强制转换,扩展是隐式转换。
例如:
int = (int)(final short + double)
分为两步:- ⑴ 首先
(final short + double)
,final short类型不会自动转换,但编译器会将符合常量short用其字面量常量代替,而已该字面量常量肯定是一个整型,在Java中默认为Int型。所以实际效果是**(int + double)**。根据计算表达式转换原则,低向高转换(int < double),所以计算结果为double. - ⑵ 然后将double类型的计算结果赋值给左边的int类型变量,因为类型不对等且是窄化转化(double -> int),所以必须显式强制转换
- 表达式计算过程:
-
所有操作数被final修饰:
- 表达式计算过程:
final double + float byte
,这是计算过程,double类型的符号常量的字面量常量肯定是一个double类型的浮点数,byte类型的符号常量的字面量肯定是一个int型的整数。所以实际效果是**(double + int)**之间的计算,根据计算表达式转换原则,低向高转换 ( int < double)。所以计算结果是一个double类型,又因为所有操作数都是final,所以编译器会直接帮你计算出结果,优化过程,所以final double + float byte
在编译后的代码中会被double类型的计算结果直接替换掉。 - 表达式赋值过程:
int = (int)double
,是一个赋值过程,如果类型不同则需要看是窄化转换 还是 扩展转换,窄化需要显式强制转换,扩展是隐式转换。所以这里很明显就是缩窄,所以要强制转换。
例如:
double = final byte + final float
,分为两个步骤:- ⑴ 首先
final byte + final float
,根据byte类型的符号常量的字面量常量必定是一个int型整数,如1
,而float类型的符号常量的字面量常量必定是一个float类型的浮点数,如1.0f
。所以实际效果是**(int + float)**。根据计算表达式转换原则,低向高转换(int < float),所以计算结果为float. - ⑵ 然后将float类型的计算结果赋值给左边的double类型变量,因为类型不对等,所以需要转换,但因为是扩展转化 (float-> double),所以隐式转换,不需要我们显式写出了。
- 表达式计算过程:
- 在byte,short,char,int类型范围内作为操作数计算的时候,自动类型转换是在计算之前就将类型转换为int型,还是计算之后将结果转换成Int型,实现的逻辑过程是怎么样?
- 在包含float,long,double等类型作为操作数计算的时候,数据的类型转换是计算之前将操作数转换还是在计算之后将结果的类型转换?
- 被final修饰操作数的情况下,变量不可变,那么类型转换是通过什么方式进行?是计算之后将结果根据等号左边的变量类型进行转换的吗?是由怎么样的机制实现的?
希望了解的朋友可以留言告诉我,谢谢!!感激不尽!!
注意:
(要区分两种转换)
- 计算之前的操作数的数据类型转换
- 计算之后得到的计算结果的数据类型转换
最终结论
计算过程中
- 计算表达式中的byte,short,char都会转换为Int,再进行计算
- 计算表达式中int,long,float,double之间的计算结果遵循表达式转换原则,低向高转换(int < long < float < double)
- 计算表达式中需要看是否有操作数是符号常量,如果有,就要看其字面量常量是什么类型
赋值过程中
- 赋值过程中,右边等式需要是不是一个常量(final),这时就要看其字面量值是什么类型,才能判断类型是否一致
- 赋值过程中,左右等式类型不一致。需看是窄化转换还是扩展转换
测试延伸
Java代码(一):
//byte的值的范围为-127~127,所以常量值结果在127范围内,依然是byte,但是计算结果超过127,就为Int类型
//需要强制转换
byte b9 = 3+3;
byte b10 = (byte) (127+3);
System.out.println("b9="+b9);
System.out.println("b10="+b10);
输出结果:
b9=6
b10=-126
分析:
- 当赋值语句的左边为
byte
类型,右边为两个变量时,就要参考上面的的情况,因为byte
的数据范围有限,是-127~127
,虚拟机无法马上得知两个变量的值是否已经超过了byte
类型的范围,所以需要强制转换为byte
类型才能赋值给左边。 - 当赋值语句的左边为
byte
类型,右边为两个常量时,在编译时就已经知道结果了,如代码所示,b9 = 3 + 3
,相当于b9 = 6
的效果,但是也有些情况要注意,如果常量的值处于byte
类型的数据范围(-127~127),则不需要强制转换(如3+3
),如果超过了则需要强制转换(如127+3
),相当于b9 = (byte)130
- byte类型的数据超过127时的强制转换的结果的运算逻辑参考网上,目前本博客不做出讨论。
Java代码(二):
final byte fa=1;
final byte fb=2;
byte b9 = fa + fb;
分析:
发现被final修饰之后的byte变量相加赋值给byte类型是可以编译通过的,并不会报错,这是为什么呢?从上一段测试中,我们可以得知,常量相加,在编译时已经知道了结果,相当于byte变量被赋值了一个数值。这里的变量被final修饰之后,对于Java而言,就相当于这两个变量已经成为了常量。所以不会报错。
Java代码(三):
byte b1=1,b2=2;
b1 = (byte) (b1 + 1);
b2 += 2;
分析:
以上是两种情况:
- 第一种是使用通常的+号运算符
- 第二种使+=运算符。
在第一种情况下,两个byte变量相加,操作数会被提升为int型再计算,所以计算结果是int类型,赋值给一个byte的变量,就必须使用显示强制类型转换,不然会报编译错误,类型不同,不允许赋值。
在第二情况下,不需要强制转换是不会报错的,为什么呢?是因为使用+=类似运算符进行计算的时候,是会将运算结果隐式强制转换等于等号左边变量的类型的。我们反编译一下编译之后的代码就可以看到。
byte b1 = 1;byte b2 = 2;
b1 = (byte) (b1 + 1);
b2 = (byte) (b2 + 2);
代码中的+=运算符在编译之后,实际的效果等于第一种情况,在编译时是会给运算结果加一个强制类型转换的。
参考资料
当然还有牛客网里大佬的解答。thanks
在此对参考过的网站和博客的作者表示感谢!!