java.util.NoSuchElementException: el escáner lee la entrada del usuario
Soy nuevo en el uso de Java, pero tengo experiencia previa con C#. El problema que tengo surge al leer la entrada del usuario desde la consola.
Me encuentro con el error "java.util.NoSuchElementException" con esta parte del código:
payment = sc.next(); // PromptCustomerPayment function
Tengo dos funciones que obtienen la entrada del usuario:
- AvisoClienteCantidad
- Pago rápido al cliente
Si no llamo a PromptCustomerQty, no aparece este error, lo que me lleva a creer que estoy haciendo algo mal con el escáner. A continuación se muestra mi ejemplo de código completo. Agradezco cualquier ayuda.
public static void main (String[] args) {
// Create a customer
// Future proofing the possabiltiies of multiple customers
Customer customer = new Customer("Will");
// Create object for each Product
// (Name,Code,Description,Price)
// Initalize Qty at 0
Product Computer = new Product("Computer","PC1003","Basic Computer",399.99);
Product Monitor = new Product("Monitor","MN1003","LCD Monitor",99.99);
Product Printer = new Product("Printer","PR1003x","Inkjet Printer",54.23);
// Define internal variables
// ## DONT CHANGE
ArrayList<Product> ProductList = new ArrayList<Product>(); // List to store Products
String formatString = "%-15s %-10s %-20s %-10s %-10s %n"; // Default format for output
// Add objects to list
ProductList.add(Computer);
ProductList.add(Monitor);
ProductList.add(Printer);
// Ask users for quantities
PromptCustomerQty(customer, ProductList);
// Ask user for payment method
PromptCustomerPayment(customer);
// Create the header
PrintHeader(customer, formatString);
// Create Body
PrintBody(ProductList, formatString);
}
public static void PromptCustomerQty(Customer customer, ArrayList<Product> ProductList) {
// Initiate a Scanner
Scanner scan = new Scanner(System.in);
// **** VARIABLES ****
int qty = 0;
// Greet Customer
System.out.println("Hello " + customer.getName());
// Loop through each item and ask for qty desired
for (Product p : ProductList) {
do {
// Ask user for qty
System.out.println("How many would you like for product: " + p.name);
System.out.print("> ");
// Get input and set qty for the object
qty = scan.nextInt();
}
while (qty < 0); // Validation
p.setQty(qty); // Set qty for object
qty = 0; // Reset count
}
// Cleanup
scan.close();
}
public static void PromptCustomerPayment (Customer customer) {
// Initiate Scanner
Scanner sc = new Scanner(System.in);
// Variables
String payment = "";
// Prompt User
do {
System.out.println("Would you like to pay in full? [Yes/No]");
System.out.print("> ");
payment = sc.next();
} while ((!payment.toLowerCase().equals("yes")) && (!payment.toLowerCase().equals("no")));
// Check/set result
if (payment.toLowerCase().equals("yes")) {
customer.setPaidInFull(true);
}
else {
customer.setPaidInFull(false);
}
// Cleanup
sc.close();
}
Esto realmente me ha desconcertado por un tiempo, pero esto es lo que encontré al final.
Cuando llama, sc.close()
en el primer método, no solo cierra su escáner sino que System.in
también cierra su flujo de entrada. Puede verificarlo imprimiendo su estado en la parte superior del segundo método como:
System.out.println(System.in.available());
Entonces, ahora, cuando vuelves a crear una instancia, Scanner
en el segundo método, no encuentra ninguna System.in
secuencia abierta y, por lo tanto, la excepción.
Dudo que haya alguna salida para reabrir System.in
porque:
public void close() throws IOException --> Closes this input stream and releases any system resources associated with this stream. The general contract of close is that it closes the input stream. A closed stream cannot perform input operations and **cannot be reopened.**
La única buena solución para su problema es iniciar Scanner
en su método principal, pasarlo como argumento en sus dos métodos y cerrarlo nuevamente en su método principal, por ejemplo:
main
bloque de código relacionado con el método:
Scanner scanner = new Scanner(System.in);
// Ask users for quantities
PromptCustomerQty(customer, ProductList, scanner );
// Ask user for payment method
PromptCustomerPayment(customer, scanner );
//close the scanner
scanner.close();
Tus métodos:
public static void PromptCustomerQty(Customer customer,
ArrayList<Product> ProductList, Scanner scanner) {
// no more scanner instantiation
...
// no more scanner close
}
public static void PromptCustomerPayment (Customer customer, Scanner sc) {
// no more scanner instantiation
...
// no more scanner close
}
Espero que esto le brinde una idea sobre la falla y su posible resolución.
El problema es
Cuando se cierra un escáner, cerrará su fuente de entrada si la fuente implementa la interfaz Closeable.
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Scanner.html
Así scan.close()
se cierra System.in
.
Para solucionarlo puedes hacer
Scanner scan
static
y no lo cierre en PromptCustomerQty. El código siguiente funciona.
public static void main (String[] args) {
// Create a customer
// Future proofing the possabiltiies of multiple customers
Customer customer = new Customer("Will");
// Create object for each Product
// (Name,Code,Description,Price)
// Initalize Qty at 0
Product Computer = new Product("Computer","PC1003","Basic Computer",399.99);
Product Monitor = new Product("Monitor","MN1003","LCD Monitor",99.99);
Product Printer = new Product("Printer","PR1003x","Inkjet Printer",54.23);
// Define internal variables
// ## DONT CHANGE
ArrayList<Product> ProductList = new ArrayList<Product>(); // List to store Products
String formatString = "%-15s %-10s %-20s %-10s %-10s %n"; // Default format for output
// Add objects to list
ProductList.add(Computer);
ProductList.add(Monitor);
ProductList.add(Printer);
// Ask users for quantities
PromptCustomerQty(customer, ProductList);
// Ask user for payment method
PromptCustomerPayment(customer);
// Create the header
PrintHeader(customer, formatString);
// Create Body
PrintBody(ProductList, formatString);
}
static Scanner scan;
public static void PromptCustomerQty(Customer customer, ArrayList<Product> ProductList) {
// Initiate a Scanner
scan = new Scanner(System.in);
// **** VARIABLES ****
int qty = 0;
// Greet Customer
System.out.println("Hello " + customer.getName());
// Loop through each item and ask for qty desired
for (Product p : ProductList) {
do {
// Ask user for qty
System.out.println("How many would you like for product: " + p.name);
System.out.print("> ");
// Get input and set qty for the object
qty = scan.nextInt();
}
while (qty < 0); // Validation
p.setQty(qty); // Set qty for object
qty = 0; // Reset count
}
// Cleanup
}
public static void PromptCustomerPayment (Customer customer) {
// Variables
String payment = "";
// Prompt User
do {
System.out.println("Would you like to pay in full? [Yes/No]");
System.out.print("> ");
payment = scan.next();
} while ((!payment.toLowerCase().equals("yes")) && (!payment.toLowerCase().equals("no")));
// Check/set result
if (payment.toLowerCase() == "yes") {
customer.setPaidInFull(true);
}
else {
customer.setPaidInFull(false);
}
}
Como nota al margen, no deberías usarlo ==
para comparar cadenas, úsalo .equals
en su lugar.
Debe eliminar las líneas de cierre del escáner:scan.close();
A mí me pasó antes y esa fue la razón.
El motivo de la excepción ya se ha explicado, sin embargo, la solución sugerida no es realmente la mejor.
Debe crear una clase que mantenga un escáner privado usando Singleton Pattern, lo que hace que ese escáner sea único en su código.
Luego puedes implementar los métodos que necesitas o puedes crear un getScanner (no recomendado) y puedes controlarlo con un booleano privado, algo así como yaCerrado.
Si no sabe cómo utilizar el patrón Singleton, aquí tiene un ejemplo:
public class Reader {
private Scanner reader;
private static Reader singleton = null;
private boolean alreadyClosed;
private Reader() {
alreadyClosed = false;
reader = new Scanner(System.in);
}
public static Reader getInstance() {
if(singleton == null) {
singleton = new Reader();
}
return singleton;
}
public int nextInt() throws AlreadyClosedException {
if(!alreadyClosed) {
return reader.nextInt();
}
throw new AlreadyClosedException(); //Custom exception
}
public double nextDouble() throws AlreadyClosedException {
if(!alreadyClosed) {
return reader.nextDouble();
}
throw new AlreadyClosedException();
}
public String nextLine() throws AlreadyClosedException {
if(!alreadyClosed) {
return reader.nextLine();
}
throw new AlreadyClosedException();
}
public void close() {
alreadyClosed = true;
reader.close();
}
}