Java编程思想学习笔记(13)

房东的猫 提交于 2020-01-29 03:58:27

Java编程思想学习笔记(13)

字符串

不可变String

String对象是不可变的。查看JDK文档你就会发现,String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。而最初的 String对象则丝亳未动。


public class Immutable {

    public static String upcase(String s) {
        return s.toUpperCase();
    }
    public static void main(String[] args) {
        String q = "howdy";
        print(q); // howdy
        String qq = upcase(q);
        print(qq); // HOWDY
        print(q); // howdy
    }


}


当把q传给upcaseo方法时,实际传递的是引用的一个拷贝。其实,每当把String对象作为方法 的参数时,都会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置上,从未动过。

回到upcaseO的定义,传人其中的引用有了名字S,只有upcase()运行的时候,局部引用S才存在。一且upcase()运行结束,s就消失了。当然了,upcase()的返回值,其实只是最终结果的引用。这足以说明,upcaseO返回的引用已经指向了一个新的对象,而原本的q则还在原地。

重载"+"与StringBuilder

String对象是不可变的,你可以给一个String对象加任意多的别名。因为String对象具有只读特性,所以指向它的任何引用都不可能改变它的值,因此,也就不会对其他的引用有什么影响。

不可变性会带来一定的效率问题。 为String对象重载的"+“操作符就是一个例子。 重载的意思是,一个操作符在应用千特定的类时,被赋予了特殊的意义(用千String的”+“与”+=" 匝习 是Java中仅有的两个重载过的操作符,而Java井不允许程序员重载任何操作符)。

public class Concatenation {

    public static void main(String[] args) {
        String mango = "mango";
        String s = "abc" + mango + "def" + 47;
        System.out.println(s);
    }


}


可以想象一下,这段代码可能是这样工作的: String可能有一个appendO方法,它会生成一个新的String对象, 以包含 “abc” 与mango连接后的字符串。 然后,该对象再与 “def” 相连,生成 另一个新的String对象,依此类推。

这种工作方式当然也行得通,但是为了生成最终的String, 此方式会产生一大堆蒂要垃圾回收的中间对象。 我猜想, Java设计师一开始就是这么做的(这也是软件设计中的一个教训:除非你用代码将系统实现,并让它动起来, 否则你无法真正了解它会有什么问题),然后他们发现其性能相当糟糕。

通过反编译上面代码,发现编译器自动引人了java.lang.StringBuilder类。 虽然我们在源代码中并没有使用 StringBuilder类, 但是编译器却自作主张地使用了它, 因为它更高效。

在这个例子中, 编译器创建了一个StringBuilder对象, 用以构造最终的String, 井为每个字符串调用一次StringBuilder的append()方法, 总计四次。 最后调用toString()生成结果, 并存为S。

无意识的递归

Java中的每个类从根本上都是继承自Object,标准容器类自然也不例外。因此容器类都有toString()方法,并且覆写了该方法,使得它生成的String结果能够表达容器自身,以及容器所包含的对象。例如ArrayList.toString(),它会遍历ArrayList中包含的所有对象,调用每个元素 上的toString()方法:

public class ArrayListDisplay {
public static void main(String[] args) {
ArrayList<Coffee> coffees = new ArrayList<Coffee>();
for(Coffee c : new CoffeeGenerator(10))
coffees.add(c);
System.out.println(coffees);
}
} /* Output:
[Americano 0, Latte 1, Americano 2, Mocha 3, Mocha 4, Breve 5, Americano
6, Latte 7, Cappuccino 8, Cappuccino 9]
*///:~

如果你希望toString()方法打印出对象的内存地址,也许你会考虑使用this关键字:

public class InfiniteRecursion {

    public String toString() {
        return " InfiniteRecursion address: " + this + "\n";
    }
    public static void main(String[] args) {
        List<InfiniteRecursion> v =
                new ArrayList<InfiniteRecursion>();
        for(int i = 0; i < 10; i++)
            v.add(new InfiniteRecursion());
        System.out.println(v);
    }


}

当你创建了lnfiniteRecursion对象,并将其打印出来的时候,你会得到一串非常长的异常。如果你将该InfiniteRecurslon对象存入一个ArrayList中,然后打印该ArrayList,你也会得到同样的异常。

其实,当如下代码运行时:


"InfiniteRecursion address: " + this

这里发生了自动类型转换,由InfiniteRecursion类型转换成String类型。因为编译器看到一
个Strin炉寸象后面跟着一个"+"'而再后面的对象不是String,于是编译器试渚将this转换成一个String。它怎么转换呢,正是通过调用this上的toStringO方法,千是就发生了递归调用。

如果你真的想要打印出对象的内存地址,应该调用Object.toString()方法,这才是负责此任务的方法。所以,你不该使用this,而是应该调用super.toStringO方法。

String上的操作

格式化输出

System.out.format()

Java SE5引人的format方法可用于PrintStream或PrintWriter对象,们),其中也包括System.out对象。formatO方法模仿自C的printfO。如果你比较怀旧的话,也可以使用printrO。以下是一个简单的示例:


public class SimpleFormat {

    public static void main(String[] args) {
        int x = 5;
        double y = 5.332542;
// The old way:
        System.out.println("Row 1: [" + x + " " + y + "]");
// The new way:
        System.out.format("Row 1: [%d %f]\n", x, y);
// or
        System.out.printf("Row 1: [%d %f]\n", x, y);
    }


}

可以看到,formatO与printf()是等价的,它们只需要一个简单的格式化字符串,加上一串参数即可,每个参数对应一个格式修饰符。

Formatter类

在Java中,所有新的格式化功能都由java.util.Formatter类处理。可以将Formatte看做一个翻译器,它将你的格式化字符串与数据翻译成需要的结果。当你创建一个Formatter对象的时候, 需要向其构造器传递一些信息,告诉它最终的结果将向哪里输出:

public class Turtle {

    private String name;
    private Formatter f;
    public Turtle(String name, Formatter f) {
        this.name = name;
        this.f = f;
    }
    public void move(int x, int y) {
        f.format("%s The Turtle is at (%d,%d)\n", name, x, y);
    }
    public static void main(String[] args) {
        PrintStream outAlias = System.out;
        Turtle tommy = new Turtle("Tommy",
                new Formatter(System.out));
        Turtle terry = new Turtle("Terry",
                new Formatter(outAlias));
        tommy.move(0,0);
        terry.move(4,8);
        tommy.move(3,4);
        terry.move(2,5);
        tommy.move(3,3);
        terry.move(3,3);
    }


}


所有的tommy都将输出到System.out, 而所有的terry则都输出到System.out的一个别名中。 Formatter的构造器经过重载可以接受多种输出目的地,不过最常用的还是PrintStreamO (如上例)、 OutputStream和File。

格式化说明符

在插入数据时,如果想要控制空格与对齐, 你需要更精细复杂的格式修饰符。 以下是其抽象的语法:


%[argument_index$][flags][width][.precision]conversion

最常见的应用是控制一个域的最小尺寸,这可以通过指定width来实现。 Formatter对象 通过在必要时添加空格,来确保一个域至少达到某个长度。 在默认的情况下,数据是右对齐,不过可以通过使用"-"标志来改变对齐方向。

与width相对的是precision, 它用来指明最大尺寸0 width可以应用于各种类型的数据转换, 并且其行为方式都一样。 precision则不然, 不是所有类型的数据都能使用precision, 而且,应用 千不同类型的数据转换时,precision的意义也不同。 在将precision应用千 String时,它表示打印 String时输出字符的最大数址。 而在将precision应用千浮点数时, 它表示小数部分要显示出来的位数(默认是6位小数), 如果小数位数过多则舍人, 太少则在尾部补零。 由千整数没有小数部分,所以precision无法应用千整数,如果你对整数应用precision, 则会触发异常。

public class Receipt {

    private double total = 0;
    private Formatter f = new Formatter(System.out);
    public void printTitle() {
        f.format("%-15s %5s %10s\n", "Item", "Qty", "Price");
        f.format("%-15s %5s %10s\n", "----", "---", "-----");
    }
    public void print(String name, int qty, double price) {
        f.format("%-15.15s %5d %10.2f\n", name, qty, price);
        total += price;
    }
    public void printTotal() {
        f.format("%-15s %5s %10.2f\n", "Tax", "", total*0.06);
        f.format("%-15s %5s %10s\n", "", "", "-----");
        f.format("%-15s %5s %10.2f\n", "Total", "",
                total * 1.06);
    }
    public static void main(String[] args) {
        Receipt receipt = new Receipt();
        receipt.printTitle();
        receipt.print("Jack’s Magic Beans", 4, 4.25);
        receipt.print("Princess Peas", 3, 5.1);
        receipt.print("Three Bears Porridge", 1, 14.29);
        receipt.printTotal();
    }


}


Formatter转换

public class Conversion {

    public static void main(String[] args) {
        Formatter f = new Formatter(System.out);
        char u = 'a';
        System.out.println("u = ‘a’");
        f.format("s: %s\n", u);
// f.format("d: %d\n", u);
        f.format("c: %c\n", u);
        f.format("b: %b\n", u);
// f.format("f: %f\n", u);
// f.format("e: %e\n", u);
// f.format("x: %x\n", u);
        f.format("h: %h\n", u);
        System.out.println("--------------------------");
        int v = 121;
        System.out.println("v = 121");
        f.format("d: %d\n", v);
        f.format("c: %c\n", v);
        f.format("b: %b\n", v);
        f.format("s: %s\n", v);
// f.format("f: %f\n", v);
// f.format("e: %e\n", v);
        f.format("x: %x\n", v);
        f.format("h: %h\n", v);
        System.out.println("--------------------------");
        BigInteger w = new BigInteger("50000000000000");
        System.out.println(
                "w = new BigInteger(\"50000000000000\")");
        f.format("d: %d\n", w);
// f.format("c: %c\n", w);
        f.format("b: %b\n", w);
        f.format("s: %s\n", w);
// f.format("f: %f\n", w);
// f.format("e: %e\n", w);
        f.format("x: %x\n", w);
        f.format("h: %h\n", w);
        System.out.println("--------------------------");
        double x = 179.543;
        System.out.println("x = 179.543");
// f.format("d: %d\n", x);
// f.format("c: %c\n", x);
        f.format("b: %b\n", x);
        f.format("s: %s\n", x);
        f.format("f: %f\n", x);
        f.format("e: %e\n", x);
// f.format("x: %x\n", x);
        f.format("h: %h\n", x);
        System.out.println("--------------------------");
        Conversion y = new Conversion();
        System.out.println("y = new Conversion()");
// f.format("d: %d\n", y);
// f.format("c: %c\n", y);
        f.format("b: %b\n", y);
        f.format("s: %s\n", y);
// f.format("f: %f\n", y);
// f.format("e: %e\n", y);
// f.format("x: %x\n", y);
        f.format("h: %h\n", y);
        System.out.println("--------------------------");
        boolean z = false;
        System.out.println("z = false");
// f.format("d: %d\n", z);
// f.format("c: %c\n", z);
        f.format("b: %b\n", z);
        f.format("s: %s\n", z);
// f.format("f: %f\n", z);
// f.format("e: %e\n", z);
// f.format("x: %x\n", z);
        f.format("h: %h\n", z);
    }


}


被注释的代码表示,针对相应类型的变量,这些转换是无效的。如果执行这些转换,则会触发异常。

注意,程序中的每个变址都用到了b转换。虽然它对各种类型都是合法的,但其行为却不一定与你想象的一致。对于boolean基本类型或Boolean对象,其转换结果是对应的trµe或false。但是,对其他类型的参数,只要该参数不为null,那转换的结果就永远都是true。即使是数字0,转换结果依然为true,而这在其他语言中(包括C),往往转换为false。所以,将b应用于非布尔类型的对象时请格外小心.

String.format()

Java SES也参考了C中的sprintf()方法,以生成格式化的String对象。String.format()是一个 static方法,它接受与Formatter.format()方法一样的参数,但返回一个String对象。当你只需使回用format()方法一次的时候,String.format()用起来很方便。例如:

public class DatabaseException extends Exception{

    public DatabaseException(int transactionID, int queryID,
                             String message) {
        super(String.format("(t%d, q%d) %s", transactionID,
                queryID, message));
    }
    public static void main(String[] args) {
        try {
            throw new DatabaseException(3, 7, "Write failed");
        } catch(Exception e) {
            System.out.println(e);
        }
    }


}


其实在String.format()内部,它也是创建一个Formatter对象,然后将你传人的参数转给该Formatter。不过,与其自己做这些事情,不如使用便捷的String.format()方法,何况这样的代码更清晰易读。

一个十六进制转储(dump)工具

在处理二进制文件时,我们经常希望以十六进制的格式看看其内容。现在,我们就将它作为第二个例子。下面的小工具使用了String.format()方法,以可读的十六进制格式将字节数组打印出来:

public class Hex {
public static String format(byte[] data) {
StringBuilder result = new StringBuilder();
int n = 0;
for(byte b : data) {
if(n % 16 == 0)
result.append(String.format("%05X: ", n));
result.append(String.format("%02X ", b));
n++;
if(n % 16 == 0) result.append("\n");
}
result.append("\n");
return result.toString();
}
public static void main(String[] args) throws Exception {
if(args.length == 0)
// Test by displaying this class file:
System.out.println(
format(BinaryFile.read("Hex.class")));
else
System.out.println(
format(BinaryFile.read(new File(args[0]))));
}
} /* Output: (Sample)
00000: CA FE BA BE 00 00 00 31 00 52 0A 00 05 00 22 07
00010: 00 23 0A 00 02 00 22 08 00 24 07 00 25 0A 00 26
00020: 00 27 0A 00 28 00 29 0A 00 02 00 2A 08 00 2B 0A
00030: 00 2C 00 2D 08 00 2E 0A 00 02 00 2F 09 00 30 00
00040: 31 08 00 32 0A 00 33 00 34 0A 00 15 00 35 0A 00
00050: 36 00 37 07 00 38 0A 00 12 00 39 0A 00 33 00 3A
...
*///:~

正则表达式

正则表达式是一种强大而灵活的文本处理工具。使用正则表达式,我们能够以编程的方式,
构造复杂的文本模式,并对输入的字符串进行搜索。一且找到了匹配这些模式的部分,你就能够随心所欲地对它们进行处理。初学正则表达式时,其语法是一个难点,但它确实是一种简洁、动态的语言。

基础

一般来说,正则表达式就是以某种方式来描述字符串,因此你可以说:“如果一个字符串含
有这些东西,那么它就是我正在找的东西。",例如,要找一个数字,它可能有一个负号在最前面,那你就可以写一个负号加上一个问号:

-?

要描述一个整数,你可以说它有一位或多位阿拉伯数字。在正则表达式中,用\d表示一位数字。

如果在其他语言中使用过正则表达式,那你立刻就能发现Java对反斜线\的不同处理。

在其他语言中,\\表示“我想要在正则表达式中插人一个普通的(字面上的)反斜线,请不要给它任何特殊的意义。”

而在Java中,\\的意思是“我要插人一个正则表达式的反斜线,所以其后的字符具有特殊的意义。

例如,如果你想表示一位数字,那么正则表达式应该是\d。如果你想插人一个普通的反斜线,则应该这样\\\\。不过换行和制表符之类的东西只需使用单反斜线:\n\t。

要表示“一个或多个之前的表达式”,应该使用+。所以,如果要表示“可能有一个负号, 后面跟着一位或多位数字”,可以这样:


-?\\d+

应用正则表达式的最简单的途径,就是利用String类内建的功能。 例如,你可以检查一个String是否匹配如上所述的正则表达式:

public class IntegerMatch {
    public static void main(String[] args) {
        System.out.println("-1234".matches("-?\\d+"));
        System.out.println("5678".matches("-?\\d+"));
        System.out.println("+911".matches("-?\\d+"));
        System.out.println("+911".matches("(-|\\+)?\\d+"));
    }


}

前两个字符串满足对应的正则表达式,匹配成功。

第三个字符串开头有一个+,它也是一个合法的整数,但与对应的正则表达式却不匹配。因此,我们的正则表达式应该描述为:“可能以一个加号或减号开头”。在正则表达式中,括号有着将表达式分组的效果,而竖直线|则表示或操作。也就是:

(-I\\+)?

这个正则表达式表示字符串的起始字符可能是一个-或+,或二者皆没有(因为后面跟着?修饰符)。 因为字符+在正则表达式中有特殊的意义,所以必须使用\\将其转义,使之成为表达式中的一个普通字符。

String类还自带了一个非常有用的正则表达式工具–splitO方法,其功能是“将字符串从正则表达式匹配的地方切开。”

public class Splitting {

    public static String knights =
            "Then, when you have found the shrubbery, you must " +
                    "cut down the mightiest tree in the forest... " +
                    "with... a herring!";
    public static void split(String regex) {
        System.out.println(
                Arrays.toString(knights.split(regex)));
    }
    public static void main(String[] args) {
        split(" "); // Doesn’t have to contain regex chars
        System.out.println("_____________________________");
        split("\\W+"); // Non-word characters
        System.out.println("_____________________________");
        split("n\\W+"); // ‘n’ followed by non-word characters
    }


}

首先看第一个语句,注意这里用的是普通的字符作为正则表达式,其中井不包含任何特殊的字符。因此第一个splitO只是按空格来划分字符串。

第二个和第三个split()都用到了\W, 它的意思是非单词字符(如果W小写,\w, 则表示一 个单词字符)。通过第二个例子可以看到,它将标点字符删除了。第三个split()表示 ”字母n后面跟着一个或多个非单词字符。” 可以看到,在原始字符串中,与正则表达式匹配的部分, 在最终结果中都不存在了。

String.split()还有一个重载的版本,它允许你限制字符串分割的次数。

String类自带的最后一个正则表达式工具是 "替换"。你可以只替换正则表达式第一个匹配的子串,或是替换所有匹配的地方。

public class Replacing {

    static String s = Splitting.knights;
    public static void main(String[] args) {
        print(s.replaceFirst("f\\w+", "located"));
        print(s.replaceAll("shrubbery|tree|herring","banana"));


    }
}


第一个表达式要匹配的是,以字母f开头,后面跟一个或多个字母(注意这里的w是小写的)。 并且只替换掉第一个匹配的部分,所以 “found” 被替换成 “located”。

第二个表达式要匹配的是三个单词中的任意一个,因为它们以竖直线分隔表示 ”或“,井且替换所有匹配的部分。

创建正则表达式

正则表达式的完整构造子列表, 请参考JDK文档java.util.regex包中的Pattern类。

下面的每一个正则表达式都能成功匹配字符序列 “Rudolph”:

public class Rudolph {

    public static void main(String[] args) {
        for (String pattern : new String[]{"Rudolph",
                "[rR]udolph", "[rR][aeiou][a-z]ol.*", "R.*"})
            System.out.println("Rudolph".matches(pattern));
    }


}


量词

量词描述了一个模式吸收输入文本的方式:

  • 贪婪型:最词总是贪婪的,除非有其他的选项被设置。 贪婪表达式会为所有可能的模式发现尽可能多的匹配。 导致此问题的一个典型理由就是假定我们的模式仅能匹配第一个可能的字符组,如果它是贪婪的,那么它就会继续往下匹配。

  • 勉投型:用问号来指定, 这个址词匹配满足模式所需的最少字符数。 因此也称作懒惰的、 最少匹配的、 非贪婪的、 或不贪婪的.

  • 占有型:目前, 这种类型的焦词只有在Java语言中才可用(在其他语言中不可用),井且 也更高级,因此我们大概不会立刻用到它。 当正则表达式被应用千字符串时,它会产生相当多的状态, 以便在匹配失败时可以回溯。 而 “占有的" 址词并不保存这些中间状态, 因此它们可以防止回溯。 它们常常用千防止正则表达式失控,因此可以使正则表达式执行起来更有效。

应该非常清楚地意识到,表达式X通常必须要用圆括号括起来, 以便它能够按照我们期望的效果去执行。 例如:

abc+

看起来它似乎应该匹配1个或多个abc序列,如果我们把它应用千输入字符串abcabcabc,则实际上会获得3个匹配。然而,这个表达式实际上表示的是:匹配ab,后面跟随1个或多个C。要表明匹配1个或多个完整的abc字符串,我们必须这样表示:

(abc)+

Pattern和Matcher

一般来说,比起功能有限的String类,我们更愿意构造功能强大的正则表达式对象。只需导人Java.util.regex包,然后用staticPattern.compile()方法来编译你的正则表达式即可。

它会根据你的String类型的正则表达式生成一个Pattern对象。接下来,把你想要检索的字符串传人 Pattern对象的matcherO方法。matcher()方法会生成一个Matcher对象,它有很多功能可用(可以参考java.util.regext.Matcher的JDK文档)。例如,它的replaceAIIO方法能将所有匹配的部分都替换成你传人的参数。`

作为第一个示例,下面的类可以用来测试正则表达式,看看它们能否匹配一个输入字符串.一个控制台参数是将要用来搜索匹配的输入字符串,后面的一个或多个参数都是正则表达式,它们将被用来在输入的第一个字符串中查找匹配。在Unix/Linux上,命令行中的正则表达式必须 用引号括起。这个程序在测试正则表达式时很有用,特别是当你想验证它们是否具备你所期待的匹配功能的时候。

public class TestRegularExpression {

    public static void main(String[] args) {
        if(args.length < 2) {
            print("Usage:\njava TestRegularExpression " +
                    "characterSequence regularExpression+");
            System.exit(0);
        }
        print("Input: \"" + args[0] + "\"");
        for(String arg : args) {
            print("Regular expression: \"" + arg + "\"");
            Pattern p = Pattern.compile(arg);
            Matcher m = p.matcher(args[0]);
            while(m.find()) {print("Match \"" + m.group() + "\" at positions " +
                    m.start() + "-" + (m.end() - 1));
            }
        }
    }
}

Pattern对象表示编译后的正则表达式。从这个例子中可以看到,我们使用已编译的Pattern 对象上的matcher()方法,加上一个输人字符串,从而共同构造了一个Matcher对象。同时, Pattern类还提供了static方法:

static boolean matches(String regex, CharSequence input)

该方法用以检查regex是否匹配整个CharScquence类型的input参数。 编译后的Pattern对象还提供了splitO方法, 它从匹配了regcx的地方分割输人字符串, 返回分割后的子字符串String数组。

通过调用Pattern.matcherO方法,井传入一个字符串参数,我们得到了一个Matcher对象。使用Matcher上的方法,我们将能够判断各种不同类型的匹配是否成功.

Matcber.fmdO方法可用来在CharSequence中查找多个匹配。例如:

public class Finding {

    public static void main(String[] args) {
        Matcher m = Pattern.compile("\\w+")
                .matcher("Evening is full of the linnet’s wings");
        while(m.find())
            printnb(m.group() + " ");
        print();
        int i = 0;
        while(m.find(i)) {
            printnb(m.group() + " ");
            i++;
        }
    }


}

模式\w+将字符串划分为单词。fmdO像迭代器那样前向遍历输人字符串。而第二个6nd0能够接收一个整数作为参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。从结果
中可以看出,后一个版本的find方法能根据其参数的值,不断重新设定搜索的起始位置。

组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为0表示整个表达式, 组号1表示被第一对括号括起的组, 依此类推。因此,在下面这个表达式,

A(B(C))D

中有三个组:组0是ABCD, 组l是BC, 组2是C。

Matcher对象提供了一系列方法,用以获取与组相关的信息: public int groupCount()返回该匹配器的模式中的分组数目, 第0组不包括在内。public String groupO 返回前一次匹配操作(例如血d()) 的第0组(整个匹配)。public String group(int i) 返回在前一次匹配操作期间指定的组号,如果匹配成功, 但是指定的组没有匹配输入字符串的任何部分,则将会返回null。 public int s伐rt(int group)返回在前一次匹配操作中寻找到的组的起始索引。 public int end(int group)返回在前一次匹配操作中寻找到的组的最后一个字符索引加一的值。

下面是正则表达式组的例子:

public class Groups {

    static public final String POEM =
            "Twas brillig, and the slithy toves\n" +
                    "Did gyre and gimble in the wabe.\n" +
                    "All mimsy were the borogoves,\n" +
                    "And the mome raths outgrabe.\n\n" +
                    "Beware the Jabberwock, my son,\n" +
                    "The jaws that bite, the claws that catch.\n" +
                    "Beware the Jubjub bird, and shun\n" +
                    "The frumious Bandersnatch.";
    public static void main(String[] args) {
        Matcher m =
                Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$")
                        .matcher(POEM);
        while(m.find()) {
            for(int j = 0; j <= m.groupCount(); j++)
                printnb("[" + m.group(j) + "]");
            print();
        }
    }
    
}

这首诗来自于Lewis Carroll的《Through the Looking Glass》中的Jabberwocky。可以看到这个正则表达式模式有许多圆括号分组, 由任意数目的非空格字符(`+)及随后的任意数目的空格字符 (\s+) 所组成。目的是捕获每行的最后3个词, 每行最后以$结束。 不过,在正常情况下是将$与整个输入序列的末端相匹配。所以我们一定要显式地告知正则表达式注意输入序列中的 区习 换行符。这可以由序列开头的模式标记(?m)来完成(模式标记马上就会介绍)。

在匹配操作成功之后, start()返回先前匹配的起始位置的索引, 而end()返回所匹配的最后字符的索引加一的值。 匹配操作失败之后(或先于一个正在进行的匹配操作去尝试)调用start() 或 end()将会产生lllegalStateException。下面的示例还同时展示了matchesO和lookingAt()的用法

public class StartEnd {
public static String input =
"As long as there is injustice, whenever a\n" +
"Targathian baby cries out, wherever a distress\n" +
"signal sounds among the stars ... We’ll be there.\n" +
"This fine ship, and this fine crew ...\n" +
"Never give up! Never surrender!";
private static class Display {
private boolean regexPrinted = false;
private String regex;
Display(String regex) { this.regex = regex; }
void display(String message) {
if(!regexPrinted) {
print(regex);
regexPrinted = true;
}
print(message);
}
}
static void examine(String s, String regex) {
Display d = new Display(regex);
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(s);
while(m.find())
d.display("find() ‘" + m.group() +
"‘ start = "+ m.start() + " end = " + m.end());
if(m.lookingAt()) // No reset() necessary
d.display("lookingAt() start = "
+ m.start() + " end = " + m.end());
if(m.matches()) // No reset() necessary
d.display("matches() start = "
+ m.start() + " end = " + m.end());
}
public static void main(String[] args) {
for(String in : input.split("\n")) {
print("input : " + in);
for(String regex : new String[]{"\\w*ere\\w*",
"\\w*ever", "T\\w+", "Never.*?!"})
examine(in, regex);
}
}
}

注意,find()可以在输入的任意位置定位正则表达式,而lookingAt()和matches()只有在正则表达式与输入的最开始处就开始匹配时才会成功。matches()只有在整个输入都匹配正则表达式时才会成功,而lookingAt沪只要输入的第一部分匹配就会成功。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!