字符串

创建字符串

字符串在Java编程中广泛使用,它是一系列字符的序列。在Java编程语言中,字符串是对象。
Java平台提供了 String 类来创建和操作字符串。
创建字符串最直接的方式是这样:

String greeting = "Hello, World!";

在此情况下,“Hello world!”是一个字符串字面量——即代码中用双引号括起来的一系列字符。编译器在代码中遇到字符串字面量时,会创建一个与其值相同的String对象——本例中即为Hello world!
与其他对象一样,你可以使用 new 关键字和构造函数创建 String 对象。String 类提供了十三个构造函数,允许你通过不同来源(如字符数组)为字符串提供初始值:

char[] helloArray = { 'H', 'e', 'l', 'l', 'o', '.' };
String helloString = new String(helloArray);
IO.println(helloString);

这段代码的最后一行显示了”Hello.”。

注:String类是不可变的,因此一旦创建,字符串对象就无法被修改。String类提供了一系列看似能修改字符串的方法(其中部分方法将在下文讨论)。由于字符串不可变,这些方法实际上是创建并返回一个包含操作结果的新字符串。

字符串长度

用于获取对象信息的操作称为访问器方法。字符串可用的访问器方法之一是 length() 方法,该方法返回字符串对象所含字符的数量。执行以下两行代码后,len 等于 17:

String palindrome = "Dot saw I was Tod";
int len = palindrome.length();

回文是指对称的单词或句子——忽略大小写和标点符号后,正向和反向拼写相同。以下是一个简短但效率低下的程序,用于反转回文字符串。它调用了字符串方法charAt(i),该方法返回字符串中第i个字符(从0开始计数)。

public class PalindromeDemo {
    public static void main(String[] args) {
        String palindrome = "Dot saw I was Tod";
        int len = palindrome.length();
        char[] tempCharArray = new char[len];
        char[] charArray = new char[len];

        // put original string in an 
        // array of chars
        for (int i = 0; i < len; i++) {
            tempCharArray[i] = palindrome.charAt(i);
        }

        // reverse array of chars
        for (int j = 0; j < len; j++) {
            charArray[j] = 
              tempCharArray[len - 1 - j];
        }

        String reversePalindrome = new String(charArray);
        IO.println(reversePalindrome);
    }
}

程序运行后输出如下

doT saw I was toD

要实现字符串反转,程序需要将字符串转换为字符数组(第一个for循环),将数组反转后转换为第二个数组(第二个for循环),最后再转换回字符串。String类提供了一个方法getChars(),可将字符串或字符串片段转换为字符数组,因此我们可以将上述程序中的第一个for循环替换为:

palindrome.getChars(0, len, tempCharArray, 0);

字符串连接

String 类包含一个用于连接两个字符串的方法:

string1.concat(string2);

这将返回一个新字符串,该字符串是在 string1 末尾追加 string2 形成的。
您也可以像这样使用字符串字面量与concat()方法:

"My name is ".concat("Rumplestiltskin");

字符串通常使用 + 运算符进行连接,例如:

"Hello," + " world" + "!";

结果是:

"Hello, world!";

+ 运算符在打印语句中被广泛使用。例如:

String string1 = "saw I was ";
IO.println("Dot " + string1 + "Tod");

打印如下:

Dot saw I was Tod

这种连接可以包含任意对象的组合。对于每个非字符串对象,都会调用其 toString() 方法将其转换为字符串。

注意:在Java SE 15之前,Java编程语言不允许字面量字符串在源文件中跨行,因此在多行字符串中必须使用+连接运算符将每行末尾连接起来。例如:

String quote = 
    "Now is the time for all good " +
    "men to come to the aid of their country.";

在打印语句中,使用 + 连接运算符在行间断开字符串的做法非常常见。
从 Java SE 15 开始,您可以编写二维字符串字面量:

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

创建格式化的字符串

您已经了解了使用 printf() 和 format() 方法打印格式化数字输出的用法。String 类提供了一个等效的类方法 format(),该方法返回的是 String 对象而非 PrintStream 对象。
使用字符串的静态format()方法,可以创建可重复使用的格式化字符串,而非仅限于单次使用的print语句。例如,与其使用

System.out.printf("The value of the float " +
                  "variable is %f, while " +
                  "the value of the " + 
                  "integer variable is %d, " +
                  "and the string is %s", 
                  floatVar, intVar, stringVar); 

你可以这样写:

String fs;
fs = String.format("The value of the float " +
                   "variable is %f, while " +
                   "the value of the " + 
                   "integer variable is %d, " +
                   " and the string is %s",
                   floatVar, intVar, stringVar);
IO.println(fs);

将字符串转换为数字

程序经常会将数值数据存储在字符串对象中——例如用户输入的值。
包装基本数值类型的Number子类(Byte、Integer、Double、Float、Long和Short)均提供名为valueOf()的类方法,用于将字符串转换为该类型的对象。以下示例 ValueOfDemo 从命令行获取两个字符串,将其转换为数字并执行算术运算:

public class ValueOfDemo {
    public static void main(String[] args) {
        // this program requires two
        // arguments on the command line
        if (args.length == 2) {
            // convert strings to numbers
            float a = (Float.valueOf(args[0])).floatValue();
            float b = (Float.valueOf(args[1])).floatValue();

            // do some arithmetic
            IO.println("a + b = " + (a + b));
            IO.println("a - b = " + (a - b));
            IO.println("a * b = " + (a * b));
            IO.println("a / b = " + (a / b));
            IO.println("a % b = " + (a % b));
        } else {
            IO.println("This program requires two command-line arguments.");
        }
    }
}

以下是使用命令行参数 4.587.2 时程序的输出结果:

a + b = 91.7
a - b = -82.7
a * b = 392.4
a / b = 0.051605508
a % b = 4.5

注意:每个包装基本数值类型的Number子类都提供了parseXXXX()方法。例如,parseFloat()可用于将字符串转换为基本数值类型。由于该方法返回的是基本类型而非对象,因此parseFloat()比valueOf()方法更直接。例如在ValueOfDemo程序中,我们可以使用:

float a = Float.parseFloat(args[0]);
float b = Float.parseFloat(args[1]);

将数字转换为字符串

有时需要将数字转换为字符串,因为需要对字符串形式的值进行操作。将数字转换为字符串有多种简单方法:

int i;
String s1 = "" + i;

或者

String s2 = String.valueOf(i);

每个数字子类都包含一个类方法 toString(),该方法将把其基本类型转换为字符串。例如:

int i;
double d;
String s3 = Integer.toString(i);
String s4 = Double.toString(d);

ToStringDemo 示例使用toString()方法将数字转换为字符串。程序随后运用若干字符串方法计算小数点前后各有多少位数:

public class ToStringDemo {
    public static void main(String[] args) {
        double d = 858.48;
        String s = Double.toString(d);

        int dot = s.indexOf('.');

        IO.println(dot + " digits before decimal point.");
        IO.println(s.length() - dot - 1 + " digits after decimal point.");
    }
}

这个程序的输出如下:

3 digits before decimal point.
2 digits after decimal point.

通过索引获取字符和子字符串

String类提供了一系列方法,用于检查字符串内容、查找字符或子字符串、转换大小写以及执行其他操作。
您可以通过调用 charAt() 访问器方法获取字符串中特定索引处的字符。第一个字符的索引为 0,而最后一个字符的索引为 length() - 1。例如,以下代码获取字符串中索引为 9 的字符:

String anotherPalindrome = "Niagara. 0 rora again!";
char aChar = anotherPalindrome.charAt(9);

索引从0开始,因此索引为9的字符是 ‘O’,如下图所示:
Char indexes in a string
若需从字符串中获取多个连续字符,可使用子字符串方法。该方法有两种版本:

  • String substring(int beginIndex, int endIndex): 返回一个新字符串,该字符串是当前字符串的子字符串。子字符串从指定的 beginIndex 开始,延伸至索引为 endIndex - 1 的字符处。
  • String substring(int beginIndex): 返回一个新字符串,该字符串是当前字符串的子字符串。整数参数指定起始字符的索引位置。在此处,返回的子字符串将延伸至原始字符串的末尾。

以下代码从Niagara回文字符串中提取从索引11开始,延伸至索引15(不包含索引15本身)的子字符串,该子字符串即为单词”roar”:

String anotherPalindrome = "Niagara. O roar again!"; 
String roar = anotherPalindrome.substring(11, 15); 

其他字符串操作方法

以下是其他几个用于操作字符串的字符串方法:

在字符串中搜索字符和子字符串

以下是用于在字符串中查找字符或子字符串的其他 String 方法。String 类提供了访问方法,用于返回特定字符或子字符串在字符串中的位置:indexOf() 和 lastIndexOf()。indexOf() 方法从字符串开头向前搜索,lastIndexOf() 方法从字符串结尾向后搜索。如果未找到字符或子字符串,indexOf() 和 lastIndexOf() 将返回 -1。
String类还提供了一个搜索方法contains,该方法用于判断字符串是否包含特定字符序列,若包含则返回true。当您仅需确认字符串是否包含某个字符序列,而精确位置无关紧要时,可使用此方法。
搜索方法如下:

  • int indexOf(int ch) 和 int lastIndexOf(int ch):返回指定字符首次(最后一次)出现的索引。
  • int indexOf(int ch, int fromIndex) 和 int lastIndexOf(int ch, int fromIndex): 返回指定字符首次(最后)出现的索引位置,从指定索引位置开始向前(向后)搜索。
  • int indexOf(String str) 和 int lastIndexOf(String str):返回指定子字符串首次(最后一次)出现的索引位置。
  • int indexOf(String str, int fromIndex) 和 int lastIndexOf(String str, int fromIndex):返回指定子字符串首次(最后一次)出现的索引位置,从指定索引开始向前(向后)搜索。
  • 布尔值 contains(字符序列 s):若字符串包含指定字符序列,则返回 true

    注:CharSequence 是由 String 类实现的一个接口。因此,你可以将字符串作为 contains() 方法的参数使用。

将字符和子字符串替换到字符串中

String类中用于向字符串插入字符或子字符串的方法非常有限。通常这些方法并不必要:你可以通过将从字符串中移除的子字符串与要插入的子字符串进行连接,来创建一个新的字符串。
String类确实提供了四个用于替换已找到字符或子字符串的方法,它们分别是:

  • String replace(char oldChar, char newChar): 返回一个新字符串,该字符串中所有 oldChar 的出现位置均被替换为newChar
  • String replace(CharSequence target, CharSequence replacement): 将该字符串中与目标字符串完全匹配的每个子字符串替换为指定的替换字符串。
  • String replaceAll(String regex, String replacement): 将此字符串中匹配给定正则表达式的每个子字符串替换为给定的替换内容。
  • String replaceFirst(String regex, String replacement): 将此字符串中第一个匹配给定正则表达式的子字符串替换为给定的替换内容。

正则表达式在名为 正则表达式 的课程中进行讲解。

String 类实战

以下类 Filename 演示了如何使用 lastIndexOf() 和 substring() 方法来提取文件名的不同部分。

注意:以下 Filename 类中的方法不进行任何错误检查,假定其参数包含完整的目录路径和带扩展名的文件名。若这些方法用于生产环境代码,则需验证参数是否构造正确。

public class Filename {
    private String fullPath;
    private char pathSeparator, 
                 extensionSeparator;

    public Filename(String str, char sep, char ext) {
        fullPath = str;
        pathSeparator = sep;
        extensionSeparator = ext;
    }

    public String extension() {
        int dot = fullPath.lastIndexOf(extensionSeparator);
        return fullPath.substring(dot + 1);
    }

    // gets filename without extension
    public String filename() {
        int dot = fullPath.lastIndexOf(extensionSeparator);
        int sep = fullPath.lastIndexOf(pathSeparator);
        return fullPath.substring(sep + 1, dot);
    }

    public String path() {
        int sep = fullPath.lastIndexOf(pathSeparator);
        return fullPath.substring(0, sep);
    }
}

以下是一个名为 FileNameDemo 的程序,它构造了一个 FileName 对象并调用其所有方法:

public class FilenameDemo {
    public static void main(String[] args) {
        final String FPATH = "/home/user/index.html";
        Filename myHomePage = new Filename(FPATH, '/', '.');
        IO.println("Extension = " + myHomePage.extension());
        IO.println("Filename = " + myHomePage.filename());
        IO.println("Path = " + myHomePage.path());
    }
}

程序输出如下

Extension = html
Filename = index
Path = /home/user

如下图所示,我们的扩展方法使用 lastIndexOf() 定位文件名中最后一个句点 (.) 的位置。随后通过 substring 方法利用lastIndexOf()的返回值提取文件名扩展名——即从句点到字符串末尾的子字符串。此代码假设文件名中包含句点;若文件名不含句点,lastIndexOf()将返回-1,substring方法将抛出StringIndexOutOfBoundsException异常。

此外,请注意扩展方法将 dot + 1 作为substring()的参数。若句点字符(.)是字符串的最后一个字符,则 dot + 1 等于字符串的长度,这比字符串的最大索引大1(因为索引从0开始)。这是 substring() 方法的合法参数,因为该方法接受等于字符串长度但不大于该长度的索引值,并将其解释为”字符串末尾”。

比较字符串与字符串片段

String 类提供了一系列用于比较字符串及字符串片段的方法。下表列出了这些方法。

  • boolean endsWith(String suffix) 和 boolean startsWith(String prefix): 如果该字符串以方法参数指定的子字符串结尾或开头,则返回 true
  • boolean startsWith(String prefix, int offset): 从索引偏移量开始检查字符串,若其开头与作为参数指定的子字符串匹配,则返回 true。
  • int compareTo(String anotherString): 比较两个字符串的字典顺序。返回一个整数,表示该字符串是否大于(结果大于0)、等于(结果等于0)或小于(结果小于0)参数。
  • int compareToIgnoreCase(String str): 比较两个字符串的字典顺序,忽略大小写差异。返回一个整数,表示该字符串是否大于(结果大于0)、等于(结果等于0)或小于(结果小于0)参数。
  • boolean equals(Object anObject): 仅当参数是一个字符串对象,且该对象表示的字符序列与本对象相同,才返回 true
  • boolean equalsIgnoreCase(String anotherString): 当且仅当参数是一个字符串对象,且该对象表示的字符序列与本对象相同(忽略大小写差异)时返回 true
  • boolean regionMatches(int toffset, String other, int ooffset, int len): 测试此字符串的指定区域是否与字符串参数的指定区域匹配。区域长度为 len,起始位置为本字符串的索引 toffset 和另一字符串的索引 offset
  • boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len): 测试此字符串的指定区域是否与String参数的指定区域匹配。区域长度为 len,起始位置为本字符串的索引 toffset 和另一字符串的索引 ooffset。布尔参数用于指示是否忽略大小写;若为 true,则比较字符时忽略大小写。
  • boolean matches(String regex): 测试该字符串是否匹配指定的正则表达式。正则表达式相关内容详见正则表达式课程。

以下程序 RegionMatchesDemo 使用 regionMatches() 方法在另一个字符串中搜索字符串:

public class RegionMatchesDemo {
    public static void main(String[] args) {
        String searchMe = "Green Eggs and Ham";
        String findMe = "Eggs";

        int searchMeLength = searchMe.length();
        int findMeLength = findMe.length();

        boolean foundIt = false;
        
        for (int i = 0; i <= (searchMeLength - findMeLength); i++) {
            if (searchMe.regionMatches(i, findMe, 0, findMeLength)) {
                foundIt = true;
                IO.println(searchMe.substring(i, i + findMeLength));
                break;
            }   
        }

        if(!foundIt) {
            IO.println("No match found");
        }
    }
}

程序输出 Eggs

该程序逐个字符遍历 searchMe() 所指向的字符串。对于每个字符,程序调用regionMatches()方法来判断以当前字符开头的子字符串是否与程序正在查找的字符串匹配。