五、控制流(2)

本章概要

  • return
  • break 和 continue
  • 臭名昭著的 goto
  • switch
  • switch 字符串

return

在 Java 中有几个关键字代表无条件分支,这意味无需任何测试即可发生。这些关键字包括 returnbreakcontinue 和跳转到带标签语句的方法,类似于其他语言中的 goto

return 关键字有两方面的作用:1.指定一个方法返回值 (在方法返回类型非 void 的情况下);2.退出当前方法,并返回作用 1 中值。我们可以利用 return 的这些特点来改写上例 IfElse.java 文件中的 test() 方法。代码示例:

// control/TestWithReturn.java
public class TestWithReturn {static int test(int testval, int target) {if (testval > target) {return +1;}if (testval < target) {return -1;}return 0; // Match}public static void main(String[] args) {System.out.println(test(10, 5));System.out.println(test(5, 10));System.out.println(test(5, 5));}
}

输出结果:

1
-1
0

这里不需要 else,因为该方法执行到 return 就结束了。

如果在方法签名中定义了返回值类型为 void,那么在代码执行结束时会有一个隐式的 return。 也就是说我们不用在总是在方法中显式地包含 return 语句。 注意:如果你的方法声明的返回值类型为非 void 类型,那么则必须确保每个代码路径都返回一个值。

break 和 continue

在任何迭代语句的主体内,都可以使用 breakcontinue 来控制循环的流程。 其中,break 表示跳出当前循环体。而 continue 表示停止本次循环,开始下一次循环。

下例向大家展示 breakcontinueforwhile 循环中的使用。代码示例:

Range.java

public class Range {// Produce sequence [start..end) incrementing by steppublic static int[] range(int start, int end, int step) {if (step == 0) {throw new IllegalArgumentException("Step cannot be zero");}int sz = Math.max(0, step >= 0 ? (end + step - 1 - start) / step : (end + step + 1 - start) / step);int[] result = new int[sz];for (int i = 0; i < sz; i++) {result[i] = start + (i * step);}return result;}  // Produce a sequence [start..end)public static int[] range(int start, int end) {return range(start, end, 1);}// Produce a sequence [0..n)public static int[] range(int n) {return range(0, n);}
}

BreakAndContinue.java

import static BASE0002.Range.range;
// control/BreakAndContinue.java
// Break 和 continue 关键字
public class BreakAndContinue {public static void main(String[] args) {for (int i = 0; i < 100; i++) { // [1]if (i == 74) {break; // 跳出循环}if (i % 9 != 0) {continue; // 下一次循环}System.out.print(i + " ");}System.out.println();// 使用 for-in 循环:for (int i : range(100)) { // [2]if (i == 74) {break; // 跳出循环}if (i % 9 != 0) {continue; // 下一次循环}System.out.print(i + " ");}System.out.println();int i = 0;//  "无限循环":while (true) { // [3]i++;int j = i * 27;if (j == 1269) {break; // 跳出循环}if (i % 10 != 0) {continue; // 循环顶部}System.out.print(i + " ");}}
}

输出结果:

0 9 18 27 36 45 54 63 72
0 9 18 27 36 45 54 63 72
10 20 30 40

[1] 在这个 for 循环中,i 的值永远不会达到 100,因为一旦 i 等于 74,break 语句就会中断循环。通常,只有在不知道中断条件何时满足时,才需要 break。因为 i 不能被 9 整除,continue 语句就会使循环从头开始。这使 i 递增)。如果能够整除,则将值显示出来。

[2] 使用 for-in 语法,结果相同。

[3] 无限 while 循环。循环内的 break 语句可中止循环。注意,continue 语句可将控制权移回循环的顶部,而不会执行 continue 之后的任何操作。 因此,只有当 i 的值可被 10 整除时才会输出。在输出中,显示值 0,因为 0%9 产生 0。还有一种无限循环的形式: for(;;)。 在编译器看来,它与 while(true) 无异,使用哪种完全取决于你的编程品味。

臭名昭著的 goto

goto 关键字 很早就在程序设计语言中出现。事实上,goto 起源于汇编(assembly language)语言中的程序控制:“若条件 A 成立,则跳到这里;否则跳到那里”。如果你读过由编译器编译后的代码,你会发现在其程序控制中充斥了大量的跳转。较之汇编产生的代码直接运行在硬件 CPU 中,Java 也会产生自己的“汇编代码”(字节码),只不过它是运行在 Java 虚拟机里的(Java Virtual Machine)。

一个源码级别跳转的 goto,为何招致名誉扫地呢?若程序总是从一处跳转到另一处,还有什么办法能识别代码的控制流程呢?随着 _Edsger Dijkstra_发表著名的 “Goto 有害” 论(Goto considered harmful)以后,goto 便从此失宠。甚至有人建议将它从关键字中剔除。

正如上述提及的经典情况,我们不应走向两个极端。问题不在 goto,而在于过度使用 goto。在极少数情况下,goto 实际上是控制流程的最佳方式。

尽管 goto 仍是 Java 的一个保留字,但其并未被正式启用。可以说, Java 中并不支持 goto。然而,在 breakcontinue 这两个关键字的身上,我们仍能看出一些 goto 的影子。它们并不属于一次跳转,而是中断循环语句的一种方法。之所以把它们纳入 goto 问题中一起讨论,是由于它们使用了相同的机制:标签。

“标签”是后面跟一个冒号的标识符。代码示例:

label1:

对 Java 来说,唯一用到标签的地方是在循环语句之前。进一步说,它实际需要紧靠在循环语句的前方 —— 在标签和循环之间置入任何语句都是不明智的。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环或者一个开关。这是由于 breakcontinue 关键字通常只中断当前循环,但若搭配标签一起使用,它们就会中断并跳转到标签所在的地方开始执行。代码示例:

label1:
outer-iteration { inner-iteration {// ...break; // [1] // ...continue; // [2] // ...continue label1; // [3] // ...break label1; // [4] } 
}

[1] break 中断内部循环,并在外部循环结束。

[2] continue 移回内部循环的起始处。但在条件 3 中,continue label1 却同时中断内部循环以及外部循环,并移至 label1 处。

[3] 随后,它实际是继续循环,但却从外部循环开始。

[4] break label1 也会中断所有循环,并回到 label1 处,但并不重新进入循环。也就是说,它实际是完全中止了两个循环。

下面是 for 循环的一个例子:

// control/LabeledFor.java
// 搭配“标签 break”的 for 循环中使用 break 和 continue
public class LabeledFor {public static void main(String[] args) {int i = 0;outer:// 此处不允许存在执行语句for (; true; ) { // 无限循环inner:// 此处不允许存在执行语句for (; i < 10; i++) {System.out.println("i = " + i);if (i == 2) {System.out.println("continue");continue;}if (i == 3) {System.out.println("break");i++; // 否则 i 永远无法获得自增// 获得自增break;}if (i == 7) {System.out.println("continue outer");i++;  // 否则 i 永远无法获得自增// 获得自增continue outer;}if (i == 8) {System.out.println("break outer");break outer;}for (int k = 0; k < 5; k++) {if (k == 3) {System.out.println("continue inner");continue inner;}}}}// 在此处无法 break 或 continue 标签}
}

输出结果:

在这里插入图片描述

注意 break 会中断 for 循环,而且在抵达 for 循环的末尾之前,递增表达式不会执行。由于 break 跳过了递增表达式,所以递增会在 i==3 的情况下直接执行。在 i==7 的情况下,continue outer 语句也会到达循环顶部,而且也会跳过递增,所以它也是直接递增的。

如果没有 break outer 语句,就没有办法在一个内部循环里找到出外部循环的路径。这是由于 break 本身只能中断最内层的循环(对于 continue 同样如此)。 当然,若想在中断循环的同时退出方法,简单地用一个 return 即可。

下面这个例子向大家展示了带标签的 break 以及 continue 语句在 while 循环中的用法:

// control/LabeledWhile.java
// 带标签的 break 和 conitue 在 while 循环中的使用
public class LabeledWhile {public static void main(String[] args) {int i = 0;outer:while (true) {System.out.println("Outer while loop");while (true) {i++;System.out.println("i = " + i);if (i == 1) {System.out.println("continue");continue;}if (i == 3) {System.out.println("continue outer");continue outer;}if (i == 5) {System.out.println("break");break;}if (i == 7) {System.out.println("break outer");break outer;}}}}
}

输出结果:

在这里插入图片描述

同样的规则亦适用于 while

  1. 简单的一个 continue 会退回最内层循环的开头(顶部),并继续执行。
  2. 带有标签的 continue 会到达标签的位置,并重新进入紧接在那个标签后面的循环。
  3. break 会中断当前循环,并移离当前标签的末尾。
  4. 带标签的 break 会中断当前循环,并移离由那个标签指示的循环的末尾。

大家要记住的重点是:在 Java 里需要使用标签的唯一理由就是因为有循环嵌套存在,而且想从多层嵌套中 breakcontinue

breakcontinue 标签在编码中的使用频率相对较低 (此前的语言中很少使用或没有先例),所以我们很少在代码里看到它们。

Dijkstra“Goto 有害” 论文中,他最反对的就是标签,而非 goto。他观察到 BUG 的数量似乎随着程序中标签的数量而增加。标签和 goto 使得程序难以分析。但是,Java 标签不会造成这方面的问题,因为它们的应用场景受到限制,无法用于以临时方式传输控制。由此也引出了一个有趣的情形:对语言能力的限制,反而使它这项特性更加有价值。

switch

switch 有时也被划归为一种选择语句。根据整数表达式的值,switch 语句可以从一系列代码中选出一段去执行。它的格式如下:

switch(integral-selector) {case integral-value1 : statement; break;case integral-value2 : statement;	break;case integral-value3 : statement;	break;case integral-value4 : statement;	break;case integral-value5 : statement;	break;// ...default: statement;
}

其中,integral-selector (整数选择因子)是一个能够产生整数值的表达式,switch 能够将这个表达式的结果与每个 integral-value (整数值)相比较。若发现相符的,就执行对应的语句(简单或复合语句,其中并不需要括号)。若没有发现相符的,就执行 default 语句。

在上面的定义中,大家会注意到每个 case 均以一个 break 结尾。这样可使执行流程跳转至 switch 主体的末尾。这是构建 switch 语句的一种传统方式,但 break 是可选的。若省略 break, 会继续执行后面的 case 语句的代码,直到遇到一个 break 为止。通常我们不想出现这种情况,但对有经验的程序员来说,也许能够善加利用。注意最后的 default 语句没有 break,因为执行流程已到了 break 的跳转目的地。当然,如果考虑到编程风格方面的原因,完全可以在 default 语句的末尾放置一个 break,尽管它并没有任何实际的作用。

switch 语句是一种实现多路选择的干净利落的一种方式(比如从一系列执行路径中挑选一个)。但它要求使用一个选择因子,并且必须是 intchar 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它们在 switch 语句里是不会工作的。对于非整数类型(Java 7 以上版本中的 String 型除外),则必须使用一系列 if 语句。 在后面中,我们将会了解到枚举类型被用来搭配 switch 工作,并优雅地解决了这种限制。

下面这个例子可随机生成字母,并判断它们是元音还是辅音字母:

import java.util.*;
// control/VowelsAndConsonants.java
// switch 执行语句的演示
public class VowelsAndConsonants {public static void main(String[] args) {Random rand = new Random(47);for (int i = 0; i < 100; i++) {int c = rand.nextInt(26) + 'a';System.out.print((char) c + ", " + c + ": ");switch (c) {case 'a':case 'e':case 'i':case 'o':case 'u':System.out.println("vowel");break;case 'y':case 'w':System.out.println("Sometimes vowel");break;default:System.out.println("consonant");}}}
}

输出结果:

y, 121: Sometimes vowel
n, 110: consonant
z, 122: consonant
b, 98: consonant
r, 114: consonant
n, 110: consonant
y, 121: Sometimes vowel
g, 103: consonant
c, 99: consonant
f, 102: consonant
o, 111: vowel
w, 119: Sometimes vowel
z, 122: consonant...

由于 Random.nextInt(26) 会产生 0 到 25 之间的一个值,所以在其上加上一个偏移量 a,即可产生小写字母。在 case 语句中,使用单引号引起的字符也会产生用于比较的整数值。

请注意 case 语句能够堆叠在一起,为一段代码形成多重匹配,即只要符合多种条件中的一种,就执行那段特别的代码。这时也应该注意将 break 语句置于特定 case 的末尾,否则控制流程会继续往下执行,处理后面的 case。在下面的语句中:

int c = rand.nextInt(26) + 'a';

此处 Random.nextInt() 将产生 0~25 之间的一个随机 int 值,它将被加到 a 上。这表示 a 将自动被转换为 int 以执行加法。为了把 c 当作字符打印,必须将其转型为 char;否则,将会输出整数。

switch 字符串

Java 7 增加了在字符串上 switch 的用法。 下例展示了从一组 String 中选择可能值的传统方法,以及新式方法:

// control/StringSwitch.java
public class StringSwitch {public static void main(String[] args) {String color = "red";// 老的方式: 使用 if-then 判断if ("red".equals(color)) {System.out.println("RED");} else if ("green".equals(color)) {System.out.println("GREEN");} else if ("blue".equals(color)) {System.out.println("BLUE");} else if ("yellow".equals(color)) {System.out.println("YELLOW");} else {System.out.println("Unknown");}// 新的方法: 字符串搭配 switchswitch (color) {case "red":System.out.println("RED");break;case "green":System.out.println("GREEN");break;case "blue":System.out.println("BLUE");break;case "yellow":System.out.println("YELLOW");break;default:System.out.println("Unknown");break;}}
}

输出结果:

RED
RED

一旦理解了 switch,你会明白这其实就是一个逻辑扩展的语法糖。新的编码方式能使得结果更清晰,更易于理解和维护。

作为 switch 字符串的第二个例子,我们重新访问 Math.random()。 它是否产生从 0 到 1 的值,包括还是不包括值 1 呢?在数学术语中,它属于 (0,1)、[0,1)、(0,1]、[0,1] 中的哪种呢?(方括号表示“包括”,而括号表示“不包括”)

下面是一个可能提供答案的测试程序。 所有命令行参数都作为 String 对象传递,因此我们可以 switch 参数来决定要做什么。 那么问题来了:如果用户不提供参数 ,索引到 args 的数组就会导致程序失败。 解决这个问题,我们需要预先检查数组的长度,若长度为 0,则使用空字符串 "" 替代;否则,选择 args 数组中的第一个元素:

TimedAbort.java

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;public class TimedAbort {private volatile boolean restart = true;public TimedAbort(double t, String msg) {CompletableFuture.runAsync(() -> {try {while (restart) {restart = false;TimeUnit.MILLISECONDS.sleep((int) (1000 * t));}} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(msg);System.exit(0);});}public TimedAbort(double t) {this(t, "TimedAbort " + t);}public void restart() {restart = true;}
}
// control/RandomBounds.java
// Math.random() 会产生 0.0 和 1.0 吗?
// {java RandomBounds lower}
public class RandomBounds {public static void main(String[] args) {new TimedAbort(3);switch (args.length == 0 ? "" : args[0]) {case "lower":while (Math.random() != 0.0) {; // 保持重试}System.out.println("Produced 0.0!");break;case "upper":while (Math.random() != 1.0) {; // 保持重试}System.out.println("Produced 1.0!");break;default:System.out.println("Usage:");System.out.println("\tRandomBounds lower");System.out.println("\tRandomBounds upper");System.exit(1);}}
}

要运行该程序,请键入以下任一命令:

java RandomBounds lower 
// 或者
java RandomBounds upper

TimedAbort 类可使程序在三秒后中止。从结果来看,似乎 Math.random() 产生的随机值里不包含 0.0 或 1.0。 这就是该测试容易混淆的地方:若要考虑 0 至 1 之间所有不同 double 数值的可能性,那么这个测试的耗费的时间可能超出一个人的寿命了。 这里我们直接给出正确的结果:Math.random() 的结果集范围包含 0.0 ,不包含 1.0。 在数学术语中,可用 [0,1) 来表示。由此可知,我们必须小心分析实验并了解它们的局限性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/12584.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不管如何吐槽,购买iPhone的用户依然义无反顾,苹果继续增长

市调机构IDC公布的二季度数据显示&#xff0c;苹果成为前五名之中除华为之外第二家取得增长的手机品牌&#xff0c;而其他国产手机品牌的出货量都在下滑&#xff0c;显示出国内的消费者仍然在热烈追捧iPhone。 二季度苹果在国内市场的手机出货量同比增长6%&#xff0c;虽然增速…

Android Studio下载及安装和Gradle的配置

文章目录 下载安装修改Sdk的位置创建项目修改Gradle的位置查看AS版本工具栏--View项工具栏--Build下的功能说明Build Variants视图说明下载模拟器&#xff08;avd&#xff09;/安卓虚拟设备屏幕熄灭功能关闭虚拟设备功能删除自己开发的应用软件将开发的应用运行到虚拟设备上。 …

从分片传输到并行传输之大文件传输加速技术

随着大文件的传输需求越来越多&#xff0c;传输过程中也会遇到很多困难&#xff0c;比如传输速度慢、文件安全性低等。为了克服这些困难&#xff0c;探讨各种大文件传输加速技术。其中&#xff0c;分片传输和并行传输是两种比较常见的技术&#xff0c;下面将对它们进行详细说明…

Java版知识付费源码 Spring Cloud+Spring Boot+Mybatis+uniapp+前后端分离实现知识付费平台

提供职业教育、企业培训、知识付费系统搭建服务。系统功能包含&#xff1a;录播课、直播课、题库、营销、公司组织架构、员工入职培训等。 提供私有化部署&#xff0c;免费售后&#xff0c;专业技术指导&#xff0c;支持PC、APP、H5、小程序多终端同步&#xff0c;支持二次开发…

【Java】Java多线程编程基础

文章目录 1. 进程与线程1.1 进程与线程的基本认识1.1.1 进程&#xff08;Process&#xff09;1.1.2 线程&#xff08;Thread&#xff09; 1.2 为什么会有线程1.2.1 以看视频为例 2. 多线程实现2.1 Thread类实现多线程2.2 Runnable接口实现多线程2.3 Callable接口实现多线程2.3 …

CTF线下赛AWD知识点【持续完善ing】

文章目录 CTF线下赛AWD知识点AWD规则前期准备SSH登录口令登录密钥登录 改密码SSH密码修改mysql密码修改 备份数据备份目录备份数据库 查找后门 自动提交flag防御思路基础查杀寻找最近20分钟修改过的文件寻找行数最短的文件关键字查杀查找命令执行函数 文件监控杀不死马0x01.杀进…

Grafana - TDEngine搭建数据监测报警系统

TDengine 与开源数据可视化系统 Grafana 快速集成搭建数据监测报警系统 一、介绍二、前置条件三、Grafana 安装及配置3.1 下载3.2 安装3.2.1 windows安装 - 图形界面3.2.2 linux安装 - 安装脚本 四、Grafana的TDEngine配置及使用4.1 登录4.2 安装 Grafana Plugin 并配置数据源4…

学习笔记|大模型优质Prompt开发与应用课(二)|第一节:大模型应用密码—Prompt的一千种打开方式

文章目录 第一节:大模型应用密码—Prompt的一千种打开方式01你可能听过一个小故事1910华盛顿纺织厂罢工事件 02 小问题:哪些场景会被提效类目一︰减少重复性工作的成本&#xff08;降本)例如∶做策划初稿、写JD、润色文案prompt生成结果prompt生成结果prompt生成结果promptprom…

NodeJs后端项目使用docker打包部署

docker安装看之前的文章 默认已经安装好docker并且配置没有问题 拉取项目 https://gitee.com/coder-msc/docker-node 本地跑一个看看 pnpm install pnpm start 本地访问 http://localhost:1301/getname?name%E5%93%88%E5%88%A9%E6%B3%A2%E7%89%B9项目整个上传服务器 查看…

简化Java单元测试数据

用EasyModeling简化Java单元测试 EasyModeling 是我在2021年圣诞假期期间开发的一个 Java 注解处理器&#xff0c;采用 Apache-2.0 开源协议。它可以帮助 Java 单元测试的编写者快速构造用于测试的数据模型实例&#xff0c;简化 Java 项目在单元测试中准备测试数据的工作&…

老胡的周刊(第101期)

老胡的信息周刊[1]&#xff0c;记录这周我看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。 &#x1f3af; 项目 ollama[2] 你可以下载 Ollama 在本地运行 Ll…

链表基础知识

一、什么是链表 链表是一种物理存储结构上非连续&#xff0c;非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 链表的结构是多式多样的&#xff0c;当时通常用的也就是两种&#xff1a; &#xff08;1&#xff09;第一种是无头非循环单向…

live-server本地起node服务解决跨域问题

一、初始化node,构建package.json NPM 全局安装live-server npm install -g live-server在当前项目文件夹下cmd运行&#xff1a; npm init -y此时会在根目录下生成一个package.json文件。 二.生成代理脚本 在根文件夹新建一个build.js文件&#xff08;名字可以自定义) var …

Linux中的pause函数

2023年7月29日&#xff0c;周六上午 函数原型 在Linux中&#xff0c;pause()函数用于使当前进程暂停执行&#xff0c;直到接收到一个信号。 #include <unistd.h>int pause(void);pause()函数不接受任何参数。 通常&#xff0c;pause()函数用于编写简单的信号处理程序&…

并发编程可能出现的核心问题

2.1非可见性 如果主内存里有个静态变量flagfalse&#xff0c;然后线程A和B在工作内存都需要操作flag&#xff0c;线程A是while(!false){}&#xff0c;而线程B将flag改为true&#xff0c;但是由于线程A和线程B之间工作内存互相不可见&#xff0c;线程A就会陷入死循环。 2.2指令…

idea如何解决导入的项目不是Maven工程(文件下面没有蓝色的方格)二

简介&#xff1a; Maven项目导入&#xff0c;idea不识别项目 解决方法&#xff1a; 选中pom.xml -- 右键 -- Add as Maven Project

devops(后端)

1.前言 该devpos架构为gitlabjenkinsharbork8s&#xff0c;项目是java项目&#xff0c;流程为从gitlab拉取项目代码到jenkins&#xff0c;jenkins通过maven将项目代码打成jar包&#xff0c;通过dockerfile构建jdk环境的镜像并把jar包放到镜像中启动&#xff0c;构建好的镜像通…

【Quartus FPGA】EMIF DDR3 读写带宽测试

在通信原理中&#xff0c;通信系统的有效性用带宽来衡量&#xff0c;带宽定义为每秒传输的比特数&#xff0c;单位 b/s&#xff0c;或 bps。在 DDR3 接口的产品设计中&#xff0c;DDR3 读/写带宽是设计者必须考虑的指标。本文主要介绍了 Quartus FPGA 平台 EMIF 参数配置&#…

DB-GPT:强强联合Langchain-Vicuna的应用实战开源项目,彻底改变与数据库的交互方式

今天看到 蚂蚁科技 Magic 开源的DB-GPT项目&#xff0c;觉得创意很好&#xff0c;集成了当前LLM的主流技术&#xff0c;主要如下 Langchain&#xff1a; 构建在LLM之上的应用开发框架HuggingFace: 模型标准&#xff0c;提供大模型管理功能Vicuna: 一个令GPT-4惊艳的开源聊天机…

[NLP]使用Alpaca-Lora基于llama模型进行微调教程

Stanford Alpaca 是在 LLaMA 整个模型上微调&#xff0c;即对预训练模型中的所有参数都进行微调&#xff08;full fine-tuning&#xff09;。但该方法对于硬件成本要求仍然偏高且训练低效。 [NLP]理解大型语言模型高效微调(PEFT) 因此&#xff0c; Alpaca-Lora 则是利用 Lora…