变量
int days;
double salary;
long earthPopulation;
boolean done;
- 在Java中,每个声明以分号结束。
- 变量名必须是一个以字母开头并由字母或数字构成的序列。
- 需要注意,与大多数程序设计语言相比,Java中“字母”和“数字”的范围更大。
- 字母包括'A'~'Z'、'a'~'z'、'_'、'$'或在某种语言中表示字母的任何Unicode字符。
- 如果想要知道哪些Unicode字符属于Java中的“字母”,可以使用Character类的isJavaIdentifierStart和isJavaIdentifierPart方法来检查。
- 尽管$是一个合法的Java字符,但不要在你自己的代码中使用这个字符。 它只用在Java编译器或其他工具生成的名字中。
- 另外,不能使用Java保留字作为变量名。
初始化变量
声明一个变量之后,必须使用赋值语句对变量进行初始化,我们最好不要用未初始化的变量。否则java编译器会认为是未定义行为。
public class UninitializedVariableError {public static void main(String[] args) {int num;// 下面这行会报错,因为num未初始化System.out.println(num); }
}
在 IntelliJ IDEA 中,当你编写这段代码时,IDE 会在代码编辑区域直接给出提示。鼠标悬停在有问题的代码行上,会弹出提示框,显示类似 “Variable 'num' might not have been initialized” 的信息,同时在代码行号旁边会有红色波浪线标记。在编译运行时,运行窗口会显示编译错误信息,通常也会提示变量
num
未初始化。
下面我们来看一段正确的示例,在代码中我已经给出了所有类型初始化的示例:
public class VariableInitialization {public static void main(String[] args) {// 初始化byte类型变量byte byteVar = 120;System.out.println("byte类型变量的值: " + byteVar);// 初始化short类型变量short shortVar = 3000;System.out.println("short类型变量的值: " + shortVar);// 初始化int类型变量int intVar = 2024;System.out.println("int类型变量的值: " + intVar);// 初始化long类型变量,需要在数字后面加L或llong longVar = 10000000000L;System.out.println("long类型变量的值: " + longVar);// 初始化float类型变量,需要在数字后面加F或ffloat floatVar = 3.14f;System.out.println("float类型变量的值: " + floatVar);// 初始化double类型变量double doubleVar = 3.1415926;System.out.println("double类型变量的值: " + doubleVar);// 初始化char类型变量char charVar = 'A';System.out.println("char类型变量的值: " + charVar);// 初始化boolean类型变量boolean booleanVar = true;System.out.println("boolean类型变量的值: " + booleanVar);}
}
- 在java中,我们对一个已经声明过的变量进行赋值,就需要将变量名放在等号(=)左侧,相应取值的java表达式放在等号的右侧。我们可以将声明放在代码中的任何地方。
- 但在Java中,变量的声明尽可能地靠近变量第一次使用的地方,这是一种良好的程序编写风格。
常量
public class BasicConstantExample {public static void main(String[] args) {// 使用 final 关键字声明一个 int 类型的常量final int DAYS_IN_WEEK = 7;// 下面这行代码会编译错误,因为常量的值不能被修改// DAYS_IN_WEEK = 8; System.out.println("一周的天数是: " + DAYS_IN_WEEK);// 声明一个 double 类型的常量final double PI = 3.14159;System.out.println("圆周率的值是: " + PI);}
}
在上述代码中,DAYS_IN_WEEK
和 PI
被 final
修饰,分别代表一周的天数和圆周率。当尝试修改这些常量的值时,编译器会报错,从而保证了常量值的不可变性。
- 在java中,关键字final表示这个变量只能被赋值一次,一旦被赋值之后,就不能够再更改了。习惯上,常量名使用全大写。
- 在Java中,经常希望某个常量可以在一个类中的多个方法中使用,通常将这些常量 称为类常量。可以使用关键字static final设置一个类常量。
一个简单的数学常量示例:
public class MathConstants {// 定义圆周率常量,使用 public static final 修饰public static final double PI = 3.1415926;// 定义自然对数的底数常量public static final double E = 2.71828;public static void main(String[] args) {// 可以直接通过类名访问类常量System.out.println("圆周率的值是: " + MathConstants.PI);System.out.println("自然对数的底数的值是: " + MathConstants.E);// 计算半径为 5 的圆的面积double radius = 5;double area = MathConstants.PI * radius * radius;System.out.println("半径为 " + radius + " 的圆的面积是: " + area);}
}
运算符
- 在Java中,使用算术运算符+、-、*、/表示加、减、乘、除运算。
- 当参与/运算的 两个操作数都是整数时,表示整数除法;否则,表示浮点除法。
- 整数的求余操作 (有时称为取模)用%表示。
- 例如,15/2等于7,15%2等于1,15.0/2等于7.5。
- 需要注意,整数被0除将会产生一个异常,而浮点数被0除将会得到无穷大或NaN结果。
算术运算符
用于执行基本的数学运算,包括加、减、乘、除、取模、自增和自减。
运算符 | 描述 | 示例 |
+ | 加法,用于两个数相加 | int a = 5 + 3; // a 的值为 8 |
- | 减法,用于两个数相减 | int b = 7 - 2; // b 的值为 5 |
* | 乘法,用于两个数相乘 | int c = 4 * 6; // c 的值为 24 |
/ | 除法,用于两个数相除。如果是整数相除,结果会舍去小数部分 |
|
% | 取模(求余数),用于求两个数相除的余数 | int e = 10 % 3; // e 的值为 1 |
++ | 自增运算符,分为前置自增(++i )和后置自增(i++ )。前置自增先将变量的值加 1,再使用变量;后置自增先使用变量,再将变量的值加1 | int i = 5; int j = ++i; // i 为 6,j 为 6 int m = 5; int n = m++; // m 为 6,n 为 5 |
-- | 自减运算符,分为前置自减(--i )和后置自减(i-- )。原理与自增运算符类似 | int x = 5; int y = --x; // x 为 4,y 为 4 int p = 5; int q = p--; // p 为 4,q 为 5 |
代码示例:
//算术运算符
public class ArithmeticOperatorExample {public static void main(String[] args) {int a = 10;int b = 3;System.out.println("a + b = " + (a + b));System.out.println("a - b = " + (a - b));System.out.println("a * b = " + (a * b));System.out.println("a / b = " + (a / b));System.out.println("a % b = " + (a % b));int c = a++;System.out.println("a++ 后,a 的值是:" + a + ",c 的值是:" + c);c = ++a;System.out.println("++a 后,a 的值是:" + a + ",c 的值是:" + c);}
}
注:当然,程序员都知道加1、减1是数值变量最常见的操作。在Java中,借鉴了C和C++的做法,也提供了自增、自减运算符:n++将变量n的当前值加1,n--则将n的值减1。
赋值运算符
用于将一个值赋给变量,还可以结合算术运算符进行复合赋值。
运算符 | 描述 | 示例 |
= | 基本赋值运算符,将右边的值赋给左边的变量 | int x = 10; |
+= | 加等于,先将变量与右边的值相加,再将结果赋给变量 | int a = 5; a += 3; // 相当于 a = a + 3,a 的值 |
-= | 减等于,先将变量与右边的值相减,再将结果赋给变量 | int b = 7; b -= 2; // 相当于 b = b - 2,b 的值为 5 |
*= | 乘等于,先将变量与右边的值相乘,再将结果赋给变量 | int c = 4; c *= 6; // 相当于 c = c * 6,c 的值为 24 |
/= | 除等于,先将变量与右边的值相除,再将结果赋给变量 | int d = 10; d /= 2; // 相当于 d = d / 2,d 的值为 5 |
%= | 取模等于,先将变量与右边的值取模,再将结果赋给变量 | int e = 10; e %= 3; // 等价于 e = e % 3,e 的值变为 1 |
代码示例:
// 赋值运算符int num3 = 5;num3 += 2;System.out.println("使用 += 后 num3 的值: " + num3);num3 -= 1;System.out.println("使用 -= 后 num3 的值: " + num3);num3 *= 3;System.out.println("使用 *= 后 num3 的值: " + num3);num3 /= 2;System.out.println("使用 /= 后 num3 的值: " + num3);num3 %= 3;System.out.println("使用 %= 后 num3 的值: " + num3);
- 可以在赋值中使用二元运算符,这是一种很方便的简写形式。例如:
- x +=4; 等价与x = x+4;
- (一般的,我们把运算符放在 = 号的左边,如 *=或%= )。
比较运算符
比较运算符用于比较两个值的大小关系,结果为布尔类型(true
或 false
),以下是详细介绍及示例:
运算符 | 描述 | 示例 |
== | 等于,判断两个值是否相等 | int x = 5; int y = 5; boolean isEqual = (x == y); // 结果为 t |
!= | 不等于,判断两个值是否不相等 | int m = 3; int n = 4; boolean isNotEqual = (m != n); // 结果为 true |
> | 大于,判断左边的值是否大于右边的值 | int a = 7; int b = 5; boolean isGreater = (a > b); // 结果为 true |
< | 小于,判断左边的值是否小于右边的值 | int c = 2; int d = 4; boolean isLess = (c < d); // 结果为 true |
>= | 大于等于,判断左边的值是否大于或等于右边的值 | int e = 5; int f = 5; boolean isGreaterOrEqual = (e >= f); // 结果为 true |
<= | 小于等于,判断左边的值是否小于或等于右边的值 | int g = 3; int h = 4; boolean isLessOrEqual = (g <= h); // 结果为 true |
代码示例:
// 比较运算符boolean isEqual = num1 == num2;System.out.println("num1 是否等于 num2: " + isEqual);boolean isNotEqual = num1 != num2;System.out.println("num1 是否不等于 num2: " + isNotEqual);boolean isGreater = num1 > num2;System.out.println("num1 是否大于 num2: " + isGreater);boolean isLess = num1 < num2;System.out.println("num1 是否小于 num2: " + isLess);boolean isGreaterOrEqual = num1 >= num2;System.out.println("num1 是否大于等于 num2: " + isGreaterOrEqual);boolean isLessOrEqual = num1 <= num2;System.out.println("num1 是否小于等于 num2: " + isLessOrEqual);
逻辑运算符
逻辑运算符用于对布尔值进行逻辑操作,以下是常见的逻辑运算符及示例:
运算符 | 描述 | 示例 |
&& | 逻辑与,只有当两个操作数都为 true 时,结果才为 true | boolean a = true; boolean b = false; boolean result = a && b; // 结果为 false |
|| | 逻辑或,只要两个操作数中有一个为 true ,结果就为 true | boolean m = true; boolean n = false; boolean res = m||n; // 结果为 true` |
! | 逻辑非,对操作数的布尔值取反 | boolean p = true; boolean q =!p; // 结果为 false |
代码示例:
// 逻辑运算符boolean bool1 = true;boolean bool2 = false;boolean logicalAnd = bool1 && bool2;System.out.println("逻辑与结果: " + logicalAnd);boolean logicalOr = bool1 || bool2;System.out.println("逻辑或结果: " + logicalOr);boolean logicalNot =!bool1;System.out.println("逻辑非结果: " + logicalNot);
- Java沿用了C++的做法,使用&&表示逻辑“与”运算符,使用||表示逻辑“或”运算符。从!=运算符可以想到,感叹号!就是逻辑非运算符。
- &&和||运算符是按照“短路”方式来求值的:如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。
- 用&&运算符合并两个表达式,计算得到第一个表达式的真值为false,结果就不可能为true。
- 因此,第二个表达式就不必计算了。可以利用这一点来避免错误。
位运算符
位运算符用于对整数类型的二进制位进行操作,以下是常见的位运算符及示例:
运算符 | 描述 | 示例 |
& | 按位与,对两个操作数的对应二进制位进行与操作,只有当对应位都为 1 时,结果位才为 1 | int x = 5; // 二进制 0101 int y = 3; // 二进制 0011 int result = x & y; // 结果为 1,二进制 0001 |
| | 按位或,只要两个操作数中有一个为 true ,结果就为 true | `int a = 5; // 二进制 0101 int b = 3; // 二进制 0011 int orResult = a|b; // 结果为 7,二进制 0111` |
<< | 左移运算符,将操作数的二进制位向左移动指定的位数,右边空出的位用 0 填充。左移 n 位相当于将操作数乘以 2 的 n 次方 | int num = 5; int result = num << 2; |
>> | 有符号右移运算符,将操作数的二进制位向右移动指定的位数,左边空出的位用原符号位填充(正数补 0,负数补 1)。有符号右移 n 位相当于将操作数除以 2 的 n 次方并向下取整 | int positive = 20; int posResult = positive >> 2; int negative = -20; int negResult = negative >> 2; |
>>> | 无符号右移运算符,将操作数的二进制位向右移动指定的位数,左边空出的位始终用 0 填充,不考虑符号位 | int negative = -20; int result = negative >>> 2; // -20 的二进制(补码)1111 1111 1111 1111 1111 1111 1110 1100 无符号右移 2 位后 0011 1111 1111 1111 1111 1111 1111 1011,结果为 1073741819 |
^ | 按位异或,对两个操作数的对应二进制位进行异或操作,当两个对应位不同时结果位为 1,相同时结果位为 0 | int m = 5; // 二进制 0101 int n = 3; // 二进制 0011 int xorResult = m ^ n; // 结果为 6,二进制 0110 |
代码示例:
// 5. 位运算符int bitNum1 = 5; // 二进制: 0101int bitNum2 = 3; // 二进制: 0011int bitwiseAnd = bitNum1 & bitNum2;System.out.println("按位与结果: " + bitwiseAnd);int bitwiseOr = bitNum1 | bitNum2;System.out.println("按位或结果: " + bitwiseOr);int bitwiseXor = bitNum1 ^ bitNum2;System.out.println("按位异或结果: " + bitwiseXor);int bitwiseNot = ~bitNum1;System.out.println("按位取反结果: " + bitwiseNot);int leftShift = bitNum1 << 2;System.out.println("左移 2 位结果: " + leftShift);int rightShift = bitNum1 >> 1;System.out.println("右移 1 位结果: " + rightShift);int unsignedRightShift = bitNum1 >>> 1;System.out.println("无符号右移 1 位结果: " + unsignedRightShift);
三目运算符
布尔表达式 ? 表达式1 : 表达式2;
//在此表达式中,会返回x和y中较小的一个。
x<y?x:y
运算符优先级
运算符 | 结合性 |
[] . () (方法调用) | 从左向右 |
! ~ ++ -- + (一元运算) - (一元运算) () (强制类型转换)new | 从右向左 |
* / % | 从左向右 |
+ - | 从左向右 |
<< >> >>> | 从左向右 |
< <= > >= instanceof | 从左向右 |
== != | 从左向右 |
& | 从左向右 |
^ | 从左向右 |
&& | 从左向右 |
| | 从左向右 |
? : | 从右向左 |
= += -= *= /= %= &= = ^= <<= >>= >>>= | 从右向左 |
枚举类型
枚举类型是一种强类型,编译器会确保使用的是预先定义好的枚举常量,避免了错误的字符串或其他非法值的使用。
enum
关键字来定义枚举类型,例如: enum Season {SPRING, SUMMER, AUTUMN, WINTER
}
上述代码定义了一个名为Season
的枚举类型,包含了SPRING
(春天)、SUMMER
(夏天)、AUTUMN
(秋天)、WINTER
(冬天)四个枚举常量。
字符串
public class StringExample {public static void main(String[] args) {// 定义一个普通字符串String normalString = "Hello, World!";System.out.println("普通字符串: " + normalString);// 定义一个包含Unicode字符的字符串String unicodeString = "Java\u2122";System.out.println("包含Unicode字符的字符串: " + unicodeString);// 字符串是String类的实例,可以调用String类的方法int length = normalString.length();System.out.println("普通字符串的长度: " + length);boolean isEqual = normalString.equals("Hello, World!");System.out.println("普通字符串与指定内容是否相等: " + isEqual);}
}
在上述代码中:
- 定义了一个普通字符串
normalString
,它是String
类的一个实例。通过System.out.println
输出该字符串。- 定义了包含 Unicode 字符(
\u2122
代表商标符号™)的字符串unicodeString
,同样输出该字符串。- 利用
String
类的length
方法获取normalString
的长度,并输出。- 使用
String
类的equals
方法比较normalString
与指定字符串内容是否相等,并输出结果。
子串
- String类的substring方法可以从一个较大的字符串提取出一个子串。
- 在 Java 中,
String
类的substring
方法用于从一个较大的字符串中提取子串,它有两种重载形式,下面分别举例说明:
1. substring(int beginIndex)
该方法用于从指定的索引位置 beginIndex
开始截取字符串,一直到原字符串的末尾。
public class SubstringExample1 {public static void main(String[] args) {String originalString = "Hello, World!";// 从索引 7 开始截取字符串String subString = originalString.substring(7);System.out.println("原字符串: " + originalString);System.out.println("截取后的子串: " + subString);}
}
代码解释:
originalString
是原始的字符串"Hello, World!"
。originalString.substring(7)
表示从索引为 7 的字符(索引从 0 开始计数)开始截取,即字符W
,一直到字符串末尾,所以截取后的子串是"World!"
。
2. substring(int beginIndex, int endIndex)
该方法用于从指定的起始索引 beginIndex
开始截取字符串,直到指定的结束索引 endIndex
之前的位置(不包括 endIndex
位置的字符)。
public class SubstringExample2 {public static void main(String[] args) {String originalString = "Java Programming";// 从索引 0 开始,到索引 4 之前结束(不包括索引 4 的字符)String subString = originalString.substring(0, 4);System.out.println("原字符串: " + originalString);System.out.println("截取后的子串: " + subString);}
}
代码解释:
originalString
是原始的字符串"Java Programming"
。originalString.substring(0, 4)
表示从索引为 0 的字符(即字符J
)开始截取,到索引为 4 的字符(即字符P
)之前结束,所以截取后的子串是"Java"
。
注:
- 索引值必须在有效范围内,即
0
到字符串长度 - 1
之间,否则会抛出StringIndexOutofBoundsException异常。- 当
beginIndex
等于endIndex
时,截取的子串为空字符串。 substring的工作方式有一个优点:容易计算子串的长度。 字符串 s.substring(a,b)的长度为b-a。
拼接
- 与绝大多数的程序设计语言一样,Java语言允许使用+号连接(拼接)两个字符串。
- 这是最直观、最简单的字符串拼接方式,适用于少量字符串的拼接。
public class StringConcatenationExample {public static void main(String[] args) {String str1 = "Hello";String str2 = "World";String result = str1 + " " + str2;System.out.println(result);}
}
代码解释:
+
是 Java 中的字符串拼接运算符。当+
用于连接两个字符串时,会将它们连接成一个新的字符串。- 这里先将
str1
的值"Hello"
与空格字符串" "
拼接,得到"Hello "
,然后再将这个结果与str2
的值"World"
拼接,最终得到"Hello World"
。result
是一个新的字符串变量,用于存储拼接后的结果。
原理:
在编译时,Java 编译器会将使用+
运算符拼接字符串的代码转化为StringBuilder
类的操作。对于简单的拼接,这种方式很方便,但如果在循环中使用,会导致性能问题,因为每次拼接都会创建新的StringBuilder
对象。
不可变字符串
- String类没有提供用于修改字符串的方法。在 Java 中,
String
类代表不可变字符串,即字符串一旦创建,其内容就不能被改变。- 由于不能修改Java字符串中的字符,所以在Java文档中将String类对象称为不可变字符串。
- 不可变字符串有一个优点:编译器可以让字符串共享。
- 具体的工作方式,可以想象将各种字符串存放在公共的存储池中。字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串与复制的字符串共享相同的字符。
字符串常量池:Java 为了提高性能和减少内存开销,维护了一个字符串常量池。当创建字符串常量时,如
String s = "abc";
,JVM 首先会在字符串常量池中查找是否存在"abc"
这个字符串。如果存在,直接返回常量池中该字符串的引用;如果不存在,则在常量池中创建该字符串,然后返回引用。这使得相同内容的字符串常量在内存中只有一份实例,进一步体现了String
的不可变性。
代码示例:
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {private final char value[];
}
String
类内部使用一个private final char[]
数组来存储字符串内容,final
关键字确保了该数组一旦初始化,就不能再指向其他数组,从底层实现上保证了字符串的不可变性。- 由于
value
数组是private
的,外部代码无法直接访问和修改该数组,同时final
修饰符保证了数组引用的不可更改性。
总而言之,Java的设计者认为共享带来的高效率远远胜过于提取、拼接字符串所带来的低效率。查看一下程序会发现:很少需要修改字符串,而是往往需要对字符串进行比较。有一种例外情况,将来自于文件或键盘的单个字符或较短的字符串汇集成字符串。为此,Java提供了一个独立的类,这在之后会介绍。
检测字符串是否相等
1. 使用 equals
方法
equals
方法用于比较两个字符串的内容是否相等。它是String
类重写自Object
类的方法,用于专门比较字符串的实际字符序列。
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");boolean isEqual1 = str1.equals(str2);
boolean isEqual2 = str1.equals(str3); System.out.println(isEqual1);
System.out.println(isEqual2);
在上述代码中,
str1
和str2
都指向字符串常量池中的同一个"Hello"
字符串,str3
通过new
关键字创建了一个新的String
对象,但内容同样是"Hello"
。equals
方法比较的是字符串的内容,所以isEqual1
和isEqual2
都为true
。
2. 使用 equalsIgnoreCase
方法
equalsIgnoreCase
方法与equals
方法类似,也是用于比较字符串内容,但它忽略大小写。
注:请注意!请一定不要用`==`检测字符串是否相等,它只判断内存地址是否相同,即便内容一样,不同位置的字符串拷贝也会使判断出错。
只有字符串常量在虚拟机中是共享的,
+
或substring
等操作生成的字符串不共享。所以千万不要使用==
判断字符串相等,否则极易出现类似随机间歇性错误的严重 bug。
public class StringEqualityIgnoreCase {public static void main(String[] args) {String str1 = "Hello";String str2 = "hello";// 比较 str1 和 str2,忽略大小写boolean result = str1.equalsIgnoreCase(str2);System.out.println("str1.equalsIgnoreCase(str2) 的结果: " + result); }
}
代码解释:在比较
str1
和str2
时,equalsIgnoreCase
方法不考虑字母的大小写,因此即使str1
中的H
是大写,str2
中的h
是小写,该方法仍会返回true
空串与Null串
在 Java 中,空串(Empty String)和 Null
串是两个不同的概念。
空串
空串是长度为 0 的字符串,它是 String
类的一个有效实例,在内存中是实际存在的对象,只不过该对象不包含任何字符。可以通过两种方式创建空串:
// 方式一:直接使用双引号赋值
String emptyStr1 = "";
// 方式二:使用 String 类的构造函数
String emptyStr2 = new String();
判断方式
判断一个字符串是否为空串,通常可以使用 length()
方法或者 isEmpty()
方法:
String str = "";
// 使用 length() 方法判断
if (str.length() == 0) {System.out.println("字符串为空串");
}
// 使用 isEmpty() 方法判断
if (str.isEmpty()) {System.out.println("字符串为空串");
}
使用场景
- 初始化变量:在某些情况下,需要将字符串变量初始化为空串,以便后续根据业务逻辑进行赋值操作。
String result = "";
// 根据条件进行赋值
if (condition) {result = "Some value";
}
- 作为方法返回值:当方法没有合适的字符串结果需要返回时,可以返回空串。
public String getResult() {// 某些条件下没有结果return "";
}
Null 串
Null
串表示一个字符串变量没有引用任何对象,即该变量不指向任何有效的 String
实例。null
是 String变量中存放的一个特殊值,它表示目前没有任何对象与该变量关联。
String nullStr = null;
判断方式
判断一个字符串是否为 null
,需要使用 ==
运算符:
String str = null;
if (str == null) {System.out.println("字符串为 null");
}
需要注意的是,在使用 null
字符串调用方法时会抛出 NullPointerException
异常,例如:
String nullStr = null;
// 下面这行代码会抛出 NullPointerException 异常
// System.out.println(nullStr.length());
使用场景
- 表示对象未初始化:当一个字符串变量在声明时还不确定具体的值,或者在某些条件下没有合适的字符串对象可以引用时,可以将其初始化为
null
。
String userInput;
// 假设在某个方法中获取用户输入
userInput = getUserInput();
if (userInput == null) {System.out.println("未获取到用户输入");
}
- 方法参数传递:在方法调用时,如果某个字符串参数没有合适的值可以传递,可以传递
null
表示该参数未提供。
public void processString(String str) {if (str == null) {System.out.println("传入的字符串为 null");} else {// 处理字符串}
}
注意事项
在实际开发中,经常需要同时判断字符串是否为 null
或空串,可以结合 null
判断和空串判断:
String str = null;
if (str == null || str.isEmpty()) {System.out.println("字符串为 null 或空串");
}
需要注意判断顺序,先判断
null
再判断空串,避免对null
字符串调用isEmpty()
方法导致NullPointerException
异常。
码点与代码单元
- Java字符串由char值序列组成。
- char数据类型是一个采用UTF-16编码表示Unicode码点的代码单元。大多数的常用Unicode字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示。
Unicode 简介
Unicode 是一种字符编码标准,它为世界上几乎所有的字符都分配了一个唯一的数字,这个数字被称为码点。Unicode 码点的范围从
U+0000
到U+10FFFF
,可以表示超过一百万个字符。
码点(Code Point)
- 定义:码点是指 Unicode 编码中的一个数值,它唯一标识一个字符。例如,字符
'A'
的 Unicode 码点是U+0041
,字符'中'
的 Unicode 码点是U+4E2D
。在 Java 中,码点用int
类型表示,因为它的取值范围超出了char
类型所能表示的范围。- 获取码点:可以使用
String
类的codePointAt(int index)
方法来获取字符串中指定位置的码点。示例代码如下:
public class CodePointExample {public static void main(String[] args) {String str = "A中";// 获取第一个字符的码点int codePoint1 = str.codePointAt(0); // 获取第二个字符的码点int codePoint2 = str.codePointAt(1); System.out.println("字符 'A' 的码点: " + Integer.toHexString(codePoint1)); System.out.println("字符 '中' 的码点: " + Integer.toHexString(codePoint2)); }
}
遍历码点:可以使用
String
类的codePoints()
方法来遍历字符串中的所有码点。示例代码如下:
public class CodePointTraversal {public static void main(String[] args) {String str = "A中";str.codePoints().forEach(codePoint -> {System.out.println("码点: " + Integer.toHexString(codePoint));});}
}
代码单元(Code Unit)
- 定义:代码单元是字符串存储和处理的基本单位。在 Java 中,
String
类内部使用 UTF - 16 编码来存储字符,UTF - 16 编码使用 16 位(即 2 个字节)作为一个代码单元。对于 Unicode 码点在U+0000
到U+FFFF
范围内的字符(称为 BMP,基本多文种平面字符),可以用一个代码单元来表示;而对于码点在U+10000
到U+10FFFF
范围内的字符(称为补充字符),则需要用两个代码单元(即一个代理对)来表示。- 获取代码单元:可以使用
charAt(int index)
方法来获取字符串中指定位置的代码单元。示例代码如下:
public class CodeUnitExample {public static void main(String[] args) {String str = "A中";// 获取第一个代码单元char codeUnit1 = str.charAt(0); // 获取第二个代码单元char codeUnit2 = str.charAt(1); System.out.println("第一个代码单元: " + codeUnit1); System.out.println("第二个代码单元: " + codeUnit2); }
}
- 代码单元数量:可以使用
length()
方法来获取字符串中的代码单元数量。需要注意的是,对于包含补充字符的字符串,length()
方法返回的是代码单元的数量,而不是字符的实际数量。示例代码如下:
public class CodeUnitLength {public static void main(String[] args) {// 包含补充字符的字符串String str = "\uD83D\uDE00"; System.out.println("代码单元数量: " + str.length()); System.out.println("实际字符数量: " + str.codePoints().count()); }
}
码点与代码单元的关系
- 对于 BMP 字符,一个码点对应一个代码单元。
- 对于补充字符,一个码点对应两个代码单元。在处理包含补充字符的字符串时,需要特别注意,不能简单地使用
charAt()
和length()
方法,而应该使用与码点相关的方法,如codePointAt()
和codePoints()
方法。
输入输出
在 Java 中,输入输出(I/O)操作是非常重要的部分,它允许程序与外部环境进行数据交互,比如从键盘读取用户输入、将数据写入文件、从网络接收数据等。Java 的 I/O 操作主要分为标准输入输出、文件输入输出和网络输入输出。
格式化输出
输出整数
public class IntegerOutputExample {public static void main(String[] args) {int number = 12345;// 使用 System.out.print 输出整数System.out.print("整数输出结果: ");System.out.print(number);}
}
代码解释:
- 首先定义了一个
int
类型的变量number
并赋值为12345
。- 第一行
System.out.print
输出提示信息"整数输出结果: "
。- 第二行
System.out.print
输出变量number
的值。
沿用C库函数
- Java SE 沿用了 C 语言库函数中的
printf
方法风格。 - 在 C 语言里,
printf
函数是一个非常常用的输出函数,可按指定格式输出各种类型的数据。 - 而 Java 在
java.io.PrintStream
类中也提供了printf
方法,其功能和使用方式与 C 语言的printf
函数有相似之处,都是支持格式化输出。 - 代码示例:
public class PrintfExample {public static void main(String[] args) {int num = 10;double pi = 3.14159;String name = "John";// 输出整数System.out.printf("整数: %d\n", num);// 输出浮点数,保留两位小数System.out.printf("浮点数: %.2f\n", pi);// 输出字符串System.out.printf("字符串: %s\n", name);// 组合输出System.out.printf("%s 的幸运数字是 %d,他喜欢的圆周率近似值是 %.2f。\n", name, num, pi);}
}
与 C 语言 printf 的区别
注意:虽然 Java 的
printf
方法借鉴了 C 语言printf
函数的格式化输出方式,但 Java 是面向对象的语言,printf
是PrintStream
类的一个实例方法,并且 Java 有自己严格的类型检查机制,这和 C 语言有所不同。例如在 C 语言里,可能需要更手动地管理类型转换和内存,而 Java 会在编译阶段进行类型检查,保证代码的类型安全性。
用于printf的转换符
转换符 | 类型 | 举例 |
d | 十进制整数 | 159 |
x | 十六进制整数 | 9f |
o | 八进制整数 | 237 |
f | 定点浮点数 | 15.9 |
e | 指数浮点数 | 1.59e+01 |
g | 通用浮点数 | — |
a | 十六进制浮点数 | 0x1.fccdp3 |
s | 字符串 | Hello |
c | 字符 | H |
b | 布尔 | True |
h | 散列码 | 42628b2 |
tx 或 Tx(T 强制大写) | 日期时间(已经过时,应当改为使用 java.time 类) | - |
% | 百分号 | % |
n | 与平台有关的行分隔符 | — |
代码示例:
public class FormatConversionExample {public static void main(String[] args) {// 整数相关转换符int intValue = 159;System.out.printf("十进制整数(d): %d\n", intValue);System.out.printf("十六进制整数(x): %x\n", intValue);System.out.printf("八进制整数(o): %o\n", intValue);// 浮点数相关转换符double doubleValue = 15.9;System.out.printf("定点浮点数(f): %.1f\n", doubleValue);System.out.printf("指数浮点数(e): %e\n", doubleValue);System.out.printf("通用浮点数(g): %g\n", doubleValue);System.out.printf("十六进制浮点数(a): %a\n", doubleValue);// 字符串、字符、布尔、散列码相关转换符String strValue = "Hello";char charValue = 'H';boolean boolValue = true;Object obj = new Object();System.out.printf("字符串(s): %s\n", strValue);System.out.printf("字符(c): %c\n", charValue);System.out.printf("布尔(b): %b\n", boolValue);System.out.printf("散列码(h): %h\n", obj);// 百分号和行分隔符转换符System.out.printf("百分号(%%): %% \n");System.out.printf("与平台有关的行分隔符(n): 第一行%n第二行");}
}
运行结果:
十进制整数(d): 159
十六进制整数(x): 9f
八进制整数(o): 237
定点浮点数(f): 15.9
指数浮点数(e): 1.590000e+01
通用浮点数(g): 15.9
十六进制浮点数(a): 0x1.fccdp3
字符串(s): Hello
字符(c): H
布尔(b): true
散列码(h): 7852e922
百分号(%): %
与平台有关的行分隔符(n): 第一行
第二行
用于printf的转换符
public class PrintfFlagsExample {public static void main(String[] args) {double number = 3333.33;int intNumber = 159;// + 标志:打印正数和负数的符号System.out.printf("+ 标志示例:%+f\n", number);// 空格 标志:在正数之前添加空格System.out.printf("空格 标志示例:% f\n", number);// 0 标志:数字前面补0System.out.printf("0 标志示例:%010.2f\n", number);// - 标志:左对齐System.out.printf("- 标志示例:%-10.2f\n", number);// ( 标志:将负数括在括号内double negativeNumber = -3333.33;System.out.printf("( 标志示例:%(f\n", negativeNumber);//, 标志:添加分组分隔符System.out.printf(", 标志示例:%,f\n", number);// #(对于f格式)标志:包含小数点System.out.printf("#(对于f格式)标志示例:%,#.0f\n", number);// #(对于x或o格式)标志:添加前缀0x或0System.out.printf("#(对于x格式)标志示例:%#x\n", intNumber);// $ 标志:指定要格式化的参数索引System.out.printf("$ 标志示例:%1$d %1$x\n", intNumber);// < 标志:格式化前面说明的数值System.out.printf("< 标志示例:%d%<x\n", intNumber);}
}
运行结果
+ 标志示例:+3333.330000
空格 标志示例: 3333.330000
0 标志示例:003333.33
- 标志示例:3333.33
( 标志示例:(3333.33)
, 标志示例:3,333.330000
#(对于f格式)标志示例:3,333.
#(对于x格式)标志示例:0x9f
$ 标志示例:159 9f
< 标志示例:159 9f
标志 | 目的 | 示例代码中的效果 |
+ | 打印正数和负数的符号 | 输出+3333.330000 |
空格 | 在正数之前添加空格 | 输出 3333.330000 |
0 | 数字前面补 0 | 输出003333.33 |
- | 左对齐 | 输出3333.33 |
( | 将负数括在括号内 | 输出(3333.33) |
, | 添加分组分隔符 | 输出3,333.330000 |
#(对于 f 格式) | 包含小数点 | 输出3,333. |
#(对于 x 或 o 格式) | 添加前缀 0x 或 0 | 输出0x9f |
$ | 指定要格式化的参数索引 | 输出159 9f |
< | 格式化前面说明的数值 | 输出159 9f |
日期和时间转换符
import java.util.Calendar;public class DateTimeConversionExample {public static void main(String[] args) {Calendar calendar = Calendar.getInstance();System.out.println("完整的日期和时间 (c): " + String.format("%tc", calendar));System.out.println("ISO 8601日期 (F): " + String.format("%tF", calendar));System.out.println("美国格式的日期 (月/日/年) (D): " + String.format("%tD", calendar));System.out.println("24小时时间 (T): " + String.format("%tT", calendar));System.out.println("12小时时间 (r): " + String.format("%tr", calendar));System.out.println("24小时时间,没有秒 (R): " + String.format("%tR", calendar));System.out.println("4位数字的年(前面补0) (Y): " + String.format("%tY", calendar));System.out.println("年的后两位数字(前面补0) (y): " + String.format("%ty", calendar));System.out.println("年的前两位数字(前面补0) (C): " + String.format("%tC", calendar));System.out.println("月的完整拼写 (B): " + String.format("%tB", calendar));System.out.println("月的缩写 (b或h): " + String.format("%tb", calendar));System.out.println("两位数字的月(前面补0) (m): " + String.format("%tm", calendar));System.out.println("两位数字的日(前面补0) (d): " + String.format("%td", calendar));System.out.println("两位数字的日(前面不补0) (e): " + String.format("%te", calendar));System.out.println("星期几的完整拼写 (A): " + String.format("%tA", calendar));System.out.println("星期几的缩写 (a): " + String.format("%ta", calendar));System.out.println("三位数的年中第几天(前面补0),在001到366之间 (j): " + String.format("%tj", calendar));System.out.println("两位数字的小时(前面补0),在0到23之间 (H): " + String.format("%tH", calendar));System.out.println("两位数字的小时(前面不补0),在0到23之间 (k): " + String.format("%tk", calendar));System.out.println("两位数字的小时(前面补0),在01到12之间 (I): " + String.format("%tI", calendar));System.out.println("两位数字的小时(前面不补0),在1到12之间 (l): " + String.format("%tl", calendar));System.out.println("两位数字的分钟(前面补0) (M): " + String.format("%tM", calendar));System.out.println("两位数字的秒(前面补0) (S): " + String.format("%tS", calendar));System.out.println("三位数字的毫秒(前面补0) (L): " + String.format("%tL", calendar));System.out.println("九位数字的毫秒(前面补0) (N): " + String.format("%tN", calendar));System.out.println("上午或下午的标志 (p): " + String.format("%tp", calendar));System.out.println("从GMT起,RFC 822数字位移 (z): " + String.format("%tz", calendar));System.out.println("时区 (Z): " + String.format("%tZ", calendar));System.out.println("从格林尼治时间1970-01-01 00:00:00起的秒数 (s): " + String.format("%ts", calendar));System.out.println("从格林尼治时间1970-01-01 00:00:00起的毫秒数 (Q): " + String.format("%tQ", calendar));}
}
转换符 | 类型 | 示例 |
c | 完整的日期和时间 | Sun Jan 26 14:53:00 CST 2025 |
F | ISO 8601 日期 | 2025-01-26 |
D | 美国格式的日期 (月 / 日 / 年) | 01/26/25 |
T | 24 小时时间 | 14:53:00 |
r | 12 小时时间 | 02:53:00 pm |
R | 24 小时时间,没有秒 | 14:53 |
Y | 4 位数字的年(前面补 0) | 2025 |
y | 年的后两位数字(前面补 0) | 25 |
C | 年的前两位数字(前面补 0) | 20 |
B | 月的完整拼写 | January |
b 或 h | 月的缩写 | Jan |
m | 两位数字的月(前面补 0) | 01 |
d | 两位数字的日(前面补 0) | 26 |
e | 两位数字的日(前面不补 0) | 26 |
A | 星期几的完整拼写 | Sunday |
a | 星期几的缩写 | Sun |
j | 三位数的年中第几天(前面补 0),在 001 到 366 之间 | 026 |
H | 两位数字的小时(前面补 0),在 0 到 23 之间 | 14 |
k | 两位数字的小时(前面不补 0),在 0 到 23 之间 | 14 |
I | 两位数字的小时(前面补 0),在 01 到 12 之间 | 02 |
l | 两位数字的小时(前面不补 0),在 1 到 12 之间 | 2 |
M | 两位数字的分钟(前面补 0) | 53 |
S | 两位数字的秒(前面补 0) | 00 |
L | 三位数字的毫秒(前面补 0) | 123 |
N | 九位数字的毫秒(前面补 0) | 123000000 |
p | 上午或下午的标志 | pm |
z | 从 GMT 起,RFC 822 数字位移 | +0800 |
Z | 时区 | CST |
s | 从格林尼治时间 1970-01-01 00:00:00 起的秒数 | 1738009980 |
Q | 从格林尼治时间 1970-01-01 00:00:00 起的毫秒数 | 1738009980123 |