Chapter 5: Initialization and Cleanup

5.1: Constructor

Same as C++.

5.2: Method Overloading

Same as C++.

Different overloaded methods must have different signatures. Return type can not be used to distinguish different overloaded methods, because when you have two methods "void f1()" and "int f1()" and you use a call

   f1();

you may be calling either of the methods.

5.3: this Handle

The same as in C++.

As mentioned in Chapter 1 static Data Members, when a non-static method is called, a handle of the object is implicitly passed to it. Keyword this can thus be used in this method to represent the passed handle.

In a static method, because no object handles are passed, you can not use this.

* Call Other Constructors Inside a Constructor

In a constructor, when you pass a list of arguments to this keyword, it has a different meaning: you are making an explicit call to another overloaded constructor of the same class with a matching signature:

   
   Test(int a1, float a2)   // Test is a class
   {
      this(a1);
      ...
   }

There can be only one such call, and it must be the first statement in the constructor.

5.4: Clean Up Objects

Java's garbage collector (GC) only starts when the memory is facing shortage. So it is possible that during the whole process of your program it never starts. This is good because the overhead of GC is avoided.

You may have three levels of requirements on cleaning up when programming:

   
   System .gc();
   System .runFinalization();

5.5: Member Initialization

There are four ways of initialization of member objects in a class:

  1. Default initialization - only for primitive objects;
  2. Initialize it when declaring it - C++ doesn't allow it;
  3. Initialize it in constructor - typical C++ style;
  4. Initialize it afterwards when necessary

The last way of initialization applies to situations that the member may not need to be initialized. To initialize it only when necessary can save some overhead. For the same reason Java doesn't automatically initialize handles with default objects.

* Default Initialization for Primitive Objects

For primitive objects, they are assigned a default value by the compiler (see Chapter 1 "Default Values for Primitives"), and for handles of objects they are assigned null. Therefore, they do not need to be explicitly initialized at all if not necessary:

   
   public class Test {
       int i1;
       static int i2;
       public static void main(String[] argu)
       {
           Test t1 = new Test();
           System.out.println(t1.i1 + " " + i2);
       }
   }

Output will be:

   
   0 0

* Initialize when Declaring

You can assign a value to an object when declaring it in the class body, which is not allowed in C++:

   
   class Measure {
      boolean stop = true;
      int a = 10;
      Depth d1 = new Depth(a);
      int b = f();
   };

When initializing the member objects, you can use new, or call other methods - so long as those methods doesn't use members that haven't been initialized:

   
   class Test1 {
      int a = f();
      int f() {  return 3; }
   }

Expression used to initialize a member when declaring it is called field initializer.

* Initialize in Constructor

Typical C++ style. For primitive objects, even if they are initialized when declared or in the constructor later, they are still first automatically initialized with the default value.

* Order of Initialization

Like C++, the order of initialization is determined by the order of definition. Even if they are scattered between method definitions, they will still be initialized first before any method can be called by the client - even if the constructor.

Just like in C++, when a class contains child objects which is initialized with new (either initialized when declared or in constructor), when an object of the parent class is created, the child objects will be created first, then the parent class's members:

   
   class Test0 {
       Test0()
       {  System.out.println("Test0's constructor!");  }
   }
   
   class Test1 {
      Test0 t0 = new Test0();
   
      Test1()
      {  System.out.println("Test1's constructor!");  }
   }
   
   class Test {
      static void main(String argu[])
      {
         System.out.println("In main, before t1");
         Test1 t1 = new Test1();
         System.out.println("In main, after t1");
      }
   }

Output will be:

   In main, before t1
   Test0's constructor!
   Test1's constructor!
   In main, after t1

5.6: Static Member's Initialization

As we already know, static members of a class exists before any object of that class is created. Practically, a static member is created when the class file is loaded into the JVM. The class file of a class is first loaded into JVM at run time at the point when that class is first-time accessed - whether its static or non-static members or methods are accessed.

5.7: Array

Unlike in C++ where array is nothing more than a pointer to the start of a continious block of memory, in Java array is an object - it has its own data members such as "length" and some useful methods. It has boundary control.

All arrays except for primitive arrays are created in a heap.

For an array of primitive objects, the elements are these primitive objects. They are initialized to 0 automatically.

There is an constant data member in all arrays named length, which stores the size of the array. The biggest subscript of an array is length-1.

When you say

   
   Test [] t1;

t1 is only a null handle of type of Test array.

When you say

   
   Test [] t1 = new Test [12];

t1 is a handle pointing to an array of null handles. You have to assign objects to the handles before using them.

In Java, when an array is declared, it is not allowed to specify the size of the array like in C++:

   
   int a[33];

The size of an array can only be specified dynamically using new. There are two ways to initialize an array:

* Array Initialization Using new

You first use new to create an array of null handles, then you use new again to initialize each array element separately:

   
   class Test0 {
       Test0() { System.out.println("Test0 constructor!"); }
   }
   
   class Test {
       static void main(String argu[])
       {
           Test0[] t1 = new Test0[3];   // declaration and initialization of array
           System.out.println("After array created!");
           for(int i = 0; i < t1.length; i++)
             t1[i] = new Test0();         // create the objects
       }
   }

Output will be:

   
   After array created!
   Test0 constructor!
   Test0 constructor!
   Test0 constructor!

* Array Initialization Using { }

You create an array of handles and the objects which the handles are pointing to at meantime:

   
   int[] a1 = {1, 2, 3, 4, 5};
   Test[] t1 = { new Test(1), new Test(2), new Test(3) };
   Test[] t1 = new Test[ ] { new Test(1), new Test(2), new Test(3) };

In the third statement, the right operand of the assignment operator is actually a function, which creates an array and returns a handle of that array. Therefore you can use it to create a new array right at the point:

   
   void f1(Object [] obj)
   {...}
   
   f1( new Object[]( new Test[]{new String("Hello"), new Integer(333)} );

Notice that in the {} the number and type of objects is not restrained.

5.8: Multidimensional Array

Java's multidimensional arrays has a totally different meaning as C++. A Java multidimensional array is constructed by a number of nested single-dimension arrays. It starts with a single-dimension array. Each of the elements of this array can be (a handle to) another sub-array. Each element in that sub-array can still be a handle to another sub-array.

After you understand this you will easily understand the following character of Java's multidimensional array: Java's multidimensional arrays doesn't need to be "square". Take a 2-D array as example, in C++ it should look like

a11 a12 a13 a14 a15

a21 a22 a23 a24 a25

a31 a32 a33 a34 a35

a41 a42 a43 a44 a45

But in Java an array can be irregular-shaped:

a1 -> a11 a12

a2 -> a21 a22 a23 a24 a25 a26

a3 -> a31 a32 a33

a4 -> a41 a42 a43 a44

To create square arrays:

   
   Test [] [] [] t1 = new Test [3] [6] [5];  

To create irregular arrays, the dimensions of the array has to be expended one by one:

   
   import java.util.*;
   
   public class Test {
      public static  void main(String[]s)
      {
         Random r1 = new Random();
         int size = Math.abs(r1.nextInt() % 3) + 2;
         int [] [] [] a1 = new int [size] [] [];
   
         for (int i = 0; i < a1.length; i++)
         {
            size = Math.abs(r1.nextInt() % 3) + 2;
            a1[i] = new int[size] [];
   
            for (int j = 0; j < a1[i].length; j++)
            {
               size = Math.abs(r1.nextInt() % 3) + 2;
               a1[i][j] = new int[size];
            }
         }
   
         int x = 1;
   
         for(int i = 0; i < a1.length; i++)
            for(int j = 0; j < a1[i].length; j++)
               for(int k = 0; k < a1[i] [j].length; k++)
               {
                  a1[i] [j] [k] = x;
                  x++;
               }
   
         for(int i = 0; i < a1.length; i++)
            for(int j = 0; j < a1[i].length; j++)
               for(int k = 0; k < a1[i] [j].length; k++)
               {
                  System.out.println(a1[i] [j] [k]);
               }
      }
   }

Statement

   
   a[i] = new Test[size] [];

points handle a[i] to a newly created 2-D array.

From the use of "a1.length", "a1[i].length" and "a1[i] [j].length" you can see, "a1", "a1[i]", a1[i] [j]" are treated as separate arrays.