Sunday, May 11, 2014

Take a look at Local Classes

what will it be about?

Recently I tried to explain what nested classes are and what you can do with them. There were a few words about Static Nested Classes and Inner Classes. I've also mentioned that there are two more types, which are special kind of Inner Class. And today I want to write something about one of it - Local Classes.

Ok, that's all great, but what Local Class is?
Local classes - classes that are defined in a block, which is a group of zero or more statements between balanced braces.
Which means that you can define a Local Classes inside any block.
For example you can define a Local Class in a method body, a for loop, or an if clause.

create an instance

Because those classes are declared inside a block (usually inside method body) it's impossible to create an instance from outside of the block. However, we can try to write a simple test. Maybe it won't be the greatest one, but at least will prove that written code works :)

Here it is:
@Test
public void createInstanceOfLocalClass() {
    assertTrue(new WithLocalClass().isLocalClassAlive());
}
Now, let's try to create code which will pass this test. Of course, the simplest way would be to return true from the method, but we want to verify how the feature works. That's why we should be honest with ourselves and use what we want to :)

And here's the code which should make the test green:
class WithLocalClass {

    boolean isLocalClassAlive() {
        class LocalClass {
            boolean imAlive() {
                return true;
            }
        }
  
        return new LocalClass().imAlive();
    }
}

I believe that everything is obvious in here, but world offers us so many possibilities that we have to at least try with something different.

Let's make this class static and let's see what will happen:
static class LocalClass {
    boolean imAlive() {
        return true;
    }
}
Execute the test and you will see:
java.lang.Error: Unresolved compilation problem: 
    Illegal modifier for the local class LocalClass; only abstract or final is permitted
Of course, this code had no chance to compile. In Java we have only one type of classes which can be static and I presented it recently - those are Static Nested Class.
On the other hand, how this declaration should behave on each method execution? I believe that for you also such an ideas sounds ridiculous :)

However, if you want to know why there's no sense of having static classes look at this link :)

access to members of outer class

Now we will add a couple of attributes to our outer class:
class WithLocalClass {

    private static String staticAttr = "static";
    private String regular = "regular";
    final private String finalAttr = "final";

    // code
}
And we will write a few tests which will allow us to verify whether local classes have access to those attributes or not:
@Test
public void accessToStaticEnclosingClassAtribute() {
    assertSame("static", new WithLocalClass().getStatic());
}

@Test
public void accessToRegularEnclosingClassAtribute() {
    assertSame("regular", new WithLocalClass().getRegular());
}

@Test
public void accessToFinalEnclosingClassAtribute() {
    assertSame("final", new WithLocalClass().getFinal());
}
And the methods can look like this:
String getStatic() {
    class LocalClass {
        String get() {
            return WithLocalClass.staticAttr;
        }
    }
 
    return new LocalClass().get();
}

String getStatic() {
    class LocalClass {
        String get() {
            return WithLocalClass.this.regular;
        }
    }
 
    return new LocalClass().get();
}

String getStatic() {
    class LocalClass {
        String get() {
            return WithLocalClass.this.finalAttr;
        }
    }
 
    return new LocalClass().get();
}
As you can see, there's no magic in here and everything is pretty intuitive.

One additional thing is that in methods above you could use short attributes' names: staticAttr, regular, finalAttr instead of long ones. Personaly, I encourage you to use the longer one, because you won't have to bother if you would like to use the same attribute's name inside local class.

access to method variables

And what if we will have a variable inside the method body and we would like to use this variable in a local class. Is it possible? Let's check it.

Test first:
@Test
public void accessToRegularMethodVariable() {
    assertSame("regular var", new WithLocalClass().getRegularVar());
}
then code:
String getRegularVar() {
    String regularVar = "regular var";

    class LocalClass {
        String get() {
            return regularVar;
        }
    }
 
    return new LocalClass().get();
}
and finally - check whether it works.
Unfortunately, our tries ends up with following problem:
java.lang.Error: Unresolved compilation problem: 
    Cannot refer to a non-final variable regularVar inside an inner class defined in a different method
This 'non-final' looks interesting isn't it? And, by the way, gives us a hint. Ok, let's modify our variable and try with:
final String finalVar = "final var";
and run test once again. It passes, isn't it?

As you can see, local class has access to local variables, but only those one which are declared final. Why is that? Because it has to be copied so it can be used in local class. And it has to be final to be sure that variable won't change during local class's code execution. However, it's only about reference, so if variable references to an object, there's no problem if you would like to do something with an instance :)
Starting from Java 8, a local class can access to variables which are effectively final, which means never changed after it's initialized.

One more thing, which is also good to know, is that starting from Java 8, local class can access also to method's parameters (which has to be final or effectively final).

If you want to read more about this, here is a link which shows what you should do if you want to use mutable variables. Here's a few words about captured variables and here's about reasons which stands for having those variables declared as final. And the last one is about differences between final and effectively final.

shadowing

Call me lazy, but because shadowing works exactly the same as with nested and inner classes I'm just putting reference to my previous post and just take a look at examples shown there:)

and where the static is?

What I can propose to answer for this question? Well, maybe we can write a test?
@Test
public void cannotDefineStaticMember() {
    assertSame("static member", new WithLocalClass().getStaticMember());
}
Code for this should be simple to write:
String getStaticMember() {
    class LocalClass {

        private static String staticMember = "static member"; 

        String get() {
            return staticMember;
        }
    }
 
    return new LocalClass().get();
}
Nothing bad can happen... yeah, right :) Try to run it and you will see:
java.lang.Error: Unresolved compilation problem: 
    The field staticMember cannot be declared static in a non-static inner type, unless initialized with a constant expression
But I believe, that after read of first paragraph, you already expected this result and it wasn't surprising :)

The more interesting thing will come with the following change in code above:
private static final String staticMember = "static and final"; 
Try to run the test once again and you will see... green?
It works because if a primitive type or a string is defined as a constant and the value is known at compile time, the compiler replaces the constant name everywhere in the code with its value. [Oracle Documentation - Understanding Class Members]
Was that surprising?

that's all for today

I know that it took more that I promised, but hopefully it was worth to wait for this post:)

As always I'm waiting for your comments :)

No comments:

Post a Comment