java.util.NoSuchElementException: el escáner lee la entrada del usuario

Resuelto fortune asked hace 11 años • 6 respuestas

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(); 
}
fortune avatar Oct 24 '12 09:10 fortune
Aceptado

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.intambié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, Scanneren el segundo método, no encuentra ninguna System.insecuencia abierta y, por lo tanto, la excepción.

Dudo que haya alguna salida para reabrir System.inporque:

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 Scanneren su método principal, pasarlo como argumento en sus dos métodos y cerrarlo nuevamente en su método principal, por ejemplo:

mainbloque 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.

Yogendra Singh avatar Oct 24 '2012 02:10 Yogendra Singh

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 .equalsen su lugar.

 avatar Oct 24 '2012 02:10

Debe eliminar las líneas de cierre del escáner:scan.close();

A mí me pasó antes y esa fue la razón.

Shady avatar Aug 18 '2020 08:08 Shady

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();
    }   
}

Eduardo Salvadinha avatar Nov 12 '2020 15:11 Eduardo Salvadinha