HorizonZz
HorizonZz

Reputation: 28

Java Scanner not initializing in one string() even though it does in another one

The same type of scanner method is working in one place, but not in the other... I am using eclipse as my code editor if that changes any answers. All variables have been declared elsewhere if not here.

If anyone knows how to fix this, you will have my undying gratitude :)

This one is working:

private String questionPicker(String str) 
{
    question = (int)(Math.random()*42);
    System.out.println(question);
    fileChoose = str.toUpperCase();
    String returnee;
    
    Scanner is = null;
    try 
    {
        is = new Scanner(new FileInputStream("triviaQ"+fileChoose+".txt"));
    }
    catch(FileNotFoundException z)
    {
        System.out.println("Error 004: File retrieve failed.");
    }
    skipLines(is, question);
    returnee = is.nextLine();
    is.close();
    return returnee;
}

This one is not working:

public String getAnswer() 
{
    String returnee;
    
    Scanner ls = null;
    try 
    {
        ls = new Scanner(new FileInputStream("triviaA"+fileChoose+".txt"));
    }
    catch(FileNotFoundException z)
    {
        System.out.println("Error 004: File retrieve failed.");
    }
    skipLines(ls, question);
    returnee = ls.nextLine();
    ls.close();
    return returnee;
}

error message:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "java.util.Scanner.nextLine()" because "ls" is null
at jepp.JepQA.getAnswer(JepQA.java:60)
at jepp.JepGui.actionPerformed(JepGui.java:268)
at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1972)
at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2313)
at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:405)
at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:279)
at java.desktop/java.awt.Component.processMouseEvent(Component.java:6617)
at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3342)
at java.desktop/java.awt.Component.processEvent(Component.java:6382)
at java.desktop/java.awt.Container.processEvent(Container.java:2264)
at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4993)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2322)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4825)
at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4934)
at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4563)
at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4504)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2308)
at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2773)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4825)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:772)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:743)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Upvotes: 1

Views: 455

Answers (2)

hc_dev
hc_dev

Reputation: 9377

Invest in debugging NullPointerExceptions (also known as NPE). You may consult the link commented by Progman.

Step 1: stack-trace analysis

Follow the Exception (here NPE) from top to bottom:

java.lang.NullPointerException: Cannot invoke "java.util.Scanner.nextLine()" because "ls" is null

at jepp.JepQA.getAnswer(JepQA.java:60)

at jepp.JepGui.actionPerformed(JepGui.java:268)

What you can see from the top 3 lines in stack-trace:

  1. your Scanner variable ls was null. That's why any method invocation on it like ls.nextLine() will fail with a NPE.
  2. the failing invocation is in class JepQA, inside the getAnswer method, at line 60 of the file
  3. the method getAnswer was called by an action-handler, method actionPerformed in your JepGui

Step 2: Why was an object null ?

In the method getAnswer you initialize ls as null - that's OK. Then, inside the try-block, you open the Scanner on a FileInputStream connected to the file given by path:

try {
    ls = new Scanner(new FileInputStream("triviaA"+fileChoose+".txt"));
} catch(FileNotFoundException z) {
    System.out.println("Error 004: File retrieve failed.");
}

This Scanner assignment, exactly the opening of the InputStream has failed, thus ls became null here again.

Reasons for that may be:

  1. String argument passed to FileInputStream constructor was null. Check or debug-print "triviaA"+fileChoose+".txt". Also make sure that fileChoose itself is not null, otherwise the whole concatenated expression passed as argument will be null too.
  2. there was an Exception like FileNotFoundException when opening the stream or Sanner. But then, why wasn't it catched and printed as error?
  3. So, there must be another issue! Back to (1): assume the fileChoose is null and as a result the concatenated pathname (file-name), too. Then read from the docs of constructors FileInputStream(String) or FileInputStream(File), that it might internally create a File from the pathname given as String and see the explanation there:

Throws: NullPointerException - If the pathname argument is null


Update: Thanks to k314159's comment falsifying reasoning on my assumptions:

  • Probable root-cause (2): error is printed on standard out (console), easily overseen when using a GUI.
  • Impossible root-cause (3): can't be because if fileChoose = null then concatenated path argument would be "triviaAnull.txt".

Explanation:

  • (3) was tested in null-concatenating demo. Hence, if invalid path argument passed, this causes a FileNotFoundException at line ls = new FileInputStream(..) so that observed ls NPE could never have been reached. Instead thrown exception must have been catched. Since no return handling was implemented the error prints to console (not shown on GUI) and flow continues with erroneous ls = null until observed NPE thrown at ls.nextLine().

  • So (2) must be the most-probable root-cause. However the FileNotFoundException could have been caused by fileChoose = null or any other invalid path passed to construct the Scanner instance.


Conclusion

Your file-name passed as argument pathname to FileInputStream constructor was null might have been invalid, either, because the fileChoose variable was null or else.

Compare the other working method questionPicker(String str) where you assign it as fileChoose = str.toUpperCase();. If you would pass a str as null here, then it may also throw a NPE.

How to avoid: Validate/test for null inputs

Add a guard-statement (see Fail-fast principle). For example using Objects.requireNonNull(fileChoose, "answer/question file must not be null"); where the second is the error-message printed when fileChoose is null.

See also: Why should one use Objects.requireNonNull()?

Solution

As Bohemian's answer correctly spotted the design-issue:

  • the sequence and dependency of your statements (ls related)
  • unusual application of try/catch/finally pattern for I/O-Stream transactions

In case those were to roughly outlined, here a complete before/after example with explaining comments.

Before:

String returnee; // consider a default return value to avoid any null at the end

Scanner ls = null; // a null Scanner is useless, consider initializing inside try (local scope is safer)!
try { // try block can encompass the whole attempt with every dependent statement
    ls = new Scanner(new FileInputStream("triviaA"+fileChoose+".txt")); // test filename at least for non-null, before try block!
} catch(FileNotFoundException z) { //
    System.out.println("Error 004: File retrieve failed."); // perfect to log
    // but how to handle: return or assign returnee with a default 
}

skipLines(ls, question); // depends on ls, so put inside try-block
returnee = ls.nextLine(); // depends on ls, so put inside try-block

ls.close(); // safe resources: close inside finally-block!

return returnee; // what if this is (still) null

Incorporating the second outlined pattern from Bohemian:

  • everything inside try-catch-finally block

After:

// default return
String returnee = ""; // default return value

// guard-statement with fail-fast
if (fileChoose == null) {
   return returnee; // returns the default or throw
}

String filename = "triviaA"+fileChoose+".txt"; // can not fail, because validated before. Worst case, e.g. least "triviaA.txt"

try { // block of ls dependent statements

   Scanner ls = new Scanner(new FileInputStream(filename)); // filename not-null
   skipLines(ls, question); // safer, because in local/try scope
   returnee = ls.nextLine(); // safer, but may throw NoSuchElementException

} catch(FileNotFoundException notFound) { //
    System.out.println("Error 004: File retrieve failed: " + notFound.getMessage()); // perfect to log
    // but how to handle: return or assign returnee with a default 
} catch(NoSuchElementException noElement)
    // log
    // and handle!
} finally {
    if (ls != null) ls.close(); // always close (if not null)
}

return returnee; // Avoid null, at least a default. Benefit: no NPE-risk for the caller.

See also: What Sanner.nextLine() might throw

Upvotes: 1

Bohemian
Bohemian

Reputation: 425073

Just catching exceptions is not enough; you must also change the code path accordingly.

Your code has the form:

Scanner ls = null;
try {
    ls = new Scanner(...);
} catch(FileNotFoundException z) {
    // print error message
}
// do something with ls

If the exception happens, execution continues after printing the error message and ls will still be null when the do something with ls code executes, which is why you got the NullPointerException.

To fix, either exit the method on exception:

Scanner ls = null;
try {
    ls = new Scanner(...);
} catch(FileNotFoundException z) {
    // print error message
    return;
}
// do something with ls

Or move the code that uses ls inside the try:

try {
    Scanner ls = new Scanner(...);
    // do something with ls
} catch(FileNotFoundException z) {
    // print error message
}

Note also with the last version, not only do we save a line of code (assignment and declaration as one line), but the scope of ls is limited to the try block; limiting scope is good coding practice.

Upvotes: 3

Related Questions