应先用hasNextInt()校验再调用nextInt(),显式跳过非法输入并消费换行符;业务校验用IllegalArgumentException等运行时异常;Scanner包装System.in时禁止关闭;高频校验预编译Pattern,复杂规则依国标实现。

Java中用Scanner校验用户输入时如何避免InputMismatchException
直接用 Scanner.nextInt() 读取整数却没做前置检查,是触发 InputMismatchException 的最常见原因。这个异常不是业务逻辑错误,而是类型不匹配的运行时中断,一旦抛出,后续输入流位置会卡住,导致后续 nextLine() 返回空字符串等诡异行为。
正确做法是始终先用 hasNextInt()(或对应类型判断方法)探路,再调用 nextInt():
Scanner sc = new Scanner(System.in);
while (!sc.hasNextInt()) {
System.out.print("请输入一个有效整数:");
sc.next(); // 跳过非法token,否则循环卡死
}
int age = sc.nextInt();
sc.nextLine(); // 消费掉换行符,避免干扰后续nextLine()
- 不要依赖 try-catch 包裹
nextInt()来“兜底”——它无法自动清理输入缓冲区 -
sc.next()必须显式调用,否则非法输入一直留在缓冲区,造成死循环 - 读完数字后若需读字符串,务必补一句
sc.nextLine(),否则换行符残留会导致下一次nextLine()立即返回空
自定义校验逻辑该不该抛出RuntimeException
业务校验(如手机号格式、密码强度、邮箱合法性)不应抛出 InputMismatchException 或其他 JDK 输入类异常,而应统一使用 IllegalArgumentException 或自定义运行时异常(如 ValidationException)。这类异常不强制上层处理,符合“非法输入是编程错误而非意外事件”的语义。
示例:
立即学习“Java免费学习笔记(深入)”;
public static void validateEmail(String email) {
if (email == null || !email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")) {
throw new IllegalArgumentException("邮箱格式不合法:" + email);
}
}
- 避免抛出
Exception或Throwable这种宽泛类型,掩盖问题本质 - 校验失败时尽量附带原始值(如上面的
email),方便调试定位 - 不要在控制台打印错误信息后“吞掉”异常——这会让调用方误以为校验成功
结合try-with-resources与校验时的资源关闭陷阱
如果校验逻辑封装在工具类中,且内部打开了文件、数据库连接等资源,必须确保无论校验通过与否,资源都能释放。但 Scanner 本身通常包装 System.in,不建议用 try-with-resources 自动关闭——关了就再也读不到用户输入了。
-
Scanner包装System.in时,绝不能 在方法末尾调用close(),否则后续 Scanner 实例将抛出IllegalStateException - 真正需要自动管理的是校验过程中打开的外部资源(如读取配置文件验证规则),这些才适合放在 try-with-resources 块里
- 若校验方法签名含
throws IOException,说明它可能打开外部资源——调用方需决定是捕获还是继续上抛
正则校验性能差?优先用Apache Commons Validator还是手写逻辑
简单格式(如手机号、纯数字、邮箱前缀)用 String.matches() 完全够用;但高频校验(如每秒上千次登录请求)或复杂规则(如身份证号校验码、银行卡Luhn算法),正则编译开销和回溯风险会显现。
- 对固定模式(如11位手机号),预编译
Pattern比反复调用matches()快 3–5 倍 -
commons-validator提供成熟稳定的EmailValidator、UrlValidator,但引入新依赖要权衡——若只用其中1个功能,手写更轻量 - 身份证、银行卡等强规则校验,务必查国标文档实现,别信网上随手搜的正则——校验码计算逻辑错一位,整个功能就失效










