第3章选择结构 任务分时问候 3.任务描述 1 程序运行时通常是按从上到下的顺序执行的,但是有时程序会根据不同情况,或是选择 不同的语句块来执行,或是反复运行某一个语句块,或是跳转到某一语句块执行,以上几种 运行方式在程序中被称为“程序流程控制”。Java语言中的流程控制有选择(分支)结构、循 环结构和跳转语句3种。本章的任务是通过选择(分支)结构,编写分时问候的机器人程序, 我们不妨给机器人起名为“小二”,当程序运行后,机器人“小二”会根据操作系统当前的时间 给出不同的问候语。 3.任务分析 2 按照人们的生活习惯,一般粗略地把一天分为如表3-1所示几个时间段。 表3- 1 一天中的几个时间段 时间段[0,6) [6,9) [9,12) [12,14) [14,17) [17,19) [19,22) [22,24) 含义凌晨早晨上午中午下午傍晚晚上深夜 通过表中的时间段划分,可以发现,在不同的时间运行程序,程序所处的时间段不同,所 要给出的问候也不同,也就是存在多种选择或是分支情况,因此只使用顺序结构的程序方式 是不能完成任务的。在Java语言中,可以使用if结构、if-else结构、多重if-else结构、嵌套 if-else结构或switch结构来实现选择(分支)程序。 3.相关知识 3 选择结构又称为分支结构,意为“如果…… 则……”,根据条件的成立与否,再决定执行 哪些语句的一种程序结构,选择(分支)结构分为简单if结构、if-else结构、if-elseif多重结 构等,下面对几类分支结构一一进行介绍。 3.1 简单if结构 3. 基本if选择结构是单分支结构,是对某种条件做出相应的处理,表现含义为“如果…… 那么……” 。if结构的语法格式如下: ·57· if(判断条件){ [代码块] } 图3-1 简单if 结构 简单if结构如图3-1所示。 判断条件必须是一个布尔表达式,一旦条件中的值为true就执 行代码块。 注意 代码块可以是一条语句,也可以是多条语句。 代码块是可选参数。当判断条件为true时执行代码块。当代 码块省略时,可以保留花括号,也可以省略花括号,并在if语句的末 尾添加分号“;”,如果该语句块只有一条语句,花括号也可以省略不写,但是在编写程序过程 中,为了增强程序的可读性和可维护性最好不要省略。下面的代码都是正确的。 (1)if(时间在6 点前); (2)if(时间在6 点前) 问候早晨好; (3)if(时间在6 点前){ 问候早晨好; } 注意 代码(3)是较好的程序,读者在学习和编写程序时要注意一个原则:程序是人编写来操 作计算机的,要让程序正常运行,就要遵循程序的语法,但是程序不可能只运行一次,在使用 过程中要进行维护,维护人可能是自己,也可能是别人,因此所编写的程序也要让人能快速 清晰看懂,要遵循和养成良好的编程习惯。 【例3-1】 比较两个整数的大小,并输出比较结果。 【程序实现】 public class CompareTwoIntegers { public static void main(String[] args) { int num1=10,num2=11; //定义两个整数并赋值 if(num1=10) if(age>10) System.out.println("大于10 岁"); else System.out.println("等于10 岁"); else System.out.println("小于10 岁"); } } 大家在阅读这段代码时,会有些吃力,因为不能快速地找到if程序块的开头和结尾。 另外在编写程序的过程中,程序可能会需要改动,一行的代码会变成多行,变成多行后,上面 的程序就会出错,而带有括号的程序能让人清楚地了解每个程序段的开头和结尾,一行修改 为多行后也不易出错,更容易阅读,也更容易修改和维护。因此,在实际应用中,if-else各类 结构代码中的花括号都不要省略,以免造成视觉的错误与程序的混乱。 【例3-4】 假设某航空公司规定,乘客可以免费托运质量不超过40kg的行李。当行李 质量超过40千克时,对头等舱的国内乘客超重部分每千克收费3元,对其他舱的国内乘客 超重部分每千克收费5元,对外国乘客超重部分每千克的收费比国内乘客多一倍,对残疾乘 客超重部分每千元收费比正常乘客少一半。编写一个程序,能根据乘客行李质量、乘客的国 别和是否为残疾情况计算出托运行李要支付的费用。 【问题分析】 通过上面的描述,可以发现这是一个非常复杂的选择分支结构程序,分析后发现,影响 托运费用的因素有行李质量、乘客国别、舱位级别、是否为残疾人几个要素,根据各个要素的 关系,得到程序的流程图如图3-8所示。 【程序实现】 import java.util.Scanner; public class ShippingRates { public static void main(String[] args) { double weight, money; //双精度的质量、运费 String strShow="该乘客为: "; //最终要显示的信息 Scanner input=new Scanner(System.in); //输入设置 System.out.println("请输入行李质量(单位: kg): "); weight=input.nextDouble(); //从键盘输入行李质量的值 if(weight<=40){ //小于40kg 时,没有托运费 money=0; }else{ //先定义和取得关键信息 System.out.println("是国内乘客吗(1: 是,其他值: 不是)?"); int iInner=input.nextInt(); //从键盘输入是否为国内乘客 ·62· !!!""#$%&'()*+,! ·63 · System.out.println("是头等舱吗(1: 是,其他值: 不是)?"); int iHead=input.nextInt(); //从键盘输入是否为头等舱 System.out.println("是残疾人吗(1: 是,其他值: 不是)?"); int iDefomity=input.nextInt(); //从键盘输入是否为残疾人 //根据几个关键信息计算运费 if(iInner==1){ //国内乘客 strShow +="来自国内的一位乘坐"; if(iHead==1){ //头等舱 strShow +="头等舱"; if(iDefomity==1){ //残疾乘客 strShow +="的残疾乘客"; money=(weight-40)*1.5; }else{ //正常乘客 strShow+="的正常乘客"; money=(weight-40)*3; } }else{ //其他舱 strShow+="其他舱"; if(iDefomity==1){ //残疾乘客 strShow +="的残疾乘客"; money=(weight-40)*2.5; }else{ //正常乘客 strShow+="的正常乘客"; money=(weight-40)*5; } } }else{ //外国乘客 strShow +="来自国外的一位乘坐"; if(iHead==1){ //头等舱 strShow +="头等舱"; if(iDefomity==1){ //残疾乘客 strShow +="的残疾乘客"; money=(weight-40)*3; }else{ //正常乘客 strShow+="的正常乘客"; money=(weight-40)*6; } }else{ //其他舱 strShow +="其他舱"; if(iDefomity==1){ //残疾乘客 strShow +="的残疾乘客"; money=(weight-40)*5; }else{ //正常乘客 strShow+="的正常乘客"; money=(weight-40)*10; } } } } input.close(); //关闭输入设备 System.out.println(strShow); ·64· System.out.println("行李重: "+weight+",所需运费为: "+money+"元。"); } } 【运行结果】 程序的一个运行结果如下: 请输入行李质量(单位: kg): 41 是国内乘客吗(1: 是,其他值: 不是)? 1 是头等舱吗(1: 是,其他值: 不是)? 1 是残疾人吗(1: 是,其他值: 不是)? 0 该乘客为: 来自国内的一位乘坐头等舱的正常乘客 行李重: 41.0,所需运费为: 3.0 元。 3.3.5 switchcase结构 switchcase结构是多分支的开关语句结构。根据表达式的值来执行输出的程序结构。 这种结构一般用于多条件多值的分支程序。它的一般形式如下: switch(表达式){ case 常量表达式1:代码块1; [break;] case 常量表达式2:代码块2; [break;] … case 常量表达式n:代码块n; [break;] [default:代码块n+1;[break;]] } switch语句中表达式的值必须是整型或字符型,即int、short、byte和char型。 case为分支开关,case中的常量表达式的值也必须是整型或字符型的,与表达式的数据 类型相兼容的值。 代码块1是一条或多条Java语句,当常量表达式1的值与表达式的值相同时,则执行 该代码块,如果不同则继续判断,直到达到表达式n。 代码块n是一条或多条Java语句,当常量表达式n的值与表达的值相同时,则执行该 代码块,如果不同则执行default处理中的代码块。 default为可选参数,如果没有这个参数,而且所有的常量值与表达式的值都不匹配时, 那么switch语句就不会执行任何操作。 break为可选参数,主要用于跳出switch分支结构,如果没有使用break参数,则程序 会继续向下执行下一个case判断和代码块。switch分支结构的程序流程图如图3-9 所示。 【例3-5】 使用Java程序随机生成一个字母,并判断这个字母是元音字母还是辅音 字母。 【问题分析】 简单地说,元音字母包括a、e、i、o、u,而y、w 在一些词中也可以读成元音,被称为半元 音,如my、cycle、paw、few等,其他的字母则为辅音字母。程序流程图如图3-10所示。 ·65· 图3-9 switch 分支结构程序流程图 图3-10 随机生成一个字母并判断是否为元音程序流程图 【程序实现】 public class VowelsAndConsonants { ·66· public static void main(String[] args) { char c=(char) (Math.random() * 26+'a'); System.out.print("生成的字母是: "+c+","); switch (c) { case 'a': case 'e': case 'i': case 'o': case 'u': System.out.println("是元音字母。"); break; case 'y': case 'w': System.out.println("半元音字母。"); break; default: System.out.println("是辅音字母。"); } } } 【运行结果】 图3-11是程序运行3次所得到的结果。 图3-11 随机生成字母并判断是否为元音的程序的3 次运行结果 3.4 任务实现 学习了几类分支结构后,下面着手编写本章开头所提出的分时问候程序吧! 【问题分析】 通过前面的任务分析,我们知道分时问候所涉及的时间段比较多,要采用选择结构来处 理分支,程序在不同的时间段运行时,所面临的选择情况也比较多,因此采用多重if-else分 支结构来实现程序。分时问候的程序流程结构如图3-12所示。 【程序实现】 import java.util.Date; public class TipByHour { public static void main(String[] args) { Date dt=new Date(); //生成日期时间对象 int hour=dt.getHours(); //取出当前的时间 if(hour<6){ System.out.println("主人,凌晨好,您起得真早啊。"); }else if(hour<9){ System.out.println("主人,早晨好,新的一天开始了。"); }else if(hour<12){ ·67· 图3-12 分时问候程序流程图 System.out.println("主人,上午好,祝您工作愉快。"); }else if(hour<14){ System.out.println("主人,中午好,要适当休息一下啊。"); }else if(hour<17){ System.out.println("主人,下午好,要打起精神来工作啊。"); }else if(hour<19){ System.out.println("主人,傍晚好,到了吃晚饭的时间了。"); }else if(hour<22){ System.out.println("主人,晚上好,该休息放松一下了。"); }else{ System.out.println("主人,到深夜了,小二在这里提醒您: 该休息了!"); } } } 【运行结果】 运行程序,该程序会按当前的操作系统时间给出对应的问候语。图3-13是在Eclipse 控制台中的3个时间段分别运行程序后的效果。 图3-13 在3 个不同的时间段运行分时问候程序的效果 ·68· 3.5 知识拓展 3.5.1 程序流程图 本章程序设计过程中用到了程序流程图,流程图是逐步解决指定问题的步骤和方法的 一种图形化的表示方法,流程图能帮助设计者直观、清晰地分析问题或是设计解决方案,是 程序开发人员的得力助手。流程图是程序分析中最基本、最重要的分析技术,它是进行流程 程序分析过程中最基本的工具。程序流程图运用工序图示符号对生产现场的整个制造过程 做详细的记录,以便对零部件、产品在整个制造过程中的生产、加工、检验、存储等环节进行 详细的研究与分析,特别适用于分析生产过程中的成本浪费,提高经济效益。表3-2为程序 流程图的符号说明。 表3-2 程序流程图的符号说明 符 号意 义符 号意 义 计算步骤/处理过程判断和分支 程序的开始或结束文档及处理说明 子计算步骤/子处理过程连接点 输入输出指令(或数据) 流程线(控制流) 3.5.2 switch表达式 从JDK12开始引入,JDK14中正式引入新的switch语法,代码模板与语法规则如下: var res=switch(obj) { //新的switch 语法可以有返回值,可以被变量接收 // case 中的匹配值可以有多个,使用逗号分隔 //使用->箭头操作符返回匹配此case 语句的结果 case[匹配值, ...] ->直接返回选项值1; case[匹配值, ...] ->{ 在子块中使用yield 返回值(不能使用return); }; case ... //根据不同的分支,可以存在多个case //注意,如果前面的case 分支已经覆盖了所有条件的话,default 分支可以不写 //否则switch 表达式要求必须涵盖所有的可能,所以需要添加default default->其他情况下的返回选项值; }; 【例3-6】 从键盘输入一个年份和月份,使用switch表达式输出该月份的天数。 【问题分析】 一年中的月份天数有3种情况:第1种是1、3、5、7、8、10、12月份有31天,第2种是4、 6、9、11月份有30天,第3种是2月份,闰年的2月份有29天,平年的2月份有28天。因 此,本例题要先看月份,如果是前两种情况,则直接输出天数信息;如果是第3种情况,还要 ·69· 检查是闰年还是平年,根据年份类型输出天数信息。 【程序实现】 在examp包中创建类文件Examp06NewSwitch,修改该文件的内容如下: import java.util.Scanner; //根据键盘输入年份与月份,输出该月的天数 public class Examp06NewSwitch { public static void main(String[] args) { //主入口方法 Scanner sc=new Scanner(System.in); System.out.print("请输入要查看的年份:"); int year=sc.nextInt(); System.out.print("请输入要查看的月份:"); int month=sc.nextInt(); //使用switch 表达式计算出天数来 int daysNum=switch (month){ case 1,3,5,7,8,10,12->31; //月份:1,3,5,7,8,10,12 case 4,6,9,11->30; //月份:4,6,9,11 default->{ if(year%400==0 || (year%4==0 && year% 100!=0)) { //闰年,返回29 天 yield 29; }else{ //平年,返回28 天 yield 28; } } }; System.out.println(year+"年"+month+"月有:"+daysNum+"天"); } } 【运行结果】 运行程序,按提示输入一个年份,再输入一个月份,其中的一个输入实例的运行结果如 图3-14所示。 图3-14 根据输入的年份与月份输出该月的天数 3.5.3 新的日期时间API 经典的日期时间类Date从JDK1.0就有了,但使用起来比较麻烦,而且不是线程安全 的。为了解决经典时间类存在的问题,与日期时间的国际标准ISO-8601进行接轨,JDK8 的java.time包中发布了一套新的日期时间API。在新的JDK中再使用旧的Date类及其方 法时,会看到一些调用的方法上加上了删除线,并显示Xxx()isdeprecated,即该方法不推 荐使用了。主要有如下几类。 1.LocalDate、LocalTime、LocalDateTime LocalDate:本地日期,值无时区属性。 LocalTime:本地时间,值无时区属性。 LocalDateTime:本地日期与时间,值无时区属性,可以看作是LocalDate与LocalTime ·70· 的组合。 这一组类的用法类似,本书只介绍LocalDateTime的简单用法。本组类不能使用new 关键字进行实例化,可以通过now()或of()方法来实例化。 LocalDateTimeldt= LocalDateTime.now(); //获取当前日期时间 常用的方法如下。 getYear():获取当前实例的年份信息(LocalDate也有这个方法)。 getMonth():获取当前实例的月份信息(LocalDate也有这个方法)。 getDayOfMonth():获取当前实例是该月的几号(LocalDate也有这个方法)。 getDayOfWeek():获取当前实例是周几(LocalDate也有这个方法)。 getDayOfYear():获取当前实例是当年中的第几天(LocalDate也有这个方法)。 getHour():获取当前实例小时信息(LocalTime也有这个方法)。 getMinute():获取当前实例的分钟信息(LocalTime也有这个方法)。 getSecond():获取当前实例的秒信息(LocalTime也有这个方法)。 getNano():获取当前实例的纳秒信息(LocalTime也有这个方法)。 2.Instant时间戳 Instant表示了时间线上一个确切的点,定义为距离初始时间的时间差(初始时间为 UNIX元年GMT1970年1月1日00:00),经测量一天有86400秒,从初始时间开始不断 向前移动。 Instant inst=Instant.now(); 获取到的日期实例是0时区当前的日期与时间信息,如果要获取当前时区的日期时间 的信息,则要根据当前系统所在的时区,设置时间的偏移量。 Instant有如下一些常用的方法。 plusSeconds():增加一些秒数。 plusMillis():增加一些毫秒数。 plusNanos():增加一些纳秒数。 minusSeconds():减去一些秒数。 minusMillis():减去一些毫秒数。 minusNanos():减去一些纳秒数。 【例3-7】 使用Instant获取日期时间的简单示例。 【问题分析】 使用Instant获取到的是0时区的日期与时间,要获取某个时区的当地时间,要添加时 间的偏移设置。 【程序实现】 import java.time.*; //例3-7 使用Instant 获取日期时间的简单示例 public class InstantTest { public static void main(String[] args) { Instant inst=Instant.now(); //调用静态方法now 构建Instant 类对象 System.out.println("默认时间: "+inst); /*默认为0 时区的时间,通过设置时偏移量,转换到当前系统所在的时区, ·71· 结果含时区信息,时区: 东1~12 区+n,+号可以省略, 西1~12 区-n,如中国是东8 区,则为+8 */ OffsetDateTimeo dt=inst.atOffset(ZoneOffset.ofHours(8)); System.out.println("设置时间偏移量后的时间: "+odt); Instant inst2=inst.plusSeconds(5); //获取一个新的Instant 值 System.out.println("增加5 秒后的时间: "+inst2); /*也可以通过atZone 设置所在的时区来获取对应时区的时间 含时区及时区名称信息,要生成新的时间变量*/ ZonedDateTime zdt2=inst2.atZone(ZoneId.of("Asia/Shanghai")); System.out.println("本地增加5 秒后的时间: "+zdt2); } } 【运行结果】 程序的运行结果如图3-15所示。 图3-15 使用Instant 获取日期时间并转换成本地时间 3.日期时间间隔 Duration:计算两个时间之间的间隔。 Period:计算两个日期之间的间隔。 【例3-8】 计算两个时间与两个日期之间的间隔。 【问题分析】 使用Duration类计算两个时间之间的间隔,时间实例可以使用LocalTime,可以使用 LocalDateTime,也可以使用Instant生成。使用Period类计算两日期之间的间隔。 【程序实现】 import java.time.*; //例3-8 计算日期时间之间的间隔 public class PeriodAndDuration { public static void main(String[] args) throws InterruptedException { LocalTime lt1=LocalTime.now(); Thread.sleep(1000); //线程暂停1000 毫秒,即1 秒 LocalTime lt2=LocalTime.now(); Duration dura1=Duration.between(lt1, lt2); System.out.println("两个LocalTime 实例间的时间间隔:"+dura1.toMillis()+"毫秒"); System.out.println("==============================="); Instant inst1=Instant.now(); Thread.sleep(1000); //线程暂停1000 毫秒,即1 秒 Instant inst2=Instant.now(); System.out.println( "两个Instant 实例间的时间间隔:"+ Duration.between (inst1, inst2).toMillis()+"毫秒"); System.out.println("==============================="); LocalDate ld1=LocalDate.now(); LocalDate ld2=ld1.plusDays(2); //实例1 增加2 天变成实例2 ·72· System.out.println("两个LocalDate 间的日期间隔:"+ Period.between(ld1, ld2).getDays()+"天"); } } 【运行结果】 程序的运行结果如图3-16所示。 图3-16 两个时间、两个日期的间隔实例 4.DateTimeFormatter日期时间格式化 使用DateTimeFormatter日期时间格式化器对日期与时间进行格式化,其中提供了一 些静态的格式,也可以自定义格式化器对日期时间进行格式化。 【例3-9】 使用日期时间格式化器对日期时间进行格式化。 【问题分析】 本例主要测试DateTimeFormatter格式化器提供的静态格式与自定义格式的使用方 法,可以通过DateTimeFormatter类的操作,查看其提供的静态格式,其中以ISO_开头的静 态常量为国际标准日期与时间格式,如ISO_DATE、ISO_DATETIME 等,可以使用ofXxx()方 法设置自定义格式,如ofPatten(Stringpatter)等。 【程序实现】 import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; //例3-9 日期时间格式化实例 public class DateTimeFormatterTest { public static void main(String[] args) { DateTimeFormatter dtf=DateTimeFormatter.ISO_LOCAL_DATE_TIME; LocalDateTimeldt=LocalDateTime.now(); System.out.println("未格式化时间:"+ldt); // ISO 国际标准格式 System.out.println("格式化后时间:"+dtf.format(ldt)); //自定义格式 DateTimeFormatter dtf2=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); System.out.println("自定义格式:"+dtf2.format(ldt)); } } 【运行结果】 该程序的运行结果如图3-17所示。 图3-17 ISO 国际标准时间格式、本地时间格式、自定义时间格式 ·73· 3.6 本章小结 本章介绍了解决选择分支问题的简单if结构、if-else结构、多重if-else结构、if-else语 句嵌套结构、switch结构,学习了程序流程图的相关知识。学习本章后,读者应该达成如下 目标。 (1)会编写简单if结构程序。 (2)会编写if-else双分支程序。 (3)会编写if-elseif-else多分支结构程序。 (4)会编写switch分支结构程序。 (5)能说出JDK14中switch语法的新变化。 (6)能应用选择结构解决实际问题。 3.7 强化练习 3.7.1 判断题 1.简单if结构是顺序程序结构。( ) 2.if-else结构程序有2个分支。( ) 3.多重if-else结构中的花括号不能写。( ) 4.switchcase结构中的case块中必须加括号。( ) 5.switchcase结构中的default为必选参数,必须得写上,否则程序会出错。( ) 6.新的switch语法(switch表达式)的子块中要使用yield返回结果。( ) 7.LocalDateTime可以使用new关键字生成实例。( ) 3.7.2 选择题 1.阅读下面程序,分析该程序的功能是( )。 public class T1{ public static void main(String[] args) { int a,b,c; a=78; b=67; c=12; if(a>b) { if(b>c) System.out.println("The min number:"+c); else System.out.println("The min number:"+b); }else { if(c85000 部分为45% 。根据这个税率计算方法,编写一个程序,实现输入一个人的 工资,积累计算所有部分的所得税,并输出应该缴纳的个人所得税。 ·76·