Home > Blockchain >  How is inner class initialized
How is inner class initialized

Time:10-20

Below is a piece of code which provides an example of using inner class in Java. I would like to know how the inner class instance iterator is created and linked to the arrayOfInts array.

        DataStructureIterator iterator = this.new EvenIterator();

I understand that the 'ds' instance is created by the constructor of DataStructure class, but the iterator instance is of DataStructureIterator type. It seems not quite reasonable that a DataStructureIterator instance can be constructed by a constructor of another class.

Full code here:

public class DataStructure {
    
    // Create an array
    private final static int SIZE = 15;
    private int[] arrayOfInts = new int[SIZE];
    
    public DataStructure() {
        // fill the array with ascending integer values
        for (int i = 0; i < SIZE; i  ) {
            arrayOfInts[i] = i;
        }
    }
    
    public void printEven() {
        
        // Print out values of even indices of the array
        DataStructureIterator iterator = this.new EvenIterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next()   " ");
        }
        System.out.println();
    }
    
    interface DataStructureIterator extends java.util.Iterator<Integer> { } 

    // Inner class implements the DataStructureIterator interface,
    // which extends the Iterator<Integer> interface
    
    private class EvenIterator implements DataStructureIterator {
        
        // Start stepping through the array from the beginning
        private int nextIndex = 0;
        
        public boolean hasNext() {
            
            // Check if the current element is the last in the array
            return (nextIndex <= SIZE - 1);
        }        
        
        public Integer next() {
            
            // Record a value of an even index of the array
            Integer retValue = Integer.valueOf(arrayOfInts[nextIndex]);
            
            // Get the next even element
            nextIndex  = 2;
            return retValue;
        }
    }
    
    public static void main(String s[]) {
        
        // Fill the array with integer values and print out only
        // values of even indices
        DataStructure ds = new DataStructure();
        ds.printEven();
    }
}

This is a question from the Oracle's Java documents, here is the source: https://docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html

CodePudding user response:

From your Q:

It seems not quite reasonable that a DataStructureIterator instance can be constructed by a constructor of another class.

In your code, the instance of the DataStructureIterator is actually not created by the DataStructure constructor. It is created whenever the DataStructure instance method printEven is called:

public void printEven() {
        
  // Print out values of even indices of the array
  DataStructureIterator iterator = this.new EvenIterator();

  // Omitted for brevity
}

But it could also be created in the constructor as a field or set with a default value e.g.:

public class DataStructure {
  private DataStructureIterator iterator = new EvenIterator();

  // Omitted for brevity
}

If your inner class (EventIterator) was public, it could also be created "outside" of DataStructure:

// In some other Java class
DataStructure ds = new DataStructure();
DataStructure.EventIterator iterator = ds.new EventIterator();

The key thing is that outer class (DataStructure) instance if created first.

As the docs note:

To instantiate an inner class, you must first instantiate the outer class

This is because the instance of an inner class is always tied to the outer/parent class.


From your Q:

I would like to know how the inner class instance iterator is created and linked to the arrayOfInts array.

The key thing is that an instance of a inner non-static class is that always has access to the parent instance's methods/fields.

As the docs also state (emphasis added):

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.

As such, the instance of the DataStructureIterator can access the DataStructure field arrayOfInts.

But note that if you created a separate DataStructure instance e.g. ds2, then the DataStructureIterator instance within ds2 will have access to ds2.arrayOfInts but it won't have access to the ds.arrayOfInts. In your code the values of arrayOfInts will be the same but the instances are actually different (try allowing arrayOfInts to be set via the DataStructure constructor).

If DataStructureIterator was defined as a nested static class, then it won't automatically have access to arrayOfInts and it would need arrayOfInts passed in as an arg to it's constructor or to it's method.

You can think of inner non-static classes as a logical grouping of code for the parent (DataStructure class) and the docs linked above outline good reasons when to use them. But in general don't use them unless you know what you're doing.

CodePudding user response:

An inner class is instantiated the same as any other class is, just the scope of the class (and thus its instances) is different.

So yes, your inner class instance is created by a call inside the outer class method (not the constructor in the case of your example, but the printEvent method).

I don't know what you mean with the "ds instance is created by the constructor of DataStructure". If you mean that the class is initialised by the constructor, no.

CodePudding user response:

A non-static inner class is best thought of as follows (because.. they really are exactly like this, at the class level):

  • They have a secret (invisible) final field of the outer type's type.
  • All constructors, even the default (empty) one have a secret additional parameter of the same type, and they all have an additional extra syntax sugar line at the top, in the vein of this.theFinalFieldIMentionedEarlier = thatParameter;.
  • Calling new InnerClass() will silently pass this along as that parameter.
  • If no this that fits is available, you must pass the parameter yourself. However, instead of writing new InnerClass(instanceOfOuter), you write instanceOfOuter.new InnerClass() instead. The effect is identical, and at the class level, there is no difference.
  • Generics is rather wonky; if the outer has it, oof.

As a consequence:

Do not use non-static inner classes unless you really know what you are doing and you understand and want this weird field. If you don't, or if you feel this is confusing (and it is; most java programmers do not know this is how it works), you might want to consider making your inner class static, and making that field explicit - i.e. handrolling this functionality. It's not particularly complicated (requires 1 field, 1 constructor parameter, and 1 this.outer = outer; statement in the constructor, not exactly a boatload of boilerplate), and clears up rather a lot.

So how does it work - exactly as if it had that field. Because, that's exactly what javac does. inner classes mostly don't exist at the class level, they just become top level classes with funky names (Outer$Inner), a boatload of synthetic methods to bridge private methods together. In newer JVMs, there's the nestmates system that avoids some of those bridges, but they're still individual top-level classes after javac is done with it.

In other words, this:

class Outer {
  class Inner {
    public Inner(int example) {}
  }

  void test() {
    new Inner();
  }
}

is virtually identical to:

class Outer {
  static class Inner {
    private final Outer outer;
    public Inner(Outer outer, int example) {
      this.outer = outer;
    }
  }

  void test() {
    new Inner(this);
  }
}
  •  Tags:  
  • java
  • Related