16
ratingS = new JSlider(1, 5, 3); 
ratingS.setMajorTickSpacing(1);
ratingS.setPaintLabels(true);
int vote;

class SliderMoved implements ChangeListener {
    public void stateChanged(ChangeEvent e) {
        vote = ratingS.getValue();
    }
}

ratingS.addChangeListener(new SliderMoved());

If i write the above code Eclipse tells me this:

Cannot refer to a non-final variable vote inside an inner class defined in a different method

But if i add final before int vote it gives me this error:

The final local variable vote cannot be assigned, since it is defined in an enclosing type

So, how to solve?

Amarnath Balasubramanian
  • 9,300
  • 8
  • 34
  • 62
smartmouse
  • 13,912
  • 34
  • 100
  • 166
  • Try making it static or putting it in slidermoved but out of statechanged. Or access it as "this.vote" or "zxc myClass = new zxc(); and acces it as myClass.vote" – huseyin tugrul buyukisik Jan 31 '14 at 16:55
  • 3
    `vote` should be a field, not a local variable. – JB Nizet Jan 31 '14 at 16:56
  • http://stackoverflow.com/questions/1299837/cannot-refer-to-a-non-final-variable-inside-an-inner-class-defined-in-a-differen?rq=1 – Anirban Nag 'tintinmj' Jan 31 '14 at 16:56
  • I can't use "static" **Illegal modifier for the variable vote; only final is permitted** and other ways don't make my program work correctly – smartmouse Jan 31 '14 at 17:05
  • @JBNizet I don't think we can tell without more information whether `vote` should be a field or not. If this is in a method that could be called reentrantly from different threads, then making it a field would be bad. In any case, if this really is a variable used only inside the method, making it a field that could be accessed by other methods seems like a "design smell" to me. (You did mean a field in the outer class, right?) – ajb Jan 31 '14 at 17:05
  • No, I did mean a field, whatever the class/object is. It could be the outer object, or a field of a "holder" object or a field of the listener. – JB Nizet Jan 31 '14 at 17:10
  • possible duplicate of [The final local variable cannot be assigned](http://stackoverflow.com/questions/10166521/the-final-local-variable-cannot-be-assigned) – Michael Schmeißer Nov 17 '14 at 07:14

4 Answers4

28

Well, the standard trick is to use an int array of length one. Make the var final and write to var[0]. It is very important to make sure you don't create a data race. Using your code as an example:

final int[] vote = {0};

class SliderMoved implements ChangeListener {
  public void stateChanged(ChangeEvent e) {
    vote[0] = ratingS.getValue();
  }
}

Since all this will be happenenig on the EDT, including the callback invocation, you should be safe. You should also consider using the anonymous class:

ratingS.addChangeListener(new ChangeListener() {
  public void stateChanged(ChangeEvent e) { vote[0] = ratingS.getValue(); }
});
Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • Can you show me the code in my case? How should i use this trick? – smartmouse Jan 31 '14 at 17:05
  • Why do you find it strange? You can't make the contents of the array final. – Marko Topolnik Jan 31 '14 at 17:49
  • I find it strange because the error disappear just declaring it as array. I do not understand the why, anyway i don't care, i just solve my problem. Thank you very much for your help. – smartmouse Jan 31 '14 at 18:59
  • 5
    I think you *should* always understand why something either works or not. In this case you can refer only to `final` vars from within a local class, but then you can't change them. However, you *can* change the contents of an array regardless of whether the variable pointing to the array is final or not. – Marko Topolnik Jan 31 '14 at 19:22
5

Move vote to SliderMoved:

class SliderMoved implements ChangeListener {
    private int vote;
    public void stateChanged(ChangeEvent e) {
        this.vote = ratingS.getValue();
        // do something with the vote, you can even access
        // methods and fields of the outer class
    }
    public int getVote() {
        return this.vote;
    }
}

SliderMoved sm = new SliderMoved();
ratingS.addChangeListener(sm);

// if you need access to the actual rating...
int value = rattingS.getValue();

// ...or
int value2 = sm.getVote();

EDIT

Or alternatively, pass a model class to the change listener

public class Person {
    private String name;
    private int vote;
    public int getVote() {
        return this.vote;
    }
    public void setVote(int vote) {
        this.vote = vote;
    }
    // omitting other setter and getter
}

Person is used as follows:

 class SliderMoved implements ChangeListener {
    private Person person;
    public SliderMoved(Person person) {
        this.person = person;
    }
    public void stateChanged(ChangeEvent e) {
        this.person.setVote(ratingS.getValue());
    }
    public Person getPerson() {
        return this.person;
    }
}

Person person = new Person();

ratingS.addChangeListener(new SliderMoved(person));

// access the vote
int vote = person.getVote();
Peter Keller
  • 7,526
  • 2
  • 26
  • 29
  • **int value = ratingS.getValue();** can't work outside because that method is of the inner classe. **int value2 = sm.getVote();** don't give me the actual rating, just the first change – smartmouse Jan 31 '14 at 17:10
  • `vote` is updated every time the state is changed and therefore `sm.getVote()` always should return the actual value. I prefer to do the whole work in `stateChanged(ChangeEvent)` and personally, I would remove `sm.getVote()`, but this depends on your needs. – Peter Keller Jan 31 '14 at 17:33
  • You could actually use the model technique without having to pass it to the change listener, using a "holder" type design pattern. For example, outside of the change listener, you could declare: `final Person person = new Person();` and then in the change listener you can access/change the contents of the final `person` variable as long as you don't try and reassign the person itself: `person.setVote(ratingS.getValue());` IMHO, this is slightly more objecty that the array technique above :) – DarthPablo Jan 12 '16 at 08:48
2

I finally solved declaring vote as instance variable (private) in the main class.

smartmouse
  • 13,912
  • 34
  • 100
  • 166
2

Setting the Compiler compliance level to 1.8 worked for me to solve a similar problem. I don't understand the reason but you may try this.

How it works:

Right click on the project --> Properties --> Java Compiler. And in java compiler properties window, set the compiler compliance level to 1.8.

Nadeem
  • 556
  • 1
  • 4
  • 13
  • That is because in Java 1.8 the concept of effectively final variable is introduced. If the inner access is only a read access it works in 1.8. See here: http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.12.4 – Kenyakorn Ketsombut Aug 13 '15 at 08:35