68

I'm getting the following error when trying to execute statemet.executeUpdate() in my code:

Local variable statement defined in an enclosing scope must be final or effectively final.

This is my code so far:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;.

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

public class a1 {

    protected Shell shell;
    private Text text;
    private Text text_1;
    private Text text_2;
    private Text text_3;

    /**
     * Launch the application.
     * @param args
     */
    public static void main(String[] args) {
        try {
            a1 window = new a1();
            window.open();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Open the window.
     */
    public void open() {
        Display display = Display.getDefault();
        createContents();
        shell.open();
        shell.layout();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    /**
     * Create contents of the window.
     */
    protected void createContents() {

        Connection connect = null;

        ResultSet resultSet = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {
            connect = DriverManager.getConnection("jdbc:mysql://localhost/railwaydb", "root", "");
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        Statement statement = null;
        // statements allow to issue SQL queries to the database
        try {
            statement = connect.createStatement();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        shell = new Shell();
        shell.setSize(450, 300);
        shell.setText("SWT Application");

        Label lblName = new Label(shell, SWT.NONE);
        lblName.setBounds(10, 43, 47, 15);
        lblName.setText("Name");

        Label lblFrom = new Label(shell, SWT.NONE);
        lblFrom.setBounds(10, 74, 55, 15);
        lblFrom.setText("From");

        Label lblTo = new Label(shell, SWT.NONE);
        lblTo.setBounds(10, 105, 55, 15);
        lblTo.setText("To");

        Label lblPrice = new Label(shell, SWT.NONE);
        lblPrice.setBounds(10, 137, 55, 15);
        lblPrice.setText("Price");

        text = new Text(shell, SWT.BORDER);
        text.setBounds(64, 43, 76, 21);

        text_1 = new Text(shell, SWT.BORDER);
        text_1.setBounds(64, 74, 76, 21);

        text_2 = new Text(shell, SWT.BORDER);
        text_2.setBounds(64, 105, 76, 21);

        text_3 = new Text(shell, SWT.BORDER);
        text_3.setBounds(64, 137, 76, 21);

        Label lblRailwayDatabase = new Label(shell, SWT.NONE);
        lblRailwayDatabase.setBounds(174, 10, 97, 15);
        lblRailwayDatabase.setText("Railway Database");

        Label lblCreateView = new Label(shell, SWT.NONE);
        lblCreateView.setBounds(189, 43, 76, 15);
        lblCreateView.setText("Create View");

        Button btnName = new Button(shell, SWT.CHECK);
        btnName.setBounds(189, 73, 93, 16);
        btnName.setText("Name");

        Button btnFrom = new Button(shell, SWT.CHECK);
        btnFrom.setBounds(189, 105, 93, 16);
        btnFrom.setText("From");

        Button btnTo = new Button(shell, SWT.CHECK);
        btnTo.setBounds(189, 137, 93, 16);
        btnTo.setText("To");

        Button btnPrice = new Button(shell, SWT.CHECK);
        btnPrice.setBounds(189, 171, 93, 16);
        btnPrice.setText("Price");

        Button btnInsert = new Button(shell, SWT.NONE);
        btnInsert.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDown(MouseEvent e) {
                String name = text.getText();
                String from = text_1.getText();
                String to = text_2.getText();
                String price = text_3.getText();

                String query = "INSERT INTO booking (name, fromst, tost, price) VALUES ('"+name+"', '"+from+"', '"+to+"', '"+price+"')";
                try {
                    statement.executeUpdate(query);
                } catch (SQLException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
            }
        });
        btnInsert.setBounds(10, 171, 75, 25);
        btnInsert.setText("Insert");

        Button btnView = new Button(shell, SWT.NONE);
        btnView.setBounds(307, 74, 75, 25);
        btnView.setText("View");

        Button btnIndex = new Button(shell, SWT.NONE);
        btnIndex.setBounds(307, 127, 75, 25);
        btnIndex.setText("Index");

    }
}

I also tried to set statement final but the declaration gives me another error.

dic19
  • 17,821
  • 6
  • 40
  • 69
Amit Chahar
  • 2,519
  • 3
  • 18
  • 23
  • Please let us know: What was the expected result, what was the observed erroneous behaviour (error message as Baz already asked for). And: That's a lot of code! Please try to narrow down your sourcecode to the affected code lines. – Gottlieb Notschnabel Sep 17 '14 at 15:31
  • It's Java's screwball inner class/"lambda" notation. A variable referenced from inside the lambda expression (that is not declared in the lambda expression) must be either an instance variable or a local variable that is not seen to change in the calling scope. – Hot Licks Sep 17 '14 at 16:04
  • To pick on something else, your error handling is not appropriate. You catch the exception, ignore it and continue the code. Consider a scenario where `getConnection` didn't get a real connection. The generated `SQLException` is effectively ignored (other than printing a stack trace to `System.err`), and the code continues to attempt `createStatement` -- at this point, since `connect` is `null`, you get a `NullPointerException` thrown at you. You will then be tasked with investigating why there's a `null` pointer there. If you had thrown `SQLException` earlier, you'd have known immediately. – ADTC Aug 07 '15 at 06:08
  • 1
    Please only include MANDATORY code related to your question. We don't need to know your 1000 buttons/label to solve your issue. The error most likely refer to one variable so you can remove a lot of code making it easier to read for everyone. – Maude Oct 26 '17 at 20:00

4 Answers4

96

You have a scope problem indeed, because statement is a local method variable defined here:

protected void createContents() {
    ...
    Statement statement = null; // local variable
    ...
     btnInsert.addMouseListener(new MouseAdapter() { // anonymous inner class
        @Override
        public void mouseDown(MouseEvent e) {
            ...
            try {
                statement.executeUpdate(query); // local variable out of scope here
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            ...
    });
}

When you try to access this variable inside mouseDown() method you are trying to access a local variable from within an anonymous inner class and the scope is not enough. So it definitely must be final (which given your code is not possible) or declared as a class member so the inner class can access this statement variable.

Sources:


How to solve it?

You could...

Make statement a class member instead of a local variable:

public class A1 { // Note Java Code Convention, also class name should be meaningful   
    private Statement statement;
    ...
}

You could...

Define another final variable and use this one instead, as suggested by @HotLicks:

protected void createContents() {
    ...
    Statement statement = null;
    try {
        statement = connect.createStatement();
        final Statement innerStatement = statement;
    } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    ...
}

But you should...

Reconsider your approach. If statement variable won't be used until btnInsert button is pressed then it doesn't make sense to create a connection before this actually happens. You could use all local variables like this:

btnInsert.addMouseListener(new MouseAdapter() {
   @Override
   public void mouseDown(MouseEvent e) {
       try {
           Class.forName("com.mysql.jdbc.Driver");
           try (Connection connect = DriverManager.getConnection(...);
                Statement statement = connect.createStatement()) {

                // execute the statement here

           } catch (SQLException ex) {
               ex.printStackTrace();
           }

       } catch (ClassNotFoundException ex) {
           e.printStackTrace();
       }
});
Community
  • 1
  • 1
dic19
  • 17,821
  • 6
  • 40
  • 69
  • 6
    Note that the simple solution is to add a statement like `final Statement innerStatement = statement;` and refer to `innerStatement` inside `mouseDown`. – Hot Licks Sep 17 '14 at 16:06
  • thank you dic19. @hot licks but i am getting this warning :Null pointer access: The variable innerstatement can only be null at this location – Amit Chahar Sep 17 '14 at 16:06
  • 1
    @amitsingh - Because you placed it before `statement` is set. – Hot Licks Sep 17 '14 at 16:17
5

Firstly, we just CAN'T make the variable final as its state may be changing during the run of the program and our decisions within the inner class override may depend on its current state.

Secondly, good object-oriented programming practice suggests using only variables/constants that are vital to the class definition as class members. This means that if the variable we are referencing within the anonymous inner class override is just a utility variable, then it should not be listed amongst the class members.

But – as of Java 8 – we have a third option, described here :

https://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html

Starting in Java SE 8, if you declare the local class in a method, it can access the method's parameters.

So now we can simply put the code containing the new inner class & its method override into a private method whose parameters include the variable we call from inside the override. This static method is then called after the btnInsert declaration statement :-

 . . . .
 . . . .

 Statement statement = null;                                 

 . . . .
 . . . .

 Button btnInsert = new Button(shell, SWT.NONE);
 addMouseListener(Button btnInsert, Statement statement);    // Call new private method

 . . . 
 . . .
 . . . 

 private static void addMouseListener(Button btn, Statement st) // New private method giving access to statement 
 {
    btn.addMouseListener(new MouseAdapter() 
    {
      @Override
      public void mouseDown(MouseEvent e) 
      {
        String name = text.getText();
        String from = text_1.getText();
        String to = text_2.getText();
        String price = text_3.getText();
        String query = "INSERT INTO booking (name, fromst, tost,price) VALUES ('"+name+"', '"+from+"', '"+to+"', '"+price+"')";
        try 
        {
            st.executeUpdate(query);
        } 
        catch (SQLException e1) 
        {
            e1.printStackTrace();                                    // TODO Auto-generated catch block
        }
    }
  });
  return;
}

 . . . .
 . . . .
 . . . .
Trunk
  • 742
  • 9
  • 24
  • I must be missing something here. The problem is that the variable has to be final. So put it in an enclosing class? But the link you provided says "However, starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final." So this seems like the same thing, just a slightly different construct. There's still the (IMO rather unreasonable) restriction that it has to be final. – Ryan Lundy Jul 11 '19 at 08:56
  • Has to be final or *effectively* final, i.e. for the duration of computations in the current code block. – Trunk Jul 11 '19 at 09:59
  • But if I could make it final or effectively final (that is, if I didn't need to change it inside the lambda or the anonymous class), then it wouldn't be an issue as a variable either. – Ryan Lundy Jul 11 '19 at 10:07
  • In the link, start with *Starting in Java SE 8, if you declare the local class in a method, it can access the method's parameters.*. The issue was with *access* to a variable that the OP needed within an overriden method, where he needs access to the variable *st* which is defined in an enclosing class. The error message just said that *local variable statement defined in an enclosing class must be final or effectively final.* But that error message was (maybe still is?) outmoded since Java 8 as far as access is concerned because Java 8+ now allows access to parameters of an enclosing method. – Trunk Jul 11 '19 at 10:23
  • I found this which cleared it up: [Java Closures Using Mutable Objects](https://miromannino.com/blog/java-8-lambdas-limitations-closures/#java-closures-using-mutable-objects). I guess the object reference itself can't change, but any of the object's members can. I still find the Java restriction pointless, but at least this is a good workaround (despite the fact that the author of that page doesn't like it). – Ryan Lundy Jul 11 '19 at 10:40
  • This **final** thing - esp. in relation to thread safety - seems to be of more general relevance https://softwareengineering.stackexchange.com/questions/115690/why-declare-final-variables-inside-methods . And there's also the compilation optimization claims. – Trunk Jul 12 '19 at 11:46
0

not Error:

JSONObject json1 = getJsonX();

Error:

JSONObject json2 = null;
if(x == y)
   json2 = getJSONX();

Error: Local variable statement defined in an enclosing scope must be final or effectively final.

But you can write:

JSONObject json2 = (x == y) ? json2 = getJSONX() : null;
mehdi.s
  • 1
  • 1
0

I found this approach useful. This way you do not need a class nor final

 btnInsert.addMouseListener(new MouseAdapter() {
        private Statement _statement;

        public MouseAdapter setStatement(Statement _stmnt)
        {
            _statement = _stmnt;
            return this;
        }
        @Override
        public void mouseDown(MouseEvent e) {
            String name = text.getText();
            String from = text_1.getText();
            String to = text_2.getText();
            String price = text_3.getText();

            String query = "INSERT INTO booking (name, fromst, tost, price) VALUES ('"+name+"', '"+from+"', '"+to+"', '"+price+"')";
            try {
                _statement.executeUpdate(query);
            } catch (SQLException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        }
    }.setStatement(statement));
user4212919
  • 305
  • 1
  • 2
  • 7