JavaScanner用法:对象创建与构造器详解
时间:2025-07-24 23:21:34 348浏览 收藏
本篇文章主要是结合我之前面试的各种经历和实战开发中遇到的问题解决经验整理的,希望这篇《Java Scanner正确用法:对象初始化与构造器详解》对你有很大帮助!欢迎收藏,分享给更多的需要的朋友学习~
问题剖析:为什么会出现重复输入?
在Java中,当我们将Scanner对象的创建和输入操作(如myObj.nextInt())直接放置在类的成员变量声明处或实例初始化块(即 {} 包裹的代码块)中时,每次创建该类的新实例时,这些代码都会被执行。这与许多初学者预期的行为(只执行一次)大相径庭。
考虑以下示例代码片段:
public class MyClass { // 成员变量声明时进行初始化,并执行I/O Scanner myObj = new Scanner(System.in); {System.out.print("Enter a value: ");} // 实例初始化块 int value = myObj.nextInt(); // 每次创建对象都会执行此行 public static void main(String[] args) { MyClass a = new MyClass(); // 第一次执行I/O MyClass b = new MyClass(); // 第二次执行I/O MyClass c = new MyClass(); // 第三次执行I/O // ... 后续操作 } }
当main方法中执行new MyClass()时,Java虚拟机(JVM)会执行以下步骤来初始化一个新对象:
- 为对象分配内存。
- 执行所有成员变量的初始化(包括Scanner myObj = new Scanner(System.in);)。
- 执行所有实例初始化块中的代码(包括{System.out.print("Enter a value: ");})。
- 执行构造器中的代码(如果存在且被调用)。
因此,如果main方法中创建了三个MyClass实例(如new MyClass();三次),那么上述的Scanner创建、提示信息打印和nextInt()操作将分别执行三次,导致用户需要输入三次值,这显然不是我们期望的单次输入行为。这种做法不仅会导致重复输入,还可能造成Scanner资源泄露(因为创建了多个Scanner但没有关闭)。
解决方案核心:构造器的力量
解决上述问题的关键在于理解并正确使用Java的构造器(Constructor)。构造器是一种特殊的方法,用于在创建对象时初始化对象的实例变量。它是设置对象初始状态的理想场所。
将输入逻辑放置在构造器中,可以确保:
- 受控的初始化: 只有当明确创建对象并调用其构造器时,输入逻辑才会被执行。
- 参数传递: 构造器可以接受参数,例如将一个已存在的Scanner实例传递给它,从而避免在每个对象内部都创建新的Scanner。
- 职责分离: 将对象的初始化逻辑(包括获取必要的初始数据)封装在构造器中,使得类的结构更清晰。
对于Scanner对象,最佳实践是:
- 在main方法中或程序的入口点只创建一个Scanner实例,因为它通常与System.in(标准输入流)关联,而System.in是全局唯一的。
- 将这个Scanner实例作为参数传递给需要它进行输入操作的构造器或方法。
- 在程序结束时,务必关闭Scanner资源,以释放系统资源。
优化后的代码实现
下面是一个根据上述原则优化后的Java代码示例,它演示了如何正确使用Scanner和构造器来避免重复输入,并进行单位转换计算:
import java.util.Scanner; public class Test { // 1. 声明成员变量,但不在此处进行I/O操作或复杂初始化 int caratValue; double gramsPerCarat; double caratsInGrams; double gramsInMilligrams; double milligramsInPounds; // 2. 定义构造器,用于初始化对象并执行输入、计算和(可选)输出 // 构造器接受一个Scanner对象作为参数,避免重复创建Scanner public Test(Scanner scanner) { // 提示用户输入,并通过传入的Scanner获取值 System.out.print("Enter Carat Value: "); this.caratValue = scanner.nextInt(); // 将输入的值赋给成员变量 // 初始化常量和执行计算 this.gramsPerCarat = 0.20; this.caratsInGrams = this.caratValue * this.gramsPerCarat; this.gramsInMilligrams = this.caratsInGrams * 1000; this.milligramsInPounds = this.gramsInMilligrams * 0.00220462 / 1000; // 注意:0.00220462 lbs/gram, 1000 mg/gram // 在构造器中直接输出结果,或提供getter方法在main方法中输出 System.out.println("Carats in Grams: " + this.caratsInGrams); System.out.println("Grams in Milligrams: " + this.gramsInMilligrams); System.out.println("Milligrams in Pounds: " + this.milligramsInPounds); } // 如果需要从外部获取计算结果,可以提供getter方法 public double getCaratsInGrams() { return caratsInGrams; } public double getGramsInMilligrams() { return gramsInMilligrams; } public double getMilligramsInPounds() { return milligramsInPounds; } // 3. 主方法:程序的入口点 public static void main(String[] args) { // 在main方法中只创建一次Scanner对象 Scanner mainScanner = new Scanner(System.in); // 创建Test类的实例,并将唯一的Scanner对象传递给其构造器 // 此时,构造器中的输入和计算逻辑只会被执行一次 Test conversionData = new Test(mainScanner); // 如果构造器中没有直接输出,可以在这里通过getter方法获取并输出 // System.out.println("Carats in Grams (from getter): " + conversionData.getCaratsInGrams()); // System.out.println("Grams in Milligrams (from getter): " + conversionData.getGramsInMilligrams()); // System.out.println("Milligrams in Pounds (from getter): " + conversionData.getMilligramsInPounds()); // 使用完毕后,关闭Scanner以释放系统资源 mainScanner.close(); } }
关键实践与注意事项
Scanner的生命周期管理:
- 对于标准输入流System.in,通常在整个应用程序生命周期中只创建一个Scanner实例。
- 在使用完毕后,务必调用scanner.close()方法关闭Scanner。这会关闭底层的输入流,防止资源泄露。在main方法中创建并关闭Scanner是一个常见的良好实践。
职责分离:
- 将数据输入(I/O)与业务逻辑(计算)和数据输出(打印)进行适当分离。在上述示例中,构造器负责获取初始数据和进行计算,并直接输出了结果。如果业务逻辑更复杂,可以考虑将计算封装在单独的方法中,并通过getter方法提供结果。
- 避免在类字段初始化或实例初始化块中执行有副作用的操作(如I/O操作、数据库连接、网络请求等),这些操作应该放在构造器或特定的方法中,以便更好地控制其执行时机。
static关键字的考量:
- 原问题更新后的代码使用了static Scanner myObj,这确实能确保Scanner只被创建一次,并且nextInt()也只执行一次。然而,这使得myObj成为一个类级别的共享资源,如果将来有多个线程或多个对象需要独立地从System.in读取,可能会引入并发问题或逻辑混乱。
- 更推荐的做法是,将Scanner作为局部变量在main方法中创建,并通过参数传递给需要它的对象,这样可以更好地控制其作用域和生命周期。
代码可读性和维护性:
- 使用构造器初始化对象,使代码意图更清晰:一个新对象在创建时需要哪些初始数据。
- 避免“神奇的”重复行为,使程序行为更符合直觉。
通过遵循这些原则,您可以编写出更健壮、更易于理解和维护的Java应用程序。
今天关于《JavaScanner用法:对象创建与构造器详解》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
281 收藏
-
318 收藏
-
157 收藏
-
344 收藏
-
418 收藏
-
247 收藏
-
330 收藏
-
232 收藏
-
396 收藏
-
227 收藏
-
241 收藏
-
312 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 511次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 498次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习