Java程序基础
约 7390 字大约 25 分钟
2025-07-11
3.1 Java 程序基本结构
3.1.1 最小可运行单元:class
Java 是面向对象的语言,其最小可运行单元为 class。
一段完整的 Java 源代码必须以 class 关键字开始,并以对应的右花括号 } 结束。
public class Hello { // 类名必须与文件名保持一致(区分大小写)
// 类体
}- 访问修饰符:
public表示该类为公有的,可被其他类访问;若省略,则类只能在同一包内使用,且无法通过java Hello从命令行启动。 - 类命名规范
- 必须以英文字母开头,后续可跟字母、数字或下划线。
- 习惯首字母大写,采用 UpperCamelCase。
| 良好示例 | 不良示例 |
|---|---|
Hello | hello |
NoteBook | Note_Book |
VRPlayer | _World |
3.1.2 程序入口:main 方法
任何可独立执行的 Java 程序都必须包含一个如下签名的静态方法:
public static void main(String[] args) {
// 程序入口逻辑
}public:保证 JVM 可从外部调用。static:无需创建对象即可被 JVM 直接调用。void:表示无返回值。String[] args:命令行参数数组,名称习惯使用args,但可变(如argv)。
方法命名同样遵循 CamelCase,但首字母小写:
| 良好示例 | 不良示例 |
|---|---|
main | Main |
goodMorning | good_morning |
playVR | _playVR |
3.1.3 语句与分号
方法体内部的真正执行单元称为“语句”。
Java 规定每条语句必须以英文分号 ; 结束。
System.out.println("Hello, world!");3.1.4 注释体系
Java 提供三种注释形式,编译器均会忽略其内容。
单行注释
// 向屏幕输出文本多行注释
/* 多行注释开始 可跨越多行 多行注释结束 */文档注释(Javadoc)
/** * 可用于自动生成 API 文档 * @author liaoxuefeng */
文档注释通常置于 class 或方法定义之前,配合 javadoc 命令可生成标准 HTML 帮助文档。
3.1.5 代码格式与社区约定
- Java 语法对空格、换行、缩进无强制性要求,不影响编译结果。
- 为使代码易读、易维护,应遵循业界通用规范(如 Google Java Style、阿里巴巴 Java 开发手册)。
- 推荐:
- 使用 4 个空格作为缩进。
- 左大括号不换行。
- 运算符两侧留空格。
3.2 变量与数据类型
3.2.1 变量
变量(Variable)是程序中用于存储数据的一块具名存储单元,其值可以在运行时更改。它与数学方程中的“未知数”类似,但在程序执行过程中被赋予确定的值,并可多次重新赋值。
变量可分为:
- 基本类型变量(primitive variable):直接存放数值本身。
- 引用类型变量(reference variable):存放对象的引用(内存地址),后续章节详述。
Java 要求所有变量先定义、后使用。定义格式:
type identifier [= initialValue];若省略初始值,则成员变量获得系统默认值(数值类型为 0,布尔类型为 false,引用类型为 null)。局部变量无默认值,使用前必须显式初始化。
示例:
int x = 1; // 定义整型变量 x,初始化为 1变量值可在运行期反复变更;赋值运算符 = 为“覆盖性写入”,而非数学等式。
示例:
int x = 100; // x = 100
x = 200; // x 被覆盖为 200
int y = x; // y 得到 x 当前值 200
y = y + 100; // y 变为 300,x 保持不变内存变化:
- 每次赋值都会将新的二进制数据写入对应变量的存储单元。
- 变量间赋值时,值被复制,而非变量本身绑定。
3.2.2 基本数据类型
Java 提供的八种基本类型直接映射到 CPU 指令,其占用的内存大小及表示范围如下表:
| 类型 | 位数 | 字节 | 取值范围(含符号) |
|---|---|---|---|
| byte | 8 | 1 | −128 ~ 127 |
| short | 16 | 2 | −32768 ~ 32767 |
| int | 32 | 4 | −2³¹ ~ 2³¹−1 |
| long | 64 | 8 | −2⁶³ ~ 2⁶³−1 |
| float | 32 | 4 | 约 ±3.4×10³⁸(6~7 位十进制有效数字) |
| double | 64 | 8 | 约 ±1.79×10³⁰⁸(15 位十进制有效数字) |
| char | 16 | 2 | 单个 Unicode 码点(\u0000 ~ \uffff) |
| boolean | 1 | 1/4 | true / false(JVM 实现常用 1 byte 或 4 byte) |
3.2.3 整数类型
- 默认字面量为
int。表示long时需在数字后加L或l(推荐大写,避免与数字 1 混淆)。 - 支持二进制(
0b)、八进制(0)、十六进制(0x)以及千位分隔符(1_000_000)以提高可读性。
示例:
int a = 0b1010; // 二进制 10
long b = 9_999_999_999L; // 长整型必须加 L3.2.4 浮点类型
- 默认字面量为
double。表示float时需在数字后加F或f。 - 支持科学计数法(
1.23e4表示 1.23×10⁴)。
示例:
float pi = 3.14f; // 单精度
double e = 2.9979e8; // 双精度3.2.5 布尔类型
仅取 true 或 false,用于关系运算与逻辑运算结果存储。示例:
boolean isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);3.2.6 字符类型
char 表示一个 UTF-16 代码单元,可与整数强制转换。示例:
char letter = 'A';
char unicode = '\u4e2d'; // 字符 '中'3.2.7 引用类型
除基本类型外,所有类型均为引用类型,例如:
String s = "Hello";变量 s 保存的是字符串对象在堆内存中的首地址,而非字符本身。引用类型的行为与 C/C++ 指针类似,但受 JVM 管理,不会出现悬空引用。
3.2.8 常量
使用 final 修饰变量即定义常量,赋值后不可更改;命名习惯全大写并用下划线分隔。
示例:
final double PI = 3.141592653589793;常量有助于消除魔术数值、提升代码可维护性。
3.2.9 var 关键字
自 Java 10 起,可用 var 进行局部变量类型推断,编译器根据右值自动推导类型。只能用于方法内部,不能用于成员变量或形参。
var list = new ArrayList<String>(); // 推断为 ArrayList<String>作用域、可变性、性能与显式声明完全一致,仅减少冗余代码。
3.2.10 变量的作用域
变量生效范围由最近的花括号对界定。基本原则:
- 从定义语句开始,到包含该语句的最内层
}结束。 - 同层作用域内不可出现同名变量;不同作用域可重名,互不干扰。
- 最小作用域原则:尽量在最小范围内定义变量,避免不必要的生命周期延长。
示例:
{
int a = 1;
{
int b = 2;
System.out.println(a); // 可访问 a
}
// System.out.println(b); // 编译错误:b 已超出作用域
}在后续控制结构和面向对象章节中,作用域规则将继续与代码块、方法、类相结合进行讨论。
3.3 整数运算
Java 的整数运算遵循四则运算规则,可以使用任意嵌套的小括号。四则运算规则和初等数学一致。例如:
// 四则运算
public class Main {
public static void main(String[] args) {
int i = (100 + 200) * (99 - 88); // 3300
int n = 7 * (5 + (i - 9)); // 23072
System.out.println(i);
System.out.println(n);
}
}整数的数值表示不但是精确的,而且整数运算永远是精确的,即使是除法也是精确的,因为两个整数相除只能得到结果的整数部分:
int x = 12345 / 67; // 184求余运算使用 %:
int y = 12345 % 67; // 12345÷67的余数是17特别注意:整数的除法对于除数为 0 时运行时将报错,但编译不会报错。
3.3.1 溢出
要特别注意,整数由于存在范围限制,如果计算结果超出了范围,就会产生溢出,而溢出不会出错,却会得到一个奇怪的结果:
// 运算溢出
public class Main {
public static void main(String[] args) {
int x = 2147483640;
int y = 15;
int sum = x + y;
System.out.println(sum); // -2147483641
}
}要解释上述结果,我们把整数 2147483640 和 15 换成二进制做加法:
0111 1111 1111 1111 1111 1111 1111 1000
+ 0000 0000 0000 0000 0000 0000 0000 1111
-----------------------------------------
1000 0000 0000 0000 0000 0000 0000 0111由于最高位计算结果为 1,因此,加法结果变成了一个负数。
要解决上面的问题,可以把 int 换成 long 类型,由于 long 可表示的整型范围更大,所以结果就不会溢出:
long x = 2147483640;
long y = 15;
long sum = x + y;
System.out.println(sum); // 2147483655还有一种简写的运算符,即 +=,-=,*=,/=,它们的使用方法如下:
n += 100; // 3409, 相当于 n = n + 100;
n -= 100; // 3309, 相当于 n = n - 100;3.3.2 自增/自减
Java 还提供了 ++ 运算和 -- 运算,它们可以对一个整数进行加 1 和减 1 的操作:
// 自增/自减运算
public class Main {
public static void main(String[] args) {
int n = 3300;
n++; // 3301, 相当于 n = n + 1;
n--; // 3300, 相当于 n = n - 1;
int y = 100 + (++n); // 不要这么写
System.out.println(y);
}
}注意 ++ 写在前面和后面计算结果是不同的,++n 表示先加 1 再引用 n,n++ 表示先引用 n 再加 1。不建议把 ++ 运算混入到常规运算中,容易自己把自己搞懵了。
3.3.3 移位运算
在计算机中,整数总是以二进制的形式表示。例如,int 类型的整数 7 使用 4 字节表示的二进制如下:
00000000 0000000 0000000 00000111可以对整数进行移位运算。对整数 7 左移 1 位将得到整数 14,左移两位将得到整数 28:
int n = 7; // 00000000 00000000 00000000 00000111 = 7
int a = n << 1; // 00000000 00000000 00000000 00001110 = 14
int b = n << 2; // 00000000 00000000 00000000 00011100 = 28
int c = n << 28; // 01110000 00000000 00000000 00000000 = 1879048192
int d = n << 29; // 11100000 00000000 00000000 00000000 = -536870912左移 29 位时,由于最高位变成 1,因此结果变成了负数。
类似的,对整数 28 进行右移,结果如下:
int n = 7; // 00000000 00000000 00000000 00000111 = 7
int a = n >> 1; // 00000000 00000000 00000000 00000011 = 3
int b = n >> 2; // 00000000 00000000 00000000 00000001 = 1
int c = n >> 3; // 00000000 00000000 00000000 00000000 = 0如果对一个负数进行右移,最高位的 1 不动,结果仍然是一个负数:
int n = -536870912;
int a = n >> 1; // 11110000 00000000 00000000 00000000 = -268435456
int b = n >> 2; // 11111000 00000000 00000000 00000000 = -134217728
int c = n >> 28; // 11111111 11111111 11111111 11111110 = -2
int d = n >> 29; // 11111111 11111111 11111111 11111111 = -1还有一种无符号的右移运算,使用 >>>,它的特点是不管符号位,右移后高位总是补 0,因此,对一个负数进行 >>> 右移,它会变成正数,原因是最高位的 1 变成了 0:
int n = -536870912;
int a = n >>> 1; // 01110000 00000000 00000000 00000000 = 1879048192
int b = n >>> 2; // 00111000 00000000 00000000 00000000 = 939524096
int c = n >>> 29; // 00000000 00000000 00000000 00000111 = 7
int d = n >>> 31; // 00000000 00000000 00000000 00000001 = 1对 byte 和 short 类型进行移位时,会首先转换为 int 再进行位移。
注
仔细观察可发现,左移实际上就是不断地×2,右移实际上就是不断地÷2。
3.3.4 位运算
位运算是按位进行与、或、非和异或的运算。我们先来看看针对单个 bit 的位运算。
与运算的规则是,必须两个数同时为 1,结果才为 1:
n = 0 & 0; // 0
n = 0 & 1; // 0
n = 1 & 0; // 0
n = 1 & 1; // 1或运算的规则是,只要任意一个为 1,结果就为 1:
n = 0 | 0; // 0
n = 0 | 1; // 1
n = 1 | 0; // 1
n = 1 | 1; // 1非运算的规则是,0 和 1 互换:
n = ~0; // 1
n = ~1; // 0异或运算的规则是,如果两个数不同,结果为 1,否则为 0:
n = 0 ^ 0; // 0
n = 0 ^ 1; // 1
n = 1 ^ 0; // 1
n = 1 ^ 1; // 0Java 没有单个 bit 的数据类型。在 Java 中,对两个整数进行位运算,实际上就是按位对齐,然后依次对每一位进行运算。例如:
// 位运算
public class Main {
public static void main(String[] args) {
int i = 167776589; // 00001010 00000000 00010001 01001101
int n = 167776512; // 00001010 00000000 00010001 00000000
// & -----------------------------------
// 00001010 00000000 00010001 00000000
System.out.println(i & n); // 167776512
}
}上述按位与运算实际上可以看作两个整数表示的 IP 地址 10.0.17.77 和 10.0.17.0,通过与运算,可以快速判断一个 IP 是否在给定的网段内。
3.3.5 运算优先级
在 Java 的计算表达式中,运算优先级如下:
| 优先级 | 运算符示例 | |
|---|---|---|
| 1 | () | |
| 2 | ! ~ ++ -- | |
| 3 | * / % | |
| 4 | + - | |
| 5 | << >> >>> | |
| 6 | & | |
| 7 | ` | ` |
| 8 | += -= *= /= 等赋值运算 |
注
括号最优先,单目高于乘除,移位高于位与或,赋值最低。若不确定,一律显式加括号。
3.3.6 类型自动提升与强制转型
在运算过程中,如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型。例如,short 和 int 计算,结果总是 int,原因是 short 首先自动被转型为 int:
// 类型自动提升与强制转型
public class Main {
public static void main(String[] args) {
short s = 1234;
int i = 123456;
int x = s + i; // s自动转型为int
short y = s + i; // 编译错误!
}
}也可以将结果强制转型,即将大范围的整数转型为小范围的整数。强制转型使用 (类型),例如,将 int 强制转型为 short:
int i = 12345;
short s = (short) i; // 12345要注意,超出范围的强制转型会得到错误的结果,原因是转型时,int 的两个高位字节直接被扔掉,仅保留了低位的两个字节:
// 强制转型
public class Main {
public static void main(String[] args) {
int i1 = 1234567;
short s1 = (short) i1; // -10617
System.out.println(s1);
int i2 = 12345678;
short s2 = (short) i2; // 24910
System.out.println(s2);
}
}因此,强制转型的结果很可能是错的。
3.4 浮点数运算
浮点数运算与整数运算不同,仅支持加、减、乘、除等基本数值计算,不支持位运算和移位运算。虽然浮点数能表示的范围很大,但其关键特性是无法精确表示某些数值。
例如,十进制的 0.1 转换为二进制时是一个无限循环小数,因此,无论是 float 还是 double 类型,都只能存储 0.1 的近似值。相反,0.5 这样的浮点数则可以精确表示。
由于浮点数常常无法精确表示,这会导致浮点数运算产生误差。以下代码展示了这一现象:
// 浮点数运算误差
public class Main {
public static void main(String[] args) {
double x = 1.0 / 10;
double y = 1 - 9.0 / 10;
// 观察x和y是否相等:
System.out.println(x);
System.out.println(y);
}
}鉴于浮点数运算存在误差,直接比较两个浮点数是否相等可能会出错。正确的做法是判断这两个浮点数之差的绝对值是否小于一个足够小的数,例如 0.00001。
// 比较x和y是否相等,先计算其差的绝对值:
double r = Math.abs(x - y);
// 再判断绝对值是否足够小:
if (r < 0.00001) {
// 可以认为相等
} else {
// 不相等
}浮点数在内存中的表示方式比整数更为复杂。Java 的浮点数完全遵循 IEEE-754 标准,该标准也被大多数计算机平台所支持。
3.4.1 类型提升
在混合运算中,如果参与运算的数包含整型和浮点型,整型会自动提升到浮点型。
// 类型提升
public class Main {
public static void main(String[] args) {
int n = 5;
double d = 1.2 + 24.0 / n; // 6.0
System.out.println(d);
}
}需要注意的是,在一个复杂的四则运算中,两个整数之间的运算不会出现自动类型提升。例如:
double d = 1.2 + 24 / 5; // 结果不是 6.0 而是 5.2上述代码的计算结果为 5.2,这是因为编译器在计算 24 / 5 这个子表达式时,将其视为两个整数之间的运算,结果仍然是整数 4,而不是浮点数。
为了得到期望的结果,可以将 24 / 5 修改为 24.0 / 5。由于 24.0 是浮点数,因此,计算除法时会自动将 5 提升为浮点数,从而得到浮点数的结果。
3.4.2 溢出
整数运算中,除数为 0 会导致错误。但浮点数运算中,除数为 0 不会报错,而是会返回以下特殊值:
NaN:表示 Not a Number(不是一个数字)。Infinity:表示无穷大。-Infinity:表示负无穷大。
例如:
double d1 = 0.0 / 0; // NaN
double d2 = 1.0 / 0; // Infinity
double d3 = -1.0 / 0; // -Infinity在实际的运算中,这三种特殊值并不常见,简单了解即可。
3.4.3 强制转型
浮点数可以被强制转型为整数。在转型过程中,浮点数的小数部分会被直接舍弃。如果转型后的结果超出了整型能够表示的最大范围,将会返回整型的最大值。例如:
int n1 = (int) 12.3; // 12
int n2 = (int) 12.7; // 12
int n3 = (int) -12.7; // -12
int n4 = (int) (12.7 + 0.5); // 13
int n5 = (int) 1.2e20; // 2147483647如果需要进行四舍五入,可以将浮点数加上 0.5 后再进行强制转型:
// 四舍五入
public class Main {
public static void main(String[] args) {
double d = 2.6;
int n = (int) (d + 0.5);
System.out.println(n);
}
}上述代码展示了如何使用 (int) (d + 0.5) 对浮点数进行四舍五入操作。首先,将浮点数 d 加上 0.5,然后将结果强制转换为整数类型。这种方法能够有效地实现四舍五入的功能。例如,当 d 的值为 2.6 时,加上 0.5 之后的结果是 3.1,强制转换为整数后,n 的值就为 3。
3.5 布尔类型
boolean 类型只有两个值:true 和 false,用于表示真和假。
3.5.1 布尔运算
布尔运算包括以下几类:
- 比较运算符:用于比较两个值之间的关系,结果为
boolean类型。包括:>(大于),>=(大于等于),<(小于),<=(小于等于),==(等于),!=(不等于)。 - 与运算 (
&&):当且仅当两个操作数都为true时,结果才为true。 - 或运算 (
||):只要两个操作数中有一个为true,结果就为true。 - 非运算 (
!):对操作数取反,如果操作数为true,则结果为false,反之亦然。
运算符优先级如下:
| 优先级 | 运算符 |
|---|---|
| 1 | ! |
| 2 | >,>=,<,<= |
| 3 | ==,!= |
| 4 | && |
| 5 | || |
3.5.2 短路运算
短路运算是布尔运算的一个重要特性。对于 && 运算,如果第一个操作数为 false,则结果必为 false,后面的操作数将不再计算。对于 || 运算,如果第一个操作数为 true,则结果必为 true,后面的操作数将不再计算。
以下代码展示了短路运算的特性:
// 短路运算
public class Main {
public static void main(String[] args) {
boolean b = 5 < 3;
boolean result = b && (5 / 0 > 0); // 此处 5 / 0 不会报错
System.out.println(result);
}
}这段代码中,变量 b 的值为 false,因此 b && (5 / 0 > 0) 中,&& 前面的表达式已经为 false,根据与运算的规则,整个表达式的结果必然为 false,所以后面的 (5 / 0 > 0) 不会被执行,避免了除数为 0 的错误。
需要注意的是,如果 b 的值为 true,则表达式变为 true && (5 / 0 > 0),由于无法进行短路运算,后面的 (5 / 0 > 0) 表达式会被执行,从而抛出除数为 0 的异常。
类似地,对于 || 运算:
boolean result = true || (5 / 0 > 0); // true由于 || 前面的表达式已经为 true,整个表达式的结果必然为 true,所以后面的 (5 / 0 > 0) 不会被执行,避免了除数为 0 的错误。
3.5.3 三元运算符
Java 提供了一个三元运算符 b ? x : y,它根据布尔表达式 b 的结果,选择性地执行 x 或 y。如果 b 为 true,则计算 x 的值并返回;否则,计算 y 的值并返回。
以下代码展示了三元运算符的用法:
// 三元运算
public class Main {
public static void main(String[] args) {
int n = -100;
int x = n >= 0 ? n : -n;
System.out.println(x);
}
}这段代码使用三元运算符计算变量 n 的绝对值。如果 n >= 0 为 true,则 x 的值为 n;否则,x 的值为 -n。
需要注意的是,三元运算符会首先计算 b,然后根据 b 的值选择性地计算 x 或 y。此外,x 和 y 的类型必须相同,因为返回值是 x 和 y 之一。
好的,现在我将根据你提供的课件内容,整理出一份详尽、规范的笔记。
3.6 字符和字符串
在 Java 中,字符 (character) 和字符串 (String) 是两种不同的数据类型,分别用于表示单个字符和字符序列。
3.6.1 字符类型 (char)
char 是 Java 中的基本数据类型,用于存储 Unicode 字符。每个 char 变量占用两个字节的内存空间,可以表示包括英文字符、中文字符在内的所有 Unicode 字符。
char c1 = 'A';
char c2 = '中';以上代码展示了如何使用 char 类型来定义字符变量。因为 Java 内部使用 Unicode 编码表示字符,所以无论是英文字符还是中文字符,都可以用一个 char 类型来表示。
要获取字符的 Unicode 编码,可以直接将 char 类型赋值给 int 类型:
int n1 = 'A'; // 字母“A”的 Unicode 编码是 65
int n2 = '中'; // 汉字“中”的 Unicode 编码是 20013此外,还可以使用转义字符 \u 后跟 Unicode 编码的十六进制表示来定义字符:
char c3 = '\u0041'; // 'A',因为十六进制 0041 = 十进制 65
char c4 = '\u4e2d'; // '中',因为十六进制 4e2d = 十进制 200133.6.2 字符串类型 (String)
与 char 类型不同,String 是引用类型,用于表示字符串。字符串使用双引号 "" 括起来。一个字符串可以包含零个或多个字符。
String s = ""; // 空字符串,包含 0 个字符
String s1 = "A"; // 包含一个字符
String s2 = "ABC"; // 包含 3 个字符
String s3 = "中文 ABC"; // 包含 6 个字符,其中有一个空格当字符串中需要包含双引号 " 或反斜杠 \ 等特殊字符时,需要使用转义字符 \ 来表示。
String s = "abc\"xyz"; // 包含 7 个字符: a, b, c, ", x, y, z
String s2 = "abc\\xyz"; // 包含 7 个字符: a, b, c, \, x, y, z以下是一些常见的转义字符:
| 转义字符 | 含义 |
|---|---|
\" | 字符 " |
\' | 字符 ' |
\\ | 字符 \ |
\n | 换行符 |
\r | 回车符 |
\t | Tab 键 |
\u#### | Unicode 字符 |
例如:
String s = "ABC\n\u4e2d\u6587"; // 包含 6 个字符: A, B, C, 换行符, 中, 文3.6.3 字符串连接
Java 允许使用 + 运算符连接字符串。当使用 + 连接字符串和其他数据类型时,其他数据类型会先自动转换为字符串,然后再进行连接。
// 字符串连接
public class Main {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "world";
String s = s1 + " " + s2 + "!";
System.out.println(s); // Hello world!
}
}// 字符串连接
public class Main {
public static void main(String[] args) {
int age = 25;
String s = "age is " + age;
System.out.println(s); // age is 25
}
}上述代码展示了字符串连接的用法。第一个例子将多个字符串连接成一个字符串。第二个例子将整数类型 age 自动转换为字符串,然后与字符串 "age is " 连接。
3.6.3 多行字符串 (Text Blocks)
从 Java 13 开始,可以使用 """...""" 表示多行字符串,这使得表示包含多行文本的字符串更加方便。
// 多行字符串
public class Main {
public static void main(String[] args) {
String s = """
SELECT * FROM
users
WHERE id > 100
ORDER BY name DESC
""";
System.out.println(s);
}
}注
在使用多行字符串时,需要注意以下几点:
- 多行字符串的起始和结束都使用三个双引号
"""。 - 多行字符串中的换行符会被保留。
- 多行字符串会移除每一行共同的最短前缀空格。
例如,以下代码展示了多行字符串的对齐规则:
String s = """
...........SELECT * FROM
........... users
...........WHERE id > 100
...........ORDER BY name DESC
...........""";上述代码中,用 . 标注的空格都会被去掉。
3.6.4 不可变特性
Java 中的字符串是不可变的。这意味着一旦创建了一个字符串对象,就不能修改它的内容。例如:
// 字符串不可变
public class Main {
public static void main(String[] args) {
String s = "hello";
System.out.println(s); // 显示 hello
s = "world";
System.out.println(s); // 显示 world
}
}在上述代码中,s = "world" 并没有改变原始字符串 "hello" 的内容,而是创建了一个新的字符串 "world",并将变量 s 指向了新的字符串。原来的字符串 "hello" 仍然存在,只是无法通过变量 s 访问它而已。
为了更好地理解字符串的不可变性,可以考虑以下代码:
// 字符串不可变
public class Main {
public static void main(String[] args) {
String s = "hello";
String t = s;
s = "world";
System.out.println(t); // t是"hello"还是"world"?
}
}这段代码的输出结果是 "hello"。这是因为 t = s 只是将变量 t 指向了变量 s 所指向的字符串 "hello"。当 s 指向新的字符串 "world" 时,变量 t 仍然指向原来的字符串 "hello"。
3.6.5 空值 null
引用类型的变量可以指向一个空值 null,表示该变量不指向任何对象。
String s1 = null; // s1 是 null
String s2 = s1; // s2 也是 null
String s3 = ""; // s3 指向空字符串,不是 null需要注意的是,空值 null 和空字符串 "" 是不同的。空字符串是一个有效的字符串对象,而 null 表示不存在任何对象。
3.7 数组类型
当需要处理一组相同类型的变量时,可以使用数组来简化代码,避免定义多个独立的变量。
3.7.1 数组的定义与初始化
在 Java 中,定义数组类型的变量需要使用 类型[] 的形式,例如 int[]。数组必须进行初始化才能使用,初始化可以使用 new 关键字,例如 new int[5] 创建一个可以容纳 5 个 int 元素的数组。
// 数组
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[5];
ns[0] = 68;
ns[1] = 79;
ns[2] = 91;
ns[3] = 85;
ns[4] = 62;
}
}数组具有以下特点:
- 数组在创建时,所有元素会被自动初始化为默认值。例如,
int类型的元素初始化为0,double类型初始化为0.0,boolean类型初始化为false。 - 数组一旦创建后,其大小就不可更改。
3.7.2 访问数组元素
要访问数组中的元素,可以使用索引。数组的索引从 0 开始,到 数组长度 - 1 结束。可以使用 数组变量名[索引] 的方式访问数组元素。
可以使用 数组变量.length 来获取数组的大小(即数组中元素的个数)。
// 数组
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[5];
System.out.println(ns.length); // 5
}
}需要注意的是,数组是引用类型。如果使用索引访问数组元素时,索引超出了范围,程序在运行时会抛出 ArrayIndexOutOfBoundsException 错误。
// 数组
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[5];
int n = 5;
System.out.println(ns[n]); // 索引n不能超出范围
}
}3.7.3 数组的简化初始化
可以在定义数组的同时直接初始化数组的元素,这样可以省略数组的大小,由编译器自动推算。
// 数组
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[] { 68, 79, 91, 85, 62 };
System.out.println(ns.length); // 编译器自动推算数组大小为5
}
}更进一步,可以简写为:
int[] ns = { 68, 79, 91, 85, 62 };3.7.4 数组大小的不可变性
虽然数组变量可以指向不同的数组,但数组的大小本身是不可变的。例如:
// 数组
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns;
ns = new int[] { 68, 79, 91, 85, 62 };
System.out.println(ns.length); // 5
ns = new int[] { 1, 2, 3 };
System.out.println(ns.length); // 3
}
}在上面的代码中,ns 先指向一个包含 5 个元素的数组,然后又指向一个包含 3 个元素的数组。这并不是改变了原数组的大小,而是 ns 指向了一个新的数组对象。原先的包含 5 个元素的数组仍然存在,只是无法再通过 ns 访问到它了。
3.7.5 字符串数组
当数组的元素是引用类型时,情况会有所不同。例如,字符串数组:
String[] names = {
"ABC", "XYZ", "zoo"
};在这个例子中,names 数组包含了 3 个 String 类型的元素,每个元素都是一个指向字符串对象的引用。
如果修改数组中某个元素的引用,例如:
names[1] = "cat";这并不会改变原来 names[1] 指向的字符串 "XYZ",而是将 names[1] 的引用指向了新的字符串 "cat"。结果是,字符串 "XYZ" 无法再通过 names[1] 访问到,但它本身并没有被修改。
为了更好的理解引用,请看下面的代码:
// 数组
public class Main {
public static void main(String[] args) {
String[] names = {"ABC", "XYZ", "zoo"};
String s = names[1];
names[1] = "cat";
System.out.println(s); // s是"XYZ"还是"cat"?
}
}上述代码的输出是 "XYZ"。因为 String s = names[1]; 这行代码将 s 指向了 names 数组中索引为 1 的元素,也就是字符串 "XYZ"。 之后,names[1] = "cat"; 这行代码将 names 数组中索引为 1 的元素指向了新的字符串 "cat",但是变量 s 仍然指向的是 "XYZ",所以最终输出的是 "XYZ"。