Build error: "Resource Leak" when using Scanner

Asked

Viewed 2,455 times

3

I’m solving a question from Deitel’s book question 3.17, but I have a problem in the main class (I’m doing it for Eclipse, in the Linux environment). The code is like this:

public class HealthProfile{

    private String nome;
    private String sobrenome;
    private String sexo;
    private int dia;
    private int mes;
    private int ano;
    private float altura;
    private float peso;

    public HealthProfile(String nome, String sobrenome, 
            String sexo, int dia, int mes, int ano, float altura,
            float peso)
    {
        this.nome = nome;
        this.sobrenome = sobrenome;
        this.sexo = sexo;
        this.dia = dia;
        this.mes = mes;
        this.ano = ano;
        this.altura = altura;
        this.peso = peso;
    }

    public void setNome(String nome)
    {
        this.nome = nome;
    }

    public String getNome()
    {
        return nome;
    }

    public void setSobrenome(String sobrenome)
    {
        this.sobrenome = sobrenome;
    }

    public String getSobrenome()
    {
        return sobrenome;
    }

    public void setSexo(String sexo)
    {
        this.sexo = sexo;
    }

    public String getSexo()
    {
        return sexo;
    }

    public void setDia(int dia)
    {
        if ((dia >= 1) && (dia <= 31))
            this.dia = dia;
    }

    public int getDia()
    {
        return dia;
    }

    public void setMes(int mes)
    {
        if ((mes >= 1) && (mes <= 12))
            this.mes = mes;
    }

    public int getMes()
    {
        return mes;
    }

    public void setAno(int ano)
    {
        if (ano >= 1900)
            this.ano = ano;
    }

    public int getAno() 
    {
        return ano;
    }

    public void setAltura(float altura)
    {
        if (altura >= 100)
            this.altura = altura;
    }

    public float getAltura()
    {
        return altura;
    }

    public void setPeso(float peso)
    {
        if (peso >= 70)
            this.peso = peso;
    }

    public float getPeso()
    {
        return peso;
    }

    public int idadeEmAnos() 
    {
        return 2019 - getAno();
    }

    public int maximumFrequenciaCardiaca()
    {
        return 220 - idadeEmAnos();
    }

    public double MinFrequenciaCardiacaAlvo()
    {
        return 0.5 * maximumFrequenciaCardiaca();
    }

    public double MaxFrequenciaCardiacaAlvo()
    {
        return 0.85 * maximumFrequenciaCardiaca();
    }

    public float getIMC()
    {
        float IMC = getPeso() / (getAltura() * getAltura());
        return IMC;
    }
}

And the main class is like this:

import java.util.Scanner;

public class Principal {

    public static void main() {

        HealthProfile health = new HealthProfile(null, null, null, 0, 0, 0, 0, 0);

        Scanner input = new Scanner( System.in );

        System.out.println("Informe seu nome: ");
        String nome = input.nextLine();
        health.setNome(nome);

        System.out.println("Informe seu sobrenome: ");
        String sobrenome = input.nextLine();
        health.setSobrenome(sobrenome);

        System.out.println("Informe seu sexo: ");
        String sexo = input.nextLine();
        health.setSexo(sexo);

        System.out.println("Informe seu dia de nascimento: ");
        int dia = input.nextInt();
        health.setDia(dia);

        System.out.println("Informe seu mes de nascimento: ");
        int mes = input.nextInt();
        health.setMes(mes);

        System.out.println("Informe seu ano de nascimento: ");
        int ano = input.nextInt();
        health.setAno(ano);

        System.out.println("Informe sua altura: ");
        float altura = input.nextFloat();
        health.setAltura(altura);

        System.out.println("Informe seu peso: ");
        float peso = input.nextFloat();
        health.setPeso(peso);

        System.out.printf("%s ", health.getNome());
        System.out.printf("%s%n", health.getSobrenome());
        System.out.printf("%d anos", health.idadeEmAnos());
        System.out.printf("Sua frequencia máxima é %d%n", health.maximumFrequenciaCardiaca());
        System.out.println("Frequencia Cardiaca Alvo:");
        System.out.printf("A Minima frequencia alvo é : %.0f%n", health.MinFrequenciaCardiacaAlvo());
        System.out.printf("A Maxima frequencia alvo é : %.0f%n", health.MaxFrequenciaCardiacaAlvo());
    }
}

I’d like to understand this problem you’re having in the eclipse:

Description Resource    Path    Location    Type
Resource leak: 'input' is never closed  Principal.java
/ComputadorizacaoScannerSaude/src   line 9  Java Problem

He says this problem is on line 9 that’s this one:

Scanner input = new Scanner( System.in );

In the main function.

And that’s not letting me compile the program.


Obs.: Another way I implemented and there was no problem was using the JOptionPane:

import javax.swing.JOptionPane;

public class Principal{
    public static void main(String[] args) {

        int dia, mes, ano;
        float altura, peso;

        String nome = JOptionPane.showInputDialog("Nome: ");
        String sobrenome = JOptionPane.showInputDialog("Sobrenome: ");
        String sexo = JOptionPane.showInputDialog("Sexo: ");
        dia = Integer.parseInt(JOptionPane.showInputDialog("Digite o dia do seu nascimento (em digitos)"));
        mes = Integer.parseInt(JOptionPane.showInputDialog("Digite o mês do nascimento (como antes)"));
        ano = Integer.parseInt(JOptionPane.showInputDialog("Digite o ano do seu nascimento (como antes)"));
        altura = Float.parseFloat(JOptionPane.showInputDialog("Altura(em metros): "));
        peso = Float.parseFloat(JOptionPane.showInputDialog("Peso(em quilogramas): "));

        HealthProfile health = new HealthProfile(nome, sobrenome, sexo, dia, mes, ano, altura, peso);

        String message = String.format(
                "Nome:%s\nSobrenome:%s\nSexo:%s\nData de Nascimento:%d/%d/%d\nIdade em Anos:%d\nFrequencia Cardiaca Maxima:%d\nFrequencia Cardiaca Alvo:[%.2f;%.2f]\nIMC:%.2f",
                health.getNome(), health.getSobrenome(), health.getSexo(), health.getDia(), health.getMes(),
                health.getAno(), health.idadeEmAnos(), health.maximumFrequenciaCardiaca(), health.MinFrequenciaCardiacaAlvo(),
                health.MaxFrequenciaCardiacaAlvo(), health.getIMC());

        JOptionPane.showMessageDialog(null, message);
    }

}

2 answers

4


Like general rule, it is important to close everything you opened, be a Scanner, an archive (FileInputStream/FileReader), a connection to a URL or a database, or whatever can be "opened" and "closed". That’s what the Eclipse is complaining about: you opened the Scanner, but nowhere is it closed. Then it would be enough to call the method close() at the end of the code.

Just a detail, if you do so:

Scanner input = new Scanner(System.in);
// usa o Scanner ...

input.close();

This does not guarantee that the method close() will always be called. If there is an error in the middle of the path (for example, the user typed abc when a number was expected), an exception is made and the close() is not executed (in fact any exception occurring in the middle of the way, even if it has no relation to the Scanner, will cause the same situation). To ensure the implementation of the close(), you can use a block try with finally:

Scanner input = null;
try {
    input = new Scanner(System.in);
    // usar o Scanner
} catch (Exception e) {
    // tratar os erros
} finally {
    if (input != null)
        input.close();
}

With this, the method close() is called, even if there is an error in the middle of the path (about the catch (Exception e), that is not the best way to deal with mistakes, but this is already a separate topic).

Another alternative is to use the syntax of Try-with-Resources (available from Java 7):

try (Scanner input = new Scanner(System.in)) {
    // usar o Scanner
} catch (Exception e) {
    // tratar os erros
}

In that case, the block Try-with-Resources already calls the method close() automatically at its end (even if an exception occurs within the block try).


Shall I close System.in?

The above solutions are more general and can be used with any resources that must be closed (files, streams network/database connections, etc). Specifically Try-with-Resources, any class that implements java.lang.AutoCloseable can be used inside the try.

But specifically speaking of System.in, there is some extra care to take. When you close the Scanner, the encapsulated object is also closed. In your case, the method close() will eventually close the System.in, and with that, it can no longer be used by your application. Example:

while (true) { // loop infinito
    try (Scanner input = new Scanner(System.in)) {
        System.out.println("digite um número: ");
        System.out.println(input.nextInt());
    } catch (Exception e) {
        e.printStackTrace();
        break; // se der erro, sai do while
    }
}

In the first iteration of while, the Scanner reads a number. Right away (assuming a number has been correctly typed) this number is printed, the block Try-with-Resources ends and the method close() of Scanner is called, causing System.in be closed. And in the second iteration of while, another is created Scanner with the System.in, but as this is closed, it gives error when reading the number (the error occurs even before the user can type something):

java.util.NoSuchElementException
    at java.util.Scanner.throwFor(Scanner.java:862)
    at java.util.Scanner.next(Scanner.java:1485)
    at java.util.Scanner.nextInt(Scanner.java:2117)
    at java.util.Scanner.nextInt(Scanner.java:2076)

And at that point opinions differ. Some say never close a Scanner who uses System.in, because it is the JVM that manages the System.in and you shouldn’t worry about him ("who opened that close"). Not least because it is not possible to reopen it.

But by searching the internet you will also find solutions that suggest creating a InputStream whose method close() does nothing:

Scanner input = new Scanner(new BufferedInputStream(System.in){ public void close(){} });

// ou
public class MyInputStream extends FilterInputStream {
    public MyInputStream(InputStream in) {
        super(in);
    }
    @Override
    public void close() throws IOException {
        // não faz nada
    }
}

Scanner input = new Scanner(new MyInputStream(System.in));

So you use the Scanner usually, and calls the close() at the end (or use the block Try-with-Resources), so Eclipse stops giving build error. But remember that in this case the method close() just "pretend" that you’re closing the System.in, without actually closing it.

Another option (which was suggested in another answer) is to call the method close() at the end of its programme, ensuring that the Scanner - and also the System.in - will only be closed when no one else needs it. But in that case could have a great try with a block finally (or a Try-with-Resources) around your entire code, to ensure the execution of the method even if an error occurs.


You can also ignore the Eclipse error, "pretending you didn’t see it": just go to the settings (Java -> Compiler -> Errors/Warnings) and set the "Ignore" option in the "Resource Leak" item (only it will fail to charge the error to other resources that must be closed, such as files and connections - but if you guarantee that you will never forget to close any resource, go ahead).

Another option is to leave this option as "Warning" (instead of "Ignore"), so the code compiles (but it is shown a Warning indicating that there is a problem there). But if you want, you can ignore warnings specific with the annotation java.lang.SuppressWarnings:

@SuppressWarnings("resource")
Scanner input = new Scanner(System.in);

So you ignore the Warning of System.in, but not the others (if there are files/connections being opened at other points in the code, for example).


In the specific case of System.in, I do not think it is so serious to ignore the mistake and "forget" to close it (but also it will not hurt to call close() at the end of your program), but for all other resources (files, connections, etc), it is important to close them whenever you no longer need them.

I would say that the System.in is the exception of this rule (in the same way as System.out and System.err), for being "special" and managed by JVM, and in this specific case, you could even "ignore" the Eclipse error. But feel free to choose any of the above approaches, as long as you are aware of the implications of each.

  • 2

    Well, I would say that what is worth most is the hint of the link you gave, in large letters, capital letters, bold and red: "DO NOT CLOSE A SCANNER OBJECT INITIALIZED WITH System.in !" - In my opinion, close the System.in or a Scanner using it is something simply too stupid to do. Mislead the method close() is just another way of doing gambiarra. If the eclipse is not able to identify this special case and gives a Warning, This is the eclipse problem, not the code problem, and therefore the @SuppressWarnings is more than justified.

  • 1

    @Victorstafusa I agree, I also find it unnecessary to close the System.in (and "pretend" that it is closing is in fact a gambiarra). But I opted for a more "neutral" answer, showing several alternatives, and then each draws its own conclusions...

0

At the end of your class main, place the call close().

input.close();

Classes using resources other than memory (in the case of Scanner) should provide ways to explicitly allocate / misalign these resources. We need to explicitly call close() methods to deletion of file descriptors in Finally {}, since it will execute whether an exception is thrown or not. Source: https://stackoverflow.com/questions/24573084/why-we-should-i-close-a-java-util-scanner-variable

In case your class would look like this:

import java.util.Scanner;

public class Principal {

    public static void main() {

        HealthProfile health = new HealthProfile(null, null, null, 0, 0, 0, 0, 0);

        Scanner input = new Scanner( System.in );

        System.out.println("Informe seu nome: ");
        String nome = input.nextLine();
        health.setNome(nome);

        System.out.println("Informe seu sobrenome: ");
        String sobrenome = input.nextLine();
        health.setSobrenome(sobrenome);

        System.out.println("Informe seu sexo: ");
        String sexo = input.nextLine();
        health.setSexo(sexo);

        System.out.println("Informe seu dia de nascimento: ");
        int dia = input.nextInt();
        health.setDia(dia);

        System.out.println("Informe seu mes de nascimento: ");
        int mes = input.nextInt();
        health.setMes(mes);

        System.out.println("Informe seu ano de nascimento: ");
        int ano = input.nextInt();
        health.setAno(ano);

        System.out.println("Informe sua altura: ");
        float altura = input.nextFloat();
        health.setAltura(altura);

        System.out.println("Informe seu peso: ");
        float peso = input.nextFloat();
        health.setPeso(peso);

        System.out.printf("%s ", health.getNome());
        System.out.printf("%s%n", health.getSobrenome());
        System.out.printf("%d anos", health.idadeEmAnos());
        System.out.printf("Sua frequencia máxima é %d%n", health.maximumFrequenciaCardiaca());
        System.out.println("Frequencia Cardiaca Alvo:");
        System.out.printf("A Minima frequencia alvo é : %.0f%n", health.MinFrequenciaCardiacaAlvo());
        System.out.printf("A Maxima frequencia alvo é : %.0f%n", health.MaxFrequenciaCardiacaAlvo());

        input.close();

    }
}
  • It seems that now the problems are gone. The eclipse is no longer signaling. However, when I go to Ctrl + F11, a pop-up window appears with this message: "The Selection cannot be launched, and there are no recent launches.". Would you know to tell me what I’m missing?

  • Well, that’s eclipse Launch problem. Look for the green play button and select the main class, it should work. If you have solved your problem, mark the answer as correct, so that others can base themselves on the same problem.

  • Dude, I figured out the problem: It’s right here: "public Static void main()" Note what was missing: "public Static void main(String [] args)" Now the program ran smoothly.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.