Class

Java does not distinctly support the notion of a "struct", but a "class" full of data members accomplishes the same end. The default access for members of Java classes is known by the slang "friendly" (see the Modifiers section). This is not the same as public, but it is close enough for our purposes here. A "struct" is allocated just like an array or object, and then its members are accessed directly by use of the "." operator.
   class JustAStruct {   // no access modifiers, no methods, no constructors
      String name;
      int    value;
   }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         JustAStruct struct = new JustAStruct();
         struct.name  = "abcxyz";
         struct.value = 123;
         System.out.println( "struct is " + struct.name + "--" + struct.value );
   }  }
   
   // struct is abcxyz--123
Here, two simulated free functions have been added to model how OO programming used to be implemented in C: create data structions, create algorithms, and pass data structures to algorithms.
   class JustAStruct {
      String name;
      int    value;
   }
   
   public class ClassDemos {
      public static void set( JustAStruct str, String nam, int val ) {
         str.name = nam;
         str.value = val;
      }
      public static void report( JustAStruct str ) {
         System.out.println( "struct is " + str.name + "--" + str.value );
      }
   
      public static void main( String[] args ) {
         JustAStruct struct = new JustAStruct();
         set( struct, "abcxyz", 123 );
         report( struct );
   }  }
   
   // struct is abcxyz--123
A class is the co-location of state and behavior, or, the encapsulation of implementation behind a published interface. We have transitioned the "struct" into a class by:
  1. making the data members private
  2. moving the functions into the class as public member functions
  3. eliminating the "struct" arguments
  4. accessing the data members directly in the member functions
  5. calling "methods" on objects instead of passing "structs" to functions
   class ARealClass {   // private state, public methods
      private String name;
      private int    value;
   
      public void set( String nam, int val ) {
         name = nam;
         value = val;
      }
      public void report() {
         System.out.println( "object is " + name + "--" + value );
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         ARealClass object = new ARealClass();
         object.set( "abcxyz", 123 );
         object.report();
   }  }
   
   // object is abcxyz--123
Initializing variables at the same time as they are created has always been a strongly encouraged practice. Classes provide a language feature called "constructors" for just that purpose. Whenever an object is created, a constructor from its class is called automatically so that the object can be put in an "appropriate initial state".

The compiler recognizes constructor definitions because they: have the same name as the class, have no return type, and accept zero or more arguments. In the example below, the constructor provides default values of "xxx" and 999 for the class's two private data members.

   class ARealClass {         // constructor added
      private String name;
      private int    value;
   
      public ARealClass() {
         System.out.println( "ARealClass constructor" );
         set( "xxx", 999 );
      }
      public void set( String nam, int val ) {
         name = nam;
         value = val;
      }
      public void report() {
         System.out.println( "object is " + name + "--" + value );
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         ARealClass object = new ARealClass();
         object.report();
         object.set( "abcxyz", 123 );
         object.report();
   }  }
   
   // ARealClass constructor
   // object is xxx--999
   // object is abcxyz--123
Unlike C++, if the programmer forgets, and specifies a return type for a member function intended to serve as a constructor; Java will treat that function as a normal member function. The code below compiles, and the client of class ARealClass can call the method void ARealClass() just like any other member function.
   class ARealClass {         // constructors do not have a return type
      public ARealClass() {
         System.out.println( "constructor" );
      }
      public void ARealClass() {
         System.out.println( "void method" );
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         System.out.println( "main" );
         ARealClass object = new ARealClass();
         object.ARealClass();
   }  }
   
   // main
   // constructor
   // void method
Function overloading was discussed in the Basics section. Below, the set() function can be called with 0, 1, or 2 arguments. Constructors can also be overloaded. That is not happening in the example. The only way to create an instance of class ARealClass is by supplying a single String argument. In main(), when no arguments are supplied, the compiler complains that no arguments is not an option.

In both Java and C++, if a class does not define any constructors, then a no-argument constructor (or "default constructor") is provided by the compiler automatically. But, as soon any explicit constructors are defined, then a default constructor is not supplied by the compiler.

   class ARealClass {         // function overloading, missing constructor
      private String name;
      private int    value;
   
      public ARealClass( String name ) {
         System.out.println( "ARealClass constructor" );
         set( name );
      }
      public void set() {
         set( "xxx", 999 );
      }
      public void set( String name ) {
         set( name, 999 );
      }
      public void set( String nam, int val ) {
         name = nam;
         value = val;
      }
      public void report() {
         System.out.println( "object is " + name + "--" + value );
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         // ARealClass object = new ARealClass();
         // Error: No constructor matching ARealClass() found in class ARealClass
         ARealClass object = new ARealClass( "aaa" );
         object.report();
         object.set( "abcxyz", 123 );
         object.report();
   }  }
   
   // ARealClass constructor
   // object is aaa--999
   // object is abcxyz--123
Four overloaded constructors are demonstrated below. There are now 4 ways of creating instances of this class.
   class ARealClass {
      private String name;
      private int    value;
      public ARealClass() {
         System.out.println( "ARealClass default constructor" );
         set( "xxx", 999 );
      }
      public ARealClass( String s ) {
         System.out.println( "ARealClass 1-String-arg constructor" );
         set( s, 999 );
      }
      public ARealClass( int v ) {
         System.out.println( "ARealClass 1-int-arg constructor" );
         set( "xxx", v );
      }
      public ARealClass( String s, int v ) {
         System.out.println( "ARealClass 2-arg constructor" );
         set( s, v );
      }
      public void set( String s, int v ) {
         name  = s;
         value = v;
      }
      public void report() {
         System.out.println( "object is " + name + "--" + value );
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         ARealClass obj1 = new ARealClass();
         ARealClass obj2 = new ARealClass( "second" );
         ARealClass obj3 = new ARealClass( 333 );
         ARealClass obj4 = new ARealClass( "fourth", 444 );
         obj1.report();
         obj2.report();
         obj3.report();
         obj4.report();
         obj1.set( "abcxyz", 123 );
         obj1.report();
   }  }
   
   // ARealClass default constructor
   // ARealClass 1-String-arg constructor
   // ARealClass 1-int-arg constructor
   // ARealClass 2-arg constructor
   // object is xxx--999
   // object is second--999
   // object is xxx--333
   // object is fourth--444
   // object is abcxyz--123
This is a group exercise. It is just like the first several examples at the top. We start with a data structure and algorithms, and the exercise is to convert it to a class with private attributes and public methods.
   class RunningTotal {
      int total;
      int count;
   }
   
   public class ClassDemos {
      public static void add( RunningTotal str, int num ) { 
         str.total += num;
         str.count++;
      }
      public static int getTotal( RunningTotal str ) { 
         return str.total;
      }
      public static double getAverage( RunningTotal str ) { 
         return (double)str.total / str.count;
      }
   
      public static void main( String[] args ) {
         RunningTotal rt = new RunningTotal();
         rt.total = 0;
         rt.count = 0;
   
         System.out.print( "Add(a), Total(t), Avg(v), Exit(x): " );
         char ch = Read.aChar();
         while (ch != 'x') {
            if (ch == 'a') {
               System.out.print( "   enter an integer: " );
               add( rt, Read.anInt() );
            } else if (ch == 't')
               System.out.println( "   total is " + getTotal( rt ) );
            else if (ch == 'v')
               System.out.println( "   average is " + getAverage( rt ) );
            System.out.print( "Add(a), Total(t), Avg(v), Exit(x): " );
            ch = Read.aChar();
   }  }  }
   
   // Add(a), Total(t), Avg(v), Exit(x): a
   //    enter an integer: 5
   // Add(a), Total(t), Avg(v), Exit(x): a
   //    enter an integer: 6
   // Add(a), Total(t), Avg(v), Exit(x): a
   //    enter an integer: 7
   // Add(a), Total(t), Avg(v), Exit(x): t
   //    total is 18
   // Add(a), Total(t), Avg(v), Exit(x): v
   //    average is 6.0
   // Add(a), Total(t), Avg(v), Exit(x): a
   //    enter an integer: 8
   // Add(a), Total(t), Avg(v), Exit(x): t
   //    total is 26
   // Add(a), Total(t), Avg(v), Exit(x): v
   //    average is 6.5
   // Add(a), Total(t), Avg(v), Exit(x): x
The "school solution". Unlike C++, Java allows data members to be statically initialized without the requirement for a constructor.
   class RunningTotal {
      private int total = 0;
      private int count;
   
      public RunningTotal() {
         total = count = 0;
      }
      public void add( int num ) { 
         total += num;
         count++;
      }
      public int getTotal() { 
         return total;
      }
      public double getAverage() { 
         return (double)total / count;
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         RunningTotal rt = new RunningTotal();
   
         System.out.print( "Add(a), Total(t), Avg(v), Exit(x): " );
         char ch = Read.aChar();
         while (ch != 'x') {
            if (ch == 'a') {
               System.out.print( "   enter an integer: " );
               rt.add( Read.anInt() );
            } else if (ch == 't')
               System.out.println( "   total is " + rt.getTotal() );
            else if (ch == 'v')
               System.out.println( "   average is " + rt.getAverage() );
            System.out.print( "Add(a), Total(t), Avg(v), Exit(x): " );
            ch = Read.aChar();
   }  }  }
   
   ////////////////////////////// lab - BasicClass \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
Java provides a way for the programmer to tell the compiler how to "map" from one constructor to another. The construct is called the "this function" here. If you use this construct, it has to be the first executable line of code in the body of a constructor. It really serves as a weak replacement for Java's lack of support for default arguments.
   class ARealClass {        // "this" function - 
      private String name;   //    mapping from one ctor to another
      private int    value;
      public ARealClass() {
         this( "xxx", 999 );
      }
      public ARealClass( String s ) {
         this( s, 999 );
      }
      public ARealClass( int v ) {
         this( "xxx", v );
      }
      public ARealClass( String s, int v ) {
         System.out.println( "ARealClass 2-arg constructor" );
         set( s, v );
      }
      public void set( String s, int v ) {
         name  = s;
         value = v;
      }
      public void report() {
         System.out.println( "object is " + name + "--" + value );
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         ARealClass obj1 = new ARealClass();
         ARealClass obj2 = new ARealClass( "second" );
         ARealClass obj3 = new ARealClass( 333 );
         ARealClass obj4 = new ARealClass( "fourth", 444 );
         obj1.report();
         obj2.report();
         obj3.report();
         obj4.report();
         obj1.set( "abcxyz", 123 );
         obj1.report();
   }  }
   
   // ARealClass 2-arg constructor
   // ARealClass 2-arg constructor
   // ARealClass 2-arg constructor
   // ARealClass 2-arg constructor
   // object is xxx--999
   // object is second--999
   // object is xxx--333
   // object is fourth--444
   // object is abcxyz--123
Whenever an object is passed to println(), its class name and an address are output by default.
   class ARealClass {        // toString() function
      private String name;
      private int    value;
      public ARealClass( String s, int v ) {
         name  = s;
         value = v;
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         ARealClass object = new ARealClass( "abcxyz", 123 );
         System.out.println( "object is " + object );
   }  }
   
   // object is ARealClass@f10d6714
If you would like more meaningful output, then you can define a toString() method, and it will be called automatically when an instance of the class is passed to println(). Of course, you can also treat it like any other public member function, and call it directly.
   class ARealClass {          // toString() function
      private String name;
      private int    value;
      public ARealClass( String s, int v ) {
         name  = s;
         value = v;
      }
      public String toString() {
         return name + "--" + value;
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         ARealClass object = new ARealClass( "abcxyz", 123 );
         System.out.println( "object is " + object );
         System.out.println( "object is " + object.toString() );
   }  }
   
   // object is abcxyz--123
   // object is abcxyz--123
This is a puzzle. See if you can guess what functionality has been encapsulated in the class BlackBox before you review its implementation below.
   public class ClassDemos {         // BlackBox - a demo class and a riddle
      public static void main( String[] args ) {
         BlackBox bb = new BlackBox();
         while (true) {
            System.out.print( "+1(i) -1(d) x2(t) /2(h) ^2(s): " );
            char ch = Read.aChar();
            if      (ch == 'i') bb.increment();
            else if (ch == 'd') bb.decrement();
            else if (ch == 't') bb.dubble();
            else if (ch == 'h') bb.half();
            else if (ch == 's') bb.square();
            System.out.println( "   BlackBox is " + bb );
   }  }  }
   
   // C:> java ClassDemos
   // +1(i) -1(d) x2(t) /2(h) ^2(s): i
   //    BlackBox is 1
   // +1(i) -1(d) x2(t) /2(h) ^2(s): t
   //    BlackBox is 2
   // +1(i) -1(d) x2(t) /2(h) ^2(s): i
   //    BlackBox is 3
   // +1(i) -1(d) x2(t) /2(h) ^2(s): s
   //    BlackBox is 11
   // +1(i) -1(d) x2(t) /2(h) ^2(s): d
   //    BlackBox is 10
   // +1(i) -1(d) x2(t) /2(h) ^2(s): d
   //    BlackBox is 7
   // +1(i) -1(d) x2(t) /2(h) ^2(s): s
   //    BlackBox is 61
The class BlackBox is just a "wrapper" around a single private int attribute. The only "magic" occurs in the toString() method, where the decimal value is converted to octal.
   class BlackBox {
      private int value = 0;
      public void increment() { value++; }
      public void decrement() { value--; }
      public void dubble()    { value *= 2; }
      public void half()      { value /= 2; }
      public void square()    { value *= value; }
      public String toString() {
         return Integer.toOctalString( value );
   }  }
In C++, the first line of main() would actually allocate 3 instances of class ARealClass, and call the default constructor for each instance. In Java, you cannot allocate an array of default-initialized objects. The first new below allocates an array of ARealClass object references. The second new allocates each individual ARealClass object. If you know at compile-time how many objects need to be created, then you can collapse the two steps into something like the last logical line of main().
   class ARealClass {           // array of objects
       public ARealClass() {
          System.out.println( "default constructor" );
       }
       public ARealClass( int i ) {
          System.out.println( "constructor " + i ); 
   }   }
   
   public class ClassDemos {
       public static void main( String[] args ) {
          ARealClass[] arr = new ARealClass[3]; // only allocates the array
          for (int i=0; i < 3; i++)
             arr[i] = new ARealClass( i );      // allocates each object
          System.out.println();
          // allocates the array AND allocates each object
          ARealClass[] arr2 = { new ARealClass(),  new ARealClass(3),
                                new ARealClass(4), new ARealClass(5) };
   }  }
   
   // constructor 0
   // constructor 1
   // constructor 2
   //
   // default constructor
   // constructor 3
   // constructor 4
   // constructor 5
Here is a very rough checklist that can be used for architecting a class.
  1. Conceive the abstraction
  2. Model the behavior (interface)
  3. Model the state (attributes)
  4. Complete the implementation
We can take main() below as our requirements for the first step. In step 2, we identify the need for a 1-argument constructor and a getNumber() method. To maintain the abstraction (the useful fiction) of a bounded random number generator class, we need a single int attribute. Then the last step would be to actually implement our two methods.
   class RandomHuston {
      private int max;
   
      public RandomHuston( int m ) {
         max = m;
      }
      public int getNumber() { 
         return (int) (Math.random() * max);
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         RandomHuston generator = new RandomHuston( 10 );
         for (int i=0; i < 35; i++)
            System.out.print( generator.getNumber() + " " );
   }  }
   
   // 5 2 7 5 8 3 0 2 1 4 2 9 2 8 7 0 4 9 7 2 7 5 7 3 2 9 3 2 3 2 4 2 4 8 0
In the example above, the only reason for having class ClassDemos is to be the "host" (or "repository") for main(). We could just as well collapse the two classes into one class. But that one class would have to have the name of its enclosing ".java" file. So in this case, the class RandomHuston has been retired.
   public class ClassDemos {         // class is the test driver
      private int max;
   
      public ClassDemos( int m ) {
         max = m;
      }
      public int getNumber() { 
         return (int) (Math.random() * max);
      }
   
      public static void main( String[] args ) {
         ClassDemos generator = new ClassDemos( 10 );
         for (int i=0; i < 35; i++)
            System.out.print( generator.getNumber() + " " );
   }  }
   
   // 9 6 0 1 0 3 3 5 5 1 4 7 2 0 2 3 4 3 3 5 2 0 5 0 1 0 6 2 1 8 1 1 4 3 9
   
   ////////////////////////////// lab - GuessGame \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - StackLab \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - IntegerLab \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
Here is another example that can be used to reflect on the rough checklist presented above.
  1. Conceive the abstraction
  2. Model the behavior (interface)
  3. Model the state (attributes)
  4. Complete the implementation
   class RandomRange {               // 
      private int min;
      private int range;
   
      public RandomRange( int minn, int max ) {
         min   = minn;
         range = max - min + 1;
      }
      public int getNumber() { 
         return (int)(Math.random()*1000) % range + min;
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         if (args.length != 2) {
            System.out.println( "usage: ClassDemos min max" );
            return;
         }
         int min = Integer.parseInt(args[0]);
         int max = Integer.parseInt(args[1]);
   
         RandomRange generator = new RandomRange( min, max );
         int[]       arr    = new int[max+3];
   
         for (int i=0; i < 100; i++)
            arr[ generator.getNumber() ]++;
   
         for (int i=0; i < arr.length; i++)
            System.out.print( (i<10?" ":"") + i + "  " );
         System.out.println();
   
         for (int i=0; i < arr.length; i++)
            System.out.print( (arr[i]<10?" ":"") + arr[i] + "  " );
         System.out.println();
   
         for (int i=0, total=0; i < arr.length; i++) {
            total += arr[i];
            System.out.print( (total<10?" ":"") + total + "  " );
         }
         System.out.println();
   }  }
   
   // D:\Java> java ClassDemos
   // usage: ClassDemos min max
   // 
   // D:\Java> java ClassDemos 1 6
   //  0   1   2   3   4   5   6   7   8
   //  0  14  23  22   9  18  14   0   0
   //  0  14  37  59  68  86  100  100  100
   // 
   // D:\Java> java ClassDemos 2 4
   //  0   1   2   3   4   5   6
   //  0   0  33  32  35   0   0
   //  0   0  33  65  100  100  100
   // 
   // D:\Java> java ClassDemos 3 4
   //  0   1   2   3   4   5   6
   //  0   0   0  53  47   0   0
   //  0   0   0  53  100  100  100
   
   ////////////////////////////// lab - NumberLab \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - FractionLab \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
static members (class attributes and class methods) work the same in Java as in C++. static attributes are shared across all instances of the class. Any object of the class can read and/or write to any static attribute. When one object changes the value of a static attribute, all objects will be affected. Unlike C++, static attributes can be initialized in the same line where they are declared.

static methods may be used before any instances of the class have been created. They can be accessed globally (just like C free functions) by simply prepending the class name and the "dot" operator. static methods (which include the main() function) may not access any data members that are not themselves static. Attempting to access non-static members will cause a compiler error.

   class ARealClass {                  // "static"
      private int notShared;           //    class attribute
      private static int shared = 1;   //    class method
   
      public ARealClass() {     // non-static member functions can
         notShared = shared++;  //    access all data members
      }
      public String toString() {
         return "id-" + notShared + ",shared-" + shared;
      }
      public void set( int in ) {
         shared = in;
      }
      public static int get() {
         // notShared++;        // COMPILER ERROR!
         return shared;         // static member functions can only
   }  }                         //    access static data members
   
   public class ClassDemos {
      public static void main( String[] args ) {
         ARealClass first  = new ARealClass();
         ARealClass second = new ARealClass();
         first.set( 42 );
         System.out.println( "first is " + first );
         System.out.println( "second is " + second );
         System.out.println( "shared is " + ARealClass.get() );
         // NOT recommended - not obvious that this is a static method
         System.out.println( "shared is " + second.get() );
   }  }
   
   // first is id-1,shared-42
   // second is id-2,shared-42
   // shared is 42
   // shared is 42
The class ARealClass1 is defined at global scope. That means: it is globally accessible (which makes things easy, but less encapsulated), and it "pollutes" the global namespace (could conflict with some class name that already exists). If we define a class inside another class, then: it is less accessible, it doesn't "pollute", and it has access to the private members of the enclosing class. The bottom line is - there are trade-offs.

A class defined within another class can only be instantiated if the enclosing class has already been instantiated. This is different than the way it works in C++. There is a problem below in trying to create an instance of class ARealClass2. To make things more intuitive and easier, an "inner" class can be defined as static.

The code below produces the following files: ARealClass1.class, ClassDemos.class, ClassDemos$ARealClass2.class, and ClassDemos$ARealClass3.class.

   class ARealClass1 { }             // static inner class
   
   public class ClassDemos {
   
      class ARealClass2 { }
   
      static class ARealClass3 { }
   
      public static void main( String[] args ) {
         ARealClass1 a = new ARealClass1();
         // ARealClass2 b = new ARealClass2();
         // Error: No enclosing instance of class ClassDemos is in scope;
         // an explicit one must be provided when creating inner class 
         // ClassDemos.ARealClass2, as in "outer. new Inner()"
         ARealClass2 b = new ClassDemos(). new ARealClass2();
         ClassDemos  c = new ClassDemos();
         ARealClass2 d = c. new ARealClass2();
         ARealClass3 e = new ARealClass3();
   }  }
main() is a static member function of class ClassDemos. That means it can access other static members of the class, even private members. main() is not a member of class ARealClass. That means it can only access public and "friendly" members of that class.
   class ARealClass {                // static, private, friendly
      private static int privateAttribute;
              static int friendlyAttribute;  // friendly
   }
   
   public class ClassDemos {
      private        int nonStaticAttribute;
      private static int privateStaticAttribute;
   
      public static void main( String[] args ) {
         nonStaticAttribute     = 42;        // COMPILER ERROR!
         privateStaticAttribute = 42;        // not an error
         ARealClass.privateAttribute  = 24;  // COMPILER ERROR!
         ARealClass.friendlyAttribute = 24;  // not an error
   }  }
Beyond constructors, Java provides "anonymous" initialization blocks. The static initialization block is only executed the first time the class is loaded by the Java Virtual Machine. Notice that the ClassDemos static block happens immediately - because it is the class where main() is hosted. The ARealClass static block doesn't happen until the first reference to the class occurs. A non-static initialization block is executed every time an instance is created. Notice that it duplicates the functionality of the default constructor.
   class ARealClass {
      static {
         System.out.println( "class initialization block" );
      }
      {
         System.out.println( "instance initialization block" );
      }
      public ARealClass() {
         System.out.println( "default constructor" );
   }  }
   
   public class ClassDemos {
      static {
         System.out.println( "ClassDemos static initialization block" );
      }
      public static void main( String[] args ) {
         System.out.println( "main" );
         new ARealClass();
         new ARealClass();
   }  }
   
   // ClassDemos static initialization block
   // main
   // class initialization block
   // instance initialization block
   // default constructor
   // instance initialization block
   // default constructor
   
   ////////////////////////////// lab - Counted \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - SelfNamingLab \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
Just like people exchange business cards so that one individual will have the information necessary to make a "call back" on the other, objects can also exchange "references". The this reference represents the "address" of the current object. An object can pass its this reference to another, so that the other can subsequently send it a message. Below, the object that has had its first() method invoked, passes its this reference to the object it was passed as it invokes second(), so that the other object can invoke last() on it.
   class ARealClass {        // "this" reference -
      private String name;   //    an obj passing its "address" to another
   
      public ARealClass( String n ) {
         name = n;
      }
      public void first( ARealClass other ) {
         System.out.println( name + " is first" );
         other.second( this );
      }
      public void second( ARealClass other ) {
         System.out.println( name + " is second" );
         other.last();
      }
      public void last() {
         System.out.println( name + " is last" );
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         ARealClass one = new ARealClass( "one" );
         ARealClass two = new ARealClass( "two" );
         one.first( two );
         two.first( one );
   }  }
   
   // one is first
   // two is second
   // one is last
   // two is first
   // one is second
   // two is last
Here is another example of the same kind of design. main() creates a single Node object, and Node's constructor takes care of creating the next 5 Nodes. As each downstream Node is created, the upstream Node passes its "address" so that the downstream Node can intialize its "prev" attribute.
   public class ClassDemos {   // "this" reference -
      static class Node {      //    an obj passing its "address" to another
         private int  value;
         private Node prev;
         private Node next;
         public Node( int v, Node p ) {
            value = v;
            prev  = p;
            next  = ((v == 6) ? null : new Node( v+1, this ));
         }
         public void goDown() {
            System.out.print( value + " " );
            if (next != null) next.goDown();
            else prev.goBack();
         }
         public void goBack() {
            System.out.print( value + " " );
            if (prev != null) prev.goBack();
      }  }
   
      public static void main( String[] args ) {
         Node root = new Node( 1, null );
         root.goDown();
   }  }
   
   // 1 2 3 4 5 6 5 4 3 2 1
Another purpose served by the this reference is returning the "address" of the current object from the current method invocation, so that a subsequent method invocation can be "cascaded" (or "chained") to the previous invocation. Here, both doThis() and doThat() promise to return an object of type ARealClass, and they follow through on that promise by returning their this reference. The main() is now free to ask that returned object to perform any service its class's public interface supports.
   class ARealClass {        // "this" reference -
      private String name;   //    an obj returning its "address" for cascading
   
      public ARealClass( String n ) {
         name = n;
      }
      public ARealClass doThis( String str ) {
         System.out.println( name + " doThis " + str );
         return this;
      }
      public ARealClass doThat( String str ) {
         System.out.println( name + " doThat " +  str );
         return this;
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         ARealClass object = new ARealClass( "one" );
         object.doThis( "here" ).doThat( "there" ).doThis( "every where" );
   }  }
   
   // one doThis here
   // one doThat there
   // one doThis every where
   
   ////////////////////////////// demo - DateHuston \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - Processor \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - LinkedListLab \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
Here is an example of aggregation - a class that has attributes that are instances of other classes. Whenever an instance of class ClassDemos is created, the constructor of ClassDemos is not called until all of the attributes have been initialized. Note that the Stereo attribute could also have been initialized when it was declared, but it is different just to demonstrate flow of control.
   class ClassDemos {       // aggregation or "hasa" relationship
      static class Engine { Engine() { System.out.println( "engine" ); } }
      static class Wheel  { Wheel()  { System.out.println( "wheel" );  } }
      static class Stereo { Stereo() { System.out.println( "stereo" ); } }
   
      private Engine  eng = new Engine();
      private Wheel[] whs = { new Wheel(), new Wheel() };
      private Stereo  ste;
   
      public ClassDemos() {
         System.out.println( "ClassDemos - car" );
         ste = new Stereo();
      }
    
      public static void main( String[] args ) {
         new ClassDemos();
         System.out.println();
         new ClassDemos();
   }  }
   
   // engine
   // wheel
   // wheel
   // ClassDemos - car
   // stereo
   //
   // engine
   // wheel
   // wheel
   // ClassDemos - car
   // stereo
Aggregation ("has a") is a "whole-part" relationship. When the containing class is instantiated, all the contained objects are automatically instantiated. The life-time of the contained objects are entirely determined by the life-time of the container object. The container object "owns" the contained objects.

Association ("uses a") is a "peer-peer" relationship. The life-times of the objects participating in this informal affiliation are not in any way tied to each other. They each hold a reference to the other for the purpose of collaboration or dialogue.

In this example, the "spouse" attribute is modelling the tighter, more fundamental relationship of aggregation. The "other" attribute is modelling the looser, more temporal relationship of association.

   class PersonClass {
      private String      name;
      private PersonClass spouse;   // hasa  => ownership
      private PersonClass other;    // usesa => collaboration, simple dialogue
   
      public PersonClass( String n ) {
         name = n;
      }
      public PersonClass( String n, String s ) {
         this( n );
         // the containing object creates the contained object
         System.out.println( name + " is creating " + s );
         spouse = new PersonClass( s );
      }
      public void meet( PersonClass o ) {
         greet( o );
         other.greet( this );
      }
      public void greet( PersonClass o ) {
         System.out.println( name + " is remembering " + o.name );
         // the peer object remembers a reference to the other peer
         other = o;
      }
      public void talk() {
         System.out.println( name + " is talking" );
         other.listen();     // tell peer object to listen
         spouse.listen();    // tell contained object to listen
      }
      public void listen() {
         System.out.println( "   " + name + " is listening" );
   }  }
   
   public class ClassDemos {
      public static void main( String[] args ) {
         PersonClass fred   = new PersonClass( "Fred",   "Wilma" );
         PersonClass barney = new PersonClass( "Barney", "Betty" );
         fred.meet( barney );     System.out.println();
         fred.talk();             System.out.println();
         barney.talk();
   }  }
   
   // Fred is creating Wilma
   // Barney is creating Betty
   // Fred is remembering Barney
   // Barney is remembering Fred
   // 
   // Fred is talking
   //    Barney is listening
   //    Wilma is listening
   // 
   // Barney is talking
   //    Fred is listening
   //    Betty is listening
   
   ////////////////////////////// lab - Hasa \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - MasterMind \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Modifiers

Modifiers are Java keywords that give the compiler information about the nature of code, data, or classes. Java does not care about the ordering of modifiers. The first four modifiers below are called "access modifiers" - they control the accessibility of the feature they are attached to. ["friendly" is not a Java keyword, it is simply the name given to the access level that results from not specifying an access modifier.]

Modifier

class

data member

member function, ctor

public Yes - may be accessed by anyone, anywhere Yes - ditto Yes - ditto
protected No/Yes - only inner classes may be protected Yes - may be accessed by all subclasses, or by any class in the same package, as the class that owns the feature. More accessible than "friendly" features Yes - ditto
friendly Yes - the default. May be accessed by any class in the same package. May NOT be used by subclasses that are not in the same package. Yes - ditto Yes - ditto
private No/Yes - only inner classes may be private Yes - may only be accessed by an instance of the class Yes - ditto
final Yes - a final class may not be subclassed (e.g. String) Yes - a final data member may not be modified Yes - a final member function may not be overridden
abstract Yes - may not be instantiated, it must be subclassed No Yes - no definition or body is provided in the defining class, the implementation must be provided by a subclass. A class with one or more abstract methods must itself be abstract. Any subclass of this class must define a body for the method, or declare itself to be abstract.
static Sortof - a class may have block(s) of initialization code that are surrounded by curly braces and labeled static. This code is executed exactly once, at the time the class is loaded. Yes - shared by all instances of the class Yes - can be accessed without an instance of the class, cannot reference any non-static members of the class
native No No Yes - the implementation of the method is to be found in an "outside" library. Native code is written in a different language and compiled for a single target machine type.
transient No Yes - not output or serialized, not stored when instances of the class are "persisted" No
synchronized No No Yes - the instance is "locked" while the method is running - no other threads may access any synchronized methods until the original method completes

If you do not specify an access modifier for a class member it will be assigned "package" (or "friendly") accessibility. This means it is usable by all class methods in the same package. [It also means it is only usable by those methods (the glass is half empty).]

If a Java source file does not specify a package statement as the first non-comment statement in the file, then the classes contained in that file will be put in an unnamed default package that is always imported by other source files.
A class member may be assigned the access modifier of private protected. This is like the "protected" feature in C++. The member is accessible to derived classes, but not to classes in the same package.

Java specifies that methods may be overridden to be less "private", but they may NOT be overridden to be more "private". Given the list of ascending "public-ness", you may change the access modifier of an overridden method in derived classes by moving "down" the list.

  1. private
  2. friendly
  3. protected
  4. public
With the final keyword, you can initialize the variable after it is declared if the compiler can verify that it will not be used before it is set. Such a final variable is called a "blank final". Blank finals give you an alternative to a "get" method for data members that are only set during construction. [Gosling, p112]   Below, "id" is declared public final, it is initialized in the constructor, and it is read directly in main(). Any attempt to write to "id" is a compile error.
   class ClassDemos {
      public  final  int id;
      private static int next = 1;
   
      public ClassDemos() { id = next++; }
   
      public static void main( String[] args ) {
         ClassDemos[] array =
                      { new ClassDemos(), new ClassDemos(), new ClassDemos() };
         // array[0].id = 99;            // COMPILER ERROR!
         for (int i=0; i < array.length; i++)
            System.out.print( array[i].id + "  " );
         System.out.println();
   }  }
   
   // 1  2  3