前言

Java 是一种强类型语言,不过在 JDK 10 版本也推出了类似 JavaScript 语言的 var 关键字替代实际类型,可以用在局部变量的声明上,比如可以这样写:var name = "James Yin";,但是在编译时任然需要类型检查。这里就不展开讲了,目前我们开发主流的 JDK 版本还是 1.8,该系列文章也会基于这个版本编写。

本文主要介绍一下 Java 数据类型,结合实际开发,了解它们的特性和使用场景,避免一些常见的错误。

版本约定

在 Java 中,一共有 8 种基本类型(primitive type),其中有 4 种整型、2 种浮点类型、1 种用于表示 Unicode 编码的字符单元的字符类型 char 和 1 种用于表示真值的 boolean 类型。
数据类型 - 图1
接下来我们一一讲解,分享实际开发过程中的使用方式和注意点。

类型介绍

整型

整型用于表示没有小数部分的数值,它允许是负数。Java 提供了 4 种整型,具体内容如下表所示。

类型 存储空间(字节) 取值范围
int 4 -2 147 483 648 ~ 2 147 483 647(正好超过 20 亿)
short 2 -32 768 ~ 32 767
long 8 -9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807
byte 1 -128 ~ 127

关于整形数据的使用,我们要注意如下几点:

  1. Java 没有任何无符号(unsigned) 形式的 int、 long、 short 或 byte 类型,都是有负数的;

  2. 需要大概清楚各个类型的取值范围,int 类型最常用,我们要记住它,可能会超过 20 亿的情况就不要使用 int 类型了,可以使用更大取值范围的 long 类型;

  3. long 类型的数据,我们在定义的时候要加一个后缀 L 或 l,建议用大写 L(如 4000000000L),好区分,不要用小写 l(如 4000000000l),不好区分,很像 1;

  4. 十六进制数值有一个前缀 0x 或 0X(如 0xCAFEL)。八进制有一个前缀 0,例如,010 对应八进制中的 8。很显然,八进制表示法比较容易混淆, 所以建议最好不要使用八进制常数;

  5. 从 Java 7 开始,加上前缀 0b 或 0B 就可以写二进制数,例如,0b1001 就是 9;

  6. 同样是从 Java 7 开始,还可以为数字字面量加下划线,如用 1_000_000 或 0b1111_0100_0010_0100_0000 表示一百万,这些下划线只是为了让人更易读,Java 编译器在编译的时候会去除这些下划线。

    浮点类型

浮点类型用于表示有小数部分的数值。在 Java 中有两种浮点类型,具体内容如下表所示。

类型 存储空间(字节) 取值范围
float 4 大约 ± 3.402 823 47E+38F(有效位数为 6 ~ 7 位)
double 8 大约 ± 1.797 693 134 862 315 70E+308(有效位数为 15 位)

单精度 float 类型的使用场景非常少,基本不怎么使用。float 类型的数值有一个后缀 F 或 f(如 3.14F),没有后缀 F 的浮点数值(如 3.14)默认为 double 类型。

需要注意的是,浮点数值不适用于无法接受舍入误差的金融计算中。例如,命令 System.out.println(2.0-1.1) 将打印出 0.8999999999999999, 而不是人们想象的 0.9。

这种舍入误差的主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确地表示分数 1/10, 这就好像十进制无法精确地表示分数 1/3 一样。对于金融计算的业务,建议使用 BigDecimal 类。

char 类型

char 类型原本用于表示单个字符,具体内容如下表所示。不过,现在情况已经有所变化。如今,有些 Unicode字符可以用一个 char 值描述,另外一些 Unicode 字符则需要两个 char 值。

类型 存储空间(字节) 取值范围
char 2 0~65535(0~2^16-1)

char 类型的字面量值要用单引号括起来。 例如:’A’ 是编码值为 65 所对应的字符常量。它与 “A” 不同,”A” 是包含一个字符 A 的字符串,char 类型的值可以表示为十六进制值,其范围从 \u0000 到 \Uffff 。

boolean 类型

boolean 类型有两个值:false 和 true,用来判定逻辑条件。整型值和布尔值之间不能进行相互转换。

使用特性

单纯分析数据类型没有太大的意义,一般会结合运算符来一起使用,所以接下来我们结合运算符来说说在实际使用过程中,需要注意的一些问题。

整数类型溢出

Java 整数默认是 int 类型,小数默认是 double 类型,思考如下的计算结果存在哪些问题。

  1. int a = 10000 * 10000 * 22 * 2;
  2. // 输出:105032704,正确结果:4400000000
  3. System.out.println(a);

很多人不注意 int 类型的取值范围,貌似 int 类型可以处理所有的整数,这个是很可怕的,因为一旦你的计算结果超过 int 类型的取值范围后,最终结果将不可控,你不知道最终到底会是一个什么值。

比如上面的输出结果和正确的值完全不一样,int 类型的最大取值是 2,147,483,647,44 亿已经超出 int 类型的取值范围,即数据溢出了,这个时候就不能再使用 int 类型了,需要使用取值范围更大的整数类型 long 类型。

这种低级错误,犯了是要打板子的……,但是我不止一次在实际的项目代码中,发现有人就会犯类似的错误。

接下来,我们尝试修改一下代码,看看下面哪种写法能正确输出计算结果:

  1. // 写法1:105032704
  2. long a1 = (long)(10000 * 10000 * 22 * 2);
  3. System.out.println(a1);
  4. // 写法2:-4189934592
  5. long a2 = 10000 * 10000 * 22 * (long)2;
  6. System.out.println(a2);
  7. // 写法3:4400000000
  8. long a3 = (long)10000 * 10000 * 22 * 2;
  9. System.out.println(a3);

只有写法 3 才能正确计算出结果,其他写法都不正确。为什么呢?接下来我们分析一下三种写法的计算步骤,得出他们的问题。

  • 写法1:它分成两块,一块是整数计算、一块是类型转换,类型转换是在整数计算结果出来后执行的,即10000 10000 22 * 2 计算得出 105032704 后,再把 105032704 强转成 long 类型。它在整数计算过程中使用的还是 int 类型,还是会出现数据溢出的情况。

  • 写法2:10000 10000 22,这块整数计算过程中使用的还是 int 类型,已经出现数据溢出,到 * (long)2 这块后,只是乘以 2,且类型自动升级到 long 类型。

  • 写法3:(long)10000,这块先把整个表达式的数据类型自动升级到 long 类型,再做后面的计算,44 亿在 long 类型下不会出现溢出情况。

    类型自动升级

在表达式计算中,还存在一个问题,当一个 Java 算术表达式中包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升。

Java 数据类型的自动提升规则如下:

  • 所有 byte 型、short 型和 char 型将被提升到 int 型。
  • 整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型。操作数的等级排列如下图所示,位于箭头右边类型的等级高于位于箭头左边类型的等级。

以下是 Java 表达式中类型提升等级。
image.png

数值类型之间的转换

我们经常需要将一种数值类型转换为另一种数值类型,下图给出了数值类型之间的合法转换。
image.png
在图中有 6 个实心箭头,表示无信息丢失的转换;有 3 个虚箭头,表示可能有精度损失的转换。

默认初始化

当基本数据类型作为类的成员变量时,即使不显式进行初始化,Java 也会为它们分配一个默认值,防止程序运行时错误。然而这种保证却并不适用于“局部”变量。

类型 默认值
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d
char \u0000
boolean false

我们知道了基本数据类型在作为类的成员变量时,会有默认值,而这些默认值只是 Java 初始化出来的,不是真实数据。

这里有一个问题,数据库表对应的实体类中的字段是使用基本数据类型呢还是使用引用类型呢?

考虑到基本数据类型会存在默认值,影响数据的准确性,所以在数据库表对应的实体类中不建议使用基本数据类型,建议使用引用类型,引用类型的默认值是 null。

总结

本章简单介绍了基本数据类型,以及结合实际开发,分析在使用过程中的一些注意点。该系列接下来的文章基本都是这个模式,介绍为辅,使用为主,毕竟 Java 的语法本来就是用来使用的。

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/nzxh9n 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。