字符串
从概念上讲, Java 字符串就是 Unicode 字符序列. 例如, 字符串 "Java\u2122" 由 5 个 Unicode 字符 J, a, v, a,
Java 没有内置的字符串类型, 而是标准 Java 类库中提供了一个预定义类, 叫作 String.
每个用双引号括起来的字符串都是 String 类的一个实例:
public class Test { public static void main(String[] args) { String e = ""; // an empty string String greeting = "Hello"; } }
包: java.lang
类: java.lang.String
Java 程序中的所有字符串文字都为此类的对象.
字符串的内容在创建之后就不能再被更改.
下面的代码产生了三个字符串:
public class introduction { public static void main(String[] args) { String a = "hello"; String b = " world"; System.out.println(a + b); } }
下面的代码产生了两个字符串:
public class introduction { public static void main(String[] args) { String a = "hello"; System.out.println(a); a = "world"; System.out.println(a); } }
第五行创建了一个新的字符串, 将新的字符串赋值给了变量 a, 而不是改变了第一个字符串的内容.
由于不能修改 Java 字符串中的单个字符, 所以在 Java 文档中将 String 类对象称为是不可变的 (immutable) , 如同数字 3 永远是数字 3 一样, 字符串 "Hello" 永远包含字符 H, e, l, l 和 o 的代码单元序列. 你不能修改这些值. 不过, 可以修改字符串变量的内容, 让它指向另外一个字符串. 这就如同可以让原本存放 3 的数值变量改成存放 4 —样.
当将一个字符串与一个非字符串的值进行拼接时, 后者会转换成字符串.
任何一个 Java 对象都可以转换成字符串.
空串
空串 ""
是长度为 0 的字符串. 可以调用以下代码检查一个字符串是否为空:
if (str.length() == 0) // 或 if (str.equals(""))
空串是一个 Java 对象, 有自己的串长度 (0) 和内容 (空) .
null 串
String 变量还可以存放一个特殊的值, 名为 null, 表示目前没有任何对象与该变量关联.
null 是一个特殊的值, 即空值, 值为 nul.
打印 null 字面量:
public class ValueDemo1 { public static void main(String[] args) { // 空值null不能直接打印,只能以字符串的形式打印 System.out.println("null"); } }
执行结果:
null
要检查一个字符串是否为 null, 可以使用以下代码:
if (str == null)
有时要检查一个字符串既不是 null 也不是空串, 这种情况下可以使用:
if (str != null && str.length() != 0)
首先要检查 str 不为 null.
如果在一个 null 值上调用方法, 会出现错误.
创建字符串的两种方式
创建字符串对象有两种方式:
-
直接赋值: String name = "xiaoming";
-
使用 new 关键字用不同的构造方法来创建字符串对象.
构造方法 | 说明 |
---|---|
public String() | 使用空参构造, 创建空白字符串, 不含任何内容 |
public String(String original) | 传入一个字符串, 根据传入的字符串, 创建一个新的字符串 |
public String(char[] chs) | 根据字符数组, 创建一个字符串 |
public String(byte[] chs) | 根据字节数组, 创建一个字符串 |
程序示例:
public class Test { public static void main(String[] args) { // 使用直接赋值的方式创建一个字符串对象 String s1 = "abc"; System.out.println(s1); // abc // 使用 new 关键字来获取字符串对象 // 空参构造, 获取一个空的字符串对象 String s2 = new String(); System.out.println("#" + s2 + "#"); // ## // 传递一个字符串, 根据传递进来的字符串创建一个新的字符串 String s3 = new String("Hello"); System.out.println(s3); // Hello // 传递一个字符数组, 根据字符数组的内容, 创建一个新的字符串 char[] chs = {'n', 'i', 'h', 'a', 'o'}; String s4 = new String(chs); System.out.println(s4); // nihao // 这种方式可以用来修改字符串. 因为字符串创建出来之后就不能被改变, 所以可以将字符串改为字符数组, // 再改变字符数组, 再利用改变之后的字符数组生成新的字符串 String s5 = "nihao"; // 原字符串 char[] chars = s5.toCharArray(); // 字符串变为字符数组 System.out.println(chars); // 打印字符数组, 输出: nihao chars[2] = 'p'; // 改变字符数组中的一个元素 System.out.println(chars); // 打印字符数组, 输出: nipao String s6 = new String(chars); // 根据改变之后的字符数组创建新的字符串 System.out.println(s6); // 打印字符串, 输出: nipao // 传递一个字节数组, 根据字节数组创建一个新的字符串对象 // 应用场景: 以后在网络中传递的数据都是字节信息, 一般都要把字节信息进行转换, 转成字符串, 此时就要用到这个构造. byte[] bytes = {97, 98, 99, 100}; System.out.println(bytes); // [B@4dd8dc3 String s7 = new String(bytes); System.out.println(s7); // abcd } }
内存分析
不可变字符串有一个很大的优点: 编译器可以让字符串共享.
StringTable: 串池, 即字符串常量池, 用来存放字符串.
在 JDK7 之前, StringTable 在方法区, 从 JDK7 开始, StringTable 移动到了堆内存中. 但是不管在哪里, 运行机制没有发生变化.
只有用直接赋值的方法创建的字符串对象, 才会在 StringTable 中.
如果是用 new 关键字获取的字符串对象就不在 StringTable 中.
当只用直接赋值的方式创建字符串时, 系统会检查该字符串在字符串池中是否存在, 如果不存在, 系统就创建一个新的字符串, 如果存在, 就直接复用.
直接赋值的方式:

执行 String s1 = "abc";
时, 先检查串池, 发现没有字符串 "abc", 则创建一个字符串 "abc", 在堆内存中的内容都是有地址的, 同样这个字符串 "abc" 也是有一个地址的, 这个地址被赋值给 s1.
执行 String s2 = "abc";
时, 先检查串池, 发现有字符串 "abc", 则直接复用这个字符串.
各个字符串存放在一个公共的存储池中, 字符串变量指向存储池中相应的位置, 如果复制一个字符串变量, 原始字符串和复制的字符串共享相同的字符.
C++ string 对象也会自动完成内存的分配与回收. 要通过构造器, 赋值操作符和析构器显式完成内存管理. 不过, C++ 字符串是可修改的, 也就是说, 可以修改字符串中的单个字符.
使用 new 关键字的方式:

用这种方式不会复用字符串. 每 new 一次就是开辟了一个新的小空间. 如果相同字符串较多, 这样做会浪费内存空间. 尽量使用直接赋值的方式, 因为代码简单且节约内存.
equals() 和 equalsIgnoreCase()
程序示例:
public class StringDemo1 { public static void main(String[] args) { String s1 = new String("abc"); // 记录堆里面的地址 String s2 = "abc"; // 记录串池中的地址 String s3 = "abc"; String s4 = new String("abc"); System.out.println(s1 == s2); // false, 一个是直接赋值, 一个是 new 出来的 System.out.println(s1); // abc System.out.println(s2); // abc System.out.println(s2 == s3); // true, 两个都是直接赋值得到的 System.out.println(s1 == s4); // false, 两个都是 new 出来的 } }
== 号比较的是什么:
如果是基本数据类型, 比较的就是值.
如果是引用数据类型, 比较的就是地址值.
比较内容的两个方法:
equals() 方法和 equalsIgnoreCase() 方法.
可以使用 equals() 方法检测两个字符串是否相等. 对于表达式 s.equals(t)
, 如果字符串 s 与字符串 t 相等, 则返回 true, 否则, 返回 false. 需要注意的是, s 和 t 可以是字符串变量, 也可以是字符串字面量.
例如, 以下表达式是合法的:
"Hello".equals(greeting)
要想检测两个字符串是否相等, 而不区分大小写, 可以使用 equalsIgnoreCase() 方法.
"Hello".equals("hello")
不要使用运算符 == 检测两个字符串是否相等. 这个运算符只能够确定两个字符串是否存放在同一个位置上. 当然, 如果字符串在同一个位置上, 它们必然相等. 但是, 完全有可能将多个相等的字符串副本存放在不同的位置上.
只有字符串字面量会共享, 而其他所有操作得到的字符串, 比如 new 出来的, 用 + 得到的或 substring 等操作得到的字符串并不共享.
程序示例:
public class Test { public static void main(String[] args) { String s1 = new String("abc"); String s2 = "abc"; String s3 = "abc"; String s4 = new String("abc"); System.out.println(s1.equals(s2)); // true, 一个是直接赋值, 一个是 new 出来的 System.out.println(s1 == s2); // false System.out.println(s2.equals(s3)); // true, 两个都是直接赋值得到的 System.out.println(s2 == s3); // true System.out.println(s1.equals(s4)); // true, 两个都是 new 出来的 System.out.println(s1 == s4); // false } }
当有一个字符串是从键盘读入时:
程序示例:
import java.util.Scanner; public class StringDemo1 { public static void main(String[] args) { String s1 = new String("abc"); String s2 = "abc"; Scanner sc = new Scanner(System.in); System.out.println("请输入一个字符串"); String s3 = sc.next(); // s3 是 new 出来的 System.out.println(s1 == s3); System.out.println(s2 == s3); } }
执行结果:
请输入一个字符串 abc false false
从键盘读入的字符串也被认为是 new 出来的.
阅读 next 方法的源码:

按住 Ctrl 键后左键点击 next, 即可进入 next 的源码, 或者先鼠标单击 next, 然后按下快捷键 Ctrl + b.

next 方法最终返回 token, 这就是我们从键盘录入的数据. 这个 token 是通过方法 getCompleteTokenInBuffer 得到的. 选中 getCompleteTokenInBuffer 方法, 按下快捷键 Ctrl + b, 进入 getCompleteTokenInBuffer 方法的源码.

如果 getCompleteTokenInBuffer 方法正常执行, 则返回 s, 而 s 是通过 group 得到的.
再从 group 进去.

再从 group(0) 进去.

再从 getSubSequence 进去.

再从 这里面的 subSequence 进去.

点击 122 行绿色圆圈, 找到 String 中的实现:

或者回到 getSubSequence 方法中, 在这里也能找到实现:


最终找到的位于 String 中的实现:

从 substring 进去.

从 newString 进去.

最终发现, 就是返回了一个 new 出来的 String 对象.
练习:
已知正确的用户名和密码, 请用程序实现模拟用户登录.
总共给三次机会, 登录之后, 给出相应的提示.
代码:
import java.util.Scanner; public class Test { public static void main(String[] args) { String rightName = "xiaoming"; String rightPasswd = "666"; Scanner sc = new Scanner(System.in); System.out.print("请输入用户名: "); String inputName = sc.next(); System.out.print("请输入密码: "); String inputPasswd = sc.next(); int count = 2; while (count >= 1 && (!inputName.equals(rightName) || !inputPasswd.equals(rightPasswd))) { if (!inputName.equals(rightName) && inputPasswd.equals(rightPasswd)) { System.out.print("用户名错误,请重新输入用户名: "); inputName = sc.next(); } else if (inputName.equals(rightName) && !inputPasswd.equals(rightPasswd)) { System.out.print("密码错误,请重新输入密码: "); inputPasswd = sc.next(); } else if (!inputName.equals(rightName) && !inputPasswd.equals(rightPasswd)) { System.out.print("用户名和密码均错误, 请重新输入用户名: "); inputName = sc.next(); System.out.print("请重新输入密码: "); inputPasswd = sc.next(); } --count; } if (count == 0) { System.out.println("三次机会已经全部用完, 无法继续登录."); } else { System.out.println("登录成功"); } } }
执行结果:
请输入用户名: xiaoli 请输入密码: 000 用户名和密码均错误, 请重新输入用户名: xiaofang 请重新输入密码: 111 用户名和密码均错误, 请重新输入用户名: xiaozhang 请重新输入密码: 333 三次机会已经全部用完, 无法继续登录.
另一种写法:
import java.util.Scanner; public class StringDemo1 { public static void main(String[] args) { String name = "xiaoming"; String passwd = "666"; Scanner sc = new Scanner(System.in); for (int i = 3; i > 0; i--) { System.out.print("请输入用户名: "); String inputName = sc.next(); System.out.print("请输入密码: "); String inputPasswd = sc.next(); if (inputName.equals(name) && inputPasswd.equals(passwd)) { System.out.println("登录成功."); break; } else { if (i > 1) { System.out.println("用户名或密码输入错误, 请重新输入."); System.out.println("还有 " + (i - 1) + " 次机会."); } else { System.out.print("用户名或密码输入错误, 3 次机会均已用完, 登录失败.\n"); } } } } }
执行结果:
请输入用户名: xiaozhang 请输入密码: 999 用户名或密码输入错误, 请重新输入. 还有 2 次机会. 请输入用户名: xiaowang 请输入密码: 000 用户名或密码输入错误, 请重新输入. 还有 1 次机会. 请输入用户名: xiaoli 请输入密码: 222 用户名或密码输入错误, 3 次机会均已用完, 登录失败.
compareTo()
源码:

返回值有三种情况:
-
返回 0: 调用这个方法的字符串和形参字符串相等 (字面意思的相等, 不是地址值相等)
-
返回正数: 调用这个方法的字符串按照字典顺序排在形参字符串之后
-
返回负数: 调用这个方法的字符串按照字典顺序排在形参字符串之前
该方法用于判断一个字符串是大于, 等于还是小于另一个字符串. 判断字符串大小的依据是根据它们在字典中的顺序决定的.
语法:
str1.compareTo(str2);
其返回的是一个 int 类型值.
如果两个字符串长度相同, 则返回第一个不相等的字符的 ASCII 码值的差值, 是 str1 减去 str2.
如果两个字符串长度不相同, 先一直向后比较, 如果在某一个字符串到达结束之前, 遇到了不相等的字符, 则返回第一个不相等的字符的 ASCII 码值的差值, 是 str1 减去 str2. 如果一直向后比较的过程中, 没有遇到不相等的字符, 即 str1 和 str2 中一个字符串是另一个字符串的前缀, 则返回两个字符串的字符个数之差, 是 str1 的字符个数减去 str2 的字符个数.
程序示例:
public class StringDemo { public static void main(String[] args) { // 两个字符串常量的比较 int b = "Hello".compareTo("Hello"); System.out.println(b); // 0 // 两个字符串变量的比较 String s1 = "Hello"; String s2 = "Hello"; int c = s1.compareTo(s2); System.out.println(c); // 0 // 两个字符串常量的比较 String s3 = "hello"; String s4 = "Hello"; int d = s3.compareTo(s4); System.out.println(d); // 32 } }
程序示例:
import java.util.Scanner; public class StringDemo { public static void main(String[] args) { // 从键盘输入的字符串的比较 Scanner sc = new Scanner(System.in); System.out.print("输入一个字符串: "); String inputString = sc.next(); int result = inputString.compareTo("Hello"); System.out.println(result); } }
执行结果:
输入一个字符串: Hello 0
程序示例:
public class StringDemo { public static void main(String[] args) { // 两个字符串长度相等 System.out.println("bbc".compareTo("abc")); // 1 System.out.println("abc".compareTo("acc")); // -1 } }
程序示例:
public class Test { public static void main(String[] args) { // 两个字符串长度不相等 System.out.println("bbc".compareTo("ba")); // 1 System.out.println("abcd".compareTo("ab")); // 2 System.out.println("ab".compareTo("abcdef")); // -4 } }
length()
数组长度 = 数组名.length;
这里的 length 是一个属性, 所以不加括号.
字符串的长度 = 字符串对象.length();
字符串的长度使用方法 length(), 所以要加括号.
charAt()
方法 public char charAt(int index);
根据字符串的索引返回字符.
字符串的索引的规则和数组的索引一模一样.
练习:
需求: 键盘录入一个字符串, 使用程序实现在控制台遍历该字符串.
程序示例:
import java.util.Scanner; public class StringDemo1 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.print("请输入一个字符串: "); String s = sc.next(); for (int i = 0; i < s.length(); i++) { // 这里在 IDEA 中的快捷键是 s.length().fori, 如果是数组则快捷键是 s.fori char c = s.charAt(i); System.out.println(c); } } }
执行结果:
请输入一个字符串: hello h e l l o
toUpperCase(), toLowerCase(), isUpperCase(), isLowerCase(), isDigit()
作用: 字符全部变为大/小写.
程序示例:
public static void main(String[] args) { String s = "Hello"; // 字符串变量 System.out.println(s.toUpperCase()); // HELLO System.out.println(s); // Hello, 说明不影响原来的字符串 System.out.println("length".toUpperCase()); // LENGTH System.out.println("ok123".toUpperCase()); // OK123, 说明只对小写字母起作用, 其他字符不变 System.out.println(s.toLowerCase()); // hello }
练习:
统计字符次数
键盘录入一个字符串, 统计该字符串中大写字母字符, 小写字母字符, 数字字符出现的次数 (不考虑其他字符)
程序示例:
import java.util.Scanner; public class StringDemo1 { public static void main(String[] args) { // 键盘录入一个字符串 Scanner sc = new Scanner(System.in); System.out.print("请输入一个字符串: "); String str = sc.next(); // 统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数 int upper = 0, lower = 0, digit = 0; for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (Character.isUpperCase(c)) upper++; else if (Character.isLowerCase(c)) lower++; else if (Character.isDigit(c)) digit++; } // 输出结果 System.out.println("大写字母有" + upper + "个."); System.out.println("小写字母有" + lower + "个."); System.out.println("数字字符有" + digit + "个."); } }
执行结果:
请输入一个字符串: Hello123 大写字母有1个. 小写字母有4个. 数字字符有3个.
练习:
拼接字符串
定义一个方法, 把 int 数组中的数据按照指定的格式拼接成一个字符串返回,
调用该方法, 并在控制台输出结果.
例如:
数组为 int[] arr = {1,2,3};
执行方法后的输出结果为: [1, 2, 3]
代码:
public class Test { public static void main(String[] args) { // 给定了一个数字数组 int[] arr = {1, 2, 3}; String str = arrToString(arr); System.out.println(str); } public static String arrToString(int[] arr) { // 如果传递进来的数组是空, 则返回一个空字符串 if (arr == null) return ""; // 如果传递进来的数组的长度为零, // 例如这样定义一个数组: int[] arr=new int[0];, 那么数组 arr 的长度就是 0, 这样的数组是不能存放任何内容的. // 那么返回一对方括号, 括号内没有任何内容. if (arr.length == 0) return "[]"; // 假如传递进来的数组是有内容的, 那么就要进行拼接 String res = "["; for (int i = 0; i < arr.length; i++) { if (i != arr.length - 1) { res = res + arr[i] + ", "; } else { res = res + arr[i] + "]"; } } return res; } }
执行结果:
[1, 2, 3]
练习:
字符串反转
定义一个方法, 实现字符串反转.
键盘录入一个字符串, 调用该方法后, 在控制台输出结果.
例如, 键盘录入 abc, 输出结果 cba
批量修改: shift + f6
代码:
import java.util.Scanner; public class StringDemo1 { public static void main(String[] args) { // 从键盘输入一个字符串 Scanner sc = new Scanner(System.in); System.out.print("输入一个字符串: "); String str = sc.next(); System.out.println(str); String str2 = convertString(str); System.out.println(str2); } // 定义一个方法, 反转一个字符串 public static String convertString(String str) { char[] arr = new char[str.length()]; arr = str.toCharArray(); int i = 0, j = arr.length - 1; for (; i <= j; i++, j--) { char tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } String res = new String(arr); return res; } }
执行结果:
输入一个字符串: Hello Hello olleH
另一种更好的写法:
import java.util.Scanner; public class StringDemo1 { public static void main(String[] args) { // 从键盘输入一个字符串 Scanner sc = new Scanner(System.in); System.out.print("输入一个字符串: "); String str = sc.next(); System.out.println(str); String str2 = convertString(str); System.out.println(str2); } // 定义一个方法, 反转一个字符串 public static String convertString(String str) { String res = ""; for (int i = str.length() - 1; i >= 0; i--) { res += str.charAt(i); } return res; } }
执行结果相同.
练习:
要求: 金额转换, 将数字转换为大写汉字, 汉字一共是七位.
代码:
import java.util.Scanner; public class StringDemo1 { public static void main(String[] args) { System.out.print("请输入一个整数: "); Scanner sc = new Scanner(System.in); int num = sc.nextInt(); char[] arr1 = {'零', '零', '零', '零', '零', '零', '零'}; char[] arr2 = {'佰', '拾', '万', '仟', '佰', '拾', '元'}; int tmp = num; int index = arr1.length - 1; while (tmp != 0) { int n = tmp % 10; tmp = tmp / 10; char c = toChar(n); arr1[index] = c; index--; } for (int i = 0; i < arr1.length; i++) { System.out.print(arr1[i]); System.out.print(arr2[i]); } } // 将数字转换为对应的汉字 public static char toChar(int n) { switch (n) { case 0: return '零'; case 1: return '壹'; case 2: return '贰'; case 3: return '叁'; case 4: return '肆'; case 5: return '伍'; case 6: return '陆'; case 7: return '柒'; case 8: return '捌'; case 9: return '玖'; default: return '拾'; } } }
另一种写法:
import java.util.Scanner; public class StringDemo1 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int num; char[] arr1 = {'零', '零', '零', '零', '零', '零', '零'}; // 数字 char[] arr2 = {'佰', '拾', '万', '仟', '佰', '拾', '元'}; // 单位 // 输入整数并检查范围的正确性 while (true) { System.out.print("请输入一个整数: "); num = sc.nextInt(); if (num >= 0 && num <= 9999999) { break; } else { System.out.println("金额输入错误, 请重新输入."); } } // 将 arr1 中的数字转换为大写的中文 int index = arr1.length - 1; while (num != 0) { int n = num % 10; num /= 10; char c = getCapital(n); arr1[index] = c; // 直接修改 arr1 index--; } // 把大写的数字和单位拼接为一个字符串 String res = ""; for (int i = 0; i < arr1.length; i++) { res = res + arr1[i] + arr2[i]; } System.out.println(res); } // 将数字转换为大写的中文 private static char getCapital(int num) { char[] hashMap = {'零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'}; return hashMap[num]; } }
另一种写法:
import java.util.Scanner; public class Test { public static void main(String[] args) { char[] arr1 = {'零', '零', '零', '零', '零', '零', '零'}; // 数字 char[] arr2 = {'佰', '拾', '万', '仟', '佰', '拾', '元'}; // 单位 System.out.print("请输入一个整数: "); Scanner sc = new Scanner(System.in); int num = sc.nextInt(); char[] arr3 = convertNum(num); for (int i = arr3.length - 1, j = arr1.length - 1; i >= 0; i--, j--) { // arr1 和 arr3 的索引要分开 arr1[j] = arr3[i]; } for (int i = 0; i < arr1.length; i++) { System.out.print(arr1[i] + "" + arr2[i]); } } public static char[] convertNum(int num) { String s = num + ""; // 数字转化为字符串 char[] arr = s.toCharArray(); // 字符串转化为数组, 因为数字不能直接转化为数组, 所以借助字符串为媒介 char[] newArr = new char[arr.length]; for (int i = 0; i < arr.length; i++) { newArr[i] = toChar(arr[i]); } return newArr; } // 将数字字符转换为对应的汉字 public static char toChar(char n) { int num = n - '0'; char[] hashMap = {'零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'}; return hashMap[num]; } }
执行结果:
请输入一个整数: 2135 零佰零拾零万贰仟壹佰叁拾伍元
另一种写法:
import java.util.Scanner; public class Test { public static void main(String[] args) { // 1. 键盘录入一个金额 Scanner sc = new Scanner(System.in); int money; while (true) { System.out.println("请录入一个金额"); money = sc.nextInt(); if (money >= 0 && money <= 9999999) { break; } else { System.out.println("金额无效"); } } // 定义一个变量用来表示钱的大写 String moneyStr = ""; // 2. 得到 money 里面的每一位数字, 再转成中文 while (true) { // 2135 // 从右往左获取数据, 因为右侧是数据的个位 int ge = money % 10; String capitalNumber = getCapitalNumber(ge); // 把转换之后的大写拼接到 moneyStr 当中 moneyStr = capitalNumber + moneyStr; // 注意拼接的先后顺序 // 第一次循环 : "伍" + "" = "伍" // 第二次循环 : "叁" + "伍" = "叁伍" // 去掉刚刚获取的数据 money = money / 10; // 如果数字上的每一位全部获取到了, 那么 money 记录的就是 0, 此时循环结束 if (money == 0) { break; } } // 3. 在前面补 0, 补齐 7 位 int count = 7 - moneyStr.length(); for (int i = 0; i < count; i++) { moneyStr = "零" + moneyStr; } System.out.println(moneyStr); // 零零零贰壹叁伍 // 4. 插入单位 // 定义一个数组表示单位 String[] arr = {"佰", "拾", "万", "仟", "佰", "拾", "元"}; // 零 零 零 贰 壹 叁 伍 // 遍历 moneyStr, 依次得到 零 零 零 贰 壹 叁 伍 // 然后把 arr 的单位插入进去 String result = ""; for (int i = 0; i < moneyStr.length(); i++) { char c = moneyStr.charAt(i); // 把大写数字和单位拼接到 result 当中 result = result + c + arr[i]; } // 5. 打印最终结果 System.out.println(result); } // 定义一个方法把数字变成大写的中文 // 1 -- 壹 public static String getCapitalNumber(int number) { // 定义数组, 让数字跟大写的中文产生一个对应关系 String[] arr = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"}; // 返回结果 return arr[number]; } }
substring() 方法
String 类的 substring() 方法可以从一个较大的字符串提取出一个子串.
类似于 C 和 C++, Java 字符串中的代码单元和码点从 0 开始计数.
String substring(int beginIndex, int endIndex); // 区间范围: 左闭右开.
substring 的工作方式有一个优点: 很容易计算子串的长度. 字符串 s.substring(a, b) 的长度为 b-a.
有一个重载的方法:
String substring(int beginIndex); // 默认截取到末尾.
substring 返回一个新的字符串, 对原始的字符串没有影响.
练习: 手机号屏蔽
程序示例:
public class Test { public static void main(String[] args) { // 先给定一个手机号 String phoneNumber = "15940379881"; // 截取前三位 String start = phoneNumber.substring(0, 3); // 截取后四位 String end = phoneNumber.substring(7); // 拼接 String result = start + "****" + end; // 打印 System.out.println(result); } }
执行结果:
159****9881
练习:
身份证查看
1, 2 位: 省份
3, 4 位: 城市
5, 6 位: 区县
7-14 位: 出生年, 月, 日
15, 16 位: 所在地派出所
17 位: 性别 (奇数男性, 偶数女性)
18 位:个人信息码 (随机产生)
根据信息:
7-14 位: 出生年, 月, 日
17 位: 性别 (奇数男性, 偶数女性)
打印信息:
人物信息为
出生年月日: XXXX年X月X日
性别为: 男/女
程序示例:
public class StringDemo1 { public static void main(String[] args) { String num = "342625199707300017"; String year = num.substring(6, 10); String month = num.substring(10, 12); String day = num.substring(12, 14); String gender = num.substring(16, 17); char gen; if (Integer.parseInt(gender) % 2 == 1) gen = '男'; else gen = '女'; System.out.println("人物信息为:"); System.out.println("出生年月日: " + year + "年" + month + "月" + day + "日"); System.out.println("性别为: " + gen); } }
执行结果:
人物信息为: 出生年月日: 1997年07月30日 性别为: 男
replace()
Java 提供了一个方法执行替换: String replace(旧值, 新值);
练习: 敏感词替换
代码 1:
public class StringDemo1 { public static void main(String[] args) { String talk = "你真的, TMD"; // 将敏感词 TMD 替换为 *** String res = talk.replace("TMD", "***"); System.out.println(res); } }
执行结果:
你真的, ***
代码 2, 有多个敏感词的情况:
public class StringDemo1 { public static void main(String[] args) { String talk = "你真的, TMD, SB, CNM"; // 将敏感词替换为 *** // 定义一个敏感词库 String[] arr = {"TMD", "SB", "CNM"}; for (int i = 0; i < arr.length; i++) { talk = talk.replace(arr[i], "***"); } System.out.println(talk); }
执行结果:
你真的, ***, ***, ***
一个字符串中有多个相同敏感词的情况:
public class StringDemo1 { public static void main(String[] args) { String talk = "你真的, SB, SB, SB"; // 将敏感词替换为 *** talk = talk.replace("SB", "***"); // 替换之后的字符串赋值给 talk System.out.println(talk); } }
执行结果:
你真的, ***, ***, ***
可以看见, 一次性进行了全部替换.
字符串中没有需要替换的内容时, 替换不起作用, 相当于没有进行这一步.
join()
如果需要把多个字符串放在一起, 用一个界定符分隔, 可以使用静态 join() 方法.
程序示例:
public class StringDemo { public static void main(String[] args) { String all = String.join("/", "S", "M", "L", "XL"); System.out.println(all); // S/M/L/XL } }
repeat()
在 Java11 中, 还提供了一个 repeat() 方法.
程序示例:
public class StringDemo { public static void main(String[] args) { String repeated = "Java".repeat(3); System.out.println(repeated); // JavaJavaJava } }
字符串拼接的原理
第一种情况: 拼接过程中没有指定变量去接收结果, 只有一个最终的变量接收最终的结果.
public class StringDemo { public static void main(String[] args) { String s = "a" + "b" + "c"; System.out.println(s); // abc } }
拼接时触发字符的优化机制, 编译时就已经是最终结果了.
使用 javac 进行编译时, Java 会进行检查, 检查是否有变量参与, 如果没有变量参与, 则 Java 将待拼接的内容立刻进行拼接, class 文件中的内容与直接赋值得到 String 对象的结果一样. 上面的 java 源文件编译得到的 class 文件的内容如下:
public class StringDemo { public static void main(String[] args) { String s = "abc"; System.out.println(s); // abc } }
这是在执行之前即编译阶段所做的工作. 拼接到一起的字符串和直接赋值的字符串一样, 都存在于串池中.
代码:
public class StringBuilderDemo { public static void main(String[] args) { String s1 = "abc"; // 直接赋值 String s2 = "a" + "b" + "c"; // 拼接, 都存在于串池中, 因为上面的赋值导致串池中已经有了字符串 "abc", 因此这里就直接复用了这个字符串 System.out.println(s1 == s2); // true } }
第二种情况: 拼接时有变量参与.
public class Test { public static void main(String[] args) { String s1 = "a"; String s2 = s1 + "b"; String s3 = s2 + "c"; System.out.println(s3); // abc } }
在 JDK 8 以前, 遇到这种情况, 底层使用 StringBuilder 进行拼接, 内存图:

首先第一步, main 方法先进栈.
然后执行第一行语句 String s1 = "a";
, 这是直接赋值, 所以在栈的 main 方法的空间内会生成一个变量 s1, 同时在串池中生成一个字符串 "a" (因为此时串池没有字符串 "a", 没有就会新建, 有了的话就直接复用). 此时 s1 记录的就是串池中 "a" 的地址值.
接着执行语句 String s2 = s1 + "b";
, 此时会在串池中生成一个字符串 "b". 因为此处有变量参与, 则在堆内存中, 首先创建一个 StringBuilder 对象, 即相当于执行了 new StringBuilder();
, 然后通过 append 方法将 a 和 b 都放到这个对象当中. 然后底层还会调用 toString 方法, 将这个 StringBuilder 对象变回字符, 这个字符串的内容就是 "ab". 所以执行语句 String s2 = s1 + "b";
时就相当于执行了语句 new StringBuilder().append(s1).append("b").toString();
.
接着执行到语句 String s3 = s2 + "c";
, 会在串池中新建一个字符串 "c", 同时在堆内存中创建一个新的 StringBuilder 对象,将 s2 和 "c" 拼接完了之后放进这个对象, 然后调用 toString 方法创建一个新的字符串对象, 即 "abc".

结论: 一个变量和一个字符串每进行一次加操作, 在堆内存中最少会有两个对象, 一个是 StringBuilder 对象, 一个是 String 对象. 这样非常浪费空间, 速度也会下降.
到了 JDK 8, Java 会先预估需要的空间, 创建一个数组, 存放拼接的内容, 再将数组转为字符串, 但是预估需要时间, 而且在有多处拼接时可能预估得不准确, 性能依然较差. 因此在拼接时, 推荐使用 StringBuilder 或者 StringJoiner. 这个由数组转化而来的字符串, 也是 new 出来的, 也是存在于堆内存中, 而不是串池中.
总结:当字符串拼接遇到变量时:
JDK 8 之前, 系统底层会自动创建一个 StringBuilder 对象, 然后再调用其 append 方法完成拼接, 拼接后, 在调用其 toString 方法, 转换为 String 类型, 而 toString 方法的底层是 new 了一个字符串对象.
JDK 8 及之后, 系统会预估字符串拼接之后的总大小, 把要拼接的内容都放在数组中, 此时也是在堆内存中产生一个新的字符串.
示例:


基本数据类型和 String 类型的转换
在程序开发中, 经常需要将基本数据类型转成 String 类型, 或者将 String 类型转成基本数据类型.
基本类型转字符串: 基本类型 + ""
程序示例:
public class StringToBasic { public static void main(String[] args) { int n1 = 100; float f1 = 1.1F; double d1 = 4.5; boolean b1 = true; String s1 = n1 + ""; String s2 = f1 + ""; String s3 = d1 + ""; String s4 = b1 + ""; System.out.println(s1); System.out.println(s2); System.out.println(s3); System.out.println(s4); } }
运行结果:
100 1.1 4.5 true
String 类型转基本数据类型语法: 通过基本类型的包装类调用 parseXX
方法即可. XX
对应着一个类型.
Java 里, 每一个基本数据类型都对应一个包装类.
程序示例:
public class StringToBasic { public static void main(String[] args) { String s5 = "123"; int num1 = Integer.parseInt(s5); double num2 = Double.parseDouble(s5); float num3 = Float.parseFloat(s5); long num4 = Long.parseLong(s5); byte num5 = Byte.parseByte(s5); boolean b = Boolean.parseBoolean("true"); short num6 = Short.parseShort(s5); System.out.println(num1); System.out.println(num2); System.out.println(num3); System.out.println(num4); System.out.println(num5); System.out.println(num6); System.out.println(b); } }
运行结果:
123 123.0 123.0 123 123 123 true
在将 String 类型转成基本数据类型时, 要确保 String 类型能够转成有有效的数据, 比如可以把 "123"
转成一个整数, 但是不能把 "hello"
转成一个整数. 如果格式不正确, 就会抛出异常, 程序就会终止. 这个异常在编译时不会发现, 在执行时会体现出来.
文本块
利用 Java15 新增的文本块 (textblock) 特性, 可以很容易地提供跨多行的字符串字面量. 文本块以 """ 开头 (这是开始 """), 后面是一个换行符, 并以另一个 """ 结尾 (这是结束 """)
开始 """ 后面的换行符不作为字符串字面量的一部分.
程序示例:
public class Test { public static void main(String[] args) { String s1 = """ ok hello morning"""; // 如果不想要最后一行后面的换行符, 可以让结束紧跟在最后一个字符后面 System.out.println(s1); System.out.println("=========================="); String s2 = """ ni hao a """; System.out.println(s2); System.out.println("======================="); // 有一个转义序列只能在文本块中使用. 行尾的 \ 会把这一行与下一行连接起来. String s3 = """ hello \ world"""; System.out.println(s3); System.out.println("==========================="); } }
执行结果:
ok hello morning ========================== ni hao a ======================= hello world ===========================
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术