一、基本数据类型转换
在上一章节中,我们已经基本了解了JAVA八大基本数据类型,在实际开发时,不同数据类型的相互转换时有发生。JAVA对类型转换有着一定的语法规定。实际上,类型转换是一个比较大的话题,其中涉及到了面向对象思想和JAVA对面向对象中对“多态”这一特征的支持。在这篇文章中我们不对类型转换做深入研究,下面我们先来了解一下JAVA八个基本数据类型之间的转换会有一些什么样的语法规则。
##1.类型提升##
所谓类型提升,指的是表数范围小的变量自动转换为表数范围大的变量,换句话说,能够存储数值的范围较小的类型向能够存储数值的范围较大的类型进行转换。即将小的提升为大的。而这种类型提升的发生,主要在赋值语句中。例如赋值语句:int a = b;在JAVA中,=符号并不是数学中的等于号,而是赋值运算符,即将=操作符右边的操作数的值赋给(赋予)操作符右边的操作数,也就是说,将变量b的值赋给变量a。
在上面这张图中,箭头左边的类型可以自动转换为箭头右边的类型,反之则不行。
public class Main {
public static void main(String[] args) {
char c = '我';
int i1= c;//char类型的变量c在赋值过程时自动提升为int类型
long l = 999999999999999l;
float f = l;//long类型的变量l在赋值过程时自动提升为float类型
}
}
如果对基本的数据类型进行计算,只要类型比int型小的类型,在运算前都会自动提升为int类型,然后再进行运算。对于表达式中有大于int类型的值时,将会先全部提升为其中类型最大的那个类型,然后再进行运算。也就是说,无论表达式如何复杂,最终的运算结果的类型取决于表达式中所出现的最大的那个类型。
关于类型的大小排列顺序遵守上图中的排列顺序,即箭头右边的类型大于箭头左边的类型。
public class Main {
public static void main(String[] args) {
/* 表达式中的类型提升 */
byte b = 120;
short s = 1111;
int x = b + s;//在计算=号右边的值之前,=右边的每一项数值都会提升为int型
//表达式:2017 * 3.14f + 3.0中3.0所属的类型最大,因此计算结果为double型
//里不能将double型转换为float型,所以这条语句是错误的。
float d = 2017 * 3.14f + 3.0;
//正确语句
double d2 = 2017 * 3.14f + 3.0;
//下面这两条语句都是正确的,是因为ll类型可以提升为float类型。
long ll = 999999999999l + 20;
float ff = 999999999999l + 20;
}
}
总之,类型提升遵守小类型可自动转换为大类型的原则。在表达式中,运算表达式之前,会将表达式的每一项全部转换为其中类型最大的那一项的类型(小于int型的直接转换为int型),表达式的计算结果的类型取决于表达式中所出现的最大的那个类型。
##2.强制类型转换##
在了解了类型提升之后,我们知道,小的类型可以往大的类型转,那么大的类型是否可以向小的类型转呢?列如下面的代码:
public class Main {
public static void main(String[] args) {
long bigNum = 999999l;
int smallNum = bigNum ;
}
}
实际上,这样写,在编译时是不通过的。因为小的类型往大的类型转,是无法自动完成的,
但是JAVA支持强制类型转换(可以理解成手动转换),这里用到了类型转换运算符(type),看下面代码:
public class Main {
public static void main(String[] args) {
long bigNum = 999999l;
int smallNum = (long)bigNum;
}
}
即可以将类型转换运算符右边的值强制转换为运算符括号中指定的类型,上面是将大类型long型强制转换为小类型int型;
实际上,这种强制的类型转换会造成一定的负面影响。我们将类型大的(存储值的范围大的)变量比作一个大瓶子,将类型小的(存储值的范围小的)比作一个小瓶子。往瓶子里面转水的过程就是给变量赋值的过程,我们将小瓶子里面的水倒进大瓶子里面自然不会有问题(自动类型转换)。当进行强制类型转换时,类似于把一个大瓶子里的水倒入一个小瓶子,如果大瓶子里的水不多还好,但如果大瓶子里的水很多,将会引起溢出,从而造成数据溢出。这种转换也被称为"缩小转换"。(此处引用《JAVA疯狂讲义》书中一文的举例)
在强制转换时,如果将浮点型的float和double型的变量转换为整数型,会造成浮点型的小数位丢失,这称为“截尾”。如: float f = 3.14f; int x = (int)f; 那么最终x的值为3;3.14后面的小数位将被舍弃。如果将一个超过小类型变量所存储的值的范围的大数值强制转换为这个小类型时,将会造成“溢出”。而关于溢出,JAVA对于溢出转换的结果计算原理,感兴趣的可以自行查询相关资料进行了解,这里不在论述。因为在实际开发中,我们要尽量避免这种对于基本数据类型的强制类型转换,实际上,类型转换运算符更多的是用于自定义对象和对象之间转换(向下转型),而这是后面章节要讲述的内容。
二、操作符
对于以上操作符,这里不对每一个操作符进行细述,下面只针对几个操作符进行重点讲解:
##1.自增、自减操作符##
前置自增:在计算表达式时,先对前置自增操作符左边的变量做加1的运算,然后运算表达式的结果。
int x = 5;
int y = ++x + 20;
先将x加1,结果为6,然后再做6 + 20的运算,最终y=26,x=6;
后置自增:在计算表达式时,先运算出表达式的结果,然后对后置自增操作符左边的变量做加1的运算
int x = 5;
int y = x++ + 20;
先计算5+20,结果y=25,然后将5加1,最终y=25,x=6;
前置自减和后置自减的运算方式同上,只不过是减1而已。实际上,自增和自减的前置与后置的区别就在于,被自增或自减操作的操作数在计算表达式前实行自增减和在计算表达式后实行自增减的区别而已,前置自增减会影响计算结果,而后置自增减不会影响计算结果,但他们最后都会影响被操作符操作的变量。
##2.关系操作符中的短路问题##
在使用&&、||操作符时,当出现只计算一个操作数(操作数既可以是一个字面值、变量、方法也可以是一个表达式)后就决定了整个计算结果后,就不对另一个操作数进行计算的情况称为"短路"。实际上,短路是一种更高效的计算方式,因为一旦计算了一个操作就可以得到结果,为什么还要多花一些时间再去计算另一个呢。所以,在实际开发中,做关系运算时,尽量使用用&&、||操作,但必须要注意它是短路的计算方式,而且是先计算前一个操作数的。另外需要注意的是,由于是短路的计算,在使用&&、||操作符时,如果计算完操作符左边的操作数后就已经决定了整个结算结果后,那么操作符右边的操作数就不会进行计算,如果右边的操作数是一个方法,并且你一定要求在计算时调用这个方法,请使用&、|操作符,否则它不会去执行调用这个方法。
##3.操作符的优先级##
优先级的问题即是一个计算顺序的问题,JAVA对多个操作符的计算顺序做了特别规定。
列如数学上对加减乘除实现先乘除后加减的原则,JAVA亦是如此,但这只是其中之一。详细见下表:
在开发中,一个表达式过于复杂时,当你不记得或者不确定优先级时,使用括号,因为被括号括起来的最先运算。
JAVA中的操作符在JAVA中是非常常用的一个知识点,但其中的按位操作符和取位操作符用得较少,甚至用不到,因为这两种操作符的存在实际上是由于JAVA历史性遗留的一个问题,为了兼容对底层硬件的操作。所以对于这两种运算符大可不必着重掌握。类型转换操作符要慎用,容易出现类型转换错误的异常。当if-else语句过于简单时,可以用三元操作符去代替它,可以使语句看起来更简洁。