好好学Java:从零基础到项目实战
上QQ阅读APP看书,第一时间看更新

3.3.3 冒号的几种用法

Java中的标点符号主要有两类用途:一类是运算符,包括加号“+”、减号“-”、乘号“*”、除号“/”、取余号“%”、等号“=”、大于号“>”、小于号“<”、与号“&”、或号“|”、非号“!”、异或号“^”等;另一类是分隔符,包括区分代码块的花括号“{}”,容纳特定语句的圆括号“()”,标明数组元素的方括号“[]”,分隔长句的分号“;”,分隔短句的逗号“,”,分隔包名、类名、方法名的点号“.”,等等。当然还有几个特殊的分隔符,比如三元运算符“?:”,它的完整形式为“式子A ?式子B :式子C”。当式子A成立时,得到式子B的结果;当式子A不成立时,得到式子C的结果。在这些标点符号中,尤以冒号最为特殊,之所以这么说,是因为Java编程一遇到特殊的分隔场景,基本都拿冒号做标记。

冒号除了用在三元运算符“?:”中外,至少还有其他3种用法,分别介绍如下。

1.switch-case的分支标记

冒号的第二种用法出现在多路分支中。犹记得当年switch-case并肩作战,switch给出了待判断的变量名,每个case带着数值再拖上具体的处理语句,case条件与处理语句之间以冒号分隔,其他情况由default处理,也通过冒号同处理语句区分开。譬如下面的多路分支代码,就能看到冒号的分隔作用:

        // switch允许判断某个变量的多个取值,并分别进行单独处理
        switch (seq) {
            case 1:  // seq值为1时进入该分支
                System.out.println("凉风有信的谜底是“讽”");
                break;  // 跳出多路分支,即跳到switch分支的右花括号之后
            case 2:  // seq值为2时进入该分支
                System.out.println("秋月无边的谜底是“二”");
                break;  // 跳出多路分支,即跳到switch分支的右花括号之后
            default:  // seq值为其他时进入该分支
                System.out.println("您的按键有误");
                break;  // 跳出多路分支,即跳到switch分支的右花括号之后
        }

2.for循环的数组元素遍历

冒号的第三种用法跟数组的循环遍历有关。要想把某个数组里的所有元素数值都打印出来,就得通过for循环依次取出数组的每个元素,再打印该元素的数值。以整型数组为例,利用for语句遍历并打印元素的代码如下:

    int[] numbers={2, 3, 5, 7};
    for (int i=0; i < numbers.length; i++) {
        int number=numbers[i];  // 获取下标为i的元素,并赋值给名为number的变量
        System.out.println("number=" + number);
    }

上面的循环语句很常规,用法形式也很常见,无非是依次取出数组里的每个元素罢了。倘若此时不修改元素数值,仅仅是读取数值的话,那么可以简化成一套通用的循环模板,就像以下循环语句一样(完整代码见本章源码的src\com\control\array\ColonErgodic.java):

    int[] numbers={2, 3, 5, 7};
    // 在for循环中,可利用“变量类型 变量名称 : 数组名称”的形式,直接把数组元素赋值给该变量
    for (int number : numbers) {
        System.out.println("number="+number);
    }

上述的Java代码把原循环内部的变量number提前放到for后面的圆括号中,并且number与数组numbers之间用冒号分开,表示每次循环处理之前都把数组元素逐个赋值给number变量,然后循环内部即可直接处理该变量。如此这般便优化了先前的for循环代码,免去了冗余的数组下标、判断条件以及自增操作。

3.多层循环的跳转标记

冒号的第四种用法也与循环语句有关,但不限于for循环,而是与for和while都有关联。前述的循环处理基本上都只有一层循环,然而实际开发中常常会遇到多层循环,也就是一个循环内部嵌套了另一个循环,看起来像是层峦叠嶂、反复重叠,故又被称作多重循环。例如有一个二维数组,要想把它里面的所有元素都打印出来,这便需要两层循环才能搞定,第一层循环负责遍历第一个维度的下标,而第二层循环负责遍历第二个维度的下标,编码上则需一个for循环嵌套另一个for循环,具体的Java实现代码如下(完整代码见本章源码的src\com\control\array\ColonJump.java):

        double[][] triangle={ {-2.0, 0.0}, {0.0, -1.0}, {2.0, 1.0} };
        // 下面通过多重循环依次打印二维数组里面的所有元素
        for (int i=0; i<triangle.length; i++) {
            for (int j=0; j<triangle[i].length; j++) {
                System.out.println("value="+triangle[i][j]);
            }
        }

可见以上的多重循环代码还是挺简单的,并未涉及复杂的break和continue操作。即使用到break和continue,处理逻辑也没有什么特别之处,因为break语句只能跳出当前层次的循环,不能跳出上个层次的循环,continue语句同理。所以要想从内层循环跳出外层循环,就得设置一个标记,从内层循环跳到外层循环时,通过判断该标记再决定是否立刻跳出外层循环。仍以前面的二维数组为例,假设在内层循环找到某个元素为0.0,则立即结束全部循环(包括外层循环和内层循环),按此思路编写的代码例子如下:

    // 处理要求:一旦发现数组元素等于0.0,就立即从第二层循环跳出第一层循环(跳出两层循环)
    for (int i=0; i<triangle.length; i++) {
        boolean isFound=false;  // 该布尔变量用来标记是否找到0.0
        for (int j=0; j<triangle[i].length; j++) {
            if (triangle[i][j] == 0.0) {
                isFound=true;  // 找到了0.0
                System.out.println("simple found 0.0");
                break;  // 跳出第二层循环
            }
        }
        if (isFound) {  // 根据布尔变量判断是否找到了0.0
            break;  // 跳出第一层循环
        }
    }

以上代码固然实现了功能要求,但是两个break的写法着实令人憋屈,而且布尔变量isFound纯粹是到此一游。有没有一种写法允许代码直接从内层循环跳出外层循环呢?与其让布尔变量做标记,不如给外层循环加一个记号,然后内层循环就能告诉编译器,接下来的break语句要跳出指定标记的循环。这时冒号便派上用场了,通过形如“标记名称: for或者while”的表达式,即可给指定循环起一个外号,于是语句“break标记名称”便实现了跳出指定循环的需求。使用新写法改造前面的循环跳出代码,修改之后的代码如下:

    // 下面的loop1是一个记号,连同后面的冒号加在for前面,表示它指代这个for循环
    loop1 : for (int i=0; i<triangle.length; i++) {
        for (int j=0; j<triangle[i].length; j++) {
            if (triangle[i][j] == 0.0) {  // 找到了0.0,准备跳出外层循环
                System.out.println("loop1 found 0.0");
                break loop1;  // 跳出loop1代表的循环,也就是跳出第一层循环
            }
        }
    }

以上代码先在外层的for循环之前添加“loop1 : ”,表明外层循环的绰号叫loop1,然后内层循环的break语句改成“break loop1;”,表示跳出loop1这个外层循环,这样只需一个break语句就跳出多重循环了。除了break语句外,continue语句也允许带上标记名称,比如“continue loop1”表示继续loop1这个外层循环的下一次循环处理,并且while循环同样认可在break和continue后面添加标记。当然,利用前面介绍的冒号的第三种用法,上面的多重循环还能简化成以下代码:

    // 下面用到了两种冒号:一种用来标记循环;另一种用来简化数组遍历
    loop2 : for (double[] dot : triangle) {  // dot等价于前面的triangle[i]
        for (double coordinate : dot) {  // coordinate等价于前面的triangle[i][j]
            if (coordinate == 0.0) {  // 找到了0.0,准备跳出外层循环
                System.out.println("loop2 found 0.0");
                break loop2;  // 跳出loop2代表的循环
            }
        }
    }

如此一来,上述的循环代码联合应用了冒号的两种用法,代码也变得更加精练了。