Java 是一种强类型语言,这就意味着必须为每一个变量声明一种类型。在 Java 中基本数据类型共有 8 种,包括 4 种整型、2 种浮点型、1 种用于表现 Unicode 编码的字符单元的字符类型 char 和一种用于表示真值的 boolean 类型
~
本篇主要记录内容包括:基础数据类型、对象包装器、字符串、数组
Java 是一种强类型语言,这就意味着必须为每一个变量声明一种类型。在 Java 中基本数据类型共有 8 种,包括 4 种整型、2 种浮点型、1 种用于表现 Unicode 编码的字符单元的字符类型 char 和一种用于表示真值的 boolean 类型。
整型用于表示没有小数部分的数值,它允许是负数。Java 提供了 4 种整形类型。
数据类型 | 存储需求 | 默认值 | 取值范围 |
---|---|---|---|
int | 4 字节,32 位 | 0 | - 2^31 ~ 2^31-1 |
short | 2 字节,16 位 | 0 | - 327,68 ~ 32,767 |
long | 8 字节,64 位 | 0L | - 2^63 ~ 2^63-1 |
byte | 1 字节,8 位 | 0 | - 128 ~127 |
在通常情况下,int 类型最常用。但如果表示星球上的居住人数,就需要使用 long 类型了。byte 和 short 类型主要用于特定的应用场合,例如,底层的文件处理或者需要控制占用存储空间量的大数组。
在 Java 中,整型的范围与运行 Java 代码的机器无关。这就解决了软件从一个平台移植到另一个平台,或者在同一个平台中的不同操作系统之间进行移植给程序员带来的诸多问题。与此相反,C 和 C++ 程序需要针对不同的处理器选择最为高效的整型,这样就有可能造成一个在 32 位处理器上运行很好的 C 程序在 16 位系统上运行却发生整数溢出。由于 Java 程序必须保证在所有机器上都能够得到相同的运行结果,所以各种数据类型的取值范围必须固定。
长整型数值有一个后缀 L 或 l(如4000000000L)。十六进制数值有一个前缀 0x 或 0X(如 0xCAFE)。八进制有一个前缀 0,例如,010 对应八进制中的 8。很显然,八进制表示法比较容易混淆,所以建议最好不要使用八进制常数。
从 Java 7 开始,加上前缀 0b 或 0B 就可以写二进制数。例如,0b1001 就是 9。另外,同样是从 Java 7 开始,还可以为数字字面量加下划线,如用 1_000_00(0或0b1111_0100_0010_0100_0000)表示一百万。这些下划线只是为了让人更易读。Java 编译器会去除这些下划线。
浮点类型用于表示有小数部分的数值。在 Java 中有 2 种浮点类型。
数据类型 | 存储需求 | 默认值 | 取值范围 |
---|---|---|---|
float | 4 字节,32 位 | 0.0f | -2^31 ~ 2^31-1(有效位数6~7) |
double | 8 字节,64 位 | 0.0d | -2^63 ~ 2^63-1(有效位数15位) |
double 表示这种类型的数值精度是 float 类型的两倍(有人称之为双精度数值)。绝大部分应用程序都采用 double 类型。在很多情况下,float 类型的精度很难满足需求。实际上,只有很少的情况适合使用 float 类型,例如,需要单精度数据的库,或者需要存储大量数据。
float 类型的数值有一个后缀 F 或 f(例如,3.14F)。没有后缀 F 的浮点数值(如3.14)默认为 double 类型。当然,也可以在浮点数值后面添加后缀 D 或 d(例如,3.14D)。
所有的浮点数值计算都遵循 IEEE 754 规范。具体来说,有三个特殊的浮点数值用于表示溢出和出错情况的:● 正无穷大(Infinity)● 负无穷大(-Infinity)● 不是一个数字(NaN)。例如,使用一个正数除以 0 将得到正无穷大,使用一个负数除以 0 将得到负无穷大,0.0 除以 0.0 或对一个负数开方将得到一个非数。正无穷大通过 Double 或 Float 类的 POSITIVE_INFINITY 表示;负无穷大通过 Double 或 Float 类的 NEGATIVE_INFINITY 表示,非数通过 Double 或 Float 类 的NaN 表示。
警告:浮点数值不适用于无法接受舍入误差的金融计算中。例如,命令 System.out.println(2.0-1.1)将打印出 0.8999999999999999,而不是人们想象的 0.9。这种舍入误差的主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确地表示分数 1/10。这就好像十进制无法精确地表示分数 1/3 一样。如果在数值计算中不允许有任何舍入误差,就应该使用 BigDecimal 类。
char 类型原本用于表示单个字符。不过,现在情况已经有所变化。如今,有些 Unicode 字符可以用一个 char 值描述,另外一些 Unicode 字符则需要两个 char值。
char 类型的字面量值要用单引号括起来。例如:'A’ 是编码值为 65 所对应的字符常量。它与 “A” 不同,“A” 是包含一个字符 A 的字符串。char 类型的值可以表示为十六进制值,其范围从 \u0000 到 \Uffff。例如:\u2122 表示注册符号(TM), \u03C0 表示希腊字母 π。
除了转义序列 \u 之外,还有一些用于表示特殊字符的转义序列。
转译序列 | 名称 | Unicode 转译序列 | 转译序列 | 名称 | Unicode 转译序列 |
---|---|---|---|---|---|
\b | 推格 | \u0008 | \" | 双引号 | \u0022 |
\t | 制表 | \u0009 | \’ | 单引号 | \u0027 |
\n | 换行 | \u0001 | \\ | 反斜杠 | \u005c |
\r | 回车 | \u000b |
所有这些转义序列都可以出现在加引号的字符字面量或字符串中。
警告:Unicode 转义序列会在解析代码之前得到处理。例如,“\u0022+\u0022” 并不是一个由引号 (U+0022) 包围加号构成的字符串。实际上,\u0022 会在解析之前转换为 “,这会得到 “”+”",也就是一个空串。
boolean(布尔)类型有两个值:false 和 true,用来判定逻辑条件。整型值和布尔值之间不能进行相互转换。
基础类型间的等级低到高:byte
< char
< short
< int
< long
< float
< double
,类型的转换分为自动转换与强制转换,自动转换:运算过程中,低级可以自动向高级转换;强制转换:高级需要强制转换为低级,可能会丢失精度规则:
=
右边先自动转换成表达式中最高级的数据类型,再进行运算;=
左边数据类型级别 >
右边数据类型级别,右边会自动升级;=
左边数据类型级别 <
右边数据类型级别,需要强制转换右边数据类型;byte
、 short
、 char
、int
、long
时,超过类型最大值,超过需要强转。有时,需要将 int 这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类。例如,Integer 类对应基本类型 int。通常,这些类称为包装器(wrapper)。这些对象包装器类拥有很明显的名字:Integer、Long、Float、Double、Short、Byte、Character、Void 和 Boolean(前6个类派生于公共的超类Number)。对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是 final,因此不能定义它们的子类。
假设想定义一个整型数组列表。而尖括号中的类型参数不允许是基本类型,也就是说,不允许写成 ArrayList
。这里就用到了 Integer 对象包装器类。我们可以声明一个 Integer 对象的数组列表。
警告:由于每个值分别包装在对象中,所以 ArrayList
的效率远远低于 int[ ]
数组。因此,应该用它构造小型集合,其原因是此时程序员操作的方便性要比执行效率更加重要。
幸运的是,有一个很有用的特性,从而更加便于添加 int 类型的元素到 ArrayList
中。这个调用 list.add(3);
将自动变换成 List.add(Integer.valueOf(3));
。相反地,当将一个 Integer 对象赋给一个 int 值时,将会自动地拆箱。也就是说,编译器将语句 int n = list.get(i);
翻译成 int n = list.get(i).intValue();
。甚至在算术表达式中也能够自动地装箱和拆箱。例如,可以将自增操作符应用于一个包装器引用。
大多数情况下,容易有一种假象,即基本类型与它们的对象包装器是一样的,只是它们的相等性不同。大家知道,== 运算符也可以应用于对象包装器对象,只不过检测的是对象是否指向同一个存储区域,因此,下面的比较通常不会成立:
Integer a = 1000;
Integer b = 1000;
if(a==b){...}
然而,Java 实现却有可能(may)让它成立。如果将经常出现的值包装到同一个对象中,这种比较就有可能成立。这种不确定的结果并不是我们所希望的。解决这个问题的办法是在两个包装器对象比较时调用 equals 方法。
Ps:自动装箱规范要求 boolean、byte、char ≤ 127,介于 -128~127 之间的 short 和 int 被包装到固定的对象中。例如,如果在前面的例子中将 a 和 b 初始化为 100,对它们进行比较的结果一定成立。
关于自动装箱还有几点需要说明:
如果基本的整数和浮点数精度不能够满足需求,那么可以使用 java.math 包中的两个很有用的类:BigInteger 和 BigDecimal。这两个类可以处理包含任意长度数字序列的数值。BigInteger 类实现了任意精度的整数运算,BigDecimal 实现了任意精度的浮点数运算。使用静态的 valueOf 方法可以将普通的数值转换为大数值:BigInteger a = BigInteger.valueOf(100);
。
遗憾的是,不能使用人们熟悉的算术运算符(如:+和*)处理大数值。而需要使用大数值类中的 add 和 multiply 方法。
从概念上讲,Java 字符串就是 Unicode 字符序列。例如,串 Java\u2122
由 5 个 Unicode 字符 J、a、v、a 和 TM。Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义类,很自然地叫做 String。每个用双引号括起来的字符串都是 String 类的一个实例。
Java 中的 String 类包含了 50 多个方法。令人惊讶的是绝大多数都很有用,可以设想使用的频繁非常高。下面的汇总了一部分最常用的方法。
方法名 | 方法说明 | 方法名 | 方法说明 |
---|---|---|---|
length() | 获取字符串的长度 | charAt(int index) | 返回下标所在的cha值(字符) |
equals(String s) | 判断两个字符串内容是否相同 | equalsIgnoreCase(String s) | 不区分大小写判断两个字符串内容是否相同 |
indexOf(String s) | 返回字串第一次出现的位置,没出现则返回-1 | lastIndexOf(String s) | 返回字串最后一次出现的位置,没出现返回-1 |
starstWith(String prefix) | 判断字符串是否以prefix为前缀开头 | endsWith(String suffix) | 判断字符串是否以suffix为后缀结尾 |
toLowerCase() | 返回字符串的小写形式 | toUpperCase() | 返回字符串的大写形式 |
substring(int startindex,int endindex) | 返回从startindex开始到endindex结束的字串 | contains(String s) | 判断是否包含字串s |
replaceAll(String oldSrt,String newSrt) | 替换原有字符串中的字串为目标字串 | concat(String s) | 字符串拼接,相当于+ |
split(String split) | 以指定字符串分割后返回字符串数组 | tirm() | 返回字符串两边消除空格后的字符串 |
getBytes() | 返回字符串的字节数组 | isEmpty() | 判断字符串是否为空 |
tocharArray() | 将此字符串转换为一个新的字符数组 | compareTo | 将字符串与另一个对象进行比较 |
hashCode() | 返回字符串的哈希值 |
String 使用数组存储字符串的内容,数组使用关键词 final 修饰,因此数组内容不可变,使用 String 定义的字符串的值也是不可变的,因此每次对 String 的修改操作都会创建新的 String 对象,导致效率低下且占用大量内存空间。
StringBuffer 和 StringBuilder 都是 AbstractStringBuilder 的子类,同样使用数组存储字符串的内容,由于数组没有使用关键词 final 修饰,因此数组内容可变,StringBuffer 和 StringBuilder 都是可变类型,可以对字符串的内容进行修改,且不会因为修改而创建新的对象。
在需要经常对字符串的内容进行修改的情况下,应使用 StringBuffer 或 StringBuilder,在时间和空间方面都显著优于 String。
StringBuffer 对定义的方法或者调用的方法使用了关键词 synchronized 修饰,而 StringBuilder 的方法没有使用关键词 synchronized 修饰。由于 StringBuffer 对方法加了同步锁,因此其效率略低于 StringBuilder,但是在多线程的环境下,StringBuilder 不能保证线程安全,因此 StringBuffer 是更优的选择。
数组是一种数据结构,用来存储同一类型值的集合。通过一个整型下标可以访问数组中的每一个值。例如,如果 a 是一个整型数组,a[i] 就是数组中下标为 i 的整数。在声明数组变量时,需要指出数组类型(数据元素类型紧跟[])和数组变量的名字,例如:int[] a;
。
数组初始化:在定义数组时只指定数组的长度,由系统自动为元素 赋初值 的方式称作动态初始化。
格式:
数据类型[] 数组名 = new 数据类型[长度]; int[] arr = new int[4];
类型[] 数组名 = new 类型[]{元素,元素,……}; int[] arr = new int[]{1,2,3,4};
类型[] 数组名 = {元素,元素,元素,……}; int[] arr = { 1, 2, 3, 4 };
数组的工具类:java.util.Arrays,由于数组对象本身并没有什么方法可以提供我们调用,但是API中提供了一个工具类Arrays供我们使用,从而可以对数据对象进行一些基本的操作。Arrays 类中的方法都是 static 修饰的静态方法,使用的时候可以直接使用类名进行调用,而不用使用对象来调用。例如:Arrays.toString(string);
。下面的汇总了一部分最常用的方法。
方法名 | 方法说明 | 方法名 | 方法说明 |
---|---|---|---|
System.out.println(Arrays.toString(ints)); | 返回数组的字符串格式 | Arrays.sort(ints); | 数组按照升序排序 |
Arrays.sort(ints,0,4); | 排序其他用法,对指定下标范围进行排序 | Arrays.fill(ints,1); | 给数组元素填充相同的值 |
Arrays.deepToString(ints) | 返回多维数组的字符串格式 | Arrays.equals(ints[0],ints[1])) | 比较两个数组的元素是否相等 |
多维数组将使用多个下标访问数组元素,它适用于表示表格或更加复杂的排列形式。
二维数组定义格式:
//第一种定义格式
//相当于定义了一个3*4的二维数组,即二维数组的长度为3,二维数组中的每个元素又是一个长度为4的数组
- int[][] arr = new int[3][4];//不推荐
//第二种定义格式
//第二种方式和第一种类似,只是数组中每个元素的长度不确定,必须要new:arr[0] = new int[5];...
int[][] arr = new int[3][];
//第三种定义格式
//二维数组中定义了三个元素,这三个元素都是数组,分别为{1,2}、{3,4,5,6}、{7,8,9}
int[][] arr = {{1,2},{3,4,5,6},{7,8,9}};
二维数组内存:比如,int[][] arr = new int[3][2];
,外层数组长在内存开辟连续的 3 个大的内存空间,每一个内存空间都对应的有地址值,每一个大内存空间里又开辟连续的两个小的内存空间。