0

I wrote a Java program that is supposed to take marks of two students, store them in the array, calculate their individual average marks and display it. The class name is Student. It has a function marks() that takes marks of 5 subjects input from the user and store it in the array. It works fine if I use one object to access it. the problem arises when I'm creating a second object. When I create a new object, it should create a new instance of the array, isn't it? But it's not doing so I suppose. It works for other data type values like a String, or int, etc. The code is as follows:

import java.util.Scanner;

public class A3_student_marks {
    public static void main(String[] args) {
        Student s1 = new Student();
        System.out.println("Enter marks of student 1:");
        s1.marks();
        
        Student s2 = new Student();
        System.out.println("Enter marks of student 2:");
        s2.marks();

        s1.display();
        s2.display();
    }
}

class Student
{
    private int[] m;
    int avg = 0;

    public Student()
    {
        this.m = new int[5];
    }

    public void marks()
    {
        Scanner sc = new Scanner(System.in);
        try{
            for(int i=0;i<5;i++)
            {
                System.out.print("Enter marks of subject "+(i+1)+": ");
                m[i] = sc.nextInt();
                avg += m[i];
            }   

            avg /= 5; 
        }
        finally
        {
            sc.close();
        }
    }

    public void display()
    {
        System.out.println("Average marks: "+avg);
    }
}

It takes the input for the first time when called using first object. But as soon as the call using second object starts, it displays the following exceptions:

Enter marks of student 1:
Enter marks of subject 1: 1
Enter marks of subject 2: 2
Enter marks of subject 3: 3
Enter marks of subject 4: 4
Enter marks of subject 5: 5
Enter marks of student 2:
Enter marks of subject 1: Exception in thread "main" java.util.NoSuchElementException
        at java. Base/java.util.Scanner.throwFor(Scanner.java:945)
        at java. Base/java.util.Scanner.next(Scanner.java:1602)
        at java. Base/java.util.Scanner.nextInt(Scanner.java:2267)
        at java. Base/java.util.Scanner.nextInt(Scanner.java:2221)
        at Student.marks(A3_student_marks.java:35)
        at A3_student_marks.main(A3_student_marks.java:11)

Please help me out. I can't seem to understand what's happening. Initially I declared the array without any access specifier. Then I added the 'private' access specifier. It's still the same.

Please help me out. I can't seem to understand what's happening. Initially I declared the array without any access specifier. Then I added the 'private' access specifier. It's still the same. I just wan't to know why isn't it working. P.S. I know you can calculate the average without an array here, but I just wanna know what is wrong here

Rohan Nag
  • 1
  • 1
  • This is nothing to do with the array. Read the stack trace. It is the Scanner which can't find the next integer. – tgdavies Jul 02 '23 at 10:45
  • 1
    Don't create a new `Scanner` for each student -- just create one in `main` and pass it to `marks`. – tgdavies Jul 02 '23 at 10:46
  • @tgdavies I tried creating the ```Scanner``` object in the main method and passing it to the ```marks()``` function, the issue still remains the same – Rohan Nag Jul 02 '23 at 11:21
  • *It has a function marks()* We talk about *methods* in Java, really. They should have names that are based on [verbs](https://technojeeves.com/index.php/aliasjava1/106-java-style-conventions), not nouns. So not marks() but maybe `storeMarks()`. You're right to wonder about the array (not that it has anything to do with your problem, as @tgdavies says) but each `Student` has its own array, which you can use directly. Again, `m` is a very poor name for that array. Your classes should be self-documenting – g00se Jul 02 '23 at 11:46
  • 4
    The problem is that the scanner is closed at the end of the `marks` method. This has the effect of closing `System.in` and makes it impossible to read subsequent input. – Tim Moore Jul 02 '23 at 11:49
  • try this link:https://stackoverflow.com/questions/13042008/java-util-nosuchelementexception-scanner-reading-user-input. Closing the scanner is causing weird behaviour – Aladdin Jul 02 '23 at 11:50
  • You should also not bundle up the filling of the marks array with the `Student` class itself. Its responsibility is simply to store data and state, not to do IO – g00se Jul 02 '23 at 11:51
  • As @TimMoore pointed out, you need to not close the `Scanner` in `marks` too. – tgdavies Jul 02 '23 at 11:51
  • In this case, I would give your main class a loader method such as `public static Student fillMarks(Student s) { try(Scanner sc = new Scanner(System.in)) {int[] marks = new int[5];int index = 0;for(int i = 0;i < 5;i++) { marks[i] = sc.nextInt();}s.setMarks(marks);}return s;}` That way, you can use it n times without confusion between each instance – g00se Jul 02 '23 at 12:19

1 Answers1

2

"... It works fine if I use one object to access it. the problem arises when I'm creating a second object. ..."

This is because you are closing the System#in InputStream on your initial instance.

Here is an excerpt from the JavaDoc, for System#in.

"The "standard" input stream. ... Typically this stream corresponds to keyboard input or another input source specified by the host environment or user. ..."

And, here is an excerpt from the JavaDoc for Scanner#close.

"... if [the] underlying readable also implements the Closeable interface then the readable's close method will be invoked. ..."

Thus, the System#in InputStream, which implements Closeable, will be closed.

Additionally, since System#in is static, it will only be assigned the standard-in stream once; whenever that is—whenever the JVM runs a static initialization on the System class, for your application.
Here is a relevant discussion on this topic, StackOverflow – When is the out object of the System class initialized in Java?.

Thus, simply resolving System#in from a new Scanner instance, will actually not produce a new standard-in stream, and will actually be accessing the same System#in instance as the previous invocation.

It is somewhat confusing, as you would think the Java implementers would have added some type of mechanism to prevent the System#in from being closed.
I think that a better way to look at it is, it is more logical to allow for it to close—in terms of design principles.

Essentially, you will want to call close after all of the standard-in input for your application has been read, because of the way the Closeable design structure works.

In this case, supply the Scanner to the marks method, as opposed to instantiating a new Scanner for each Student#marks invocation.

public void marks(Scanner sc)
{
    for(int i=0;i<5;i++)
    {
        System.out.print("Enter marks of subject "+(i+1)+": ");
        m[i] = sc.nextInt();
        avg += m[i];
    }

    avg /= 5;
}

And then, you can call each method, from main, as follows.

Scanner sc = new Scanner(System.in);

Student s1 = new Student();
System.out.println("Enter marks of student 1:");
s1.marks(sc);

Student s2 = new Student();
System.out.println("Enter marks of student 2:");
s2.marks(sc);

s1.display();
s2.display();

sc.close();

Here is an example input, and output.

Enter marks of student 1:
Enter marks of subject 1: 1
Enter marks of subject 2: 2
Enter marks of subject 3: 3
Enter marks of subject 4: 4
Enter marks of subject 5: 5
Enter marks of student 2:
Enter marks of subject 1: 1
Enter marks of subject 2: 2
Enter marks of subject 3: 3
Enter marks of subject 4: 4
Enter marks of subject 5: 5
Average marks: 3
Average marks: 3

Here are some responses from your other inquiries, despite not being directly correlated to the error.

"... When I create a new object, it should create a new instance of the array, isn't it? ..."

Correct, a new array instance will be created for that object.

"... as soon as the call using second object starts, it displays the following exceptions ..."

The reason that specific exception was being thrown was because the Scanner object was unable to procure a value from the System#in stream, which had been closed.

"... Initially I declared the array without any access specifier. Then I added the 'private' access specifier. It's still the same. ..."

The access modifier is only going to dictate the visibility of the value to other objects.
If you are outside of the scope of Student, then accessing m will not work—unless it's declared as public.

For example, Scanner#nextInt is public, so we can access it from within the Student class.

Reilas
  • 3,297
  • 2
  • 4
  • 17