整数型数据在Java中有 4 种表示方式,分别是十进制、八进制、十六进制、二进制。不过要注意的是,二进制的写法是在 Java7 中引入的,对于 Java7 之前的版本不支持该语法。默认为十进制,以 0 开始表示八进制,以 0x 开始表示十六进制,以 0b 开始表示二进制。十进制、八进制、十六进制有什么区别?请看:(二进制可以参考之前的文章:计算机基础知识——二进制)
十进制:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17...
八进制:0,1,2,3,4,5,6,7,10,11,12,13,14,15,16,17,20,21...
十六进制:0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,10,11...
接下来我们在代码中试验一下以上几种写法,请看以下代码:
public class IntegerTypeTest01 {
public static void main(String[] args) {
System.out.println("十进制:" + 10);
System.out.println("八进制:" + 010);
System.out.println("十六进制:" + 0x10);
System.out.println("二进制:" + 0b10);
}
}
我们对以上的程序进行编译与运行,请看下图运行结果:
通过测试确实可以看到八进制 10 是 8,十六进制的 10 是 16,二进制的 10 是 2.在实际的开发中大部分还是直接使用十进制的方式,因为十进制对于我们来说更直观,程序的可读性会更好一些。
在 Java 语言当中,整数型字面量被当作 int 类型处理,也就是说在程序中只要遇到整数型的数字,该数字会默认被当做 int 类型来处理,如果想表示 long 类型则需要在字面量后面添加 L/l,建议大写 L,因为小写 l 和 1 不好区分。请看以下程序:
public class IntegerTypeTest02 {
public static void main(String[] args) {
int a = 10;
long b = 10L;
}
}
在以上的代码中 int a = 10; 表示声明一个 int 类型的变量 a,然后给 a 变量赋值 10,其中 10是一个整数型字面值,根据以上规则,10 默认被当做 int 类型来处理,那么 int a = 10; 就表示 int 类型的字面量 10 赋值给 int 类型的变量 a,这个过程是不存在类型转换的。
另外在以上代码中 long b = 10L; 表示声明一个 long 类型的变量 b,然后给 b 变量赋值 10L,由于 10 后面添加有 L,则编译器会将 10L 当做 long 类型来处理,long 类型的字面量 10L 赋值给 long 类型的变量 b,这个过程也是不存在类型转换的。
接下来我们在以上代码的基础之上继续编写,请看以下代码:
public class IntegerTypeTest02{
public static void main(String[] args){
int a = 10;
long b = 10L;
long c = 10;
System.out.println(c);
}
}
我们可以看到在第5行新增了一行代码:long c; ,这行代码是什么原理呢?我们先来编译以下,看看是否符合 Java 的语法规则,请看下图编译结果:
接下来我们在运行一下,请看下图运行结果:
通过以上的测试我们可以看到 long c = 10; 这种编写方式符合语法规则,并且也可以正常运行,我们来分析一下,10 是整数型字面量,按照以上规则,Java 中会将 10 默认当做 int 类型处理,而 c 变量是 long 类型,int 类型可以赋值给 long 类型的变量吗?答案是可以的,因为我们已经测试过了。这是因为 int 占用 4 个字节,而 long 占用 8 个字节,在 Java 中小容量可以直接赋值给大容量,这个过程被称为自动类型转换。
接下来我们对以上代码继续编写,请看以下代码:
public class IntegerTypeTest02{
public static void main(String[] args){
int a = 10;
long b = 10L;
long c = 10;
System.out.println(c);
int d = c;
}
}
我们可以看到在第 7 行新增了一行代码,int d = c; ,我们先对以上代码进行编译,请看以下编译结果:
我们可以看到编译器报错了,也就是以上编写方式不符合 Java 语法规则,我们来看一看为什么,首先我们看到错误提示信息是:不兼容的类型,从 long 转换到 int 可能会有损失。这是因为 c 变量是 long 类型占用 8 个字节,而负责接收的 d 变量是 int 类型占用 4 个字节,很明显是大容量转换成小容量,好比一大玻璃杯中的水倒向小玻璃杯,最终的结果可能会使水溢出,因为小玻璃杯可能放不下。编译器检测到这种情况的时候就不会自作主张了,需要程序员来指定,因为数据的损失需要程序员知晓,毕竟数据损失是一件很严重的事情,而编译器自身是不会负责的,于是 Java 中规定大容量如果需要转换成小容量,则程序员必须手动添加强制类型转换符才能编译通过,这个过程我们称为强制类型转换。我们对以上代码进行修改,请看以下修改之后的代码:
public class IntegerTypeTest02 {
public static void main(String[] args) {
int a = 10;
long b = 10L;
long c = 10;
System.out.println(c);
int d = (int)c; // 强制类型转换
System.out.println(d);
}
}
我们可以看到将第 7 行的代码修改为:int d = (int)c; ,这就是强制类型转换,语法格式是在需要强转的数据前添加小括号,小括号中写上要转换的类型,我们对以上的程序编译并运行,请看下图结果:
通过以上的测试我们得出这样一条结论:一个数据在赋值给一个变量的时候存在三种不同的情况,第一种情况是类型一致,不存在类型转换;第二种情况是小容量可以自动赋值给大容量,称为自动类型转换;第三种情况是大容量不能直接赋值给小容量,大容量如果一定要赋值给小容量的话,必须添加强制类型转换符进行强制类型转换操作。不过需要注意的是,强制类型转换在使用的时候一定要谨慎,因为可能会导致精度损失,因为大杯水倒入小杯中,可能会导致水的溢出,不过这也不全都是,也可能精度不会损失,如果大杯中的水很少,这个时候倒入小杯中也可能是不溢出的。就像以上的运行结果,虽然进行了强制类型转换,但并没有损失精度。接下来我们一起来看看精度损失是什么情况,请看以下代码:
public class IntegerTypeTest03 {
public static void main(String[] args) {
int a = 300;
byte b = (byte)a;
System.out.println("b = " + b);
}
}
我们可以看到在以上代码中,int 类型的变量 a 强转为 byte 类型,我们对以上代码进行编译并运行,请看下图运行结果:
4 个字节的 int 类型 300 强转为 1 个字节的 byte 类型,最终的结果是 44,为什么呢?这是因为首先 int 类型的 300 对应的二进制码是:00000000 00000000 00000001 00101100,强制类型转换的时候会变成 1 个字节,这个时候底层是将前 3 个字节砍掉了,也就是最后的二进制码是:00101100,这个二进制码对应的是 44。所以精度损失之后的结果就是 44 了。接下来我们再来看一下精度损失之后成为负数的情况,请看以下代码:
public class IntegerTypeTest04 {
public static void main(String[] args) {
int a = 150;
byte b = (byte)a;
System.out.println("b = " + b);
}
}
我们对以上的代码进行编译和运行,请看下图结果:
为什么以上的运行结果是 -106 呢?这是因为计算机在任何情况下都是采用二进制补码的形式存储数据的(为什么采用二进制补码形式存储数据,这里就不再赘述了,不做学术研究)。计算机二进制编码方式包括原码、反码、补码。对于正数来说原码、反码、补码是同一个。对于负数来说,负数的反码是在其原码的基础上, 符号位不变,其余各个位取反,例如:-15 的原码是:10001111,-15 反码是:11110000。负数的补码是其反码再加 1。例如:-15 的补码是 11110000 加 1:11110001。换句话说 -15 最终在计算机上会采用 11110001 二进制来表示。
我们再来看看以上的程序:int a = 150。4 个字节的 150 对应的二进制是:00000000 00000000 00000000 10010110,强转时前 3 个字节砍掉,最终计算机存储的二进制为: 10010110,我们之前说过最终存储在计算机中的是二进制补码形式,也就是说 10010110 现在是二进制补码形式,我们通过补码推出原码,负数的补码是反码+1,所以 10010110 减 1 就是反码 10010101,反码的符号位不变,其余位取反就能得出原码:11101010,而这个值就是-106。对于以上原理大家了解即可,在实际的开发中很少使用。
接下来我们再来看一段程序,分析以下程序错在哪里,为什么以及怎么解决?
public class IntegerTypeTest05 {
public static void main(String[] args) {
long num = 2147483648;
}
}
我们对以上的程序进行编译,请看下图结果:
我们可以看到,编译报错了,为什么呢?原因是:Java 程序见到 2147483648 这个整数的时候,默认将其当做 int 类型来处理,但这个数字本身已经超出了 int 类型的取值范围(int 类型最大值是 2147483647),所以编译报错了,注意:这里编译报错的原因并不是说 long 类型存不下,long 类型的变量完全可以存储这个数字,以上程序出现的错误是在赋值之前,还没有进行到赋值运算,数字本身已经超出 int 类型范围,自己崩掉了。怎么解决以上的问题呢?其实很简单,我们只要让 java 程序认为 2147483648 是一个 long 类型的数据就行了,也就是说在该数字后面添加 L 问题就解决了(long num = 2147483648L;)。
接下来,一起来看一下以下程序是否可以编译通过,请看代码:
public class IntegerTypeTest06 {
public static void main(String[] args) {
byte b = 1;
System.out.println(b);
}
}
我们来分析一下以上的代码:byte b = 1;,1 是整数型字面量,在 java 中默认被当做 int 类型来处理,int 类型占用 4 个字节,b 变量是 byte 类型占用 1 个字节,根据上面说的,大容量无法直接赋值给小容量,要想赋值需要进行强制类型转换,这里没有强转,所以按理说编译是报错的,接下来我们来看一下编译结果,请看下图:
我们发现,编译通过了,这是为什么呢?我来给大家解释一下,这是因为在 Java 语言有这样一条规定,如果当一个整数型字面量没有超出 byte 类型取值范围时,可以直接赋值给 byte 类型变量。那么如果整数型字面量超出 byte 类型取值范围会怎样呢?我们来测试一下,请看以下代码:
public class IntegerTypeTest06 {
public static void main(String[] args) {
byte b = 1;
System.out.println(b);
byte x = 127;
System.out.println(x);
byte z = 128;
System.out.println(z);
}
}
我们对以上的代码进行编译,请看下图编译结果:
我们可以看到编译报错了,错误信息是第 7 行:不兼容的类型。从 int 转换到 byte 可能会有损失。对于以上程序的第 5 行并没有报错。以上程序要想编译通过必须进行强制类型转换,请看以下代码:
public class IntegerTypeTest06 {
public static void main(String[] args) {
byte b = 1;
System.out.println(b);
byte x = 127;
System.out.println(x);
byte z = (byte)128;
System.out.println(z);
}
}
我们对以上的程序进行编译并运行,请看下图结果:
我们通过测试结果可以看出程序正常编译并运行了,这也印证了我们上面所说:当整数型字面量没有超出 byte 类型取值范围时,可以直接赋值。不过,如果超出了 byte 类型的取值范围,在使用时必须进行强制类型转换。但需要注意的是强制类型转换会导致精度的损失,例如以上代码中 int 类型的 128 强转为 byte 之后结果是-128(这是因为计算机以二进制补码形式存储数字),还是要谨慎使用。
其实除了 byte 类型有这样的规则之外,short 和 char 也具有同样的规则,接下来我们先对short 进行测试,代码如下所示:
public class IntegerTypeTest07 {
public static void main(String[] args) {
short s1 = 32767;
System.out.println(s1);
short s2 = 32768;
System.out.println(s2);
}
}
我们对以上的代码进行编译,请看下图编译结果:
通过以上的结果可以看出第 3 行代码编译通过了,但是第 5 行编译报错了,这是因为 short类型最大值是 32767。对于第 5 行的 32768 已经超出了 short 类型取值范围,同样如果要使用的话需要进行强制类型转换,这里就不再演示了。接下来我们再来看一看 char 类型,char 同样满足以上规则,当没有超出 char 类型取值范围时,可以直接赋值,请看以下代码:
public class IntegerTypeTest08 {
public static void main(String[] args) {
char c1 = 97;
char c2 = 98;
System.out.println(c1);
System.out.println(c2);
}
}
我们对以上的程序进行编译并运行,请看下图结果:
通过以上的测试可以看出当没有超出 char 类型取值范围的时候,整数型字面量是可以直接赋值给 char 类型变量的,但结果为什么会是字符 a 和 b 呢?这是因为程序 char c1 = 97;在实际执行的时候存在隐式的类型转换,会自动将 int 转换成 char,由于 char 最终是一个字符,而 97正好是字符 a 的 ASCII 码,所以最终结果是字符 a 和 b。那么如果超出 char 类型取值范围会怎样呢(char 最大值是 65535)?请看以下代码:
public class IntegerTypeTest08 {
public static void main(String[] args) {
char c1 = 97;
char c2 = 98;
System.out.println(c1);
System.out.println(c2);
char c3 = 65536;
}
}
我们对以上代码进行编译,请看下图编译结果:
通过以上测试我们同样看到一旦超出 char 类型取值范围时就不能直接赋值了,要修改以上的错误也是需要进行强制类型转换操作,这里就不再演示了。
综上所述,大家记住一个结论:当一个整数型的字面量没有超出 byte,short,char 的取值范围,可以将该字面量直接赋值给byte,short,char 类型的变量,如果超出范围则需要添加强制类型转换符。
最后做一下总结,本文主要讲解了讲解了这么几个问题:第一,Java 中的整数型字面量有四种表示方式,但最常用的还是十进制;第二,整数型字面量被当做 int 处理,如果想当做 long 处理,需要在后面添加 L 或 l;第三,小容量转换为大容量被称为自动类型转换;第四,大容量转换成小容量称为强制类型转换,强转时需要添加强制类型转换符,但要注意强转可能损失精度;第五,当整数型字面量没有超出byte、short、char 的取值范围,可直接赋值。
(本文仅代表个人观点,不喜勿喷!)