OPO (other people's objects)

When an instance of a primitive type is declared, the memory for that variable is allocated as part of the operation. When an instance of a class (a Java-library-defined type like String, or a user-defined type like MyDreamClass) is declared, the memory for that object is not allocated. In fact, a variable that is an instance of some class type is not the object itself, but rather it is a "reference" to the object (an "object reference"). What this means is that objects (like arrays) are never created on the "stack"; they can only be created on the "heap".

Before you can use the variable, the actual storage for the object must be allocated. This is done with the new operator. The syntax is new type( arg1, arg2, ... ).

   class OPOdemos {
      public static void main( String[] args ) {
         String str;
         // System.out.println( "str is --" + str + "--" );
         // Error: Variable str may not have been initialized.
         str = new String( "123 abc" );
         System.out.println( "str is --" + str + "--" );
   }  }
   
   // str is --123 abc--
If you want to set-up an array of objects, then 2 levels of allocation are required. The first new is creating an array of object references. The second new inside the loop is creating each object and initializing each object reference.
   class OPOdemos {
      public static void main( String[] args ) {
         String[] array = new String[3];
         for (int i=0; i < array.length; i++)
            array[i] = new String( "element " + i );
         for (int i=0; i < array.length; i++)
            System.out.println( array[i] );
   }  }
   
   // element 0
   // element 1
   // element 2
Each class defines some number of "methods" that may be used by clients of the class. These operations are accessed just like members of an old-fashioned "struct" in C or Pascal. The only difference is that they represent functions instead of data.
   class OPOdemos {
      public static void main( String[] args ) {
         String str = new String( "123 xyz" );
         System.out.println( "the length of str is " + str.length() );
         System.out.println( "the first character is " + str.charAt( 0 ) );
         System.out.println( "the last is " + str.charAt( str.length()-1 ) );
   }  }
   
   // the length of str is 7
   // the first character is 1
   // the last is z
Most of the classes in Java's standard library require that a "fully qualified class name" be specified in order for the compiler to find the class.
   class OPOdemos {
      public static void main( String[] args ) {
         // Date today = new Date();
         // Error: class Date not found.
         java.util.Date today = new java.util.Date();
         System.out.println( "today is " + today );
   }  }
   
   // today is Thu Jan 06 10:50:53 CST 2000
The class Date is encapsulated in (or assigned to) the "package" called java.util. The "package" construct allows classes to be partitioned hierarchically. This minimizes the possibility of naming conflicts between subsystems, and, provides a mechanism for configuration management. The class String is encapsulated in the "package" called java.lang. The package java.lang never needs to be referenced explicitly. It is always "included" by default.

An alternative to providing a "fully qualified class name" whenever a package-encapsulated class is referenced is the import statement.

   import java.util.Date;
   
   class OPOdemos {
      public static void main( String[] args ) {
         Date today = new Date();
         System.out.println( "today is " + today );
   }  }
   
   // today is Thu Jan 06 11:20:24 CST 2000
Yet another short-hand alternative to the "fully qualified class name" is the use of ".*". This is called "import on demand", and it makes all members of the package visible with considerably less typing effort.
   import java.util.*;
   
   class OPOdemos {
      public static void main( String[] args ) throws InterruptedException {
         Date start = new Date();
         Thread.sleep( 2000 );
         Date finish = new Date();
         System.out.println( "ellapsed time is "
            + (finish.getTime() - start.getTime()) + " milliseconds" );
         System.out.println( "start < finish is " + start.before( finish ) );
         System.out.println( "start > finish is " + start.after( finish ) );
   }  }
   
   // ellapsed time is 2000 milliseconds
   // start < finish is true
   // start > finish is false
The class DateFormat allows for formatting (date => text), parsing (text => date), and normalization. The date is represented as a Date object or as the milliseconds since January 1, 1970, 00:00:00 GMT. SimpleDateFormat supports user-defined patterns for date-time formatting.
   import java.util.Date;
   import java.text.*;
   
   class OPOdemos {
      public static void main( String[] args ) throws ParseException {
         Date today = new Date();
         System.out.println( "today is " + today );
   
         DateFormat df = DateFormat.getTimeInstance();
         System.out.println( "default time is " + df.format( today ) );
   
         df = DateFormat.getDateInstance();
         // df = new SimpleDateFormat( "MM/dd/yyyy HH:mm a" );
         System.out.println( "default date is " + df.format( today ) );
   
         String str;
         System.out.print( "Enter date: " );
         str = Read.aString();
         while ( ! str.equals( "quit" )) {
            System.out.println( "   date object is " + df.parse( str ) );
            System.out.print( "Enter date: " );
            str = Read.aString();
   }  }  }
   
   // ///// DateFormat.getDateInstance() \\\\\
   // today is Fri Jan 07 10:43:48 CST 2000
   // default time is 10:43:48 AM
   // default date is Jan 7, 2000
   // Enter date: feb 29, 2000
   //    date object is Tue Feb 29 00:00:00 CST 2000
   // Enter date: feb 30, 2000
   //    date object is Wed Mar 01 00:00:00 CST 2000
   // Enter date: feb 30, 00
   //    date object is Mon Mar 01 00:00:00 CST 0001
   // Enter date: 1/7/2000
   // ParseException: Unparseable date: "1/7/2000"
   
   // ///// new SimpleDateFormat( "MM/dd/yyyy HH:mm a" ) \\\\\
   // today is Fri Jan 07 10:55:02 CST 2000
   // default time is 10:55:02 AM
   // default date is 01/07/2000 10:55 AM
   // Enter date: 2/2/2000 11:11 AM
   //    date object is Wed Feb 02 11:11:00 CST 2000
   // Enter date: 02/30/2000 12:34 am
   //    date object is Wed Mar 01 12:34:00 CST 2000
   // Enter date: 02/30/2000 12:34
   // ParseException: Unparseable date: "02/30/2000 12:34"
Java's standard library includes a boat-load of "collection" classes. One of these is Stack. All collection classes in Java store and return objects.
   import java.util.Stack;
   
   class OPOdemos {
      public static void main( String[] args ) {
         Stack s = new java.util.Stack();
         for (int i=0; i < args.length; i++)
            s.push( args[i] );
         System.out.println( "size of stack is " + s.size() );
         while ( ! s.empty())
            System.out.println( s.pop() );
   }  }
   
   // D:\Java> java OPOdemos one two three four
   // size of stack is 4
   // four
   // three
   // two
   // one
Because all collection classes in Java store and return objects, they do not support Java's primitive data types. For this reason, each primitive data type has a corresponding "wrapper" class that can be used to reshape a primitive type into an abstract type. All of these wrapper classes: begin with a capital letter (to distinguish their name from the matching primitive type's name), reside in the java.lang package, and provide an excellent place to stage utility functionality like parseInt() and toHexString().
   import java.util.*;
   
   class OPOdemos {
      public static void main( String[] args ) {
         Stack s = new Stack();
         for (int i=1; i <= Integer.parseInt(args[0]); i++)
            s.push( new Integer( i ) );
         System.out.println( "int 2 is at position " + s.search( new Integer(2) ));
         while ( ! s.empty())
            System.out.print( s.pop() + " " );
   }  }
   
   // D:\Java> java OPOdemos 6
   // int 2 is at position 5
   // 6 5 4 3 2 1
LinkedList provides uniformly named methods to get, remove and insert an element at the beginning and end of the list. These operations allow linked lists to be used as a stack, queue, or double-ended queue (deque). Note that LinkedList objects know how to intelligently interact with println().
   import java.util.*;
   
   class OPOdemos {
      public static void main( String[] args ) {
         LinkedList list = new LinkedList();
         for (int i=1; i <= Integer.parseInt(args[0]); i++)
            list.add( new Integer( i ) );
         for (int i=1; i <= Integer.parseInt(args[0]); i++)
            if (i % 2 == 1)
               list.addFirst( new Integer( i ) );
            else
               list.addLast( new Integer( i ) );
         System.out.println( "list is " + list );
         for (int i=0; i < list.size(); i++)
            System.out.print( list.get( i ) + " " );
         System.out.print( "\ncontains  2? " + list.contains( new Integer(2) ));
         System.out.print( "\ncontains 22? " + list.contains( new Integer(22) ));
   }  }
   
   // D:\Java> java OPOdemos 6
   // list is [5, 3, 1, 1, 2, 3, 4, 5, 6, 2, 4, 6]
   // 5 3 1 1 2 3 4 5 6 2 4 6
   // contains  2? true
   // contains 22? false
   
   ////////////////////////////// lab - LinkedList \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
The Vector class implements a growable array of objects Like an array, it contains components that can be accessed using an integer index. However, the size of a Vector can grow or shrink as needed to accommodate adding and removing items after the Vector has been created.

Each vector tries to optimize storage management by maintaining a capacity and a capacityIncrement. The capacity is always at least as large as the vector size; it is usually larger because as components are added to the vector, the vector's storage increases in chunks the size of capacityIncrement. An application can increase the capacity of a vector before inserting a large number of components; this reduces the amount of incremental reallocation.

   import java.util.*;
   
   class OPOdemos {
      public static void main( String[] args ) {
         Vector v = new Vector();
         System.out.println( "capacity is " + v.capacity()
            + ", size is " + v.size() );
   
         for (int i=1; i < 5; i++) {
            v.addElement( new Integer(i) );
            v.addElement( new Double(i) );
            v.addElement( new String( "str" + i) );
         }
         System.out.println( "capacity is " + v.capacity()
            + ", size is " + v.size() );
   
         int index = v.indexOf( new Integer( 3 ) );
         v.setElementAt( new Integer( 6 ), index );
   
         Object obj;
         for (int i=0; i < v.size(); i++) {
            obj = v.elementAt( i ); 
            System.out.print( obj + "--" );
         }
         System.out.println();
   
         Integer value = new Integer( 2 );
         System.out.println( "contains 2? " + v.contains( value ) );
         v.removeElement( value );
         System.out.println( "contains 2? " + v.contains( value ) );
         v.removeAllElements();
         System.out.println( "capacity is " + v.capacity()
            + ", size is " + v.size() );
   }  }
   
   // capacity is 10, size is 0
   // capacity is 20, size is 12
   // 1--1.0--str1--2--2.0--str2--6--3.0--str3--4--4.0--str4--
   // contains 2? true
   // contains 2? false
   // capacity is 20, size is 0
   
   ////////////////////////////// lab - Vector \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
A very common programming problem is splitting a string up into chunks (or tokens). Typically, white space (spaces, tabs, newlines) are used to distinguish one token from the next; but any character(s) could be used as "delimiters". The example below employs the 1-argument constructor for class StringTokenizer. This results in the default delimiters being used to tokenize the String.
   import java.util.StringTokenizer;
   
   class OPOdemos {
      public static void main( String[] args ) {
         StringTokenizer st = new StringTokenizer( "this is\na\ttest" );
         while (st.hasMoreTokens())
            System.out.print( st.nextToken() + " -- " );
         System.out.println();
   }  }
   
   // this -- is -- a -- test --
The 2-argument constructor is demonstrated next. The second argument is a list of characters to be used as token delimiters.
   import java.util.*;
   
   class OPOdemos {
      public static void main( String[] args ) {
         StringTokenizer st =
            new StringTokenizer( "|abc|def ghi|||jkl, mno|pqr|", "|" );
         System.out.println( "number of tokens is " + st.countTokens() );
         while (st.hasMoreTokens())
            System.out.println( "-" + st.nextToken() + "-" );
   }  }
   
   // number of tokens is 4
   // -abc-
   // -def ghi-
   // -jkl, mno-
   // -pqr-
The previous example did not preserve "empty" tokens. If that behavior is desirable then the 3-argument constructor can be used to "roll your own" empty-token-detection capability.
   import java.util.StringTokenizer;
   
   class OPOdemos {
      public static void main( String[] args ) {
         StringTokenizer st =
            new StringTokenizer( "123,,345,456, ,789"   ,   " ,"   ,   true );
         while (st.hasMoreTokens())
            System.out.print( "-" + st.nextToken() + "-" );
         System.out.println();
   }  }
   
   // -123--,--,--345--,--456--,-- --,--789-
   
   ////////////////////////////// lab - JackAndJill \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - SimpleCalculator \\\\\\\\\\\\\\\\\\\\\\\\\\

The String class

String implements a read-only sequence of characters. All string literals in Java programs, such as "abc", are implemented as instances of this class. Strings are constant; their values cannot be changed after they are created. The String's object reference may be changed to refer to a different String object, but the value of that object may not change.
   class OPOdemos {
      public static void main( String[] args ) {
         String first  = new String( "hello" );
         String second = " world";
         String third  = first + second;
         System.out.println( "third is -" + third + "-" );
         String alias = first;
         first = "goodbye";
         System.out.println( "alias is -" + alias + "-, first is -" + first + "-");
   }  }
   
   // third is -hello world-
   // alias is -hello-, first is -goodbye-
equals() compares the String being messaged to the object being passed. The result is true if and only if the argument is not null and is a String object that represents the same sequence of characters as the target String. compareTo() returns: 0 if the argument String is equal to the messaged String, less than 0 if the messaged String is lexicographically less than the argument String, and greater than 0 otherwise.
   class OPOdemos {
      public static void main( String[] args ) {
         String one = new String( "abcd" );
         String two;
         two = "ab" + "cD";
         System.out.println( "one is " + one + ", two is " + two );
         System.out.println( "one.equals() is " + one.equals( two ) );
         System.out.println( "one.compareTo() is " + one.compareTo( two ) );
         System.out.println( "two.compareTo() is " + two.compareTo( one ) );
   }  }
   
   // one is abcd, two is abcD
   // one.equals() is false
   // one.compareTo() is 32
   // two.compareTo() is -32
"In general, using "==" to compare Strings will give you the wrong results." [Gosling, p165]   This is because "==" compares one object reference to another object reference (i.e. comparing one address or another); it does not compare the contents of objects. But, if String literals are involved directly in the equality operation, or indirectly (a String object was initialized with a String literal), then the caching (and reuse) of these literals internal to the JVM will probably cause the comparison to work correctly.

The example below has the object references s and t referring to the same cached String literal, while u is an object reference to an entirely new object with the same value. Intuition would suggest that the third comparison should succeed. But because s and u represent two entirely different objects, the comparison fails.

   class OPOdemos {               // String equality
      public static void main( String[] args ) {
         String s = new String( "abc" );
         String t = s;
         String u = new String( s );
         if (s == t)      System.out.println( "s == t" );
         if (s.equals(t)) System.out.println( "s.equals(t)" );
         if (s == u)      System.out.println( "s == u" );
         if (s.equals(u)) System.out.println( "s.equals(u)" );
   }  }
   
   // s == t
   // s.equals(t)
   // s.equals(u)
Strings are immutable in Java - you cannot change the contents of a String. You can however change which String instance a String object reference is referring to. Consider the code below -
   String message = "file not found";
   message = message + "!!";
The second line is not changing the original String object. It is taking the original, creating a new String object, and causing the "message" object reference to to refer to this new String. The Java compiler actually rewrites the second line as -
   message = new StringBuffer(message).append("!!").toString();
Some of the methods in class String seem to suggest that it is possible to change a String, but what is really happening is: the String is cloned as a StringBuffer, the StringBuffer is changed, the StringBuffer is converted back to a String, and the new String is returned.
   class OPOdemos {
      public static void main( String[] args ) {
         String first  = new String( "the quick brown fox" );
         String second = first.replace( ' ', '-' );
         System.out.println( "first  is -" + first + "-" );
         System.out.println( "second is -" + second + "-" );
         first = first.replace( ' ', '-' );
         System.out.println( "first2 is -" + first + "-" );
   }  }
   
   // first is  -the quick brown fox-
   // second is -the-quick-brown-fox-
   // first2 is -the-quick-brown-fox-
   
   ////////////////////////////// lab - String compare \\\\\\\\\\\\\\\\\\\\\\\\\\\\

The StringBuffer class

StringBuffer implements a mutable sequence of characters. The principal operations on a StringBuffer are append() and insert(), which are overloaded to accept data of any type. Each effectively converts the specified parameter to a String and then appends or inserts the characters of that String to the StringBuffer. append() always adds these characters at the end of the buffer; insert() adds the characters at the specified location.

Every StringBuffer has an internal capacity. As long as the length of the character sequence contained in the instance does not exceed this capacity, it is not adjusted. If the internal buffer overflows, it is automatically made larger.

Note that append() and insert() not only modify the object they are called on, but they also return that object to the println() call.

   class OPOdemos {
      public static void main( String[] args ) {
         StringBuffer sb = new StringBuffer( "start" );
         System.out.println( "append le --- " + sb.append( "le" ) );
         sb.setLength( sb.length() - 2 );
         System.out.println( "truncate le - " + sb );
         System.out.println( "insert le --- " + sb.insert( 4, "le" ) );
         for (int i=0; i < sb.length(); i++)
            sb.setCharAt( i, (char) (sb.charAt(i)-32) );
         System.out.println( "upper case -- " + sb );
   }  }
   
   // append le --- startle
   // truncate le - start
   // insert le --- starlet
   // upper case -- STARLET
The following example adds a ternary operator expression to skip over space characters in the convert-to-upper-case loop. Also notice that explicitly calling toString() is optional.
   class OPOdemos {
      public static void main( String[] args ) {
         StringBuffer sb = new StringBuffer( "java" );
         System.out.println( sb );
         sb.append( " is real" );
         System.out.println( sb.toString() );
         System.out.println( sb.insert( 8, "sur" ) );
         char ch;
         for (int i=0; i < sb.length(); i++)
            sb.setCharAt( i, (ch = sb.charAt(i)) == ' ' ? ch : (char)(ch - 32) );
         System.out.println( sb );
   }  }
   
   // java
   // java is real
   // java is surreal
   // JAVA IS SURREAL
   
   ////////////////////////////// lab - StringBuffer cycle \\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - StringBuffer reverse \\\\\\\\\\\\\\\\\\\\\\
The class Math in package java.lang contains methods for performing basic numeric operations such as the elementary exponential, logarithm, square root, and trigonometric functions. Notice that all the methods demonstrated here are class methods.
   class OPOdemos {
      public static void main( String[] args ) {
         System.out.println( "PI is " + Math.PI );
         System.out.println( "2 sqrt is " + Math.sqrt( 2. ) );
         System.out.println( "2 raised to .5 is " + Math.pow( 2., .5 ) );
         System.out.println( "4 raised to 3 is " + Math.pow( 4., 3. ) );
         System.out.println( "2 sqrt-squared is " + Math.pow( Math.sqrt(2.), 2. ));
         System.out.println( "-42 absolute value is " + Math.abs( -42 ) );
         System.out.println( "4.4 floor is " + Math.floor( 4.4 ) );
         System.out.println( "4.4 ceil is " + Math.ceil( 4.4 ) );
         System.out.println( "4.4 rounded is " + Math.round( 4.4 ) );
         System.out.println( "4.5 rounded is " + Math.round( 4.5 ) );
         System.out.println( "42 tens is " + (42 / 10) );
         System.out.println( "42 ones is " + (42 % 10) );
   }  }
   
   // PI is 3.141592653589793
   // 2 sqrt is 1.4142135623730951
   // 2 raised to .5 is 1.4142135623730951
   // 4 raised to 3 is 64.0
   // 2 sqrt-squared is 2.0000000000000004
   // -42 absolute value is 42
   // 4.4 floor is 4.0
   // 4.4 ceil is 5.0
   // 4.4 rounded is 4
   // 4.5 rounded is 5
   // 42 tens is 4
   // 42 ones is 2
The class method random() in class Math in package java.lang returns a double value between 0.0 (inclusive) and 1.0 (exclusive). The class Random in package java.util can be instantiated as an object. The method nextInt(int) returns an int value between 0 (inclusive) and the specified argument (exclusive). If two instances of Random are created with the same seed, and the same sequence of method calls is made for each, they will generate and return identical sequences of numbers.
   class OPOdemos {
      public static void main( String[] args ) {
         int max = Integer.parseInt( args[0] );
         for (int i=0; i < 35; i++)
            System.out.print( (int)(java.lang.Math.random() * max) + " " );
         System.out.println();
   
         for (int i=0; i < 35; i++)
            System.out.print( (int)(Math.random() * 1000) % max + " " );
         System.out.println();
   
         java.util.Random obj = new java.util.Random();
         for (int i=0; i < 35; i++)
            System.out.print( obj.nextInt( max ) + " " );
         System.out.println();
   }  }
   
   // D:\Java> java OPOdemos 10
   // 1 4 0 3 1 6 8 7 9 2 4 0 8 4 2 3 9 1 4 6 1 2 1 1 5 1 1 7 2 4 1 4 1 7 8
   // 6 9 4 9 7 8 5 4 1 9 1 4 3 5 2 3 2 3 3 5 1 4 6 6 2 7 4 2 3 7 7 3 1 4 9
   // 9 6 8 2 4 3 5 7 5 8 6 4 5 5 8 9 3 0 4 4 8 5 9 8 8 1 6 6 2 5 0 1 3 9 2
   
   ////////////////////////////// lab - Math \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
The System class contains several useful class attributes and methods. It cannot be instantiated. Some of the facilities it provides are: standard input, standard output, and standard error output streams; access to externally defined "properties"; and a means of loading files and libraries.
   import java.util.Properties;
   import java.util.Date;
   
   class OPOdemos {
      public static void main( String[] args ) {
         Properties array = System.getProperties();
         array.list( System.out );
         System.out.println( "dir - " + System.getProperty( "user.dir" ));
         System.out.println( "sep - " + System.getProperty( "file.separator" ));
         System.out.println( "now - " + new Date( System.currentTimeMillis() ));
         while (true)
            System.exit( 1 );
   }  }
   
   // -- listing properties --
   // java.version=1.2.1
   // java.home=d:\jdk1.2.1\jre
   // java.io.tmpdir=D:\TEMP\
   // os.name=Windows NT
   // user.name=vhuston
   // sun.boot.library.path=d:\jdk1.2.1\jre\bin
   // java.vm.version=1.2.1
   // path.separator=;
   // file.separator=\
   // user.dir=D:\Java
   // dir - D:\Java
   // sep - \
   // now - Thu Jan 06 15:25:17 CST 2000
Working with Java's AWT (Abstract Window Toolkit) is very straight-forward. The standard Frame class is usable, but incomplete. The FrameClose class has been supplied for you, and extends the functionality of the standard Frame class.
   import java.awt.*;
   
   public class OPOdemos {
      public static void main( String[] args ) {
         FrameClose f = new FrameClose();
         f.setTitle( "Title bar string" );
         f.setSize( 200, 100 );
         f.setLocation( 200, 200 );
         f.setVisible( true );
   }  }
Here, the Frame object is being created, and, having its title bar initialized at the same time.
   import java.awt.*;
   
   public class OPOdemos {
      public static void main( String[] args ) {
         FrameClose f = new FrameClose( "Title bar string" );
         f.setSize( 200, 100 );
         f.setLocation( 200, 200 );
         f.setVisible( true );
   }  }
A Label object has now been added to the Frame object. By default, Label objects are left justified.
   import java.awt.*;
   
   public class OPOdemos {
      public static void main( String[] args ) {
         FrameClose f = new FrameClose( "Label window" );
         Label      lab = new Label( "Here is a label" );
         // Label   lab = new Label( "Here is a label", Label.CENTER );
         f.add( lab );
         f.setSize( 200, 100 );
         f.setLocation( 200, 200 );
         f.setVisible( true );
   }  }
Button objects are capable of generating events in response to user interaction; but, they are not capable of responding to (or handling) these events. A separate class must be architected to provide this functionality; then, an instance of that class is instantiated and registered with each Button object. The EventListener class has been written for you. A Button object is the "source" of events, and a EventListener object is the "destination" of events.

TextField objects are capable of receiving keyboard input and then generating an event when the user presses the "Enter" key. The EventListener class is capable of handling both Button and TextField events.

Instead of assigning a fixed width and height to the Frame object, pack() tells the Frame to negotiate a "preferred" size for all its children.

   import java.awt.*;
   
   public class OPOdemos {
      public static void main( String[] args ) {
         FrameClose f = new FrameClose( "Button window" );
         Button     component = new Button( "press me" );
         component.addActionListener( new EventListener() );
         // TextField  component = new TextField( "initial text", 20 );
         // component.addActionListener( new EventListener() );
         f.add( component );
         // f.setSize( 200, 100 );
         f.pack();
         f.setLocation( 200, 200 );
         f.setVisible( true );
   }  }
   
   // press me
   // press me
   // window disposing and exiting
   // ///// initial text plus more
   // ///// brand new string
   // ///// window disposing and exiting
   
   ////////////////////////////// lab - FrameClose and Label \\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - FrameClose and Button \\\\\\\\\\\\\\\\\\\\\
When a Frame object manages more than one child, it needs a "layout manager" object to be specified. The FlowLayout class causes the children to be layed-out from left to right. The GridLayout class causes the children to be layed-out in a regular grid of rows and columns. The two layout managers behave differently to resize requests. Notice that a single Button object reference is being reused to create three distinct Button objects, and, that a single EventListener object is servicing all three Button objects.
   import java.awt.*;
   
   public class OPOdemos {
      public static void main( String[] args ) {
         FrameClose f = new FrameClose( "3 Button window" );
         f.setLayout( new FlowLayout() );
         // f.setLayout( new GridLayout(1,3) );
         // f.setLayout( new GridLayout(3,1) );
         EventListener bl = new EventListener();
         Button btn = new Button( "first" );
         btn.addActionListener( bl );
         f.add( btn );
         btn = new Button( "second" );
         btn.addActionListener( bl );
         f.add( btn );
         btn = new Button( "third" );
         btn.addActionListener( bl );
         f.add( btn );
         f.pack();
         f.setVisible( true );
   }  }
   
   // label is first, data is first
   // label is second, data is second
   // label is third, data is third
   // window disposing and exiting
   
   ////////////////////////////// lab - Flow, Layout \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Parameter passing

All parameters to Java methods are passed by value. However - when the parameter is an object reference, the object reference - not the object itself - is what is passed by value. Thus, you can change which object a parameter refers to inside the method without affecting the reference that was passed. But, if you change any of the object's state, the object remains changed when control is returned to the caller. Java does not pass objects by reference; it passes object references by value. "There is exactly one parameter passing mode in Java - pass by value - and that helps keep things simple." [Gosling, p40]
   import java.util.Stack;
   
   public class ClassDemos {
      public static void foo( Stack st ) {  // modify the object
         for (int i=11; i < 15; i++) st.push( new Integer(i) );
      }
      public static void bar( Stack st ) {  // modify the obj reference
         st = new Stack();
         for (int i=21; i < 25; i++) st.push( new Integer(i) );
         System.out.println( "stack in bar:     " + st );
      }
      public static void main( String[] args ) {
         Stack s = new Stack();
         for (int i=0; i < 5; i++) s.push( new Integer(i) );
         while ( ! s.empty()) System.out.print( s.pop() + " " );
         System.out.println();
   
         System.out.println( "stack before foo: " + s );
         foo( s );
         System.out.println( "stack after foo:  " + s );
         bar( s );
         System.out.println( "stack after bar:  " + s );
   }  }
   
   // 4 3 2 1 0
   // stack before foo: []
   // stack after foo:  [11, 12, 13, 14]
   // stack in bar:     [21, 22, 23, 24]
   // stack after bar:  [11, 12, 13, 14]