开篇:一个"巡检成绩表处理"程序
上一章的成绩统计里,数组已经露过脸了:int[] scores = {92, 85, ...}。这一章把它讲透,再把日常开发中刷屏的 String 方法一口气过完。还是先看一段完整程序——录入一组成绩,做排序展示、查最高最低、格式化输出一张简易成绩表:
public class ScoreTableApp { public static void main(String[] args) { // 1. 声明并初始化数组 int[] scores = {85, 92, 74, 66, 90}; String[] names = {"张伟", "李强", "王磊", "赵鹏", "陈刚"}; // 2. 求最高分、最低分、平均分 int max = scores[0]; int min = scores[0]; int sum = 0; for (int i = 0; i < scores.length; i++) { if (scores[i] > max) { max = scores[i]; } if (scores[i] < min) { min = scores[i]; } sum += scores[i]; } double avg = (double) sum / scores.length; // 3. 打印成绩表(拼接出整齐的表头) StringBuilder table = new StringBuilder(); table.append("===== 巡检成绩表 =====\n"); for (int i = 0; i < names.length; i++) { String name = names[i]; // 名字不足 3 个字的后面补空格对齐 if (name.length() < 3) { name = name + " "; } table.append(name).append(":").append(scores[i]).append(" 分\n"); } System.out.print(table.toString()); // 4. 输出统计结论 System.out.println("最高分:" + max + ",最低分:" + min + ",平均分:" + avg); // 5. 找出第一个优秀者的名字 for (int i = 0; i < scores.length; i++) { if (scores[i] >= 90) { System.out.println("首个优秀者:" + names[i].trim()); break; } } } }运行结果:
===== 巡检成绩表 ===== 张伟 :85 分 李强 :92 分 王磊 :74 分 赵鹏 :66 分 陈刚 :90 分 最高分:92,最低分:66,平均分:81.4 首个优秀者:李强程序里涉及三样东西:数组、String 方法、StringBuilder,分三部分讲。
第一部分:数组——定长的同类型容器
声明与初始化的两种姿势
// 姿势一:声明时直接给值(最常用) int[] scores = {85, 92, 74, 66, 90}; // 姿势二:只给长度,元素拿默认值 int[] arr = new int[5]; // 5 个元素,全是 0 String[] names = new String[3]; // 3 个元素,全是 null要点:
int[]是一个整体——"int 的数组"这种类型。数组可以装基本类型,也可以装引用类型。new int[5]创建后元素自动填默认值:int 是 0,double 是 0.0,boolean 是 false,引用类型(包括 String)是 null。数组长度创建时定死,之后不能变。这是数组最大的局限,也是后来 ArrayList(第九章)登场的理由。
三件套:下标、length、遍历
scores[0] // 取第一个元素,下标从 0 开始 scores.length // 长度:5(注意:数组的 length 是属性,没有括号) scores[i] = 100; // 按下标赋值遍历的两种写法(第三章已讲,这里对号入座):
for (int i = 0; i < scores.length; i++) { ... } // 要下标用经典 for for (int s : scores) { ... } // 纯取值用增强 for必知坑:数组越界(ArrayIndexOutOfBoundsException)
下标的合法范围是0 到 length-1,访问scores[5](长度就是 5 时)运行期直接抛异常:
int[] scores = {85, 92, 74}; System.out.println(scores[3]); // 编译能过,运行崩溃:ArrayIndexOutOfBoundsException这类异常是新手期最常见的运行时报错。越界十有八九来自i <= scores.length这种边界写错,记住标准姿势是< length。
顺带一提:多维数组(如int[][] matrix)存在,但业务代码里用得很少,软考偶尔露脸,知道int[3][4]是 3 行 4 列即可,本系列不展开。
第二部分:String 的常用方法——就这一把,天天用
String 类的方法有几十个,日常高频的如下表,结合案例逐个看:
| 方法 | 作用 | 例子 |
|---|---|---|
length() | 字符个数 | "锚杆".length()→ 2 |
charAt(i) | 取下标 i 的字符 | "锚杆".charAt(0)→'锚' |
substring(a, b) | 截取 [a, b) 区间 | "设备巡检".substring(0, 2)→"设备" |
indexOf(str) | 找子串首次出现的位置 | "3号皮带".indexOf("皮带")→ 2 |
contains(str) | 是否包含子串 | "井下-580m".contains("580")→ true |
equals(str) | 内容比较 | 第二章铁律 |
equalsIgnoreCase(str) | 忽略大小写比较 | "abc".equalsIgnoreCase("ABC")→ true |
trim() | 去掉首尾空白 | " 维修 ".trim()→"维修" |
split(分隔符) | 拆成字符串数组 | "85,92,74".split(",")→["85","92","74"] |
replace(旧, 新) | 替换全部匹配 | "a-b-c".replace("-", "/")→"a/b/c" |
startsWith / endsWith | 前缀/后缀判断 | 文件名、编号判断常用 |
toUpperCase / toLowerCase | 大小写转换 | 统一格式用 |
结合案例看几个典型的:
① 名字对齐用的length():
if (name.length() < 3) { name = name + " "; // 不足 3 字补空格,表格才齐 }注意区分:数组的 length 是属性(scores.length),String 的 length 是方法(name.length())。带不带括号是经典笔误点,也是考点。
② 解析 CSV 风格的数据,split()+trim()是黄金搭档:
String line = " 张伟 , 92 , 综采一队 "; String[] parts = line.split(","); String name = parts[0].trim(); // "张伟" int score = Integer.parseInt(parts[1].trim()); // 92(字符串转 int,第十章细讲)③ 判断后再截取,indexOf()配合substring():
String location = "井下-580m东翼大巷"; int idx = location.indexOf("m"); if (idx >= 0) { // indexOf 找不到会返回 -1,先判断 String depth = location.substring(3, idx + 1); // "580m" }④ 前缀判断处理编号:
String code = "WX-2026-0341"; if (code.startsWith("WX-")) { System.out.println("这是一条维修单"); }别忘了第二章讲过的 String 不可变性:这些方法全部返回新字符串,原字符串纹丝不动。name.trim()之后要把结果接回来——name = name.trim();,光调用不接收等于白调用。
第三部分:StringBuilder——循环拼接的唯一正确姿势
案例里拼成绩表用的是 StringBuilder:
StringBuilder table = new StringBuilder(); table.append("===== 巡检成绩表 =====\n"); for (int i = 0; i < names.length; i++) { table.append(name).append(":").append(scores[i]).append(" 分\n"); } System.out.print(table.toString());为什么不用熟悉的+?回顾第二章:String 不可变,每次+都新建一个对象。循环 1000 次就产生 1000 个临时字符串对象,内存和速度都是浪费。StringBuilder 内部是一块可变的字符缓冲区,append是原地追加,不产生新对象,最后toString()一次成型。
用法就三个动作:
StringBuilder sb = new StringBuilder(); // 1. 创建 sb.append("a").append(100).append("b"); // 2. 追加(可链式,什么类型都能塞) String result = sb.toString(); // 3. 转成 String规则一句话:循环里拼字符串,用 StringBuilder;一次性拼两三个,用+就行(编译器对简单的+拼接有优化,不用矫枉过正)。
案例里还有个细节:\n是换行符(转义字符)。常用的转义就三个:\n换行、\t制表符、\"在字符串里表示一个双引号本身。
一个隐患:两个数组"按下标对应"有多脆弱?
回头看案例的数据结构:
int[] scores = {85, 92, 74, 66, 90}; String[] names = {"张伟", "李强", "王磊", "赵鹏", "陈刚"};"张伟得 85 分"这个关系,靠的是两个数组下标对齐来维系的:names[i] 的成绩是 scores[i]。程序跑对的前提是录入时两边顺序严格一致。
问题在于:这种对应关系编译器完全不管。假设录入时手一抖,names 里漏了一个人或者顺序排错,程序照样编译通过、照样运行,只是从此以后"李强的成绩"全是别人的——结果张冠李戴,还不会有任何报错提示。这是数组时代最隐蔽的逻辑 bug 温床。
解法其实直觉上很简单:把"姓名、成绩、班组"捆在一起,装进一个"工人"里,让它们物理上不可分。这个"捆在一起"的手段,就是下一章的主角——类与对象。
常踩的坑(排雷清单)
数组越界:下标合法范围 0 ~ length-1,边界一律写
i < length。length 带不带括号:数组
scores.length(属性),Stringname.length()(方法),记死这个区别。String 方法不接收返回值:
s.trim()之后 s 没变!字符串不可变,必须s = s.trim()。substring 的右边界不含:
substring(0, 2)取下标 0 和 1,左闭右开,和很多人的直觉相反。indexOf 找不到返回 -1:直接用返回值当下标会越界,先判
>= 0。循环里用
+拼长字符串:几百上千次循环后性能明显下降,该上 StringBuilder。多数组按下标对应:数据错位不报错,是最隐蔽的逻辑 bug 温床。
本章小结
数组:定长、同类型;两种初始化姿势;元素默认值(int 为 0、引用为 null);越界是运行时异常。
String 高频方法就那一把:
length()、substring()、indexOf()、contains()、trim()、split()、replace()、startsWith等,全部返回新串。StringBuilder:
new→append→toString()三步,循环拼接的唯一正确姿势。数组按下标对应多份数据的痛点,引出了下一章的主角。
下一章是全系列的地基:类与对象——字段、方法、构造器、this 与 static。我们会把"姓名+成绩+班组"封装成一个真正的"工人对象",亲手解决本章结尾的数据错位问题。