2

I am studying Inner Class in Java and I have a problem related to reference of variables in the outer methods. For example, I have a source code to count how many times compareTo() methods is called during the sorting:

        int counter = 0;
        Date[] dates = new Date[100];
        for(int i = 0; i < dates.length; i++)
        {
            dates[i] = new Date()
            {
                public int compareTo(Date other)
                {
                    counter++;
                    return super.compareTo(other);
                }
            };
        }
        Arrays.sort(dates);
        System.out.println(counter + " comparisons");

As executing the source code, you can see that there exists an error in the using of counter++. To solve this problem, some people told me that I should change like this:

        int[] counter = new int[1];
        Date[] dates = new Date[100];
        for(int i = 0; i < dates.length; i++)
        {
            dates[i] = new Date()
            {
                public int compareTo(Date other)
                {
                    counter[0]++;
                    return super.compareTo(other);
                }
            };
        }
        Arrays.sort(dates);
        System.out.println(counter[0] + " comparisons");

I am confused that what is the difference between these two codes and what is the reason for this error and its solution?

Hoang Nam
  • 548
  • 3
  • 18
  • You can only use final/effectively final variables in anonymous classes and lambda expressions. Using an `int[]` is a workaround to that (which may or may not be right for your situation), since the variable `counter` is recognized as final by the compiler, but the elements inside it can still be changed. Related: [Why are only final variables accessible in anonymous class?](https://stackoverflow.com/questions/4732544/why-are-only-final-variables-accessible-in-anonymous-class). Also see [atomic variables](https://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html). – user Aug 06 '20 at 15:37
  • @user I am sorry but how could the conversion ``int counter`` into ``int[] counter`` change counter to be **effectively final**? – Hoang Nam Aug 06 '20 at 15:41
  • The conversion to an array doesn't make it effectively final. In the first example, you changed the value of `counter` using `counter++`, so it wasn't final. However, in the second example, you're not changing the variable `counter` itself, you're changing elements inside it, so `counter` is effectively final. If you're unsure if a variable is effectively final or not, mark it `final` and see if the compiler complains – user Aug 06 '20 at 15:41

1 Answers1

2

You're creating a snippet of code which can 'travel'. The code in the {} that belong to your new Date() declaration isn't run right where you wrote it; it is attached to this date object you've made, and goes with it. This date object can travel: It can be stored in a field. Maybe it is run 18 days from now, in a completely different thread. The VM has no idea, so it needs to be prepared for that to happen.

So let's say it does: What is to happen to your 'counter' variable?

Normally, local variables are stored 'on the stack' and are destroyed as the method exits. But in that case we'd be destroying a variable that your travelling code still has access to, so what does that mean, 18 days from now, when your date compareTo code is invoked?

Let's say that the VM silently 'upgrades' the variables; instead of declaring it on the stack like normal, it declares it on the heap so that the variable can survive the method exiting.

Allright. What if compareTo is invoked in another thread? Should it now be possible to mark a local variable as 'volatile'? Is it okay to state that, in java, even local variables may show race conditions?

This is a judgemental call; something for the language designers to decide.

Java's language designers decided against silent upgrade into heap and against allowing locals to potentially be subjected to multi-thread access.

Therefore, any local variable that you access in any codeblock that can 'travel'* must either [A] be declared final or [B] act as if it could have been, in which case java will silently make it final for you.

The change is that counter, the variable itself, does not change in the second snippet: it is a reference to an array, and that reference never changes. Effectively you've added the level of indirection and the heap access yourself: arrays exist on the heap.

For what its worth, I find usage of AtomicX more readable. So if you need an int that is modifyable in traveling code, don't do new int[1]; do new AtomicInteger. If you need a modifiable string, use new AtomicReference<String>(), not new String[1].

NB: Yes, in this specific code, the counter variable is only used, even by the sort op, within this method and the counter var can go away once this method ends, but the compiler doesn't do that kind of extremely in depth analysis to figure that out, it uses the much simpler rule of: Wanna access a local var from outer scipe within 'travelling' code? Not allowed - unless it is (effectively) final.

*) Traveling code is anything inside a method local or anonymous class definition, and anything in a lambda. So:


void method() {
    class MethodLocalClassDef {
        // anything here is considered 'travelling'
    }

    Object o = new Object() {
        // this is an anonymous class def,
        // and anything in here is 'travelling'
    };

    Runnable r = () -> {
        // this is a lambda, and considered 'travelling'
    };
}
rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • Your explanation is so great which even explains the reason for using ``final`` variables in local methods. But I have a question: Java is a high-level language that has a "garbage collection" mode. How could it assure that data of ``counter`` on Heap can be stored even when the program's terminated? – Hoang Nam Aug 06 '20 at 16:06
  • 1
    +1 on `Atomic*`, not only for readability but if you're writing a quick and dirty concurrent code, they save a lot of headache and boilerplate as long as you use them appropriately. – Rogue Aug 06 '20 at 16:42