文章目录
- 基本数值类型、数组和类型转换
- 1.1 整数类型
- 1.2 浮点数类型
- 1.3 布尔类型
- 1.4 字符类型
- 1.5 数组类型
- 1.5.1 一维数组
- 1.5.2 多维数组
- 1.6 数据类型的转换
- 1.6.1 自动类型转换
- 1.6.2 强制类型转换
基本数值类型、数组和类型转换
本文为书籍《Java编程的逻辑》1和《剑指Java:核心原理与应用实践》2阅读笔记
1.1 整数类型
整数类型有byte
、short
、int
和long
,分别占1、2、4、8个字节(一个字节占8 bit
),取值范围如下表所示:
类型名 | 存储空间 | 取值范围 |
---|---|---|
byte | 1 byte | − 2 7 ∼ 2 7 − 1 -2^7\sim2^{7}-1 −27∼27−1 |
short | 2 bytes | − 2 15 ∼ 2 15 − 1 -2^{15}\sim2^{15}-1 −215∼215−1 |
int | 4 bytes | − 2 31 ∼ 2 31 − 1 -2^{31}\sim2^{31}-1 −231∼231−1 |
long | 8 bytes | − 2 63 ∼ 2 63 − 1 -2^{63}\sim2^{63}-1 −263∼263−1 |
byte b = 23;
short s = 3333;
int i = 9999;
long l = 32323;
在给long
类型赋值时,如果常量超过了int
的表示范围,需要在常量后面加大写或小写字母L
,即L
或l
,例如:
long a = 3232343433L;
之所以需要加L
或l
,是因为数字常量默认为是int
类型。
1.2 浮点数类型
小数类型有float
和double
,占用的内存空间分别是 4 4 4和 8 8 8字节,有不同的取值范围和精度,double
表示的范围更大,精度更高,具体如下表所示:
类型名 | 存储空间 | 取值范围 |
---|---|---|
float | 4 bytes | − 3.40 3 38 ∼ 3.40 3 38 -3.403^{38}\sim3.403^{38} −3.40338∼3.40338 |
double | 8 bytes | − 1.79 8 308 ∼ 1.79 8 308 -1.798^{308}\sim1.798^{308} −1.798308∼1.798308 |
对于double
,直接把熟悉的浮点数表示赋值给变量即可,但对于float
,需要在数字后面加大写字母F
或小写字母f
。这是由于小数常量默认是double
类型。
double d = 333.33;
float f = 333.33f;
1.3 布尔类型
布尔(boolean
)类型很简单,直接使用true
或false
赋值,分别表示真和假,例如:
boolean b = true;
b = false;
1.4 字符类型
字符类型char
用于表示一个字符,这个字符可以是中文字符,也可以是英文字符,char
占用的内存空间是两个字节。字符型常量值的表示形式有如下几种:
- 用单引号括起来的单个字符的常量值,如
'a'
、'9'
等,注意:"""
是错误的,里面不能没有内容,必须有单个字符; - 转义字符
'\n'
、'\t'
等; - 直接使用
Unicode
值,格式是'\u××××'
,其中××××
表示一个16
进制的整数; - 直接使用十进制整型常量值,如
100
、98
等。
public class TestCharacter{public static void main(String[] args){char c1 ='a';char c2 ='\"'char c3 ='\u0043'; char c4 =100;System.out.printIn("c1:" + c1);System.out.printIn("c2:" + c2);System.out.println("c3:" + c3);System.out.printIn("c4:" + c4);}
}
大部分的常用字符用一个char
就可以表示,但有的特殊字符用一个char
表示不了,字符型底层依然是以整型(相当于无符号整型)存储的,关于char
更多其他细节,后面会展开。
1.5 数组类型
1.5.1 一维数组
基本类型的数组有3
种赋值形式,如下所示:
int[] arr = {1,2,3}; // 第一种
int[] arr = new int[]{1,2,3}; // 第二种
int[] arr = new int[3]; // 第三种
arr[0]=1; arr[1]=2; arr[2]=3;
第1
种和第2
种都是预先知道数组的内容,而第3
种是先分配长度,然后再给每个元素赋值。第3
种形式中,即使没有给每个元素赋值,每个元素也都有一个默认值,这个默认值跟数组类型有关,数值类型的值为0
,boolean
为false
,char
为空字符。
数组长度也可以动态确定,如下所示:
int length = ... ; //根据一些条件动态计算
int arr = new int[length];
数组长度虽然可以动态确定,但定了之后就不可以再改变。数组有一个length
属性,是只读属性。还有一个小细节,不能在给定初始值的同时给定长度,即如下格式是不允许的:
int[] arr = new int[3]{1,2,3}
我们可以这么理解,因为初始值已经决定了长度,再给个长度,如果还不一致,计算机将无所适从。数组类型和基本类型是有明显不同的,一个基本类型变量,内存中只会有一块对应的内存空间。但数组有两块:一块用于存储数组内容本身,另一块用于存储内容的位置。用一个例子来说明,有一个int
变量a
,以及一个int
数组变量arr
,其代码、变量对应的内存地址和内存内容如下表所示:
代码 | 内存地址 | 内存数据 |
---|---|---|
int a=100; | 1000 | 100 |
int[] arr={1,2,3} | 2000 | 3000 |
3000 | 1 | |
3004 | 2 | |
3008 | 3 |
基本类型a
的内存地址是1000
,这个位置存储的就是它的值100
。数组类型arr
的内存地址是2000
,这个位置存储的值是一个位置3000
,3000
开始的位置存储的才是实际的数据"1, 2, 3"
。
为什么数组要用两块空间?不能只用一块空间吗?我们来看下面这段代码:
int[] arrA = {1,2,3};
int[] arrB = {4,5,6,7};
arrA = arrB;
这段代码中,arrA
初始的长度是3
,arrB
的长度是4
,后来将arrB
的值赋给了arrA
。如果arrA
对应的内存空间是直接存储的数组内容,那么它将没有足够的空间去容纳arrB
的所有元素。用两块空间存储就简单得多,arrA
存储的值就变成了和arrB
的一样,存储的都是数组内容{4,5,6,7}
的地址,此后访问arrA
就和arrB
是一样的了,而arrA {1,2,3}
的内存空间由于不再被引用会进行垃圾回收,如下所示:
arrA {1,2,3}\\arrB -> {4,5,6,7}
由上也可以看出,给数组变量赋值和给数组中元素赋值是两回事,给数组中元素赋值是改变数组内容,而给数组变量赋值则会让变量指向一个不同的位置。
我们说数组的长度是不可以变的,不可变指的是数组的内容空间,一经分配,长度就不能再变了,但可以改变数组变量的值,让它指向一个长度不同的空间,就像上例中arrA
后来指向了arrB
一样。
1.5.2 多维数组
当要存储一组数据时,可以考虑使用一维数组,那么当有多组数据需要存储和处理时,就需要用到多维数组。
数组的标记是中括号,用一个中括号来表示一维数组,如果要声明二维数组,那么就需要用两个中括号来表示。语法格式如下所示:
-
格式1:
元素数据类型[][] 数组名;
// 推荐 -
格式2:
元素数据类型[] 数组名[];
-
格式3:
元素数据类型 数组名[][];
示例代码:
int[][] arr;
int[] arr[];
int arr[][];
和一维数组一样,二维数组的初始化同样分为静态初始化和动态初始化。动态初始化又分为固定列数和不固定列数两种情况。
所谓静态初始化,是指在编译期间,就确定了行数和列数,并且也明确了每个元素的值。语法格式如下所示:
- 格式1:
元素数据类型 [][] 数组名 = new 元素数据举型 [][] {{ 元素1, 元素2, ......}, { 元素1, 元素2, ......},......};
- 格式2:
元素数据类型 [][] 数组名 = {{ 元素1, 元素2, ......}, { 元素1, 元素2, ......},......};
需要指出的是,格式2
已将声明和初始化合二为一,格式1
可以将声明和初始化分开。
int[][] arr =new int[][]{{10,20,30,40,50},{45,6,8}};
int[][] arr = {{10,20,30,40,50},{45,6,8}};
从如上所示的代码中可以看出,arr
中相当于有两个一维数组,分别是{10, 20, 30, 40, 50}
和{45, 6, 8}
,如果分别把{10, 20, 30, 40, 50}
和{45, 6, 8}
看成一个整体,那么arr
就相当于有两个元素,即arr.length=2
。
第一个数组元素arr[0]
的元素是{10, 20, 30, 40, 50}
,而很明显arr[0]
又是一个一维数组,其长度为5
,即arr[0].length=5
。第二个元素arr[1]
的元素值是{45,6,8}
,而arr[1]
也是一个一维数组,其长度为3
,即arr[1].length=3
。
二维数组再在内存中存储的格式如下:arr
是二维数组类型的变量,里面有两个元素,存储的是首地址,而c
和arr[1]
均是一个一维数组,因此arr[0]
和arr[1]
中仍然存储一维数组的首地址。
所谓动态初始化,是指在对数组初始化时,只是确定数组的行数和列数,甚至行数和列数都需要在程序运行期间才能确定。当确定完数组的行数和列数之后,数组的元素是默认值。动态初始化又分为两种,一种是每行的列数可以相同,另一种是每行的列数可以不同。
- 语法格式1:
元素数据类型[][] 数组名 = new 元素数据类型[行数][列数];
。语法格式中,行数和列数是固定的,固定列数,即里面所有一维数组的元素个数是一致的。示例代码:int [][] arr=new int[3][2]
。arr
指向一个长度为3
的数组对象,其中的每个元素都为存储的一维数组的首地址。每个一维数组中的元素都有默认值,相当于:{{0, 0}, {0, 0}, {0, 0}}
。 - 语法格式2:
元素数据类型[][] 数组名 = new 元素数据类型[行数][];
。语法格式中,行数固定但是列数不固定。不固定列数,即里面所有一维数组的元素个数可以不相同。示例代码:int [][]=new int[3][]
,使用一维数组时必须先初始化,因为此时一维数组值为null
。相当于:{null, null, null}
1.6 数据类型的转换
根据开发需要,不同数据类型的值经常需要进行相互转换,我们需要掌握基本数据类型的转换。Java
语言只支持布尔型之外的七大基本数据类型间的转换,它们之间的转换类型分为自动类型转换和强制类型转换。
1.6.1 自动类型转换
当我们将一个基本数据类型的值直接赋给另一个不同类型的基本数据类型的变量时,如果赋值成功,则说明此时系统进行了一次自动类型转换,Java
语言规范将其称为Wideding Conversion
。如果赋值失败,则说明系统不能进行自动类型转换。自动类型转换需要满足目标类型的表示范围大于源类型的示数范围的要求,这就相当于将一个体积为2
立方厘米的立方体放在体积为5
立方厘米的立方体盒子中。
基本数据类型的自动转换关系如下图所示,根据箭头指向,左边类型的值可以自动转换成右边类型,反之,则不可以。
自动类型转换的注意事项。
- 当多种类型的数据进行混合运算时,系统首先自动将所有数据转换成容量最大的数据类型,再进行计算。例如,
float
字节数比long
字节数小,但是float
的容量更大。 byte
、short
、char
之间不会相互转换,它们在计算时首先会转换为int
类型。- 布尔类型不能与其他数据类型进行运算。
- 当把任何基本数据类型的值和字符串(
String
)进行"+“运算时,此时的”+"不再是加法的意思,而是表示连接运算。此时,运算的结果是String
型。
1.6.2 强制类型转换
在一些开发场景中,我们也需要面对将上图中箭头右边类型转换为左边类型的情况,这时候就要进行强制转换,否则编译会报错,代码如下所示:
int a=200;
byte b = a; //编译报错
可以试想一下,如果将一个体积为5
立方厘米的立方体放在体积为2
立方厘米的立方体盒子中,正常来讲,是不是放不进去。如果要放进去,则要削去一部分体积,从而造成数据丢失。所以Java
语言规范将强制类型转换称为Narrow Conversion
。强制类型转换的语法格式如下所示:
(目标类型)数据值
示例代码:
int num = 98
short s = (short)num;
System.out.println(s); //结果:98
double d=9.95;
int i = (int)d;
System.out.printIn(i); //结果:9
具体规则如下所示:
- 如果目标类型和源类型都为整型,如将
32
位int
类型强转为8
位byte
类型,则需要截断前面的24
位,只保留右边8
位。例如,int
型255
强制转为byte
时,将会得到-1
。 - 目标类型是整型,源类型是浮点型,将直接截断浮点型的小数部位,这会造成精度损失。例如:
int iValue=(int )12.5
,将其强转后最终得到的iValue
的值是12
。
[1]马俊昌.Java编程的逻辑[M].北京:机械工业出版社,2018. ↩︎
[2]尚硅谷教育.剑指Java:核心原理与应用实践[M].北京:电子工业出版社,2023. ↩︎