java.util.NoSuchElementException — Сканер считывает пользовательский ввод

#java #java.util.scanner #nosuchelementexception

#java #ввод #java.util.scanner #исключение nosuchelementexception

Вопрос:

Я новичок в использовании Java, но у меня есть некоторый предыдущий опыт работы с C #. Проблема, с которой я сталкиваюсь, связана с чтением пользовательского ввода с консоли.

Я сталкиваюсь с ошибкой «java.util.NoSuchElementException» в этой части кода:

 payment = sc.next(); // PromptCustomerPayment function
  

У меня есть две функции, которые получают пользовательский ввод:

  • PromptCustomerQty
  • PromptCustomerPayment

Если я не вызываю PromptCustomerQty, то я не получаю эту ошибку, что наводит меня на мысль, что я делаю что-то не так со сканером. Ниже приведен мой полный пример кода. Я ценю любую помощь.

 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")) amp;amp; (!payment.toLowerCase().equals("no")));
    
    // Check/set result
    if (payment.toLowerCase().equals("yes")) {
        customer.setPaidInFull(true);
    }
    else {
        customer.setPaidInFull(false);
    }
    
    // Cleanup
    sc.close(); 
}
  

Ответ №1:

Это действительно озадачивало меня некоторое время, но это то, что я нашел в конце.

Когда вы вызываете sc.close() первый метод, он не только закрывает ваш сканер, но и закрывает ваш System.in поток ввода. Вы можете проверить это, напечатав его статус в самом верху второго метода как :

     System.out.println(System.in.available());
  

Итак, теперь, когда вы повторно создаете экземпляр, Scanner во втором методе, он не находит никакого открытого System.in потока и, следовательно, исключение.

Я сомневаюсь, что есть какой-либо выход для повторного открытия System.in , потому что:

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

Единственное хорошее решение вашей проблемы — инициировать Scanner в вашем основном методе, передать это в качестве аргумента в ваших двух методах и снова закрыть его в вашем основном методе, например:

main блок кода, связанный с методом:

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

Ваши методы:

  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
 }
  

Надеюсь, это даст вам некоторое представление о сбое и возможном разрешении.

Комментарии:

1. Спасибо за ответ. Ваше объяснение того, что на самом деле происходит за кулисами, очень полезно

2. Спасибо за ваш ответ. Это на самом деле правда!

3. Боже мой, я так долго зависал, пока не нашел этот пост. Большое вам спасибо! Ты потрясающий.

4. Хотя хорошей практикой является закрытие сканера, чтобы избежать этой ошибки, не было бы лучше вообще никогда не закрывать сканер. Это позволяет избежать всех этих ошибок. Мы всегда можем подавить предупреждения

5. Мне было интересно, является ли это случаем вводящего в заблуждение исключения. Т.Е. существует четкое условие закрытого входного потока, и все же поведение таково, что в нем не было ничего плохого, — просто такого элемента нет… Напоминает мне хаос во время выполнения, вызванный NullPointerException, только в более мягкой форме.

Ответ №2:

Проблема в том

Когда сканер закрыт, он закроет свой источник ввода, если источник реализует закрываемый интерфейс.

http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Scanner.html

Таким образом, scan.close() закрывается System.in .

Чтобы исправить это, вы можете сделать

Scanner scan static и не закрывайте его в PromptCustomerQty. Приведенный ниже код работает.

 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")) amp;amp; (!payment.toLowerCase().equals("no")));

// Check/set result
if (payment.toLowerCase() == "yes") {
    customer.setPaidInFull(true);
}
else {
    customer.setPaidInFull(false);
}
}
  

Кстати, вы не должны использовать == для сравнения строк, используйте .equals вместо этого.

Комментарии:

1. Но они используются разными методами, верно? Где они создаются и закрываются.

2. В исходном коде они есть. В фиксированном коде я использую один экземпляр сканера для обоих методов.

3. Я все еще озадачен, почему sc.next() или даже sc.nextLine() во втором методе возникает исключение.

4. @YogendraSingh: Вызывает ли nextLine() такое же исключение?

5. @BheshGurung: Для меня это говорит java.util.NoSuchElementException: No line found

Ответ №3:

Вам нужно удалить закрывающие строки сканера: scan.close();

Это случалось со мной раньше, и это было причиной.

Ответ №4:

причина исключения уже была объяснена, однако предлагаемое решение на самом деле не самое лучшее.

Вы должны создать класс, который сохраняет сканер как закрытый, используя шаблон Singleton, который делает этот сканер уникальным в вашем коде.

Затем вы можете реализовать нужные вам методы или создать getScanner (не рекомендуется) и управлять им с помощью частного логического значения, что-то вроде alreadyClosed.

Если вы не знаете, как использовать одноэлементный шаблон, вот пример:

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

  

Ответ №5:

Для всех, кто попал сюда во время сдачи онлайн-экзамена на сайте, подобном HackerRank-

Вы можете получить это сообщение, если пытаетесь протестировать свой (возможно) идеально подходящий код, нажав кнопку для выполнения main () с пользовательским вводом.

В этом случае вам нужно нажать другую кнопку, что-то вроде «Запустить модульные тесты». Скорее всего, вас оценивают только по тому, проходит ли код модульные тесты, которые они написали, а не по вашей способности рефакторировать код до меньшего количества LOC или вашему стилю кодирования.

Ответ №6:

Нашел этот поток, потому что столкнулся с той же проблемой. После поиска причины отсюда, метод, который я нашел полезным, заключался в использовании отдельных объектов сканера для разных целей (поскольку в противном случае он дает ошибки при использовании нескольких входных данных с помощью одного объекта сканера), а затем закрывает все объекты сканера, когда использование всех объектов сканера завершено. Сделано для более четкого кода и после этого не возникало никаких проблем.