Java Basics

These notes present the basics of Java in outline form. They only discuss the ways in which Java differs from C++. If something is not discussed, such as for loops, if-then-else statements, or constructors, it is because they are identical in both C++ and Java.


I. Java's built-in types

    1. boolean -- true/false
    2. byte -- 8 bit integer
    3. char -- character
    4. double -- double precision floating point number
    5. float -- single precision floating point number
    6. int -- integer
    7. long -- long integer
    8. short -- short integer

II. The String class--String is a built-in class that replaces C++'s
    char * strings

    A. Strings are immutable--once they are created, their contents cannot
       be changed

       Example:  String a = "Hello";
                 a = a.concat(" World");

		 In this example, the concatenation operation creates a
		 completely new string to hold "Hello World" and then makes
		 a point to the new string. The old string object
		 remains unaltered with its contents still being "Hello".
		 Since no other variables point to it, it is eventually
		 garbage collected.

    B. Strings should be compared using a string's equals method.
      
      Example: String token;
               String day = "Wednesday";
               ...
	       if (token.equals("+")) ...
	       else if (token.equals(day)) ...

    C. Use a StringBuilder or StringBuffer object if you 
	want to modify/edit a string
	1) StringBuilder is designed to be used with single threaded
		applications and is faster than StringBuffer
	2) StringBuffer works with multiple threaded applications. 
	3) The two classes are interchangeable, in the sense that they
		support the same interface

III. Enumerated Types: Java 1.5 introduced a sophisticated mechanism for
     
     enumerated types. Follow the link to read Sun's documentation. Some
     salient features of Java's enumerated types are that:
     
     1. An enumerated type is actually a class and each enumerated constant
        is an object. 

        a. Because enumerated types are classes, they can be declared 
           independently of any class, and placed in their own .java file

     2. An enumerated type may associated values with each enumerated
        constant using a constructor and then declaring accessor methods
        that allow a user to query for these values. 
        a. See the planets example in the Java documentation for a detailed 
           example.
        b. As another example of associating values with enumerated constants,
           you might want to associated point sizes with enumerated constants
           for font sizes. For example, small might be a 9 point font, medium
           a 12 point font, large a 24 point font, and so on.

IV. Scope rules

    1. A variable may be declared within

        a. a class
	b. a method
	c. a block
	d. a statement

    2. A variable may not be declared anywhere outside a class (i.e., there
       are no global variables in Java)

    3. A variable that is declared within a block cannot have the same
        name as a variable in an enclosing block or method. This rule is
	different from C/C++ where a variable may have any name when it is
	declared within a block.

	Example: The following code will cause the Java compiler to
	 generate an error message because j is declared
	 both in the body of the method and in the body of the
	 for loop:

	 int countPositive(int [] a) {
	     int count = 0;
	     int i, j;

	     for (i = 0; i < a.length; i++) {
	         int j = a[i];
		 if (j > 0) count++;
	     }
             j = a.length - count;
	     System.out.println("number of negatives = " + j);
	     return count;
	 }

       a. It is okay to have two identical variable declarations in the
          same block, as long as they are not nested. For example, the
          following code is valid:

          int countPositive(...) {
             ...
             for (int i = 0; i < 10; i++) {
		 ...
	     }
             for (int i = 20; i > 10; i--) {
		 ...
	     }				 
	  }

     4. Once a variable goes out of scope it is undefined. Hence if I had
	only declared j in the block which is inside the for loop in the
	above code (and also not declared it outside the block), j would
	not be accessible outside the block.

V. Arrays: They are objects, not primitive types
    1. Must be explicitly allocated using the new operator
    2. The declaration uses empty brackets since the size of the array
        is not known until it is allocated.

	a. The brackets can be either after the type name or after the
	    variable name

	e.g., int a [];
	      int [] b;
	      int c [] = new int[6];
	      int [] d = new int[10];

	b. You can also provide a set of items to initialize an array, in
	   which case new is not required:

	   String weekdays [] = {"Monday", "Tuesday", "Wednesday",
	                         "Thursday", "Friday"};

    3. The length field returns the number of elements

        e.g., System.out.println(c.length) will print 6

    4. Multi-dimensional arrays are arrays of arrays

        e.g., int a [][] = new int[4][5];
	      int b [][] = new int[4][];
	          b[0] = new int[5];
		  b[1] = new int[4];
		  ...

    5. For Each: Java 1.5 introduces a nifty construct for iterating through
	a collection of objects, such as an array:

	e.g., int[] arrayOfInts = { 32, 87, 3, 589, 12, 1076, 2000, 8, 622 };
	      int sum = 0;
              for (int element : arrayOfInts) {
	      	sum += element;
	      }

VI. Type conversions and type comparisons

    1. Java automatically performs a type conversion if the conversion does
        not entail a loss of precision. For example, Java will convert an
	int to a double:

	int a;
	double b;

	b = a; // okay because there is no loss of precision

    2. Java will generate an error message if the type conversion would
        entail a loss of precision:

	a = b; // not okay because the fractional portion will be lost when
	       // b's value is assigned to a

	 a. In C++ such a conversion will only generate a warning message

    3. The programmer may explicitly downcast (also called narrowing)
        one type to a less precise type, in which case Java will perform
	a pre-defined narrowing operation:

	a = (int) b; // okay. Java will truncate the fractional part of b's
	             // value and assign it to a

    4. The instanceof operator allows you to test whether an object
	is of a specified class or array type. The operator returns true
	if the comparison type is either the object's type or a superclass
	of the object's type. For example, if Stack is a subclass of
	LinkedList, then if b is a Stack, then:

	 b instanceof Stack
	 b instanceof LinkedList
	 b instanceof Object

	 are all true. A way of seeing why this makes sense, is that you
	 typically ask if an object is an instance of something because
	 you want to either cast to that type, or because you want to
	 use a certain method. It is always safe to lose "precision" by
	 casting to a superclass object, which is a less precise object.

VII. Classes
    A. Variables whose types are classes cannot be allocated on the stack.
       In other words, Java does not allow value objects. All objects are
       allocated from the heap by calling new.

       1. Java does not have a concept of pointers. When you create an
           object and assign the result to a variable, Java does indeed
	   assign a pointer to the object to the variable, but you do
	   not have access to the pointer like you do in C++ (e.g., in C++
	   you can increment or decrement a pointer). 

       2. All member accesses are performed via the dot (.) operator

       3. When an assignment statement such as a = b is executed,
           a is made to point to the same object to which b
	   points. If a modifies a member of this object, then
	   b will also appear to change. For example:

	   Stack a;
	   Stack b = new Stack();
	   b.push(3);
	   a = b;
	   a.push(5); // b's stack now contains both 3 and 5

	   a. this treatment is different than in C++ where a would
	      receive a copy of b's object because a and b
	      would both be value objects. In C++, a's stack would 
	      contain 3 and 5 after the push operation but b's stack
	      would only contain 3.

       4. The == operator compares pointers, not the contents of two objects.
          We say that the == operator performs a shallow comparison,
	  rather than a deep comparison. This comparision of pointers
	  is why you need to use a string's equals method to compare the
	  values of two strings.

    B. Declaration of instance variables in a class: Unlike C++, you may
        assign a default value to a variable when it is declared. Java
	will assign this value to the variable before the object's constructor
	is called. 

	Example: class Stack {
	            int size = 10;
		    int top = 0;
		    ...
		 }

	1.  To accomplish the same type of initialization in C++ you
		 would have to ensure that every constructor explicitly
		 performed this initialization or called a method that
		 performed this initialization.

	2. Since the initializer code is run each time an instance is created,
	   	the initialization code may involve function calls, but it
	 	may not depend on values that are passed into the constructor,
		since the initialization statements are executed before the
		constructor is executed.

    C. Initialization blocks: You can use an unnamed block, called an
        initialization block, to perform initialization actions on a
        class's members, before the constructor is called. 

       1. If you create an initialization block, then it will also be called 
           when an object is first created

       2. The code in the initialization block is executed after the default
           values are assigned to variables but before the constructor is 
	   called. It is helpful when something more complicated than a 
	   single line initialization has to be performed, such as the
	   assigning of computed values to array elements.

       3. Example: class RandomValues {
	   		int randomNums[] = new int[10];
			// Initialization block
			{
			  for(int i=0; i<randomNums.length; i++)
			  {
			    randomNums[i] = (int)(100.0*Math.random());
		          }
                        }
		    }

       4. Initialization blocks are in effect copied into each constructor for
	  that class. Hence it provides a way for constructors to share a
	  generic piece of code. In C++ you would accomplish the same sharing
	  by placing the generic code in the 0-argument constructor and then
	  making each constructor explicitly call the 0-argument constructor.
	  This approach has two drawbacks. First, you might not want to 
	  provide a public 0-argument constructor (so then you have to go to
	  the trouble of protecting the 0-argument constructor). Second, you
	  have to remember to call the 0-argument constructor in each
	  constructor. That may not be hard to remember to do when the class
	  is first created, but a maintainer of the class might not know that
	  fact and inadvertently add a new constructor that does not call the
	  0-argument constructor and hence does not perform some important
	  initialization.

    D. Static keyword: declares a variable to be a class member.
        
	1. A static variable is essentially a global variable for that class
	   and all instances of that class share its value. 
	2. There is only one copy of the variable and it is stored with the
	   class rather than with an instance

	   Example:  static String color = "red";

    E. static blocks: static blocks are similar to initialization blocks
        but are proceeded by the keyword static

       1. they are called when the class is first loaded and can
           be used to initialize static members

       2. this treatment differs from C++ in which a static member must be
           declared and initialized in a .cpp file as well as being declared
	   in a .h file.

    F. Finalize method: replaces C++'s destructor and is only needed to
       release non-Java resources, such as a file handle

        1. signature: void finalize() {...}
	2. is called by the garbage collector when the object is finally
	   destroyed
        3. cannot count on when it will be called because you cannot count
	   on when the object will be garbage collected

	   a. if you need to ensure that a final set of actions is performed
	      on an object, create your own method and then call it when
	      you are ready to release the object

	   b. do not call finalize yourself since it will also get
	      called by the garbage collector. Doing so may cause your
	      program to try to release the same set of resources twice, and
	      the second time you won't own them anymore. Trying to release
	      resources you don't own could create unpredictable results.

    G. Parameter passing

        1. Java uses pass-by-value, not pass-by-reference

	2. Can return objects since they're allocated off the heap

	3. Passing a reference to an object allows the method to modify
	     the contents of the object. This may seem like a contradiction
	     to the pass-by-value dictum but if you think about it, it
	     makes sense. The reference value (i.e., pointer value) gets
	     copied, and hence the parameter has a pointer to the same object
	     as the argument

	4. C++ uses both pass-by-value and pass-by-reference

	    a. Place an ampersand (&) after the type in a parameter declaration
	        to make the parameter a reference parameter:

		void swap(int &a, int &b) {
		    int temp = a;
		    a = b;
		    b = temp;
		}

		actual call: swap(x, y);

            b. When C++ sees a reference parameter, it passes a pointer to
	        the argument rather than a copy of the argument to the
		method. 

		i. When you assign a value to a reference parameter, the value
		    is assigned to the argument's memory location, thus
		    modifying the original argument

		ii. When you access a reference parameter on the right side
		     of an assignment statement, C++ return's the current
		     value at the argument's memory location

	    c. Reference parameters were introduced into C++ to reduce
	        the need to explicitly pass addresses of arguments to
		methods and to explicitly dereference parameters in methods.
		Compare how swap must be implement and called using the
		pass-by-value method as compared to the pass-by-reference
		method:

		void swap(*int a, int *b) {
		    int temp = *a;
		    *a = *b;
		    *b = temp;
		}

		actual call: swap(&x, &y)

		As you can see, there are many more opportunities to introduce
		an error when you have to explicitly take the address of
		arguments and dereference parameters.

	    d. Unfortunately, in practice reference parameters are confusing
	        to use and the designers of Java decided to do away with
		them. Note however that because of the way Java copies 
		reference values to parameters, Java really is doing 
		a form of pass-by-reference for objects.

    H. Nested classes: Classes can be nested inside a Java class just as
        in C++

        1. A nested class has access to all the members and methods of its 
	    enclosing class

	2. An instance of a nested class is associated with the instance of
	    the class which creates it. A nested class cannot be created
	    independently of its outer class

	    e.g., class foo {
	            ...
		    public class goo { ... }
		  }

		  foo.goo a = new foo.goo() will fail because a goo can only
		  be created by an instance of a foo

	    a. The java compiler creates a foo$x.class for goo
                where x is a number

	3. The compiled class name for the inner class will be
	     OuterClassName$InnerClassName.class. For example, goo
	     will be compiled to foo$goo.class.

    I. Final keyword: Java's way of creating a constant

        1. Memory for the final keyword is shared by all instances so you
	   do not have to declare them static.
	2. If you want the constant to be accessible outside the class, you
           must declare it both public and static.

VIII. Writing a C++-like Program in Java: If you want to write a program
	   in Java that is essentially a main program with a number of
	   function calls (i.e., you don't want to use Java's object mechanism),
	   then the way to do it is to place the code that would normally
	   go in main into a constructor. For example, here is a simple C++
	   program:

	   int sum(int x, int y) { return x + y; }
	   int multiply(int x, int y) { return x * y; }
	   int main(int argc, char *argv[]) {
	      sum(3,6);
	      multiply(9,5);
	   }

	   In Java, a comparable program should be written as:

	   class Arithmetic {
	     public Arithmetic() {
	       sum(3,6);
	       multiply(9,5);
	     }

	     int sum(int x, int y) { return x + y; }
	     int multiply(int x, int y) { return x * y; }

	     public static void main(String args[]) {
	       new Arithmetic();
	     }
	   }

	   A lot of Java newbie's might think this code is too complicated
	   and try to write it more simply as:

	   class Arithmetic {
	     int sum(...)
	     int multiply(...)

	     public static void main(String args[]) {
	       sum(3,6);
	       multiply(9,5);
	     }
	   }

	   The java compiler will complain about this code though because
	   sum and multiply have not been declared static as well. If you
	   call a function from a static function, then the called function
	   must also be declared static. The reason is that non-static
	   functions expect a pointer to an object to be passed to them via
	   the this variable. However, static functions don't have
	   objects associated with them and hence cannot pass a reference to
	   an object to a non-static function. Trying to keep track of all
	   the function calls made from a static function becomes tiresome,
	   and hence it is best to simply move the code that typically 
	   appears in main to a constructor instead, and then invoke that
	   code by having main create a single instance of the class.