第四章 控制语句

161 阅读7分钟

Java语言使用控制语句来引导程序执行流程并根据程序状态的变化进行分支。Java的程序控制语句可以分为以下几类:选择语句、迭代语句和跳转语句。选择语句使程序能够根据表达式的结果或变量的状态选择不同的执行路径。迭代语句使程序能够重复执行一个或多个语句(即迭代语句形成循环)。跳转语句允许程序以非线性方式执行。在这一章我们将讨论所有的控制语句。

4.1 Java的选择语句

Java支持两种选择语句:if和switch。这些语句允许你根据只有在运行时才知道的条件来控制程序的执行流程。你会惊喜地发现这两个语句包含了强大和灵活的功能。

4.1.1 if语句

在Java中,if语句是一种条件语句,用于根据给定的条件执行不同的代码块。if语句的语法如下:

sqlCopy codeif (condition) {
    // 如果conditiontrue,则执行此代码块
}

其中,condition是一个布尔表达式,当它的值为true时,执行if语句后面的代码块,否则不执行,直接跳到if代码块后面的代码执行,它的执行流程图如下:

thirteen-02.png

来写个示例:

int age = 20;
if (age < 30) {
    System.out.println("青春年华");
}

输出:

青春年华

4.1.2 if-else 语句

if-else语句是Java的条件分支语句。它可以用来将程序的执行流程路由到两条不同的路径。以下是if-else语句的一般形式:

if(条件){
    语句1;
// 如果条件为 true,则执行这块代码
} 
else{
    语句2;
}


这里,每个语句可以是单个语句或由花括号括起来的复合语句(即块)。条件可以是任何返回值为布尔值的表达式。if语句的工作原理如下:如果条件为真,则执行语句1。否则,执行在else后面的语句2(如果存在)。在任何情况下,两个语句都不会同时执行。

我们用一个流程图表示一下:

thirteen-03.png

举一个例子:

int a, b;
//...
if(a < b) a = 0;
else
b = 0;

这里,如果a小于b,则将a设置为零。否则,将b设置为零。在任何情况下,它们都不会同时被设置为零。

通常,用于控制if语句的表达式将涉及关系运算符。然而,这不是技术上必需的。可以使用单个布尔变量来控制if语句,如下面代码所示:

boolean dataAvailable
    //...
if(dataAvailable)
    ProcessData();
else
    waitForMoreData();

请务必记住当if后面只有一条控制语句的时候,可以直接跟在if后面,否则务必把多条语句用花括号括起来。例如如下的代码片段:

int bytesAvailable;
//...
if(bytesAvailable>0){
    ProcessData();
    bytesAvailable-=n;
} else
    waitForMoreData();

在这里,如果bytesAvailable大于零,则if块中的两个语句都会被执行。 有些程序员在使用if时,即使每个子句中只有一个语句,也会写上花括号。这样可以轻松地在以后添加另一个语句,而无需担心忘记花括号,同时也增加了程序的可读性。

4.1.3 嵌套if语句

嵌套if语句是指在一个if语句中又嵌套了一个或多个if语句,用于在特定条件下执行不同的代码块。也就是说,当一个条件满足时,会执行一个if语句中的代码块,而在这个if语句中,又包含了一个或多个if语句,用于在更具体的条件下执行另一个代码块。

if 嵌套语句的格式如下:

if(外侧条件){    
     // 外侧条件为 true 时执行的代码 
    if(内侧条件){  
        // 内侧条件为 true 时执行的代码
    }    
}  

画个流程图表示一下:

thirteen-05.png

嵌套if语句在程序中经常被使用,但过多的嵌套可能会使代码难以阅读和理解,因此需要谨慎使用。下面的代码展示了嵌套的if语句:

if(i==10){
    if(j<20) a=b;
    if(k>100) c=d;//这个if和
    else a=c;//这个else相关联
}
else a=d;//这个else和最初的if(i==10)相关联

正如注释所示,最后的这个else并不是和if(j<20) 这个代码块相关,而是和最初的if(i==10)相关联,因为if(j<20) 被嵌套在了if(i==10)的代码块中。

4.1.4 if-else if梯形结构

if-else-if梯形结构是一种常见的编程结构,它基于一系列嵌套的if语句构成。它的结构如下所示:

if(条件1) 
语句1; 
else if(条件2) 
语句2; 
else if(条件3) 
语句3; 
... else 语句n;

if语句从上往下依次执行,一旦满足其中一个if语句的条件,与其相关联的语句就会被执行,然后程序跳过其余的条件语句。如果没有任何一个条件被满足,那么将会执行最后的else语句。最后的else语句充当了一个默认的条件,即如果所有其他条件测试都失败,则执行最后的else语句。如果没有最后的else语句并且所有其他条件都为假,则不会执行任何操作。下面是这种结构的流程图:

thirteen-04.png

来写个示例:

int age = 31;
if (age < 30) {
    System.out.println("青春年华");
} else if (age >= 30 && age < 40 ) {
    System.out.println("而立之年");
} else if (age >= 40 && age < 50 ) {
    System.out.println("不惑之年");
} else {
    System.out.println("知天命");
}

输出:

而立之年

4.1.5 switch 语句

switch语句是Java的多路分支语句。它提供了一种简单的根据表达式的值将执行程序分派到代码的不同部分的方法。因此,它通常比一系列大量的if-else-if语句提供更好的替代方案。下面是switch语句的一般形式:

switch(表达式) {    
case 可选值1:    
 // 可选值1匹配后执行的代码;    
 break;  // 该关键字是可选项
case 可选值2:    
 // 可选值2匹配后执行的代码;    
 break;  // 该关键字是可选项
......    
    
default: // 该关键字是可选项     
 // 所有可选值都不匹配后执行的代码 
}    

在JDK 7之前的Java版本中,表达式的结果类型必须为byte、short、int、char或枚举类型。(枚举类型将在后面的章节描述)从JDK 7开始,表达式也可以是String类型。在case语句中指定的每可选个值必须是唯一的常量表达式(例如文本值)。不允许重复的case值。每个值的类型必须与表达式的类型兼容。 switch语句的工作原理如下:将表达式的值与case语句中的每个值进行比较。如果找到匹配项,则执行该case语句后面的代码序列。如果没有一个常量匹配表达式的值,则执行default语句。但是,default语句是可选的。如果没有任何case匹配并且没有default,则不执行任何操作。 break语句用于在switch语句内部终止语句序列。当遇到break语句时,将“跳出”switch的多路选择,执行后面的代码。 它的流程图如下:

thirteen-06.png

下面是一个使用switch语句的简单示例:

package com.mycompany.sampleswitch;

//switch的一个简单示例
public class SampleSwitch {
    public static void main(String[] args) {
        for(int i=0;i<6;i++)
            switch(i){
                case 0:
                    System.out.println("i是0");
                    break;
                case 1:
                    System.out.println("i是1");
                    break;
                case 2:
                    System.out.println("i是2");
                    break;
                case 3:
                    System.out.println("i是3");
                    break;
                default:
                    System.out.println("i比3大");
                
            }
    }
}

它的输出为:

i是0
i是1
i是2
i是3
i比3大
i比3大

如你所见,每次循环时,与i匹配的case常量关联的语句将被执行,其他所有语句将被跳过。在i大于3之后,没有任何case语句匹配,因此执行default语句。break语句是可选的。如果省略break,执行将继续进入下一个case。有时需要我们会设立多个没有break语句的case。例如以下程序:

package com.mycompany.missingbreak;


public class MissingBreak {
    public static void main(String[] args) {
        for(int i=0;i<12;i++)
        {
            switch(i)
            {
                case 0:
                case 1:
                case 2:
                case 3:
                case 4:
                    System.out.println("i比5小");
                    break;
                case 5:
                case 6:
                case 7:
                case 8:
                case 9:
                    System.out.println("i比10小");
                    break;
                default:
                    System.out.println("i大于等于10");
               
            }
        }
    }
}

它的输出为:

i5i5i5i5i5i10i10i10i10i10i大于等于10
i大于等于10

程序会判断每个case是否和变量相同,直到达到break语句(或switch的结尾)。 尽管前面的示例显然是为了说明而编造的,但在真实的程序中省略break语句具有许多实际应用,例如以下有关季节的程序:

public class SwitchDemo {

    public static void main(String[] args) {
        int month=4;
        
        String season;
        
        switch(month){
            case 12:
            case 1:
            case 2:
                season="Winter";
                break;
            case 3:
            case 4:
            case 5:
                season="Spring";
                break;
            case 6:
            case 7:
            case 8:
                season="Summer";
                break;
            case 9:
            case 10:
            case 11:
                season="Autumn";
                break;
            default:
                season="Bogus Month";             
        }
        System.out.println("April is in the "+season+".");
    }
    
}

在Java 7中,switch语句开始支持String值。但是,使用支持String的switch的主要问题是存在NullPointerException被抛出的风险。因为equals方法用于测试匹配的项,而在switch语句中使用的变量可能为null。此外,由于使用了equals,比较区分大小写。因此在使用String的时候,要特别留意。

4.1.6 嵌套switch

在Java中,嵌套switch语句是一种使用switch语句嵌套在另一个switch语句中的结构。它允许您根据多个条件来执行多个操作。以下是嵌套switch语句的示例:

public class NestedSwitchExample {
   public static void main(String[] args) {
      int i = 2;
      int j = 3;
      
      switch(i) {
         case 1:
            System.out.println("i is 1");
            break;
         case 2:
            switch(j) {
               case 1:
                  System.out.println("j is 1");
                  break;
               case 2:
                  System.out.println("j is 2");
                  break;
               case 3:
                  System.out.println("j is 3");
                  break;
               default:
                  System.out.println("j is not 1, 2, or 3");
                  break;
            }
            break;
         case 3:
            System.out.println("i is 3");
            break;
         default:
            System.out.println("i is not 1, 2, or 3");
            break;
      }
   }
}

在上面的示例中,我们首先使用一个switch语句来检查变量i的值。如果i等于1,则打印“i是1”字符串。如果i等于2,则进入嵌套的switch语句,以检查变量j的值。如果j等于1,则打印“j是1”字符串,如果j等于2,则打印“j是2”字符串,如果j等于3,则打印“j是3”字符串。如果j不等于1、2或3,则打印“j不是1、2或3”字符串。如果i等于3,则打印“i是3”字符串。如果i不等于1、2或3,则打印“i不是1、2或3”字符串。

嵌套switch语句可以使代码更加简洁,并使您可以根据多个条件执行多个操作。

4.2 循环语句

所谓循环就是在特定的条件满足的情况下,反复执行特定代码。为什么我们需要循环呢?举两个例子:当我们要打印100次helloworld或者我们想实现1-10的加和

1+2+3+4+5....
int sum = 0;
sum = sum + 1;
sum = sum + 2;
sum = sum + 3;
sum = sum + 4;
sum = sum + 5; 

可以发现有一些是相同的内容。这些相同的内容我们就可以采用循环的方式来实现。

Java中有三种主要的循环结构:

  • while 循环
  • do…while 循环
  • for 循环

下面我们来一一介绍。

4.2.1 while循环

正如其名称所示,while语句会在某个条件保持有效的情况下重复执行循环内的指令。while语句的结构如下:

while(条件){  
//循环体  
}  

它的流程图为:

thirteen-09.png

大多数情况下,在使用while语句时,我们需要首先声明一个变量作为循环计数器。下面的代码展示了while语句的工作原理的一个示例。

package com.mycompany.whiledemo;

//展示while循环
public class WhileDemo {
    public static void main(String[] args) {
        int n=10;
        
        while(n>0)
        {
            System.out.println("n为"+n);
            n--;
        }
    }
}

上面的例子中计数器为n,它的初始值为10,意味着这个循环要执行10次,因为每次循环,n都会减去1。当n为0的时候while的条件不再满足,跳出循环。

while循环体(或Java的任何其他循环)可以什么都没有。这是因为Java中的空语句(只包含分号的语句)在语法上是合法的。例如以下程序:

package com.mycompany.whilenobody;

public class WhileNoBody {
    public static void main(String[] args) {
        int i,j;
        
        i=100;
        j=200;
        //寻找i与j的中位数
        while(++i<--j);//这个循环中循环体里什么都没有
        
        System.out.println("中位数是"+i);
    }
}

他的输出结果为:

中位数是150

每次循环i的值增加,j的值减少,然后i,j相互比较。如果i的新值仍然小于j的新值,则循环重复。如果i等于或大于j,则循环停止。退出循环时,i将保持一个处于i和j原始值中间的值。(当然,只有当i一开始小于j时,此过程才有效。)这个while不需要循环体;所有操作都在条件表达式本身中完成。在专业编写的Java代码中,当控制表达式可以自行处理所有细节时,经常使用没有循环体的短循环。

4.2.2 do-while

如果while循环的控制条件表达式一开始为false,则循环体将根本不执行。然而,有时候即使条件表达式一开始为false,我们也希望至少执行一次循环体。换句话说,有时候您想在循环结束时而不是在开始时测试终止条件。幸运的是,Java提供了一个恰好做到这一点的循环:do-while。do-while循环总是至少执行一次循环体,因为其条件表达式位于循环底部。它的一般形式是:

do {
// 循环体
} while (条件表达式);

每次do-while循环首先执行循环体,然后判断条件表达式。如果该表达式为true,则循环将重复。否则,循环终止。与Java的所有循环一样,条件必须是布尔表达式。 它的流程图为:

thirteen-10.png

下面是一个例子:

public class DoWhileDemo {
   public static void main(String[] args){
      int x = 10;
 
      do{
         System.out.print("value of x : " + x );
         x++;
         System.out.print("\n");
      }while( x < 20 );
   }
}

它的输出为:

value of x : 10
value of x : 11
value of x : 12
value of x : 13
value of x : 14
value of x : 15
value of x : 16
value of x : 17
value of x : 18
value of x : 19

当您处理菜单选择时,do-while循环特别有用,因为通常希望菜单循环的主体至少执行一次。例如以下程序,它实现了一个非常简单的帮助系统,用于Java的选择和迭代语句:

package com.mycompany.menudowhile;

//用do-while循环做一个建议的java菜单
public class MenuDoWhile {
    public static void main(String[] args) 
        throws java.io.IOException{
        char choice;
        do{
            System.out.println("帮助:");
            System.out.println("1. if");
            System.out.println("2. switch");
            System.out.println("3. while");
            System.out.println("4. do-while");
            System.out.println("5. for\n");
            System.out.println("选择一个");
            choice=(char) System.in.read();
        }while(choice<'1'||choice>'5');
    
        
        System.out.println("\n");
        
        switch(choice){
            case '1':
                System.out.println("if:\n");
                System.out.println("if(条件) 语句");
                System.out.println("else 语句\n");
                break;
            case '2':
                System.out.println("switch:\n");
                System.out.println("switch(表达式){");
                System.out.println("case +常量");
                System.out.println("语句序列");
                System.out.println("break;");
                System.out.println("}");
                break;
            case '3':
                System.out.println("while:\n");
                System.out.println("while(条件) 语句");
                break;
                
            case '4':
                System.out.println("do-while:\n");
                System.out.println("do {");
                System.out.println("语句\n");
                System.out.println("}while(条件)");
                break;
            case '5':
                System.out.println("for:\n");
                System.out.println("之后在讲");
                break;
                
        }
    }
}

当选择一个选项时,他的结果为:

帮助:
1. if
2. switch
3. while
4. do-while
5. for

选择一个
1


if:

if(条件) 语句
else 语句

在该程序中,do-while循环用于验证用户输入的选项是否是有效。如果无效,则提示用户重新输入。由于菜单必须至少显示一次,因此我们使用do-while实现此目。我们通过调用System.in.read( )从键盘读取字符。这是Java的控制台输入函数之一。这将在后面的章节介绍。它从标准输入中读取字符(作为整数返回,这就是为什么返回值被强制转换为char的原因)。默认情况下,标准输入是行缓冲的,因此在将任何键入的字符发送到程序之前,必须按Enter键。Java的控制台输入可能有些麻烦。此外,大多数真实的Java程序都将使用图形用户界面(GUI)。出于这些原因,在本笔记中并没有多少使用控制台输入。另一个要考虑的问题是:因为使用了System.in.read( ),所以程序必须指定throws java.io.IOException子句。这一行是处理输入错误的必要部分。它是Java的异常处理功能的一部分,这将在后面章节中讨论。

4.2.3 for

for循环有两种形式。第一种是自Java原始版本以来一直在使用的传统形式。第二种是JDK 5添加的较新的“for-each”形式。我们将讨论这两种类型的for循环,首先介绍传统形式。以下是传统for语句的一般形式:

for(初始变量;条件;迭代部分){  
// 循环体
}  

如果循环体只有一个语句被重复执行,就可以不需要花括号。for循环的操作如下。当循环开始时,首先执行循环的初始变量部分。通常,这是一个表达式,设置循环控制变量的值,该变量作为控制循环的计数器。初始化表达式只执行一次。接下来,不断地判断中间的条件。这必须是一个布尔表达式,它将循环控制变量与目标值进行比较。如果这个表达式为真,则执行循环体。如果为假,则循环终止。接下来,执行循环的迭代部分。这通常也是一个表达式,用于增加或减少循环控制变量。整个流程就是首先评估条件表达式,然后执行循环体,然后在每次循环体执行完成时执行迭代表达式。这个过程重复,直到中间的条件表达式为false,用流程图表示为:

thirteen-08.png

下面是一个简单的Java for循环示例:

for (int i = 0; i < 5; i++) {
  System.out.println(i);
}

这段代码将打印数字0到4。

4.2.3.1 在for循环内部声明循环控制变量

通常情况下,控制for循环的变量仅用于循环本身,不在其他地方使用。当循环控制变量仅用于循环目的且不在其他地方使用时,可以在for的初始化部分内声明变量。例如,以下是前面的程序,使循环控制变量n在for内部作为int类型声明:

for (int n = 1; n <= 10; n++) {
    System.out.println(n);
}

这样做的好处是,当循环结束时,该变量的作用域也随之结束,不会影响其他代码块中的同名变量。

当我们在for循环内部声明变量时,有一个重要的点需要记住:该变量的作用域在for语句结束时结束。也就是说,该变量的作用域仅限于for循环。在for循环外,该变量将不再存在。如果您需要在程序的其他地方使用循环控制变量,则无法在for循环内部声明它。当循环控制变量不需要在程序的其他地方使用时,大多数Java程序员会将其声明为for内部的变量。例如,以下是一个测试质数的简单程序。请注意,循环控制变量i在for内部声明为int类型,因为它不需要在其他地方使用。

for (int i = 2; i <= number / 2; ++i) {
    // condition for nonprime number
    if (number % i == 0) {
        isPrime = false;
        break;
    }
}

4.2.3.2 逗号

有时我们会想在for循环的初始化部分和迭代部分中包含多个语句。例如以下程序中的循环:

package com.mycompany.forsample1;


public class ForSample1 {

    public static void main(String[] args) {
        int a,b;
        
        b=4;
        for(a=1;a<b;a++)
        {
            System.out.println("a="+a);
            System.out.println("b="+b);
            b--;
        }
    }
}

如您所见,该循环由两个变量的交互控制。如果在for语句本身中就包含这两个变量,而不是在循环内部处理b,将会非常简洁直观。幸运的是,Java提供了一种方法来实现这一点。为了允许两个或更多的变量控制for循环,Java允许在for循环的初始化和迭代部分中包含多个语句。每个语句之间用逗号分隔。下面我们将使用逗号修改前面的程序:

package com.mycompany.forsample1;


public class ForSample1 {

    public static void main(String[] args) {
        int a,b;
        
        b=4;
        for(a=1;a<b;a++,b--)//用逗号分隔的迭代部分,两条语句都会执行
        {
            System.out.println("a="+a);
            System.out.println("b="+b);
        }
    }
}

在这个例子中,我们在初始化部分设置了a和b的值。在迭代部分中,逗号分隔的两个语句在每次循环重复时执行。该程序生成以下输出:

a=1
b=4
a=2
b=3

4.2.3.3 for循环的变体

for循环支持多种变体,增强了其功能和适用性。它如此灵活的原因是,它的三个部分——初始化、条件测试和迭代——不必仅用于那些目的。事实上,for的这三个部分可以用于任何您需要的目的。让我们看一些例子。

其中最常见的变体之一就是条件表达式。具体而言,这个表达式有时可以不需要测试循环控制变量是否等于某个目标值。实际上,控制for的条件可以是任何布尔表达式。例如以下代码片段:

boolean done=false;

for(int i=1;!done;i++){
    //...
    if(interrupted()) done=true;
}

在这个例子中,for循环会一直运行,直到布尔变量done被设置为true。它不会测试变量i的值。 这里有另一个有趣的for循环变体。初始化表达式、迭代表达式或两者都可以缺省,如下所示:

package com.mycompany.forvar;


public class ForVar {

    public static void main(String[] args) {
        int i;
        boolean done=false;
        
        i=0;
        for(;!done;){
            System.out.println("i is "+i);
            if(i==10) done=true;
            i++;
        }
    }
}

在这里,初始化和迭代表达式已经移出了for循环。因此,for的某些部分是空的。虽然在这个简单的例子中这没有任何价值——实际上,这会被认为是相当糟糕的风格——但有时这种方法是有意义的。例如,如果初始条件是通过程序中其他复杂表达式设置的,或者如果循环控制变量以非连续方式更改,这种方式可能是合适的,由循环体内发生的操作决定了这种变化。

这里还有另一种for循环的变体。如果将for的三个部分都留空,可以故意创建一个无限循环(永远不会终止的循环)。例如:

for(;;){
//....
}

这个循环会永远运行,因为没有任何条件使其终止。虽然有一些程序(例如操作系统命令处理器)需要一个无限循环,但大多数“无限循环”实际上只是具有特殊终止要求的循环。

4.2.4 for-each

for each是Java中的一种循环结构,它可以用于遍历数组或集合中的元素。它的使用规则比普通的 for 循环还要简单,不需要初始变量,不需要条件,不需要下标来自增或者自减。for each循环的语法格式如下:

for (元素类型t 元素变量x : 遍历对象obj) {
    // 对x进行操作
}

其中,元素类型t是数组或集合中元素的类型,元素变量x是当前遍历到的元素,遍历对象obj是要遍历的数组或集合。通过指定遍历的对象,for将会从数组或集合的第一个元素,自动遍历到最后一个元素。

下面是一个使用for each循环遍历数组的例子:

int[] nums = {1, 2, 3, 4, 5};
for (int num : nums) {
    System.out.println(num);
}

这个例子中,我们定义了一个整型数组nums,并使用for each循环遍历了它的每一个元素。在循环体中,我们使用System.out.println(num)语句输出了当前遍历到的元素。在for each循环中,元素变量是只读的,不能用于修改数组和赋值。这是因为for each循环是基于迭代器实现的,而迭代器是只读的,不能修改集合中的元素。我们用一个例子来说明一下:

package com.mycompany.foreachnochange;

//这里的for-each是只读的!
public class ForEachNoChange {

    public static void main(String[] args) {
        int[] nums={1,2,3,4,5,6,7,8,9,10};
        
        for(int x:nums){
            System.out.println(x+"");
            x=x*10;//这个语句没有任何影响!
        }
        System.out.println();
        
        for(int x:nums)
        {
            System.out.println(x+" ");
        }
        
        System.out.println();
    }
}

它的输出为:

1
2
3
4
5
6
7
8
9
10

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 

在第一个for循环里,我们尝试将x的值自增10倍,但是并没有成功,通过第二个for循环的输出,我们看到数组的值并没有改变,这是因为迭代器x的值是只读的,因此我们不能改变迭代器的值。

4.2.4.1 遍历多维数组

加强版的for循环也适用于多维数组。在Java中,多维数组由数组组成的数组构成。(例如,二维数组是一维数组的数组。)这在遍历多维数组时很重要,因为每次迭代获取的是下一个数组,而不是单个元素。此外,for循环中的迭代变量必须与获取的数组类型兼容。例如,在二维数组的情况下,迭代变量必须是一个对一维数组的引用。通常,在使用for-each循环遍历N维数组时,获取到的对象将是N-1维数组。使用嵌套的for-each循环按行顺序获取二维数组的元素,从第一个遍历到最后一个。

package com.mycompany.foreachdemo2d;

//用for-each遍历二维数组
public class ForEachDemo2D {
    public static void main(String[] args) {
        int sum=0;
        int nums[][]=new int[3][5];
        
        //首先对二维数组赋值
        for(int i=0;i<3;i++)
            for(int j=0;j<5;j++)
                nums[i][j]=(i+1)*(j+1);
        
        //用for-each去遍历数组并且计算数组的总和:
        for(int x[]:nums){
            for(int y:x){
                System.out.println("值为 "+y);
                sum+=y;
            }
        }
        System.out.println("总和为 "+sum);
    }
}

它的输出为:

值为 1
值为 2
值为 3
值为 4
值为 5
值为 2
值为 4
值为 6
值为 8
值为 10
值为 3
值为 6
值为 9
值为 12
值为 15
总和为 90

在程序中,特别注意这一行:

for(int x[]: nums) { 

请注意x的声明方式。它是对一个整数类型的一维数组的引用。这是非常重要的,因为for循环的每一次迭代都会获取二维数组nums中的下一个一维数组。然后内部的for循环会循环遍历每个一维数组,并显示每个元素的值。

4.2.4.2 for-each循环的应用

由于for-each风格的循环只能按顺序循环遍历数组,从开始到结束,因此您可能认为它的用途有限,但事实并非如此。大量算法正是需要这种机制。其中最常见的之一就是搜索。例如,以下程序使用for循环在未排序的数组中搜索一个值。如果找到该值,则停止搜索。

package com.mycompany.searchdemo;

//用for-each循环搜索一个数组
public class SearchDemo {

    public static void main(String[] args) {
        int nums[]={6,8,3,7,5,4,1,4};
        int val=5;
        boolean found=false;
        
        //用for-each循环搜索值为val的变量
        for(int x:nums){
            if(x==val){
                found=true;
                break;
            }
        }
        if(found)
            System.out.println("Value found!");
    }
}

在这种应用中,for-each风格的循环是一个很好的选择,因为搜索未排序的数组需要按顺序检查每个元素。(当然,如果数组已排序,可以使用二分搜索。)其他类型的应用程序,如计算平均值、查找一组数的最小值或最大值、查找重复项等,也可以用for-each循环。 尽管我们在本章的示例中一直在使用数组,但foreach风格的循环在操作集合时特别有用,这在第二部分中有所描述。更一般地说,只要该集合满足一定的约束条件(这些约束条件在第19章中描述),for循环就可以循环遍历任何对象集合的元素。

4.2.5 for循环中的局部类型推断

在第三章中,我们介绍到JDK 10引入了一种称为局部变量类型推断的功能,允许从其初始化程序的类型推断出局部变量的类型。要使用局部变量类型推断,变量的类型必须声明为 var,并且必须初始化变量。在传统for循环中声明和初始化循环控制变量,或在for-each循环中指定迭代变量时,都可以在for循环中使用局部变量类型推断。例如以下的程序:

package com.mycompany.typeinferenceinfor;

//在for里面运用局部类型推断
public class TypeInferenceInFor {
    public static void main(String[] args) {
        System.out.print("x的值为:");
        for(var x=2.5;x<100.0;x=x*2)
            System.out.print(x+"");
        System.out.println();
        
        //在迭代中使用局部变量推理
        int[] nums={1,2,3,4,5,6};
        System.out.print("数组中的值为:");
        for(var v:nums)
            System.out.print(v+" ");
        System.out.println();
    }
}

它的输出为:

x的值为:2.55.010.020.040.080.0
数组中的值为:1 2 3 4 5 6 

在这个例子中,循环控制变量x在这行代码中:

for (var x = 2.5; x < 100.0; x = x * 2)

被推断为double类型,因为它的初始化值默认是double类型的。迭代变量v在这行代码中:

for(var v : nums)

被推断为int类型,因为它是数组nums的元素类型。

4.2.6 嵌套循环

和其他所有编程语言一样,Java允许循环嵌套,即一个循环可以位于另一个循环内部。例如,下面是一个嵌套for循环的程序:

package com.mycompany.netedfor;

public class NetedFor {

    public static void main(String[] args) {
        int i,j;
        
        for(i=0;i<10;i++){
            for(j=i;j<10;j++)
                System.out.print(".");
            System.out.println();
        }
    }
}

它的输出为:

..........
.........
........
.......
......
.....
....
...
..
.

4.3 跳跃语句

在前面的示例中,我们提到过使用break语句来退出switch选择语句。有三种方法可以退出循环与选择语句:

• break语句可以退出循环,并且如果break后面加上了循环的标签,它将中断带有该标签的循环;这在我们有多层嵌套循环时很有用,因为我们可以从任何嵌套循环中灵活退出。

• continue语句跳过这一次循环的代码执行,并继续执行下一次的循环。

• return语句用于退出方法,因此,如果循环、if或switch语句位于方法的主体中,则也用于退出循环。在最佳实践方面,不应滥用return语句来退出方法,因为它们可能会使执行流程难以跟踪。

4.3.1 break语句

在Java中,break语句有三种用途。首先,它会终止switch语句中的语句序列。其次,它可以用于退出循环。第三,它可以用作一种goto形式。在这一小节,我们将着重介绍后两种用法。

4.3.1.1 使用break退出循环

使用break,您可以强制立即终止循环,绕过条件表达式和循环体中的任何剩余代码。当在循环内遇到break语句时,循环将终止,并且程序控制在循环后的下一条语句处继续执行。它的流程图为:

thirteen-12.png

以下是一个简单的示例:

package com.mycompany.breakloop1;

//用break推出循环
public class BreakLoop1 {
    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            if(i==10) break;//如果i累积到了10则退出循环
            System.out.println("i: "+i);
        }
        System.out.println("循环完成!");
    }
}

正如您所看到的,尽管for循环设计为从0到99运行,但是break语句导致其在i等于10时提前终止。break语句可以与Java的任何循环一起使用,甚至包括没有终止条件的无限循环。以下程序使用while循环编码,并用break终止while,其输出与刚才显示的相同。

package com.mycompany.breakloop2;


public class BreakLoop2 {

    public static void main(String[] args) {
        int i=0;
        
        while(i<100){
            if(i==10) break;//在i等于10的时候终止循环
            System.out.println("i:"+i);
            i++;
        }
        System.out.println("循环完成!");
    }
}

当在一组嵌套循环中使用break时,break语句只会跳出最内层的循环。例如:

package com.mycompany.breakloop3;

//在嵌套循环中使用break
public class BreakLoop3 {
    public static void main(String[] args) {
        for(int i=0;i<3;i++){
            System.out.println("Pass"+i+": ");
            for(int j=0;j<100;j++){
                if(j==10) break;//如果j等于10则终止循环
                System.out.print(j+" ");
            }
            System.out.println();
        }
        System.out.println("Loop is complete.");
    }
}

它的输出为:

Pass0: 
0 1 2 3 4 5 6 7 8 9 
Pass1: 
0 1 2 3 4 5 6 7 8 9 
Pass2: 
0 1 2 3 4 5 6 7 8 9 
Loop is complete.

正如您所看到的,内部循环中的break语句仅导致内部循环终止,而外部循环不受影响。 以下是关于break的另外两个要点:首先,在循环中可以出现多个break语句。但是,要小心。过多的break语句会破坏您的代码结构。其次,终止switch语句的break仅影响该switch语句,而不影响其他的语句。


请记住,break语句不是设计用来提供正常的循环终止手段的。循环的条件表达式才是达成此目的的适当方式。只有在出现某种特殊情况时,才应使用break语句来取消循环。

4.3.1.2 使用break当作另一种形式的goto

除了可以在switch语句和循环中使用外,break语句也可以单独使用,这时它能起到goto语句的作用。goto语句是一种控制流语句,用于将程序控制转移到代码中的另一个位置。它可以用于任何目标位置,例如在同一个函数中的其他语句、另一个函数或同一程序中的任何位置。Java不支持goto语句,因为goto语句太过随意并且忽视了代码的结构,这通常使得代码难以理解和难以维护。goto语句的存在也抵消了某些编译器优化。然而,在某些情况下,goto是一种有价值且合法的流控制结构。例如,在退出一组嵌套的循环时,goto可能会很有用。为处理这种情况,Java定义了一个扩展形式的break语句。通过使用这种形式的break,您可以从一个或多个代码块的执行中转移出来。这些块不必是循环或switch的一部分。它们可以是任何普通的代码块。此外,您可以通过break后面的标签精确指定接下来代码执行的位置。您将看到的,break给您带来了goto的好处,并且屏蔽了它的弊端。带标签的break语句的一般形式如下所示:

break label;

这个标签(label)是一个标识符,用于标识一个代码块。当执行带标签的break语句时,程序会直接跳出带有该标签的代码块。这个标签必须与一个循环或switch语句相关联,以便break语句知道要跳出哪个代码块。

使用带标签的break语句可以在Java中实现一些复杂的控制流操作,例如从多层嵌套的循环中跳出。同时,它也可以让代码更加清晰易懂,因为标签可以很好地描述跳转的目的地。但是,过度使用带标签的break语句也会导致代码变得复杂难懂,应该谨慎使用。下面我们用一个例子去阐明这种用法:

package com.mycompany.breaklabel;

//带标签的break的示例
public class BreakLabel {
    public static void main(String[] args) {
        boolean t=true;
        
        first:{
            second:{
                third:{
                    System.out.println("Before the break");
                    if(t) break second;//用于结束第二个标签快
                    System.out.println("This won't execute");
                }
                System.out.println("This won't execute");
            }
            System.out.println("This is after second block");
        }
    }
}

我们在一个代码块的开头设置一个标签为一个代码块命名。标签是任何有效的Java标识符后紧跟一个冒号。一旦给一个代码块添加了标签,就可以在break语句中使用这个标签。这样做会导致程序在标记块的结尾处(这个块的右括号“}”)继续执行。上面的程序展示了三个嵌套的代码块,每个都有自己的标签。break语句导致执行跳转到标记为“second”的块的结尾之后,跳过了两个println()语句。它的输出结果为:

Before the break
This is after second block

带标记的break语句最常见的用途之一是从嵌套循环中退出。例如,在下面的程序中,外部循环只执行一次:

package com.mycompany.breakloop4;


public class BreakLoop4 {

    public static void main(String[] args) {
        outer:for(int i=0;i<3;i++){
            System.out.print("Pass"+i+":");
            for(int j=0;j<100;j++){
                if(j==10) break outer;
                System.out.print(j+" ");
            }
            System.out.println("This will not print");
        }
        System.out.println("Loops complete!");
    }
}

它的输出为:

Pass0:0 1 2 3 4 5 6 7 8 9 Loops complete!

正如你所看到的,当内部循环中断到外部循环时,两个循环都已经终止了。请注意,这个示例给for语句打了标签,它的目标是它后面紧跟的代码块。

注意break+标签只能在标签所在的代码块内使用,不能在标签指定的代码块的外面使用,例如下面的代码是错误的:

package com.mycompany.breakerr;

//这个程序是错误的!
public class BreakErr {

    public static void main(String[] args) {
        one:for(int i=0;i<3;i++){
            System.out.print("Pass"+i+":");
          
        }
        for(int j=0;j<100;j++){
            if(j==10) break one;//错误!
            System.out.print(j+" ");
        }
    }
}

由于语句break one;在one标签所指定的代码块外面,因此将会报错。

4.3.2 continue语句

continue 语句并不会终止循环,但可以根据给定的条件跳过当前循环中的某些步骤。换句话说,它停止了当前循环剩余语句的执行,并且继续了下一次的循环。

我们将使用数组遍历示例进行实验,这一次,我们将使用 continue 语句跳过打印具有偶数索引的元素。

package com.mycompany.continuedemo1;

public class ContinueDemo1 {

    public static void main(String[] args) {
        int[] nums;
        nums=new int [15];
        for(int i=0;i<15;i++)
        {
            nums[i]=i;
        }
        for(int i=0;i<15;i++)
        {
            if(i%2==0)
                continue;//当为偶数的时候跳过下面的语句
            System.out.print(i+" ");
        }
    }
}

它的输出为:

1 3 5 7 9 11 13 

此代码使用 % 运算符来检查 i 是否为偶数。如果是,循环将继续而不打印数组的元素。

与 break 语句一样,continue 语句也可以指定一个标签来描述要继续的封闭循环。以下是一个使用 continue 语句打印从 0 到 9 的三角形乘法表的示例程序:

package com.mycompany.continuelabeldemo;

//continue加上标签的示例
public class ContinueLabelDemo {

    public static void main(String[] args) {
        outer:for(int i=0;i<10;i++){
            for(int j=0;j<10;j++){
                if(j>i){
                    System.out.println();
                    continue outer;
                }
                System.out.print(" "+(i*j));
            }
        }
        System.out.println();
    }
}

在这个示例中,continue 语句在j>i的条件下终止了对 j 的循环计数,并继续进行对 i 的下一次迭代。以下是该程序的输出结果:

 0
 0 1
 0 2 4
 0 3 6 9
 0 4 8 12 16
 0 5 10 15 20 25
 0 6 12 18 24 30 36
 0 7 14 21 28 35 42 49
 0 8 16 24 32 40 48 56 64
 0 9 18 27 36 45 54 63 72 81

4.3.3 return 语句

最后一个要介绍的控制语句是 return。return 语句用于显式地从方法中返回。也就是说,它会导致程序控制返回到调用该方法的位置。因此,它被归类为跳转语句。虽然关于 return 的完整讨论必须等到第 6 章讨论方法时才能进行,但在这里简要介绍一下 return。 在一个方法内部的任何地方,我们都可以使用 return 语句使执行分支返回到该方法的调用处。因此,return 语句能起到立即终止执行它所在的方法的作用。我们用下面的例子来说明:

package com.mycompany.returndemo;

public class ReturnDemo {
    public static final int arr[] = {5, 1, 4, 2, 3};
    public static void main(String[] args) {
        int foundIdx=findEven(arr);
        if(foundIdx!=-1){
            System.out.println("First even is at:"+foundIdx);
        }
    }
    public static int findEven(int ...arr){
        for(int i=0;i<arr.length;i++){
            if(arr[i]%2==0){
                return i;
            }
        }
        return -1;
    }
}

在上面的示例当中,我们定义了一个方法findEven,它用于在给定的数组中(...args)寻找奇数,寻找的结果有两种情况,其一就是存在奇数,那么我们将返回奇数所在的索引i,如果找不到则返回-1,该函数返回到语句int foundIdx=findEven(arr);处,并且将返回的结果赋值给了变量foundIdx