Checker InnerClasses   as of Julia version 2.5.0 (built on 4 Jul 2018)

belongs to group Basic

Identify problems in inner classes


This checker identifies problems related to the definition of inner classes. Namely, inner classes can be defined in Java as static or non-static. The former cannot directly access instance fields of the outer class, while the latter have the ability to refer to the instance of the outer class passed at time of their creation and consequently also to its instance fields. However, this comes at the price of embedding an implicit reference, in each instance of the inner class, to the wrapping instance of the outer class, which makes objects larger and prevents garbage collection of the outer instance. This can result in memory exhaustion. Moreover, non-static inner classes expose the risk of ambiguity for method calls that could refer to both local methods and methods of the outer class.

Action: Check if inner classes can be made static, consequently saving memory and reducing the burden on the garbage collector. Avoid using synonyms for methods in the inner class and in the outer class.

Examples


Consider the following program:

public class InnerClasses {
  public void foo() {}
  public void test() {}

  public class C1 {
    public void foo() {}
    public void test() {
      foo();
    }
  }

  public static class C2 {
    public void foo() {}
    public void test() {
      foo();
    }
  }

  public final InnerClasses o1 = new InnerClasses() {
		
    @Override
    public void test() {
      foo();
    }
  };

  public final InnerClasses o2 = new InnerClasses() {

    @Override
    public void test() {
      foo();
    }

    @Override
    public void foo() {}
  };

  public final static InnerClasses o3 = new InnerClasses() {
		
    @Override
    public void test() {
      foo();
    }
  };

  public final static InnerClasses o4 = new InnerClasses() {

    @Override
    public void test() {
      foo();
    }

    @Override
    public void foo() {}
  };
}

This checker issues the following warnings:

InnerClasses.java:5: [InnerClasses: InnerClassShouldBeStaticWarning] Class InnerClasses$C1 should be made static
InnerClasses.java:8: [InnerClasses: AmbiguousCallFromInnerClassWarning] This call to foo is ambiguous, since a synonym method appears in the outer class as well

since inner class C1 does not access the outer class instance: it does not access its instance fields nor calls its instance methods. hence, it does not need the implicit reference to the instance of the outer class and could be made static to reduce the burden on the garbage collector. Moreover, the call to foo() at line 8 could be interpreted as both directed to the method of the inner class itself or the synonym method of the outer class. This ambiguity might be a risk of correctness for the code.

In this example, the programmer should for instance rewrite the inner class C1 as follows:

public static class C1 {
  public void moo() {}
  public void test() {
    moo();
  }
}

Note that no warning is issued instead for class C2, which is already static and consequently cannot refer to the outer instance, nor to method foo() of the outer instance. For this reason, the call to foo() at line 15 can only refer to the definition of the method at line 13 and is unambiguous. Moreover, the calls to foo() at lines 31 and 50 are unambiguous as well and Julia issues no warning there, although the two (anonymous) inner classes in the example define their own foo() methods. These are, however, redefinitions of a method of the superclass, hence not extra synonym methods.

Handling of anonymous inner classes

By default, this checker does not verify if anonymous inner classes can be made static. This is for instance the case of the anonymous inner classes at lines 19 and 27, that do not refer to the instance of the outer class. However, the problem with the burden on the garbage collector is still present for anonymous inner classes, in particular if they are returned from methods or are executor objects, such as threads or Android's AsyncTask's or Handler's. The checkAnonymousForStatic option allows one to check when anonymous inner classes can be made static. In the previous example, that option would generate two more warnings:

InnerClasses.java:19: [InnerClasses: InnerClassShouldBeStaticWarning] Refactor this anonymous inner class and make it into a named static class
InnerClasses.java:27: [InnerClasses: InnerClassShouldBeStaticWarning] Refactor this anonymous inner class and make it into a named static class

Since anonymous inner classes cannot be defined as static in Java, Julia suggests to define a named static class, typically private, such as the inner class

private static class C3 extends InnerClasses {

  @Override
  public void test() {
    foo();
  }
}

and then modify line 19 into

public final InnerClasses o1 = new C3();