Sunday, April 6, 2014

Take a look at Nested Classes

what will it be about?

After a few posts about Enums I want to present you another interesting feature of Java which is Nested Classes.
In next a few articles I will try to introduce to you inner, static nested, local and anonymous classes. Of course, everything with usage of TDD :)

I will start with some basics - how to create a particular type of class, what is allowed and what's not. After introduction we will move forward and I will present some code which will show the ways of usage of those classes and, what's more important, the problems that result from their use.

I've gathered some material, I wrote a couple tests and it's turned around there is more text and code than I expected. That's why I decided to split it into a few shorter posts. Hopefully next ones will be published shortly, everything depends on how fast I will have a possibility to edit them.

Ok, let's move to the interesting part :)

nested, static nested and inner

As usual, let's start with terminology.

A Nested Classes are classes which are defined within another class. We can divide them into two categories:
  • Static Nested Class - nested class declared as static.
  • Inner Class - non-static nested class.

let's create an instance

We have got following class with one inner and one static nested class:
class OuterClass {

    static class StaticNestedClass {
  
        boolean amINested() {
            return true;
        }
    }
 
    class InnerClass {
  
        boolean amIInner() {
            return true;
        }
    }
}

And now we can try to create an instances of those classes:
@Test
public void createInstanceOfStaticNestedClassWithoutInstanceOfOuter() {
    assertTrue(new OuterClass.StaticNestedClass().amINested());
}

@Test
public void createInstanceOfStaticNestedClassWithInstanceOfOuter() {
    assertTrue(new OuterClass.new StaticNestedClass().amINested());
}

@Test
public void createInstanceOfInnerClassWithoutInstanceOfOuter() {
    assertTrue(new OuterClass.InnerClass().amIInner());
}

@Test
public void createInstanceOfInenerClassWithInstanceOfOuter() {
    assertTrue(new OuterClass().new InnerClass().amIInner());
}

I believe that your IDE will highlight methods createInstanceOfStaticNestedClassWithInstanceOfOuter() and createInstanceOfInnerClassWithoutInstanceOfOuter(), but try to run tests anyway.

As you can see, in both cases we received compilation problems.

It's "Syntax error on token "new", delete this token" for createInstanceOfStaticNestedClassWithInstanceOfOuter() and the reason why it appears is because the static nested class is nested in another only for packaging convenience. It's independent from instance of the outer class, we can create it even when not a single outer class exists and it's life time is not related with life time of any instance of outer class.

With createInstanceOfInnerClassWithoutInstanceOfOuter() it's "No enclosing instance of type OuterClass is accessible. Must qualify the allocation with an enclosing instance of type OuterClass (e.g. x.new A() where x is an instance of OuterClass)". I believe, the reason why it failed is obvious to you. This is the same as with non-static methods and members. We cannot use them without an object, because those are part of the instance, aren't common for all objects.

static in inner class

Declaration of any method or class member as static in inner class acts exactly the same in both cases, that's why I will present only one example, with static method. I've added following method to InnerClass:
static boolean canContainStaticMethod() { 
    return false; 
}

And I wrote two tests for this:
@Test
public void innerCannotContainStaticMethod() {
    assertFalse(new OuterClass().InnerClass.canContainStaticMethod());
}
 
@Test
public void innerCannotContainStaticMethod2() {
    assertFalse(new OuterClass().new InnerClass().canContainStaticMethod());
}

If you will run those code it won't compile and you will see "The method canContainStaticMethod cannot be declared static".
Why? It's simple. As I showed in previous paragraph you cannot access a non-static inner class instance without having an outer class instance. There is no static way to have an access to InnerClass and, in particular, to its static methods.

static in static nested class

Now we have following method in our StaticNestedClass:
static boolean canContainStaticMethod() { 
    return true; 
}

and test:
@Test
public void outerCanContainStaticMethod() {
    assertTrue(OuterClass.StaticNestedClass.canContainStaticMethod());
}

And it's passes.
As I wrote above "static nested class is nested in another only for packaging convenience" and can be treated as top-level class. This lead us to simple conclusion - if top-level class can contain statics, there is no problem to add them to static nested class.

access to members of outer class

Ok, and what about access to members of enclosing class? Is it possible or not? Well, let's check it :)

I've added one static field to OuterClass:
class OuterClass {
 
    private static int favouriteNumber = 13;
    // some code
}

and I've written two tests:
@Test
public void staticNestedHasAccessToStaticMember() {
    assertEquals(13, new OuterClass.StaticNestedClass().getFavouriteNumber());
}

@Test
public void innerHasAccessToStaticMember() {
    assertEquals(13, new OuterClass().new InnerClass().getFavouriteNumber());
}

And it lead me to following implementation:
static class StaticNestedClass {
    // some code
    int getFavouriteNumber() {
        return OuterClass.favouriteNumber;
    }
}

// some code

class InnerClass {
    // some code
    int getFavouriteNumber() {
        return OuterClass.favouriteNumber;
    }
}

And we succeed. After running tests everything is green :)

What about regular class attributes?
I will present how we can do it with inner class in next paragraph, so let's take a look at static nested class. We already know that it can be treated as top-level class which means that it can have access to members of the enclosing class only via reference, but because of the scope it should be possible to have access to private attributes as well.

Am I right?
@Test 
public void staticNestedHasAccessToPrivateMemeber() {
    String sebastian = "Sebastian";
    OuterClass outer = new OuterClass(sebastian);
 
    assertEquals(sebastian, new OuterClass.StaticNestedClass().getName(outer));
}

And new method in StaticNestedClass:
String getName(OuterClass outer) {
    return outer.name;
}

Ok, this time I was right :)

shadowing

What if member of enclosing class have got the same name as member of inner class? Let's check it:
@Test
public void shadowing() {
    String innerName = "Inner";
    String outerName = "Outer";
    OuterClass.InnerClass inner = new OuterClass(outerName).new InnerClass(innerName);

    assertSame(innerName, inner.getName());
    assertSame(outerName, inner.getOuterName());
}

And now let's try to make test pass. I've end up with following code:
class OuterClass {

    // some code

    OuterClass(String name) {
        this.name = name;
    }
}

class InnerClass {

    // some code

    InnerClass(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    String getOuterName() {
        return OuterClass.this.name;
    }
}

As you can see, if you want to use member of enclosing class you just have to use 'this' keyword. Simple and intuitive :)

summary and what's next?

That's all for today. Hopefully you liked it :)
Next time I will write something about Local Classes which are one of two special kind of Inner Class.

As usual I'm waiting for your comments.

No comments:

Post a Comment