From docs:
As with instance methods and variables, an inner class is associated
with an instance of its enclosing class and has direct access to that
object's methods and fields.

Now taking the second example first, when i
is declared in parent class, the inner class can access it, because inner class has access to entire parent object. And i
is correctly referenced as Main.this.i
from inner class.Now compiler secretly makes a copy of this.i
inside inner class. this
never changes inside the object, this.i
will point to correct object for both inner and outer class. Compiler is happy.
In first example, i
is not part of parent class, its declared inside a method hence its reference is now on stack, and not on heap. (In this case, i
doesn't get to live on that big outer circle shown in above diagram). Now compiler must secretly make a copy of i
inside inner class as well. But it is afraid that method's i
may change on stack. Compiler can't use outer class connection to get to i
here, and it can't bother to copy i
reference from stack every time it changes. So, it is decided that you must make the method's i
reference unchangeable, or in other words, final
.
The obscure secret tricks Java plays so that you can access outer fields from inner classes is explained in detail here: http://techtracer.com/2008/04/14/mystery-of-accessibility-in-local-inner-classes/
and discussed more "why's" here : http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg04044.html
tl;dr: Instance fields are directly accessible by inner classes, local fields must be made final to be accessible by a inner class.