Search code examples
for-loopjavafxforeachindexoutofboundsexception

Exception occurred when iterating though for loop that includes mouseEvent action


the following exception message appears when I iterate using NORMAL for-loop that includes mouseEvent action through radioButtons.

Message: Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException

But when I iterate using for-each loop, there is no problem!

please help me, thank you

Concerned code part:


 HBox p1 = new HBox();
       
        RadioButton Red = new RadioButton("RED");
        RadioButton Blue = new RadioButton("Blue");
        RadioButton Black = new RadioButton("Black");
        
          ToggleGroup tg = new ToggleGroup(); 
        
          Red.setToggleGroup(tg);
        Blue.setToggleGroup(tg);
         Black.setToggleGroup(tg);
         
        
        RadioButton [] array = {Red,Blue,Black};
        
        p1.getChildren().addAll(Red,Blue,Black);
 
     
     i = 0;
     for(i = 0; i< array.length; i++){
         
         array[i].setOnAction(e->{
         
             System.out.println(array[i].getText());
         });
         
     }

Solution

  • I'm guessing, because your question is incomplete, that you made the loop index i an instance variable, for some reason. I.e. you have something like:

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.control.RadioButton;
    import javafx.scene.control.ToggleGroup;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    
    public class Demo extends Application {
        
        // Guessing:
        private int i ;
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            HBox p1 = new HBox();
    
            RadioButton red = new RadioButton("RED");
            RadioButton blue = new RadioButton("Blue");
            RadioButton black = new RadioButton("Black");
    
            ToggleGroup tg = new ToggleGroup();
    
            red.setToggleGroup(tg);
            blue.setToggleGroup(tg);
            black.setToggleGroup(tg);
    
            RadioButton[] array = { red, blue, black };
    
            p1.getChildren().addAll(red, blue, black);
    
            i = 0;
            for (i = 0; i < array.length; i++) {
    
                array[i].setOnAction(e -> {
    
                    System.out.println(array[i].getText());
                });
    
            }
            
            Scene scene = new Scene(p1, 400, 250);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            Application.launch(args);
        }
    
    }
    

    Here i is a property of the Demo instance (i.e. the application instance on which start() is invoked). In the start() method, i is initialized to 0 and then incremented each time you iterate the for loop. The for loop exits when i is no longer less than array.length (which is 3), so when the for loop exits, i==3.

    Consequently, when you press one of the buttons, the code

    System.out.println(array[i].getText());
    

    is executed. The value of i hasn't changed since the completion of the for loop, so this is equivalent to

    System.out.println(array[3].getText());
    

    and this throws an IndexOutOfBoundsException, because the value indexes in the array are 0, 1, and 2. Indeed, the complete error message is

    Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
    

    Instead, you need to use a local variable for the index in the loop:

        for (int i = 0; i < array.length; i++) {
    
            array[i].setOnAction(e -> { /* ... */ });
    
        }
    

    The problem now is that the lambda expression in the event handler cannot access the local variable i because it is not final (or "effectively final"). The solution to this is to "capture" the value of i on each iteration of the loop in a final variable:

        for (int i = 0; i < array.length; i++) {
            final int index = i ;
            array[i].setOnAction(e -> {
                System.out.println(array[index].getText());
            });
    
        }
    

    Of course, you don't actually need the index; you just need the element of the array, so you could capture that instead:

        for (int i = 0; i < array.length; i++) {
            final RadioButton button = array[i] ;
            button.setOnAction(e -> {
                System.out.println(button.getText());
            });
        }
    

    This code is completely identical (in the sense that the compiler converts the following to the same thing) to:

        for (RadioButton button : array) {
            button.setOnAction(e -> {
                System.out.println(button.getText());
            });
        }
    

    which is by far the preferred form for a for loop.

    Here's a fully-cleaned and working version of the code:

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.control.RadioButton;
    import javafx.scene.control.ToggleGroup;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    
    public class Demo extends Application {
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            HBox p1 = new HBox();
    
            RadioButton red = new RadioButton("RED");
            RadioButton blue = new RadioButton("Blue");
            RadioButton black = new RadioButton("Black");
    
            ToggleGroup tg = new ToggleGroup();
            RadioButton[] buttons = { red, blue, black };
    
            tg.getToggles().setAll(buttons);
            p1.getChildren().addAll(buttons);
    
            for (RadioButton button : buttons) {
                button.setOnAction(e -> {
                    System.out.println(button.getText());
                });
            }
            
            Scene scene = new Scene(p1, 400, 250);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            Application.launch(args);
        }
    
    }