9.11 练习

本章的示例代码位于仓库 ThinkJavaCode 的目录 ch09 中,有关如何下载这个仓库,请参阅前言中的“使用示例代码”一节。做以下的练习前,建议你先编译并运行本章的示例。

练习9-1

这个练习旨在探索 Java 类型,并补充本章前面未涉及的一些细节。

(1) 创建一个新程序,将其命名为 Test.java,并在 main 方法中编写一些用运算符 + 将不同数据类型“相加”的表达式。例如,如果将字符串和 char“相加”,结果将会如何?这会执行字符加法运算还是执行字符串拼接操作呢?结果是什么类型?你又是如何确定结果类型的?

(2) 复制并填写下面的表格。在任何两种类型的交叉位置指出对它们使用运算符 + 是否合法,如果合法,将执行什么样的操作(加法运算还是拼接操作)?结果是什么类型呢?

boolean char int double string
boolean
char
int
double
string

(3) 想想 Java 的设计者在填写这个表格时作出的一些选择。在这个表格中,有多少项因为别无选择而无法避免。又有多少项原本有同样充分理由的可能性,但 Java 的设计者只是随便选择了其中的一个。哪些项存在严重的问题?

(4) 通常情况下,语句 x++x = x + 1 完全等价,但如果 xchar,情况就不是这样了。在这种情况下,x++ 是合法的,但 x = x + 1 将导致错误。请尝试这样做,看看将出现什么样的错误消息,并试着找出错误的原因。

(5) 将 ""(空字符串)与其他类型的值相加(如 ""+5)时,结果将如何?

(6) 可将哪些类型的值赋给各种类型的变量?例如,可将 int 值赋给 double 变量,但反过来不行。

练习9-2

编写一个名为 letterHist 的方法,让它接受一个字符串参数,并返回一个表示该字符串各字母出现次数的直方图。在返回的直方图中,第 0 个元素为字母 a(不区分大小写)在这个字符串中出现的次数,第 25 个元素为字母 z 出现的次数。你的解决方案只能遍历该字符串一次。

练习9-3

这个练习旨在复习封装和泛化(参见 7.3 节)。下面的代码片段遍历了一个字符串,并检查了它包含的左括号数和右括号数是否相等:

  1. String s = "((3 + 7) * 2)";
  2. int count = 0;
  3. for (int i = 0; i < s.length(); i++) {
  4. char c = s.charAt(i);
  5. if (c == '(') {
  6. count++;
  7. } else if (c == ')') {
  8. count--;
  9. }
  10. }
  11. System.out.println(count);

(1) 请将这些代码封装到一个方法中,让这个方法接受一个字符串参数,并返回 count 的终值。

(2) 泛化这些代码使其适用于任何字符串,然后还能如何进一步泛化呢?

(3) 用多个字符串测试编写的方法,包括左右括号数相等和不等的字符串。

练习9-4

创建一个名为 Recurse.java 的程序,并在其中输入以下方法:

  1. /**
  2. * 返回给定字符串中的第一个字符。
  3. */
  4. public static char first(String s) {
  5. return s.charAt(0);
  6. }
  7. /**
  8. * 返回给定字符串中除第一个字符外的其他所有字符。
  9. */
  10. public static String rest(String s) {
  11. return s.substring(1);
  12. }
  13. /**
  14. * 返回给定字符串中除第一个和最后一个字符外的其他所有字符。
  15. */
  16. public static String middle(String s) {
  17. return s.substring(1, s.length() - 1);
  18. }
  19. /**
  20. * 返回给定字符串的长度。
  21. */
  22. public static int length(String s) {
  23. return s.length();
  24. }

(1) 在 main 中编写一些代码以测试上述的每个方法。确认它们能够正确地工作,并确保你明白它们的功能。

(2) 编写一个名为 printString 的方法,让它接受一个字符串参数,并显示这个字符串中的所有字符,且每个字符独占一行。编写这个方法时,只能用前面定义的方法,不能用其他字符串方法。另外,这个方法应为 void 方法。

(3) 编写一个名为 printBackward 的方法,其功能与 printString 相同,但按相反的顺序显示字符串中的字符,且每个字符也独占一行。同样,在编写这个方法时,你只能用前面定义的方法。

(4) 编写一个名为 reverseString 的方法,让它接受一个字符串参数,并返回一个新的字符串。这个新字符串包含的字符与参数字符串相同,但排列顺序相反。换言之,对于下述示例代码:

  1. String backwards = reverseString("coffee");
  2. System.out.println(backwards);

其输出应为:

  1. eeffoc

(5) 回文指的是顺着读和倒着读一样的单词,如 otto 和 palindromeemordnilap。一种判断单词是否为回文的方式如下:

只包含一个字母的单词是回文;单词包含两个字母时,如果这两个字母相同,那么这个单词是回文;对于其他的单词,如果第一个字母和最后一个字母相同,且余下的部分为回文,则这个单词为回文。

请编写一个名为 isPalindrome 的递归方法,让它接受一个字符串,并返回一个 boolean 值来指出这个字符串是否为回文。

练习9-5

如果一个单词包含的字母是按字母表顺序排列的,那么这个单词就是 abecedarian 单词。例如,下面列出了所有按字母顺序排列的含 6 个字母的英语单词:

abdest, acknow, acorsy, adempt, adipsy, agnosy, befist, behint, beknow, bijoux, biopsy,cestuy, chintz, deflux, dehors, dehort, deinos, diluvy, dimpsy

请编写一个名为 isAbecedarian 的方法,让它接受一个字符串,并返回一个 boolean 值,指出这个字符串表示的单词是否是按字母顺序排列的。编写的方法可以是迭代的,也可以是递归的。

练习9-6

如果一个单词包含的每个字母都刚好出现两次,那么它就是 doubloon 单词。下面是字典中的一些 doubloon 单词:

Abba, Anna, appall, appearer, appeases, arraigning, beriberi, bilabial, boob, Caucasus, coco, Dada, deed, Emmett, Hannah, horseshoer, intestines, Isis, mama, Mimi, murmur, noon, Otto, papa, peep, reappear, redder, sees, Shanghaiings, Toto

请编写一个名为 isDoubloon 的方法,让它接受一个字符串,并检查它是否为 doubloon 单词。为忽略大小写,可在检查前调用方法 toLowerCase

练习9-7

如果两个单词包含的字母相同,且其中的每个字母出现的次数也相同,那么这两个单词就是重组词。例如,单词 stop 是 pots 的重组词,而 allen downey 是 well annoyed 的重组词。

请编写一个方法,让它接受两个字符串,并检查它们是否为重组词。

练习9-8

在拼字游戏 Scrabble 中,每个玩家都有一组字母卡片,玩家需要用这些卡片拼出单词。其中的计分系统非常复杂,但一般而言,拼出的单词越长,得分越高。

假设以一个字符串(如 "quijibo")的方式指定了你手中有哪些字母卡片,并要求你判断能否用这些卡片拼出另一个字符串,如 "jib"

请编写一个名为 canSpell 的方法,让它接受两个字符串,并判断用第一个字符串指定的字母卡片能否拼出第二个字符串指定的单词。可能会有多个包含相同字母的卡片,但每个卡片只能用一次。