Java do-while 循环输入验证异常行为解析与Scanner最佳实践

Java do-while 循环输入验证异常行为解析与Scanner最佳实践

本文深入探讨了在java中使用`system.in.read()`进行`do-while`循环输入验证时,因输入缓冲区中的回车换行符导致循环意外多次执行的问题。通过分析`char`类型比较的局限性和`system.in.read()`的底层机制,文章阐明了问题根源。最终,提供了基于`java.util.scanner`类的解决方案,演示了如何使用`nextint()`方法优雅地处理整数输入,并纠正了验证循环的`while`条件,确保程序能够正确且健壮地进行用户输入验证。

System.in.read() 在 do-while 循环中引发的意外行为

在Java中,当使用do-while循环结合System.in.read()进行用户菜单选择等输入验证时,可能会遇到一个令人困惑的现象:即使输入了看似不满足循环条件的字符,循环却会意外地多执行几次。考虑以下代码示例:

public class Menu {    public static void main(String[] args)    throws java.io.IOException {        char choice;        do {            System.out.println("Help on:");            System.out.println(" 1. if");            System.out.println(" 2. while");            System.out.println(" 3. do-while");            System.out.println(" 4. for");            System.out.println(" 5. switch");            choice = (char) System.in.read();        } while(choice  '5');        // 假设这里会处理有效输入        System.out.println("You chose: " + choice);    }}

当用户输入一个无效字符,例如6,然后按下回车键,程序输出会是这样的:

Help on: 1. if 2. while 3. do-while 4. for 5. switch6Help on: 1. if 2. while 3. do-while 4. for 5. switchHelp on: 1. if 2. while 3. do-while 4. for 5. switchHelp on: 1. if 2. while 3. do-while 4. for 5. switch

可以看到,菜单内容被打印了四次(第一次是用户输入前,之后又打印了三次),这表明循环在用户输入6后又额外执行了三次,然后才停下来等待新的输入(如果它最终会等待的话)。这种行为并非预期,因为输入6应该立即导致循环条件choice > ‘5’为真,从而继续循环,但在一次有效输入后,它不应再重复显示菜单。

问题根源:输入缓冲区与字符读取

这种异常行为的根本原因在于System.in.read()的工作方式以及操作系统处理用户输入(特别是回车键)的机制。

立即学习“Java免费学习笔记(深入)”;

System.in.read()的特性: System.in.read()方法每次只从标准输入流中读取一个字节(转换为char类型)。回车键的影响: 当用户在控制台输入一个字符(例如6)并按下回车键时,实际上发送给程序的不止是字符6本身。在大多数操作系统中,回车键会产生一个或两个特殊字符:在Windows系统上,通常是回车符(r,ASCII码13)和换行符(n,ASCII码10)。在Unix/Linux/macOS系统上,通常是单个换行符(n,ASCII码10)。这些字符都会被放入输入缓冲区。循环的多次执行:第一次迭代: choice = (char) System.in.read(); 读取了用户输入的数字字符,例如’6’。此时,while(choice ‘5’)中的choice > ‘5’条件为真,循环继续。第二次迭代: 此时输入缓冲区中可能还剩下r。choice = (char) System.in.read(); 读取了r(ASCII 13)。由于’r’的ASCII值13小于’1’的ASCII值49,所以choice < '1'条件为真,循环再次继续。第三次迭代: 此时输入缓冲区中可能还剩下n。choice = (char) System.in.read(); 读取了n(ASCII 10)。同样,’n’的ASCII值10小于’1’的ASCII值49,所以choice < '1'条件为真,循环第三次继续。第四次迭代: 此时输入缓冲区已空。System.in.read()会阻塞,等待用户输入。

这就是为什么当用户输入6并按回车后,菜单会额外打印两次(Windows)或一次(Unix/Linux),加上第一次输入后的打印,总共出现三次额外菜单。

解决方案:使用 java.util.Scanner 进行健壮的输入处理

为了避免上述问题,推荐使用java.util.Scanner类来处理用户输入。Scanner提供了更高级的方法来解析不同类型的数据,并且能够更好地处理行尾符。

行者AI 行者AI

行者AI绘图创作,唤醒新的灵感,创造更多可能

行者AI 100 查看详情 行者AI

当需要读取整数输入时,Scanner.nextInt()方法会自动跳过并消耗掉行尾符,从而避免它们残留在输入缓冲区中影响后续读取。

以下是使用Scanner改进后的代码示例:

import java.util.Scanner; // 导入Scanner类public class MenuImproved {    public static void main(String[] args) {        Scanner scan = new Scanner(System.in); // 创建Scanner对象        int choice; // 声明为int类型,因为我们期望读取数字        do {            System.out.println("Help on:");            System.out.println(" 1. if");            System.out.println(" 2. while");            System.out.println(" 3. do-while");            System.out.println(" 4. for");            System.out.println(" 5. switch");            System.out.print("Enter your choice (1-5): "); // 提示用户输入            // 检查输入是否为整数,避免InputMismatchException            while (!scan.hasNextInt()) {                System.out.println("Invalid input. Please enter a number between 1 and 5.");                scan.next(); // 消耗掉非整数输入,避免无限循环                System.out.print("Enter your choice (1-5): ");            }            choice = scan.nextInt(); // 读取整数输入            // 确保消耗掉当前行的剩余部分,特别是当nextInt()后面跟着nextLine()时            // 对于本例,nextInt()会跳过行分隔符,所以通常不需要额外调用scan.nextLine()            // 但作为良好实践,如果之后有nextLine()操作,需要考虑        } while (choice  5); // 循环条件:当选择无效时继续循环        System.out.println("You chose: " + choice);        scan.close(); // 关闭Scanner,释放资源    }}

代码解释:

import java.util.Scanner;: 导入Scanner类。Scanner scan = new Scanner(System.in);: 创建一个Scanner对象,它将从标准输入流System.in读取数据。int choice;: 将choice变量声明为int类型,因为它现在将直接读取整数。scan.nextInt();: 这个方法会读取并返回输入流中的下一个整数。它会自动跳过任何空白字符,包括用户按下回车键产生的换行符,因此不会有残余字符影响后续的循环迭代。while (choice 5);: 这是用于输入验证的正确do-while循环条件。它表示“当choice小于1或者choice大于5时,继续循环”。只有当choice在1到5的有效范围内时,循环才会终止。while (!scan.hasNextInt()) { … }: 这是一个健壮性增强。它检查下一个标记是否为整数。如果不是,它会打印错误消息,并使用scan.next()消耗掉无效的输入(例如用户输入了“abc”),防止nextInt()抛出InputMismatchException并导致程序崩溃或无限循环。scan.close();: 在程序结束时关闭Scanner,释放相关系统资源。

通过使用Scanner并正确处理输入类型和循环条件,我们可以确保程序在用户输入无效值时,能够清晰地提示并重新等待输入,而不会出现意外的循环次数。

总结与最佳实践

避免直接使用 System.in.read() 进行复杂输入解析: System.in.read()适用于读取单个字符或字节流,但不适合需要解析整数、浮点数或字符串的交互式用户输入。它需要手动处理输入缓冲区中的换行符,容易出错。优先使用 java.util.Scanner 处理用户输入: Scanner类提供了强大的文本扫描功能,能够方便地读取各种数据类型(nextInt(), nextDouble(), nextLine()等),并自动处理行分隔符,大大简化了输入处理逻辑。注意 Scanner 方法的特性: nextInt()、nextDouble()等方法只读取数字部分,不会消耗行尾的换行符。如果紧接着调用nextLine(),可能会读取到一个空的字符串。在本教程的场景中,由于nextInt()后没有立即调用nextLine(),所以不是问题。但如果混合使用,需额外调用scan.nextLine()来消耗掉剩余的换行符。健壮性考虑: 在使用Scanner读取特定类型数据时,始终考虑用户可能输入不符合类型的数据。使用hasNextInt()、hasNextDouble()等方法进行预判,可以避免InputMismatchException,提高程序的健壮性。正确构建循环条件: 对于输入验证循环,确保while条件逻辑清晰,即“当输入无效时继续循环”。

遵循这些最佳实践,可以编写出更加健壮、用户体验更好的Java交互式程序。

以上就是Java do-while 循环输入验证异常行为解析与Scanner最佳实践的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/290063.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月4日 20:13:47
下一篇 2025年11月4日 20:14:12

相关推荐

  • .net是干嘛的和java的区别

    “.net”致力于敏捷、快速开发和跨平台,可以用于开发C/S结构的软件或者B/S结构的网站。区别:java是开源的跨平台的语言,主要应用在大中型企业网站开发;“.net”是跨语言的平台,主要应用在中小型公司网站开发。 本教程操作环境:windows7系统、java8&&.NET Fr…

    2025年12月17日
    000
  • c语言中switch的用法是什么?

    c语言中switch的用法是:1、switch后面括弧内的【表达式】,ANSI标准允许它为任何类型;2、当表达式的值与某一个case后面的常量表达式的值相等时,就执行此case后面的语句,否则,就执行default后面的语句。 c语言中switch的用法是: 功能:switch语句是多分支选择语句.…

    2025年12月17日
    000
  • 在switch语句中,case后的标号只能是什么?

    在switch语句中,case后的标号只能是常量表达式。在一个特定的switch语句中,每个case常量都必须具有独一无二的值,不可以重复。但是switch语句可以包含多个case标签。 switch语句用于基于不同条件执行不同动作。 语法格式: switch (变量表达式){case 常量1: 语…

    2025年12月17日
    000
  • switch语句中case后面的值必须是什么?

    switch语句中case后面的值必须是常量。因为switch语句中,会根据case标签后面的常量值,生成跳转表,只经过少数次数的比较,就可以跳到对应标签下面;如果允许变量,switch对于一个数据也只能从头到尾地进行比较,这就失去了意义。 switch语句中case后面的值必须是常量。 因为C语言…

    2025年12月17日
    000
  • 对比分析C#与Java的区别

    相同点: 都是面向对象编程的语言,都能够实现面向对象的(封装,继承,多态)思想 不同点: 1.c#中的命名空间是namespace类似于Java中的package(包),在Java中导入包用import而c#中用using。 立即学习“Java免费学习笔记(深入)”; 2.c#和Java都是从mai…

    2025年12月17日
    000
  • unsigned int几个字节

    unsigned int几个字节 C语言中unsigned int代表无符号整型。并没有确定规定它占用几个字节,具体是由编译器来决定的,例如Visual C++规定unsigned int占4字节,Turbo 2.0中,规定unsigned int占2字节,也就是说int可以占用2字节也可以占用4字…

    2025年12月17日
    000
  • c语言switch case语句怎么用

    c语言switch case语句怎么用 C语言提供了一种用于多分支选择的switch语句, 其一般形式为: switch(表达式){ case 常量表达式1: 语句1; case 常量表达式2: 语句2; … case 常量表达式n: 语句n; default: 语句n+1;} 其语义是:计算表达式…

    2025年12月17日
    000
  • c语言和java语法有区别吗?

    c语言和java语法有区别吗? c语言和java在语法上有区别,区别是: 1、C语言有指针,java没有指针; C语言的语法比较简单,但是它的亮点指针很容易出错,想要好好的运用指针是件很难的事情,用好了,对程序有很好的帮助,反之,就会让程序崩溃掉,而Java 没有指针的概念,Java更实用于开发东西…

    2025年12月17日
    000
  • Asp.net Core应用程序在Linux上部署的图文详解

    快两个月没接触.net,倒是天天在用linux,所以想尝试一下在linux运行喜欢的.net 应用。 安装CentOS 安装.Net core for Linux 创建Asp.net Core应用程序 安装Nginx 配置Nginx代理 1,安装CentOS系统 这个网上教程太多滤过。   2,安装…

    2025年12月17日 好文分享
    000
  • C#编写Windows服务程序的图文详解

    本文介绍了如何用c#创建、安装、启动、监控、卸载简单的windows service 的内容步骤和注意事项,需要的朋友可以参考下 一、创建一个Windows Service 1)创建Windows Service项目   2)对Service重命名 将Service1重命名为你服务名称,这里我们命名…

    2025年12月17日 好文分享
    000
  • 比较C#和JAVA中面向对象语法的区别

    面向对象是一种开发思想,最应该记住的一句话是万物皆对象。为了让程序更好的被理解和编写,把现实生活中描述事物的方式和思路融合进入,就成了面向对象的思想。把生活中的事物融合进程序中那么就需要描述,描述分为特征和行为两方面,而不同类别的对象特征和行为具有巨大的差异,为了更好的制定描述每一类事物的方式,那么…

    好文分享 2025年12月17日
    000
  • C++多线程编程On Linux

    POSIX多线程模型pthread.h函数: pthread_attr_t attr; //线程属性结构体,创建线程时加入的参数pthread_attr_init( &attr ); //初始化 pthread_attr_setdetachstate( &attr, PTHREAD_…

    2025年12月17日
    000
  • Linux 环境多线程编程基础设施

    本文介绍多线程环境下并行编程的基础设施。主要包括: volatile __thread Memory Barrier __sync_synchronize volatile 编译器有时候为了优化性能,会将一些变量的值缓存到寄存器中,因此如果编译器发现该变量的值没有改变的话,将从寄存器里读出该值,这样…

    好文分享 2025年12月17日
    000
  • XML中如何压缩文件_XML压缩XML文件的方法与技巧

    答案:通过ZIP/GZIP压缩、优化XML结构、使用EXI等专用格式可显著减小XML文件体积。具体包括利用通用算法压缩、精简标签与属性、采用二进制交换格式,并结合场景选择兼顾压缩率与兼容性的方案。 处理XML文件时,文件体积过大常常影响传输效率和存储成本。通过合理的压缩方法,可以显著减小XML文件的…

    2025年12月17日
    000
  • 什么是XML Infoset

    XML Infoset是W3C定义的抽象数据模型,用于标准化XML文档解析后的信息表示。它定义了11种信息项(如文档、元素、属性等),屏蔽物理格式差异,确保不同解析器对XML内容的理解一致。DOM和SAX等解析技术均基于Infoset构建:DOM将其具象化为树结构,SAX则通过事件流式暴露信息项。I…

    2025年12月17日
    000
  • XML中如何判断节点是否为叶子节点_XML判断节点是否为叶子节点的方法

    判断XML节点是否为叶子节点的关键是检查其是否有子元素。1. 使用DOM解析器时,遍历节点的子节点,若无Element类型子节点则为叶子节点;2. 使用XPath可通过表达式not(./*)筛选出没有子元素的节点;3. Python中利用ElementTree的len(node) == 0判断节点无…

    2025年12月17日
    000
  • RSS订阅中的作者信息格式

    RSS和Atom中作者信息通过或标签标识,包含姓名、邮箱及网站链接,支持多作者;正确设置有助于提升内容可信度、便于追踪与SEO。 RSS订阅中的作者信息格式,主要用于标识文章的作者,让读者知道是谁写的,方便追踪特定作者的内容。格式通常包含作者姓名、邮箱,有时还会包含作者的网站链接。 作者信息的常见格…

    2025年12月17日
    000
  • XML中如何获取根节点属性_XML获取根节点属性的操作步骤

    XML根节点有且仅有一个,可包含属性;2. Python用ET.parse解析,root.get(“属性名”)获取属性值;3. JavaScript用DOMParser解析,xmlDoc.documentElement获取根节点,getAttribute读取属性;4. Jav…

    2025年12月17日
    000
  • XML中如何提取指定节点_XML提取指定节点的详细步骤

    首先理解XML结构,明确目标节点路径;接着使用XPath表达式如//title或/books/book[@id=’1′]定位节点;然后通过Python的lxml库解析XML并执行XPath提取文本或属性;最后处理多层级节点与属性,结合条件筛选和遍历方法精准获取数据。 在处理X…

    2025年12月17日
    000
  • XML中如何去除空节点_XML去除空节点的实用方法

    答案:可通过XSLT、Python脚本或命令行工具去除XML空节点。使用XSLT模板递归复制非空节点;Python的lxml库遍历并删除无文本、无子节点、无属性的元素;XMLStarlet命令行工具执行XPath表达式快速清理空标签,处理前需明确定义空节点并备份原文件。            &lt…

    2025年12月17日
    000

发表回复

登录后才能评论
关注微信