原始类型
Java 是强类型语言,在编译时会检查所有变量、表达式的类型是否兼容。Java 为数据定义了 8 种原始类型(primitive type),分为 4 组:
- 整型:byte、short、int、long,表示整数。
- 浮点数:float、double,表示小数。
- 字符:char,表示字符集中的元素。
- Boolean:boolean,表示 true/false 值。
上述数据类型构成了自定义数据类型的基础。原始类型存在的原因之一是效率高。Java 的原始类型所占位数的大小固定,不像 C/C++ 取决于具体机器环境。
整型
4 种整型变量都是有符号数,Java 不支持无符号数。当 byte 或 short 类型参与表达式计算时,类型会提升为 int,然后再进行计算求值。
类型名 | 位宽 | 范围 |
---|---|---|
byte | 8 | \(-2^{7}\) to \(2^{7}-1\) |
short | 16 | \(-2^{15}\) to \(2^{15} - 1\) |
int | 32 | \(-2^{31}\) to \(2^{31} - 1\) |
long | 64 | \(-2^{63}\) to \(2^{63} - 1\) |
浮点型
浮点数又称实数。Java 实现了标准的浮点类型和操作符集(IEEE-754)。
类型名 | 位宽 | 范围 |
---|---|---|
double | 64 | \(4.9\times 10^{-324}\) to \(1.8 \times 10^{308}\) |
float | 32 | \(1.4\times 10^{-45}\) to \(3.4\times10^{38}\) |
字符
Java 使用 Unicode 码表示字符。Java 被创造的时候,Unicode 需要 16 位,所以 char 占 16 位。 char 可以参与部分运算,此时 char 代表字符还是 Unicode 码值,取决于上下文。char 可以使用自增运算符。
Boolean
boolean 类型的值是逻辑值,用于控制语句和循环语句。任何关系操作符产生的结果都是 boolean 类型。
字面值
整型字面值
默认的整数是 int 类型的十进制字面值(literals)。以 0 开头的字面值是八进制数,以 0x 或 0X 开头的字面值是十六进制数,以 0b 开头的字面值是二进制数。字面值中间可以使用下划线分隔,编译时自动去除,字面值的开始和结尾不允许使用下划线。下划线可以相连使用。字面值最后添加小写的 l(字母)或 L 表示是 long 类型。
浮点数字面值
默认的浮点数字面值是 double 类型。字面值后面添加 f 或 F 表示该字面值是 float 类型,添加 d 或 D 表示是 double 类型。可以使用科学计数法表示浮点数,此时底数使用 e 或 E 表示 10,如 1.0e-10 表示 \(1.0\times10^{-10}\)。使用 16 进制数表示时,使用 p 而不是 e 表示底数。如 0x12.2p2 表示 \((1\times16^{1}+2\times16^{0}+2\times16^{-1})\times 2^{2}=72.5\)。即 p 前面的数以 16 为基数,p 表示底数 2。下划线的用法和在整数中相同,也可用于小数部分。
Boolean 字面值
boolean 类型的字面值不能转换成其他类型,true 不等于 1,false 不等于 0。只能同类型赋值。
字符字面值
字符字面值可以将 Unicode 码值转成整数值进行运算。单引号括起来的单个字符为字符字面值。单引号内部以反斜线开头,紧跟着 3 位八进制数或者以'\u'开头,后面紧跟着 4 位十六进制数,都表示字符字面值。分别是八进制表示和十六进制表示。
字符串字面值
字符串字面值由双引号括起来的多个字符序列表示。每个字符都可以按照字符字面值的方式表示。Java 中字符串整体必须位于同一行,字符串内部不能直接换行。
变量
type identifier [= value,][, identifier...]
type 可以是原始类型、类的名称、接口的名称。identifier 是创建的变量名字。后面可以加上一个常量进行初始化。初始值的类型必须和变量声明的类型相同或者兼容。变量必须先声明再使用,否则编译不通过。
变量可以在声明的时候通过调用方法、使用其他变量或字面值进行初始化。
每当创建一个新块,同时也创建了一个新的作用域。在一个作用域内定义的变量在该作用域外不可见。变量的生命周期从该变量声明进入作用域开始,离开作用域为止。生命周期由作用域决定。虽然作用域可以嵌套,但是不能在内层作用域中声明和外层作用域中同名的变量。
类型转换
自动类型转换
当 1)两种类型兼容 2)目标类型可表示范围大于源类型 两个条件满足时,类型转换自动完成,此时称为 widening conversion。整数和浮点数符合这个规则,char 和 boolean 不兼容,同时数值类型也不会自动转换成这两种类型。
不兼容类型转换
当把一个可表示范围大的类型转换为可表示范围小的类型时,会有精度损失,因此不会发生自动类型转换,需要手动进行强制类型转换。int 转 byte 的方式是用待转的 int 值模 byte 可表示的最大值。当从浮点数转换到整数时,小数部分会截断,去除,整数部分取模。
targetType variable = (targetType)value
表达式中的自动类型提升
当 byte、short 和 char 类型在表达式中参与计算时,类型会自动转换成 int,中间结果是 int,最终结果会转换成(如需要)目标结果类型。
类型提升规则
计算时,表达式中的 byte、short 和 char 类型的值会提升为 int 类型。
如果参与运算的数类型为 long,则整个表达式类型提升为 long;
如果参与运算的数类型为 float,则整个表达式类型提升为 float;
如果参与运算的数类型为 double,则整个表达式类型提升为 double。
数组
一维数组
创建一维数组分两步:首先创建一个数组变量,然后使用 new 分配一块存储数组的内存,用数组变量引用该数组。
type varName[];
varName = new type[num];
// 下面一行等同于上面两行
type varName[] = new type[num];
定义数组时,如果没有赋初值,当数组类型为数值类型时,初始值默认为 0;为 boolean 类型时,默认为 false;为引用类型时,默认为 null。
type varName[] = {element1, element2,...};
给数组赋初值,但不指定数组大小时,会自动创建大小和初始元素个数相等的数组。
当使用不在有效索引范围内的索引访问数组时,会产生运行时错误。
多维数组
多维数组是数组的数组。
type varName[][] = new type[num][num];
由于多维数组是数组的数组,定义时可以仅指定第一维(最左边索引)的维数,剩下的维数可以之后指定,也可以指定不同大小的维度。
int a[][] = new int[2][];
a[0] = new int[1];
a[1] = new int[5];
多维数组初始化时,初始值可以是字面值、表达式。处于同一维的值使用大括号括起来。
int a[][] = {
{...},
{...}
};
另一种声明数组的语法
int[] a = new int[num];
int[][] a = new int[num][num];
与前面的声明方式等价。
局部变量类型推导
从 JDK10 开始,引入了一个新的上下文敏感(context-sensitive)的保留类型名(reserved type name)var
。它用于局部变量类型推导。局部变量类型推导指声明具有初始值的局部变量时,可以用 var 代替变量的类型,变量的具体类型由编译器根据初始值的类型推导得出。局部变量类型推导的优点:消除了冗长的类型名和不可知的类型名(如匿名类)的书写。
// name 为 double 类型,等价于 double name = 1.0;
var name = 1.0;
当 var 不用于局部变量类型推导时,可以当作标识符。
// 合法,var 是 int 类型变量
int var = 1;
var 用于数组时,左边不需要方括号,添加方括号将出错。
var name = new int[10];
var 的一些限制
var 可以用于数组的声明,不能用于数组的初始化。
// 错误
var name = {1,2};
var 不能作为类名,也不能作为其他引用类型名,如接口、枚举、注解、泛型参数。
var 不能用于声明异常类型、lambda 表达式、方法引用。
参考
[1] Herbert Schildt, Java The Complete Reference 11th, 2019.