← Java 基础教程

第 5 / 40 课 · 免费教程

聊天栏里的字符串魔法

让名字、等级和消息各就各位

100–120 分钟Java 11+入门

本课作品

制作控制台聊天格式化器

一条消息只要少一个空格,玩家名和正文就会挤成一团。String 让程序能够保存文字,也能寻找、比较和整理文字。这一课,我们从最小欢迎语开始,沿着索引找到每个位置,最后做出一台能把杂乱数据整理成清楚消息的聊天格式化器。

开始前:建议先完成第 1–4 课

01 / 14

空格也是信息

一条清楚的消息,应该让人一眼分清谁在说话、说了什么。
一条清楚的消息,应该让人一眼分清谁在说话、说了什么。

广播上出现“欢迎Alex加入红队”。你大概能猜懂,可一旦名字和消息更长,猜测就不够了。电脑不会自动替我们补标点和空格。字符串里的每个字符,都参与了最后的表达。

这里的“聊天栏”是作品的故事主题。程序先在控制台输出消息;真正发送到游戏服务器,需要以后学习服务器或模组接口。先把文字组织正确,是值得单独完成的一步。

  • 拼接时留意边界与空格。
  • 截取前先标出索引。
  • 比较内容与清理格式,选择不同的方法。

02 / 14

String 保存文字,也返回新文字

固定文字写在双引号中;变量名负责读取已有内容。
固定文字写在双引号中;变量名负责读取已有内容。

片段:放在 main 中,单独运行

String player = "Alex";
String team = "Red";
String message = "找到钻石!";
String welcome = "欢迎 " + player + "!";
System.out.println(welcome);
System.out.println(team + ": " + message);

运行结果

欢迎 Alex!
Red: 找到钻石!

String 的 S 要大写。"player" 是一段固定文字,player 则读取变量。"欢迎" + player 会得到“欢迎Alex”;要留空格,就明确写 "欢迎 "。

03 / 14

+ 会算,也会连接

组成一条有边界的消息
  1. 队伍[RED]
  2. 等级Lv.8
  3. 玩家Alex:
  4. 正文Hi!

连接各部分时,空格和标点要明确写在字符串里。

片段:放在 main 中,单独运行

int level = 8;
System.out.println("等级=" + level + 1);
System.out.println("等级=" + (level + 1));
int diamonds = 3, bonus = 2;
System.out.println("总钻石=" + (diamonds + bonus));
String levelText = String.valueOf(level);
System.out.println("Lv." + levelText);

运行结果

等级=81
等级=9
总钻石=5
Lv.8

第一行从左到右连接:先得到“等级=8”,再接上“1”。第二行的括号先算8+1,再把9接进消息。看到数字和文字混在一起,先决定哪些部分是计算。

04 / 14

索引从0开始,长度从数量出发

CREEPER 的七个位置
索引0123456
字符CREEPER

length() = 7;合法索引为0到6。charAt(0)是C,charAt(6)是R。

片段:放在 main 中,单独运行

String mob = "CREEPER";
System.out.println(mob.length());
System.out.println(mob.charAt(0));
System.out.println(mob.charAt(mob.length() - 1));

运行结果

7
C
R

“有七个位置”和“最后的位置编号是六”并不矛盾:我们从0开始编号。charAt(7) 越过了末尾,空字符串也没有可以读取的第0个位置。

05 / 14

substring:左边拿,右边停

切下 DIAMOND 的前一段
索引0123456
字符DIAMOND

substring(0, 3)拿索引0、1、2,得到DIA;索引3的M不包含。

片段:放在 main 中,单独运行

String ore = "DIAMOND";
System.out.println(ore.substring(0, 3));
System.out.println(ore.substring(3));
System.out.println(ore.substring(ore.length() - 3));

运行结果

DIA
MOND
OND

两个参数分别是起点和结束边界。起点包含,结束边界不包含。只给一个起点时,就取到字符串末尾。先画编号,通常比反复试数字更快。

06 / 14

搜索:有没有,在哪里?

四个问题,四种返回结果
方法所问的问题返回
contains有没有这段文字?boolean
indexOf第一次出现在哪里?int,未找到是-1
startsWith开头是不是这段文字?boolean
endsWith结尾是不是这段文字?boolean

片段:放在 main 中,单独运行

String msg = "I found diamond!";
System.out.println(msg.contains("diamond"));
System.out.println(msg.indexOf("diamond"));
System.out.println(msg.indexOf("emerald"));
System.out.println("/home now".startsWith("/"));
System.out.println(msg.endsWith("!"));

运行结果

true
8
-1
true
true

07 / 14

内容相同,未必是同一个对象

两个箱子的内容可以一样,箱子却仍然是两个。
两个箱子的内容可以一样,箱子却仍然是两个。

片段:放在 main 中,单独运行

String a = new String("diamond");
String b = new String("diamond");
System.out.println(a.equals(b));
System.out.println(a == b);
String role = null;
System.out.println("admin".equals(role));

运行结果

true
false
false

equals 比较 String 的内容;这里的 == 比较两个引用是否指向同一个对象。这个例子明确创建了两个对象,所以内容相同,引用比较仍为 false。

08 / 14

大小写转换:接住返回的新结果

归一化后再比较
  1. 原始命令/Home
  2. 得到新结果toLowerCase
    (Locale.ROOT)
  3. 比较内容/home
    与目标一致

片段:放在 main 中,单独运行

String command = "/Home";
String normalized = command.toLowerCase(java.util.Locale.ROOT);
System.out.println(normalized);
System.out.println(command.equalsIgnoreCase("/HOME"));
String team = "blue";
System.out.println(team.toUpperCase(java.util.Locale.ROOT));
System.out.println(command);

运行结果

/home
true
BLUE
/Home

最后一行仍是 /Home,因为我们没有给 command 重新赋值。对于固定格式的英文命令或标签,Locale.ROOT 让转换不依赖电脑当前的语言设置;暂时把它当成一个明确的转换选项。

09 / 14

两端空白与空内容,是两回事

空白清理站:逐项核对
数据检查结果
" Alex "length()10
strip 后的 "Alex"length()4
""isEmpty()true
" "isEmpty()false
" "isBlank()true

片段:放在 main 中,单独运行

String rawName = "   Alex   ";
String cleanName = rawName.strip();
System.out.println(rawName.length());
System.out.println(cleanName.length());
System.out.println("<" + cleanName + ">");
System.out.println("   ".isEmpty());
System.out.println("   ".isBlank());

运行结果

10
4
<Alex>
false
true

10 / 14

转义与 null:看清隐藏的规则

片段:放在 main 中,单独运行

String quote = "Alex 说:\"Hi!\"";
String twoLines = "第一行\n第二行";
String path = "C:\\Minecraft";
System.out.println(quote);
System.out.println(twoLines);
System.out.println(path);
System.out.println("bad word".replace("bad", "good"));

运行结果

Alex 说:"Hi!"
第一行
第二行
C:\Minecraft
good word

反斜杠让某些字符进入字符串:\" 表示引号,\n 表示换行,\\ 表示一个反斜杠。它们在源代码里的样子,与运行后的结果不同。

片段:放在 main 中,单独运行

String player = null;
boolean valid = player != null && !player.isBlank();
System.out.println(valid);

运行结果

false

11 / 14

先设计聊天格式,再写代码

格式明确后,读者不用猜哪一段是名字,哪一段是消息。
格式明确后,读者不用猜哪一段是名字,哪一段是消息。
格式化器的工作路线
  1. 初始数据队伍、等级
    玩家、消息
  2. 清理strip 两端空白
    队伍改大写
  3. 组织[TEAM] Lv.等级
    玩家: 消息
  4. 核对是否为空
    是否以/开头

本作品先使用写在变量里的文字。键盘读取要到下一课才学习,所以现在只需修改这些变量,再运行核对。队伍、姓名、消息清理后都应非空;等级使用1到30的练习范围。

  • 空白只在两端清理,保留消息内部的空格。
  • 不对整条消息强制改大写,否则正文也会变。
  • 输出格式固定,便于用样例逐字核对。

12 / 14

完整作品:清理、检查、组织

完整程序 · ChatFormatter.java

import java.util.Locale;

public class ChatFormatter {
    public static void main(String[] args) {
        String team = " red ", player = " Alex ";
        String message = " /home ";
        int level = 8;
        team = team.strip().toUpperCase(Locale.ROOT);
        player = player.strip();
        message = message.strip();
        boolean valid = !team.isBlank() && !player.isBlank()
            && !message.isBlank() && level >= 1 && level <= 30;
        if (!valid) {
            System.out.println("请检查队伍、名字、消息和等级");
            return;
        }
        String chat = "[" + team + "] Lv." + level
            + " " + player + ": " + message;
        System.out.println(chat);
        System.out.println("命令=" + message.startsWith("/"));
    }
}

运行结果

[RED] Lv.8 Alex: /home
命令=true

13 / 14

让测试覆盖不同边界

聊天格式化器的测试表
情况修改项主要结果命令检查
正常原始参考数据[RED] Lv.8 Alex: /hometrue
普通消息message = " Hi! "[RED] Lv.8 Alex: Hi!false
大小写team = " ReD "仍是 [RED]不变
空名字player = " "只输出检查提示不执行
越界等级level = 31只输出检查提示不执行

一张测试表同时写明“改什么”和“预期什么”,才能告诉你差异来自哪里。先只改一个字段,确认结果,再组合多个变化。空名字被拒绝时,不应继续读取它的首字符。

  • 升级:输出 message.contains("diamond") 的结果,再测试大小写差异。
  • 升级:为公告增加标题行,用 \n 将两行连起来。
  • 升级:用 replace 替换一个明确的词,观察返回的新字符串。

14 / 14

把方法与意图连起来

第五课的能力地图
  1. 组织+ 与空格
  2. 定位length / charAt
  3. 截取substring
  4. 比较equals
  5. 清理strip / 大小写
  • 基础:用三个字符串变量做两行告示牌,明确写出换行和空格。
  • 进阶:清理两端带空格的名字,核对原长度与新长度;非空时再取首尾字符。
  • 创造:为三条探险事件写消息模板,包含玩家、地点和事件,再做一次搜索或替换。

30秒自测

① substring(1, 4) 包含哪些索引?② " ".isEmpty() 与 isBlank() 相同吗?③ 运行 name.strip() 后,name 会自动改变吗?

技术参考:Java SE 11 的 String API,docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html。

TAKE IT WITH YOU

下载本课 PDF

完整课文、代码、图解与练习,选择你需要的语言。