LearningJava

From HerzbubeWiki
Jump to navigation Jump to search

About this page

I have done various little bits of Java programming over the years, but never in a structured way. When I decided that I wanted to have a go at the Sun Certified Java Programmer, I thought it was time to create this page to keep notes about the language, similar to what I did for Python and Objective-C.

Note that this page "only" contains notes about the essential language features and syntax. Additional pages are:

  • This page for learning the Java API (e.g. how to use stuff like strings, threads, file I/O, etc.)


Warning: Despite its name, this page is not suitable for learning Java - I merely use it to keep notes while I learn Java.


Top-level documentation

A few top-level documentation links:

  • Canonical Java website [1]
  • List of FAQs [2]
  • Learning the Java Language [3]
  • The Java Language Specification: [4]
  • The Java Tutorials [5]


Glossary

Also refer to Sun's Java glossary[6].

JDK
Java Development Kit. Contains, among other things, a compiler, a "private" JRE and a debugger. The main JDK is from Sun, but there are also JDKs by other manufacturers, notably Apple's implementation ("Mac OS Runtime For Java") and Blackdown Java (an implementation for Linux).
JRE
Java Runtime Environment. Consists of a JVM and a JIT.
JVM
Java Virtual Machine. An instance of a JRE.
JIT
Just-in-time compiler.
Object State
The values assigned to an object's instance variables
Object Behaviour
Method implementations
Cohesion, cohesive
A class is cohesive if it has a focused set of responsibilities
Top-level class
Any class that is not an inner class is a top-level class
Inner class
Any class that is defined within another class
Package
A container for classes and other packages. Used for organizing source code
Primitive data type
One of several "primitive" data types identified by a special keyword. The data types are: char, boolean, byte, short, int, long, double, float.
Reference variable
A variable whose value is a reference to an object.
Class variable
A variable in a class declaration that has the modifier static. The variable exists once per class, there is exactly one value associated with it.
Instance variable
A variable in a class declaration that does not have the modifier static. The variable exists once per instance of the class (= object), there are as many values associated with it as there are instances.
Member
Basically anything that is part of a class or an interface (class or instance variable, class or instance method, inner class, possibly more). Most often used to refer to instance variables and methods.
Constant specific class body
Used in enums to override certain of the enum's normal methods on a value-by-value basis.
Covariant return
If an overriding method declares a return type that is a subtype of the original return type. This is a Java 1.5 feature.
Default constructor
The constructor inserted by the Java compiler if a class has no explicit constructors. If a class has an explicit no-arg constructor, this is not the default constructor.
Marker interface
An interface that does not contain any methods. Such an interface is used to mark a class as having a certain capability.
JavaBeans
TODO
AWT
Abstract Window Toolkit. AWT is Java's original platform-independent windowing, graphics, and user-interface widget toolkit. AWT is only a thin layer over the operating system's native GUI toolkit. AWT components always have the native look and feel. AWT is now part of JFC. AWT is largely superseded by Swing. See the Wikipedia article [7].
Swing
The project code name for the lightweight GUI components in JFC. Swing draws its own widgets (by using Java 2D to call into low-level subroutines in the local graphics subsystem) instead of relying on the operating system's high-level user interface module. Swing supports different look and feels that are not dependent on the native GUI toolkit If the "native" look and feel is chosen, that look and feel is "only" emulated. See the Wikipedia article [8].
JFC
Java Foundation Classes. A set of GUI components and services which are intended to simplify the development and deployment of desktop and Internet/Intranet applications. JFC contains and extends AWT. Swing is a part of JFC. According to Wikipedia, JFC is a combination of AWT, Swing and Java2D.
SWT
Standard Widget Toolkit. An alternative to AWT/Swing, originally developed by IBM and now maintained by the Eclipse project. SWT is written in Java. To display GUI elements, the SWT implementation accesses the native GUI libraries of the OS using JNI. Programs that call SWT are portable, but the implementation of SWT, despite the fact that it is written in Java, is unique for each platform. See the Wikipedia article [9].
JNI
Java Native Interface. A programming framework that allows Java code running in a JVM to call and to be called by native applications and libraries written in other languages, such as C, C++ and assembly. See the Wikipedia article [10].


Differences to C++

  • Garbage collection (the BIG one, probably not a bad thing)
  • No multiple inheritance (excellent!)
  • Interfaces are a language construct; a class can implement multiple interfaces (I swoon!)
  • Polymorphism cannot be "turned off", i.e. forget about the "virtual" keyword in C++ (good!)
  • Instead of namespaces, Java organizes code in packages; to use a class one needs to import it from the package it lives in
  • No pointers, objects are always used as references. Some consequences:
    • No more tricky C++ slicing! (class Sub : public Base {}; Base b = Sub(); // slice me nice!)
    • Operator -> doesn't exist, only the dot notation (".") is used
  • No pass-by-reference as in C++, all arguments are passed by value. Some remarks:
    • Although it may not seem so at first glance, this is also related to pointers.
    • Because an object reference is exactly that - a reference - it can be passed around by value, i.e. copies can be made of the reference but everybody who holds a copy still refers to the same object (living on the heap). Typically, "everybody" is the caller of a method and the method body itself.
    • Because pass-by-reference does not exist, a method cannot (accidentally or intentionally) change the value of a variable used by the method caller
  • null is a literal to be used in conjunction with reference variables. No more C/C++ confusion with NULL (preprocessor macro) or 0 (numeric literal)
  • A boolean context requires a boolean value. No more C++ confusion where numeric values and object references can be used as boolean values
  • const no longer exists
    • final jumps into the breach where we need to declare variables or arguments whose values remain constant
    • However, the C++ concept of a method being declared const has no correspondence in Java
  • No operator overloading
  • Same comment styles as in C++: "//" and "/* */"


Coding Java: The Basics

Identifier names

Legal identifier names may

  • Consist of any combination of Unicode characters
  • Have any length
  • Differ in case only

The only restriction for identifier names are:

  • No language keywords such as class or try
  • The first character must be one of the following:
    • A letter
    • A "$" character
    • A "connecting" character such as "_"
  • The first characters must explicitly not be a number


Alphabetical keyword list, as of Java 1.6:

abstract
boolean
break
byte
case
catch
char
class
const
continue
default
do
double
else
extends
final
finally
float
for
goto
if
implements
import
instanceof
int
interface
long
native
new
package
private
protected
public
return
short
static
strictfp
super
switch
synchronized
this
throw
throws
transient
try
void
volatile
while
assert
enum 

The same list, but this time grouped:

# "Things"
class
interface
enum 

# Access modifiers
private
protected
public

# Non-access modifiers
static
abstract
final
strictfp
native
synchronized
transient
volatile

# Extending and implementing
extends
implements

# Primitive data types
boolean
byte
char
double
float
int
long
short

# Control structures
if
else
do
while
for
switch
case

# Flow control
break
continue
default
goto
return

# Exceptions
try
catch
finally
throw
throws

# Object references
this
super

# Operators
new
instanceof

# Package management
package
import

# Other
void
const
assert


Variable declarations

class Foo
{
  static int static_field1 = 0;  // initialize with default value when the class is loaded
  static int static_field2;      // is assigned a default value (0) when the class is loaded

  int field1 = 0;  // initialize with default value when the class is instantiated
  int field2;      // is assigned a default value (0) when the class is instantiated
  Bar field3;      // ditto, but the default value is the literal null

  final        final_field1;       // can be initialized later, but only in constructors (*not* in methods)
  final        final_field2 = 42;  // initialized here, cannot be initialized anywhere else
  final static final_field3 = 17;  // static class variables *MUST* be initialized here

  // Declaring a variable (or argument) as final makes sure that the variable's value
  // cannot be changed in the scope where the variable is valid
  void m1(final int arg1)
  {
    byte var1;       // if not initialized, local variables are *NOT* assigned a default value
    Bar var2;        // even object references remain uninitialized
    int[] var3;      // arrays are object references, therefore they are also not initialized
    int var4 = 42;   // initialize with default value
    int var5, var6;  // multiple variable declarations on one line

    final int final_var1 = 17;  // declare an initialize in one go; from now on final_var1 cannot be changed
    final int final_var2;       // declare and initialize in separate places
    final_var2 = 17;

    final_field1 = 17;  // illegal !!! can only be initialized in a constructor
    static static_var;  // illegal !!! local variables cannot be declared static

    int var1;            // OK - uninitialized variable
    if (SOME_CONDITION)
    {
      var1 = 42;         // initialize variable
    }
    int var2 = var1;     // ERROR! - compiler complains that the variable may not have been initialized; the
                         // only chance for getting this code to compile is if the compiler can see that
                         // SOME_CONDITION is *always* true, i.e. if var1 is *guaranteed* to be initialized
    try {}
    catch (Exception e)
    {
      var1 = 42;
    }
    finally {}
    int var3 = var1;     // ERROR! - again the compiler catches that the variable may not have been initialized;
                         // to get this to compile another initialization statement must be added to the try{}
                         // clause, or initialization must be moved to the finally{} clause
  }

  Foo()
  {
    final_field1 = 17;  // from now on final_field1 cannot be changed
  }
  Foo(int i) {}         // ERROR! - compiler complains that final_field1 has not been initialized
}


Primitive data types

Type Numeric type Range Size (bits) Default value Literal examples Remark
char - 0 to 65535 16 '\u0000' 'a', '\n', '\"', '\u004E', 42 (decimal), 052 (octal), 0x2a (hexadecimal) This type is expected to hold a single Unicode character
boolean - true, false JVM dependent false true, false Unlike C/C++, numbers cannot be used in a boolean context. For instance, both "boolean b = 0" and "int i = 0; if (i) {...}" will trigger a compiler error.
byte integer -128 to 127 8 0 See int
short integer -32768 to 32767 16 0 See int
int integer -2^31 to 2^31 - 1 32 0 42 (decimal), 052 (octal), 0x2a, 0X2A (both hexadecimal) If an int literal is assigned to another integer type (e.g. byte) it is implicitly converted (narrowed or widened) if it fits into the other type's range.
long integer -2^63 to 2^63 - 1 64 0L See int; also: 42L, 0x2aL
float floating point n/a 32 0.0f 42f, 42F This data type should never be used for precise values, such as currency. Use the java.math.BigDecimal class instead
double floating point n/a 64 0.0d 42.0d, 42.0D, 4.2e1 (scientific notation) As with float, this data type should not be used for currency or other precise values.

Note: All numeric types are signed! The leftmost bit (the most significant digit) is used to represent the sign, where a 1 means negative and 0 means positive. The rest of the bits represent the value, using two's complement notation.


Literals:

char c = 'a';
boolean bool1 = true;
boolean bool2 = false;
byte b = 1;       // compiler automatically narrows the int literal
short s = 1;      // ditto
int i = 1;        // no need for narrowing
long l1 = 1;      // compiler automatically widens the int literal
long l2 = 1l;     // no need for widening, this is a long literal
long l3 = 1L;     // ditto
float f1 = 1.0f;  // floating-point literals are double by default and
float f2 = 1.0F;  // must be marked as float with an "f" or "F" 
double d1 = 1.0;   
double d2 = 1.0d;
double d3 = 1.0D;


Widening conversion table
Type to convert Target types
char boolean byte short int long float double
char yes - - - yes yes yes yes
boolean - yes - - - - - -
byte - - yes yes yes yes yes yes
short - - - yes yes yes yes yes
int - - - - yes yes yes yes
long - - - - - yes yes yes
float - - - - - - yes yes
double - - - - - - - yes


Narrowing conversion rules (narrowing = assigning a larger value to a variable with lesser capacity or precision):

  • Narrowing always requires a cast
    • Either an explicit cast (e.g. byte b = (byte)42L)
    • Or, if an int literal is used and it fits into the new container, the compiler will insert an implicit cast (e.g. byte b = 42;; this also: b -= 25;)
  • Integer to integer conversion
    • Extraneous bits are lost
  • Floating point to floating point conversion
    • TODO
  • Floating point to integer conversion
    • All digits after the decimal are lost
    • Digits before the decimal: Extraneous bits are lost
  • Integer to floating point conversion
    • Loss of precision (e.g. the result of assigning 2147483647 (0x7fffffff, maxint) to a float is 2.14748365e9; the result of assigning 9223372036854775807 (0x7fffffffffffffffL, maxlong) to a float is 9.223372e18).
    • Further investigation would be needed for an exact statement, i.e. how exactly is precision lost


More examples

short s1;
int i1;
long l1;

s1 = 42 + 6;   // ERROR! - Result is an int, compiler does not narrow probably because the result is no longer a literal but an expression
s1 = 42 - 6;   // ERROR! - ditto
s1 = 42 * 6;   // ERROR! - ditto
s1 = 42 / 6;   // ERROR! - ditto

i1 = 42 + 6;   // ok - Result is an int, no need to narrow or widen
l1 = 42 + 6;   // ok - Result is an int which can be widened to long with no problems
i1 = 42 + 6L;  // ERROR! - Result is a long, compiler does not widen
l1 = 42 + 6L;  // ok - Result is a long, no need to widen


Operators

Notes:

  • Compound operators are things like +=
  • Relational operators are things like <=
    • Relational operators always result in a boolean value
    • Any combination of integer numbers (byte, short, int, long), floating-point numbers or characters can be compared
  • Equality operators are the two relational operators == and !=
    • Things that can be tested for equality:
      • Numbers
      • Characters
      • Boolean primitives
      • Object references
    • It is not possible to test for equality if the two things to test are from different categories, e.g. a number and a boolean
    • The test for equality uses the bit pattern of the two values; e.g. "5.0" and "5L" are equal
  • Remainder (modulus) operator: %
  • String concatenation uses the operators + and +=
    • If either operand is a string, the operator becomes a string concatenation operator. If neither operator is a string, the operator is used for adding numbers. Important: Evaluation is left-to-right
    • This kind of different behaviour of the same operator can be termed as "operator overloading"
  • Conditional operator: boolean ? if true : if false
    • I always have trouble remembering the sequence: Is it ? followed by :, or is it : followed by ?). A useful mnemonic might be this metaphor: The boolean expression is asking the question "is this true?", therefore it makes sense that the first separating character is the question mark (?).
  • Logical operators: &&, || and !
  • Bitwise operators: & (bitwise AND), | (bitwise OR) and ^ (bitwise XOR)
    • The bitwise operators can be used in boolean expressions such as if conditions much in the same way as their logical operator counterparts &&, || and !
    • The difference is that bitwise operators always evaluate both sides of the expression


Some examples:

// x is incremented after the == test, but the new value of x is already used in the second test
if (y == x++ || x < ++y) {}
// As expected, the second expression is not executed in both cases
if (false && x++ == x) {}
if (true || x++ == x) {}
// The expressions used here are bitwise AND/OR, therefore the second expression *IS* executed in both cases
if (false & x++ == x) {}
if (true | x++ == x) {}
// Usage of bitwise XOR in a boolean expression
if (true ^ false) {}   // true
if (false ^ true) {}   // true
if (true ^ true) {}    // false
if (false ^ false) {}  // false
// Left-to-right evaluation is important for string concatenation
String s = "foo" + 4 + 2;  // result is "foo42"
String s = 4 + 2 + "foo";  // result is "6foo"


Primitive wrapper classes, [auto][un]boxing

Primitive data type Wrapper class String constructor Extends java.lang.Number Remark
char java.lang.Character no no
boolean java.lang.Boolean yes (case insensitive "true" equates to true, everything else to false) no Can be used in a boolean context (e.g. inside an if expression) since Java 1.5 due to automatic unboxing
byte java.lang.Byte yes yes
short java.lang.Short yes yes
int java.lang.Integer yes yes
long java.lang.Long yes yes
float java.lang.Float yes yes
double java.lang.Double yes yes

Important notes:

  • Wrapper classes are final
  • Wrapper objects are immutable
  • All wrapper classes have an equals() implementation which in order to succeed requires an object of the same type AND the same value
  • All wrapper classes have a constructor that takes the exact primitive type that is to be wrapped; they do not have overloaded constructors for other primitive types
  • Wrapper classes offer various conversion methods
    • Static method valueOf() (some classes even have a second overload)
      • Purpose: Return a newly created wrapper object based on the given arguments
      • First argument is a string representation of the appropriate value type
      • Optional second argument is int radix which allows to specify the base (decimal, octal, hexadecimal, etc.) of the first argument
    • Static method parseXxx
      • Example: parseDouble("3.14")
      • Purpose: Return a primitive value based on the given arguments
      • Otherwise the same as valueOf
    • Instance method xxxValue
      • Example: byteValue()
      • Purpose: Return a primitive value that corresponds to the value encapsulated by the wrapper object
    • Instance method toString()
      • Purpose: Return a String object that represents the value encapsulated by the wrapper object
    • Integer and Long only: Static method toString()
      • Example: toString(42, 16)
      • Purpose: Return a String object that represents the specified value with the specified radix
      • First argument is the primitive value
      • Second argument is int radix
    • Integer and Long only: Static method toXxxString()
      • Example: toHexString()
      • Purpose: Similar to toString(), but the method name specifies the radix


Boxing/unboxing, or autoboxing/auto-unboxing, is a convenience feature new to Java 1.5. It helps users of primitive wrapper classes by automatically wrapping/unwrapping primitive values into/from wrapper objects. Best explained by example:

// Old style (pre Java 1.5)
Integer y = new Integer(42);     // Create the wrapper
int x = y.intValue();            // Unwrap it...
x++;                             // ...so that we can use it
y = new Integer(x);              // Re-wrap it
System.out.println("y = " + y);  // Print it

// All the operations above are still available, but it can be done much easier
Integer y = 42;                  // Create the wrapper with simplified syntax
y++;                             // Behind the scenes, the compiler generates code to unwrap/use/re-wrap
System.out.println("y = " + y);  // Print it

// Examining comparisons
Integer i1 = 128;
Integer i2 = 128;
Integer i3 = 127;
Integer i4 = 127;
Integer i5 = new Integer(127);
Integer i6 = new Integer(127);
if (i1.equals(i2)) { System.out.println("meaningfully equal"); }   // yes, they are equal
if (i3.equals(i4)) { System.out.println("meaningfully equal"); }   // yes, they are equal
if (i5.equals(i6)) { System.out.println("meaningfully equal"); }   // yes, they are equal
// No, they are not the same object. That's OK, after all we compare object references
if (i1 == i2) { System.out.println("same object"); }
// Yes, they are the same object; HUH??? It appears that if boxing is requested, the JVM
// does *NOT* always create a new wrapper object. Rather, the JVM first consults an
// internal cache whether there already exists a wrapper object that matches the primitive
// type and value to be boxed. If such an object exists in the cache, it is reused. If not,
// a new object is created and stored in the cache for future use. To prevent the cache
// from becoming too big (and using up too much memory), the JVM performs this caching only
// for the following wrapper types and primitive value ranges:
// - Boolean and Byte, for all values
// - Character, for values from \u0000 to \u007f
// - Short and Integer, for values from -128 to 127
if (i3 == i4) { System.out.println("same object"); }
// No, they are not the same object; HUH??? The rule above is valid ONLY if the wrapper objects were
// created through boxing. i5 and i6, however, were created without boxing. HOLY CRAP!
if (i5 == i6) { System.out.println("same object"); }

// Yes, same value. Here, i5 is first unboxed and then the two primitive values are compared
if (i5 == 127) { System.out.println("same value"); }

Boolean b1 = true;
Boolean b2 = null;
if (b1) { }   // OK - b is unboxed, then the condition is evaluated
if (b2) { }   // RUNTIME ERROR! - The attempt at unboxing fails at runtime with a NullPointerException

// It is not entirely clear which operators support boxing/unboxing. The following examples are known
// to work
Integer i1 = 42;      // assignment
Integer i2 = i1++;    // increment
i1 += 5;              // compound
String s = "a" + i1;  // this is *NOT* unboxing: it's string concatenation
Boolean b = true;
if (i1 == i2) {}      // equality
if (i2 < i1) {}       // relational
if (b && b) {}        // logical

// Mixing types
short s = 42;              // ok - Compiler narrows int literal to short
Short ss = 42;             // ok - Compiler narrows int literal to short
s = new Short(42);         // ERROR! - Wrapper class constructors only take the exact type (short in this case), or String. For some
                           // reason the compiler in this case does *NOT* narrow the int literal to short
s = new Short((short)42);  // ok - Make the compiler happy with an explicit cast
long l = 42;               // ok - Compiler widens int literal to long
Long ll = 42;              // ERROR! - Compiler does *NOT* widen int literal to long *AND* box
ll = 42L;                  // ok
ll = new Long(42);         // ERROR! - See same example for Short
ll = new Long(42L);        // ok
assert(! ss.equals(ll));   // For equals() to succeed, the two objects must have the same wrapper type *AND* the same value


Advanced boxing/unboxing examples:

class Foo
{
  void m1(int i) {}      // overload A
  void m1(Integer i) {}  // overload B
  void m2(long l) {}     // overload A
  void m2(Integer i) {}  // overload B
  void m3(Byte b1, Byte b2) {}  // overload A
  void m3(byte... b) {}         // overload B
  void m4(Long l) {}
  void m5(Number n) {}

  void testIt()
  {
    int i_primitive = 42;
    Integer i_wrapper = 42;
    long l_primitive = 42L;
    Byte b = 42;

    // Boxing/unboxing does not happen if there is an exact match
    m1(i_primitive);  // OK - invokes overload A
    m1(i_wrapper);    // OK - invokes overload B

    m2(i_primitive);  // SURPRISE! - invokes overload A; happens this way due to backwards compatibility (widening an int to a long has
                      // always existed, but boxing an int to an Integer is a Java 1.5 feature; widening is preferred over boxing because
                      // old code written for Java 1.4 should continue to behave the same even if it is compiled with the 1.5 compiler)
    m3(b, b);         // INTERESTING! - invokes overload A; boxing and var-args are both Java 1.5 features, so in this case
                      // backwards compatibility is not an issue; it makes sense that boxing is chosen over var-args because boxing
                      // is more specific, while var-args is more of a catch-all or last-resort option.
    m4(i_primitive);  // ERROR! - the compiler does not widen AND box; it will do either one, or the other, but not both; in other
                      // words, failing to find an int overload, it will try to either find a long overload (widen), or an Integer
                      // overload (box)
    m5(i_primitive);  // OK - the compiler first tries widening, which fails; it then tries boxing and finds a matching overload,
                      // because Integer is derived from java.lang.Number

    m4(i_wrapper);    // ERROR! - in this case wrapper types behave as normal types, and java.lang.Integer is not a sub-class of
                      // java.lang.Long
  }
}


Arrays

Array rules:

  • An array is a container OBJECT, therefore an array variable is a reference variable just like any other variable that refers to a non-primitive type
  • An array holds a fixed number of values; the number is determined when the array is created (not when it is declared), and the number cannot be changed later on
  • All array elements are of a single type
  • Arrays are allocated on the heap
  • Array elements are always given default values when the array is created, regardless of where the array is declared or instantiated. The default value is the same as if an instance variable of the element type had been declared (e.g. 0 for integer primitive types, null for reference types)


Examples for basic usage of arrays:

void m()
{
  int[] keys;         // An array of primitives
  Thread[] threads;   // Object references
  float[][] values;   // Multi-dimension array (an "array of arrays")
  int keys [];        // Legal but less readable, should be avoided
  float[] values [];  // Legal, but definitely perverted

  int[5] keys;        // ERROR! - An array's size is defined when the array OBJECT is created - not when the array is declared

  int[] keys = new int[10];            // Create a new array OBJECT; the array elements are initialized to 0
  Threads[] threads = new Threads[3];  // The array elements are initialized to null
  int x = 2;
  int[] keys = new int[] {1, 2, 3};    // Create a new array object and initialize it with values; the array length is
                                       // determined by the number of elements between braces ("{}")
  int[] keys = {1, x, 3};              // Same thing, but in an abbreviated syntax
  Threads[] threads = {new Thread(),   // Works the same for object references
                       new Thread()};
  doSomething({1, 2, 3});              // ERROR! - For unfathomable reasons, the Java compiler does not accept this
  doSomething(new int[] {1, 2, 3});    // OK

  // A multidimensional array is simply an array whose components are themselves arrays. Consequently, rows can vary in
  // length.
  int[][] keys = {{1,2}, {3,4,5,6}, {7,8,9}, {0}};  // Varying row length
  int[][] keys = new int[4][9];                     // Uniform row length

  // Not all dimensions in a multidimensional array need to be created at the same time!
  // This again allows to have varying row length.
  int[][] keys = new int[2][];   // First dimension; elements are object references initialized to null
  keys[0] = new int[2];          // Second dimension, array 1; elements are int values initialized to 0
  keys[1] = new int[4];          // Second dimension, array 2; elements are int values initialized to 0

  // Assign to array variables
  int[] integers1 = new int[7];
  int[] integers2 = integers1;    // OK - The array variable is just a reference
  int[][] integers3 = integers1;  // ERROR! - Dimensions must match
  long[] longs = integers1;       // ERROR! - Although int can be fitted into long, the compiler does not accept this
  Sub[] subs = new Sub[13];
  Base[] bases = subs;            // OK - The compiler is more tolerant when it comes to object types; if in doubt,
                                  // apply the IS-A check to find out whether an assignment like this works.
  subs = (Base[]) bases;          // OK - Casting works as expected

  Object[] objs = subs;           // OK - Sub is a subtype of Object
  objs = new Foo();               // RUNTIME ERROR! - Foo and Sub are unrelated. Compiling works, but at runtime the Sub array
                                  // retains its identity and an ArrayStoreException is thrown
  objs[0] = new SubSub();         // OK - SubSub is a subtype of Sub
  objs[0] = new Object();         // RUNTIME ERROR! - Wrong way, Object is a *SUPER*type of Sub

  // Use the built-in property "length" to determine the size of an array
  int arrayLength = keys.length;
  int arrayLength = keys[3].length;  // Here the element at index position 3 is another array

  // Use the function System.arraycopy() to copy elements from one array to another array
  int[] keys1 = {1, 2, 3, 4, 5, 6};
  int[] keys2 = new int[6];
  int sourcePos = 3;
  int destinationPos = 1;
  int length = 3;
  System.arraycopy(keys1, sourcePos, keys2, destinationPos, length);
  // keys2 now contains the following values: 0, 4, 5, 6, 0, 0
}


More advanced array usage:

class Bar {}
class Foobar extends Foo {}
class Foo
{
  // This array is an instance variable; since the array object is not created here, the variable will
  // be initialized with null when the class is instantiated. This is *not* special behaviour, it is
  // the same behaviour as with any other instance variable that is an object reference.
  int[] keys;

  // ERROR! - The compiler cannot distinguish between these two overloads
  void arrayAndVarargs(int[] args) {}
  void arrayAndVarargs(int... args) {}

  void parameterIsIntArray(int[] args) {}
  void doIt()
  {
    int i = 5;
    parameterIsIntArray(i);  // ERROR! - Single value is not converted, as one might expect, into an array of length 1
  }
}


Flow control (keywords: if, else, do, while, for, break, continue, switch, case, default)

if example:

if (expression)  // Expression must evaluate to boolean
  doIt();        // Braces can be omitted if the block consists of only a single statement
else if (expression)
{
  // do stuff
}
else
  doThat();


switch example:

final int a = 1;
final int b;
b = 2;
final Integer c = new Integer(42);
switch (expression)   // Expression must evaluate to one of certain primitive types (i.e. char, byte, short, int),
                      // a wrapper object (e.g. Integer) which will be unboxed, or (since Java 1.6) to an enum
{
  case constant1:     // Must be a *COMPILE TIME* constant! If it's a variable, it must be a constant (static final)
                      // or an instance/local final that has been initialized at declaration time
    doIt();
    break;
  case constant2:
    doThat();
                      // No break statement, execution falls through
  case constant3:
  {                   // We can use braces if we want to
    // do something
    break;
  }
  case a:             // OK - a is final and has been initialized at declaration time
    break;
  case a:             // ERROR! - case constant cannot be used more than once
    break;
  default:            // Works just like any other case, i.e. it can be placed anywhere within the switch,
    break;            // and we need a break to prevent fall-through execution
  case b:             // ERROR! - b is final but has *NOT* been initialized at declaration time
    break;
  case c:             // ERROR! - wrapper objects are not allowed as case constants; they are allowed in the
    break;            // switch() expression, though, in which case they are unboxed
}


for example:

// Basic for-loop
// - initialization-expression
//   - Consists of 0-n initialization statements
//   - Statements are separated by commas
//   - Statements may include variable declarations
//   - The scope of a variable declared here ends with the for-loop
//   - ARGH! VERY CONFUSING: A variable declared here conflicts with a variable
//     declared before the for-loop.
// - boolean-expression
//   - Must evaluate to a boolean
//   - Can be omitted, in which case the expression evaluates to true
//   - Only one expression is allowed (i.e. no comma-separated statements)
//   - Is evaluated at the end of the loop, i.e. the loop executes at least once
// - iteration-expression
//   - Consists of 0-n iteration statements
//   - Statements are separated by commas
//   - Statements are executed *BEFORE* the boolean-expression is evaluated
for (initialization-expression ; boolean-expression ; iteration-expression)
{
  // do stuff
  if (expression)
    break;          // Jump out of the loop and continue with the first statement *AFTER* the loop
  else
    continue;       // Jump to the *END* of the loop and continue with the statements in iteration-expression
}

// for-each-loop
// - declaration
//   - Declaration of a *NEW* variable
//   - Same CONFUSING scoping rules as in the regular for-loop
//   - The variable type must match the type of the array or collection elements specified in expression
//   - Elements are unboxed or widened if necessary
// - expression
//   - Must evaluate to an array or a collection
for (declaration : expression)
{
  // do stuff
}

// for-loop with labels
outer:  // label names must adhere to the rules for valid variable names
for ()
{
  inner:
  for ()
  {
    for ()
    {
      if (expression)
        break;      // Jump out of the innermost, in this case unlabeled, loop
      else
        continue;   // Jump to the end and continue with the innermost, in this case unlabeled, loop
    }
    if (expression)
      break outer;     // Jump out of the loop whose label is used
    else
      continue outer;  // Jump to the end and continue with the loop whose label is used
  }
  // ERROR! - Both break and continue must be *INSIDE* the loop whose label they use
  if (expression)
    break inner;
  else
    continue inner;
}


while example:

while (expression)  // Expression must evaluate to boolean
{
  // do stuff
  if (expression)
    break;          // Jumps out of the loop and continues with the first statement *AFTER* the loop
                    // Labelled breaks are also possible
  else
    continue;       // Jumps to the *END* of the loop and continues with evaluating the expression that is the while condition
                    // Labelled continues are also possible
}

while (int x = 2) {}  // ERROR! - variable used in expression must have been declared previously


do example:

// do-loops work exactly the same as while-loops, except that they test their expression at the end
do
{
} while (expression);   // Notice the semicolon (";") !


Exceptions (keywords: try, catch, finally, throw, throws)

The relevant parts of the exception class hierarchy:

Object
+-- Throwable
|   +-- Error
|   +-- Exception
|   |   +-- RuntimeException
|   |   +-- [...]
|   +-- [...]
+-- [...]


A few definitions:

Error
  • Errors represent unusual situations that are not caused by program errors, and indicate things that would not normally happen during program execution, such as the JVM running out of memory
  • Generally, an application won't be able to recover from an error, so it is not required to handle them
  • A class is called an error if it is, directly or indirectly, a subclass of java.lang.Error
Exception
  • A class is called an exception if it is, directly or indirectly, a subclass of java.lang.Exception
  • There are two types of exceptions: Runtime exceptions and Checked exceptions
Runtime exception
  • A runtime exception can be generated by the runtime system at any time during program execution. Method declarations do not need to include runtime exceptions
  • A class is called a runtime exception if it is, directly or indirectly, a subclass of java.lang.RuntimeException
Checked exception
  • Any exception that is not an error or a runtime exception is a checked exception
  • The name derives from the fact that the compiler "checks" that the exception has been declared by the method that throws them
Unchecked exception
  • Errors and runtime exceptions are sometimes also collectively called unchecked exceptions


Exception rules:

  • Anything that inherits Throwable can be 1) thrown, and 2) caught
  • A method does not need to declare any errors or runtime exceptions that it throws, or that it does not handle (although it can)
  • A method must declare any checked exceptions that it throws, or that it does not handle
  • A try clause needs at least one catch or finally clause
  • The order in which catch clauses appear is important for exception matching, i.e. clauses that handle more specific exceptions (= exception classes further down the inheritance tree) must appear first; the compiler will generate an error if this rule is violated


Examples:

class BaseException extends Exception {}  // Declare a custom exception
class SubException extends BaseException {}
class Foo
{
  // - Must declare AnotherException because it is thrown
  // - Must declare BaseException because it is re-thrown
  // - Do not have to declare AnException because it is handled
  // - Do not have to declare RuntimeExceptions even though it is explicitly thrown
  void doIt() throws AnotherException, BaseException
  {
    try
    {
    }
    catch(AnException e)  // Name the exception type; the exception instance *MUST* be assigned to a variable
    {
      // Things that we can get from an exception object
      e.printStackTrace();
      // Throw a different exception
      throw new AnotherException();
    }
    catch(BaseException e)  // Will also catch SubException
    {
      throw e;  // re-throw the exception
    }
    catch(Exception e)  // Later catch clauses may catch less specific exception types. If the order is wrong,
                        // there will be a compiler ERROR
    {
      // don't rethrow, i.e. handle the exception
    }
    finally  // Optional clause
    {
      // Code is always executed:
      // - If no exception is thrown: After the try block finishes executing
      // - If an exception is thrown and caught: After the catch block finishes executing
      // - If an exception is thrown and *NOT* caught: After the exception is encountered, before the method is left
      // - If a return statement is encountered: Before the method is left, but *AFTER* the return statement is executed
      //   and the return value is put on the stack
    }

    // Runtime exceptions do not have to be declared even though they are thrown explicitly
    throw new RuntimeException();
  }

  // return vs. finally example
  void doThat()
  {
    int i = 3;
    try
    {
      return ++i;  // Places the return value 4 on the stack
    }
    finally
    {
      i *= 2;      // i becomes 8, but the return value 4 is already on the stack
    }
  }
}  


Common errors and exceptions:
Exception JVM exception Programmatic exception Remarks
ArrayIndexOutOfBoundsException yes -
ClassCastException yes -
IllegalArgumentException - yes Very general exception. There are a number of more specific subclasses that say more about the concrete problem (e.g. NumberFormatException)
IllegalStateException - yes Thrown when the state of the environment doesn’t match the operation being attempted (e.g., using a Scanner that’s been closed)
NullPointerException yes -
NumberFormatException - yes E.g. Trying to create a number object or primitive by parsing a badly formatted String
AssertionError - yes Not an exception. Thrown when an assert() statement fails.
ExceptionInInitializerError yes - Thrown when attempting to initialize a static variable or an initialization block
StackOverflowError yes - E.g. recursive methods
NoClassDefFoundError yes - Thrown when the JVM can’t find a class it needs, because of a command-line error, a classpath issue, or a missing .class file


Variable argument lists (var-args)

Var-args are new in Java 1.5. Some rules:

  • Declaration format: "<type>... <parameter name>"
  • Only the last parameter in a method can be a var-args declaration
  • Inside the method, the var-args parameter is treated like an array


The following is copied straight out of some book:

class Foo
{
  // Legal
  void legalStuff1(int... x) { }          // Expects from 0 to many ints as parameters; x is an array
  void legalStuff2(char c, int... x) { }  // Expects first a char, then 0 to many ints
  void legalStuff3(Animal... animal) { }  // 0 to many Animals
  void legalStuff4(int[]... x) { }        // 0 to many int arrays; x is an array of arrays
  // Illegal
  void illegalStuff1(int x...) { }             // Bad syntax
  void illegalStuff2(int... x, char... y) { }  // Too many var-args
  void illegalStuff3(String... s, byte b) { }  // var-arg must be last
}


Some more advanced examples:

class Foo
{
  void m1(int x, int y) { }  // overload A
  void m1(byte... x) { }     // overload B

  void testIt()
  {
    byte b = 5; 
    m1(b, b);   // SURPRISE! - invokes overload A, because Java tries to be backwards compatible;
                // var-args syntax is new to Java 1.5, therefore the compiler's overload matching
                // algorithm chooses the overload that already existed pre-1.5.
  }

}


Assertions (keywords: assert)

Rules:

  • Assertions were introduced in Java 1.4
  • Assertions are inactive unless specifically enabled on the command line
  • The first argument of an assertion must evaluate to a boolean
  • The second, optional argument of an assertion must evaluate to a string. Since all values will be automatically converted to a string, the only thing that will cause problems here is "no value", e.g. a method returning void
  • Assertion errors should never be caught and handled by an exception handler - this would defeat their purpose
  • Assertions should not be used to perform normal argument validation in methods, or to validate command-line arguments
  • They may be used in private methods, though
int i = 3;
assert(i < 5);  // ok
assert(i > 5);  // throws an AssertionError
assert(i > 5) : "i is greater than 5";  // the String value of the second expression is added to the stack trace


Enable/disable assertions at runtime:

# Enable/disable assertions in all classes
java -ea                -classpath ch.herzbube Main
java -enableassertions  -classpath ch.herzbube Main
java -da                -classpath ch.herzbube Main
java -disableassertions -classpath ch.herzbube Main

# Enable assertions only in class ch.herzbube.Foo
java -ea:ch.herzbube.Foo     -classpath ch.herzbube Main
# Enable assertions in all classes except in ch.herzbube.Foo
java -ea -da:ch.herzbube.Foo -classpath ch.herzbube Main
# Enable assertions in all classes except those in the package ch.herzbube and all its subpackages
java -ea -da:ch.herzbube...  -classpath ch.herzbube Main
# Enable assertions in general, but disable assertions in system classes
java -ea -dsa                -classpath ch.herzbube Main


Compile old (pre Java 1.4) code that contains "assert" as an identifier (e.g. a variable) instead of a keyword:

javac -source 1.3 OldCode.java


Compile newer code with an explicit compiler version. When it comes to assert-as-a-keyword, these explicit -source option can be safely omitted.

javac -source 1.4 NewerCode.java
javac -source 1.5 NewerCode.java
javac -source 5   NewestCode.java     # equivalent
javac -source 1.6 NewestCode.java
javac -source 6   NewestCode.java     # equivalent


Coding Java: More on OO

Structure of a source code file (.java)

Source code files have the extension .java. The structure of such a file is best explained with an example. Note that the example demonstrates structural rules - these are not voluntary or conventions!


A file Foo.java might look like this:

// If the class defined in this file belongs to a package, the package declaration
// must appear as the first thing in the file, before any import statement or the
// class declaration.
package ch.herzbube.example;

// If any import statements are required, they must appear in between the package
// statement and the class declaration.
import ch.herzbube.example.Bar;

// There can be zero or one *public* class, interface or enum per file. If there *is*
// a public class interface or enum, the class, interface or enum name and the file
// name must be the same. A class, interface or enum is *public* only if it is
// explicitly declared as such.
public class Foo
{
  ...
}

// There can be any number of non-public classes, interfaces or enums per file, and
// the class, interface or enum name(s) and the file name are not required to be the
// same. If nothing prevents it, though, it makes a lot of sense to have the same
// name for the file and the main class, interface or enum. Without the explicit
// access modifier "public", a class, interface or enum has the access control level
// *default*, which means the class, interface or enum is visible only to other code
// in the same package.
class ExampleClass
{
  ...
}
class AnotherExampleClass
{
  ...
}


Packages and class access/visibility (keywords: package, import, public, protected, private)

Note: The following dicussion involves top-level classes/interfaces/enums. Inner classes/interfaces/enums are a separate topic and covered in a separate section.


Packages are used to conceptually organize code, and a package provides a namespace for its members. Sun suggests to use packages in reverse-domain notation, e.g. for a project "Example" I would use the package name ch.herzbube.example.


Note: The Java environment, and notably the compiler, do not assign any relationship to a sub-package and its parent package. The two packages use different package identifiers, that's all that counts. Example: ch.herzbube and ch.herzbube.example are as unrelated as com.sun.java and net.sourceforge.


A class/interface/enum is accessible/visible depending on how it is declared, and where in the package hierarchy the importing class/interface/enum lives. The rules are:

  • A class/interface/enum cannot be declared "protected" or "private" - these modifiers do not make sense on a class/interface/enum and are, in fact, not allowed in declarations.
  • A class/interface/enum that is explicitly declared "public" (using the public keyword) can be accessed from everywhere. The class/interface/enum needs to imported, though.
  • A class/interface/enum that is not explicitly declared "public" has the default access level, which means that it is visible only to other classes/interfaces/enums in the same package. "Same" meaning: The package with the exact same package identifier. Notably, another class/interface/enum that lives in a parent package or sub-package does not have any special access rights just because the packages are on the same branch of the package tree.


If a class A has access to another class B (in other words: if class B is visible to class A), then class A can do the following things. Note: This list only makes sense for classes. Due to their characteristics, interfaces and enums are treated slightly different (e.g. an enum cannot be extended, an interface is implemented, etc.).

  • Create an instance of B (unless B is declared abstract)
  • Extend B (unless B is declared final)
  • Access methods and variables in B, depending on the access control of those members


The keyword import is used 1) to declare that a class/interface/enum defined in another package is used, and 2) to be able to refer to the class in a shorthand manner. A class can be used without an import statement, but in this case the entire package path needs to be specified each time that the class is referred to. For instance:

ch.herzbube.example.Foo anInstance = new ch.herzbube.example.Foo()

The use of import makes everything much easier. These examples demonstrate how to use the keyword:

// ch.herzbube
//  +-- example           <-- Foo lives here
//  +-- package           <-- BarOne and BarTwo live here
//       +-- subpackage   <-- Sub lives here

import ch.herzbube.example.Foo;          // Single class
import ch.herzbube.package.*;            // All classes in a given package, but *NOT* in sub-packages
import static java.lang.System.out;      // Import of a single static member
import static java.lang.Integer.*;       // Import of all static members
import static java.lang.Long.MAX_VALUE;  // To demonstrate conflicts

Foo f1 = new Foo();                                          // Alias name
ch.herzbube.example.Foo f2 = new ch.herzbube.example.Foo();  // Full name; we can mix
Foo f3 = new ch.herzbube.example.Foo();                      // Silly but legal

BarOne b1 = new BarOne();  // Alias name is OK because all classes of the package have been imported
BarTwo b2 = new BarTwo();  // ditto

Sub s = new Sub();         // ERROR! - Sub-package has *NOT* been imported

out.println("42");             // Refer to an imported static member
out.println(toHexString(42));  // This can also be a function (toHexString() belongs to java.lang.Integer)
out.println(MAX_VALUE);        // ERROR! - The compiler sees MAX_VALUE both in java.lang.Integer and java.lang.Long


Declaring classes (keyword: class)

Top-level classes

The following example demonstrates how a basic class declaration looks like.

class Foo
{
  // An instance variable
  int i;
  // Assigning an initial value is optional
  int j = 42;
  // Constants are declared with "static final" and by convention use uppercase
  // names with words separated by "_" characters
  static final int MAX_FILE_SIZE_KB = 1000;
  // Unless it is abstract, a method is followed immediately by its implementation
  void m1(int arg1)
  {
    ...
  }  // no semicolon!
  // Static functions look the same
  static void m2() {}
  // Constructors don't have a return value and must be named after the class. They
  // can be placed anywhere in the class declaration
  Foo() {}
}


Initialization blocks

Some rules:

  • Are "anonymous" blocks of code, i.e. code that is not associated with a named constructor or method
  • There are static and instance initialization blocks
  • There may be several static or instance initialization blocks per class
  • A static or instance variable initialization is a simple init block that consists of the variable initialization code
  • Init blocks are executed in the order in which they appear
  • Static init blocks are executed when the class is loaded by the class loader for the first time
  • Instance init blocks are executed each time the class is instantiated, right after the superclass constructor has finished executing, but before the class' constructor starts executing


Some examples:

class Foo
{
  Foo() { y3 = 42; }    // Constructor executes last, regardless of where it appears in the class declarations

  static { x2 = 42; }   // Static init block; executed first
  static int x1 = 42;   // Static init block, disguised as variable initialization; executed after the block above
  static int x2;

  int y1 = 42;  // Instance init block, disguised as variable initialization; executed first
  int y2;
  { y2 = 42; }  // Instance init block, executed after the variable initialization block above
  int y3;
}


Inner classes

Initial notes:

  • An inner class instance is always tied to an instance of its outer class
  • The inner class instance, in fact, has a reference to the outer class instance; this is important to remember in the context of the Garbage Collector!
  • An inner class instance and an outer class instance have access to all members of each other - even if the members are private!
  • Inner classes were added to the Java language mainly for the purpose of writing event handlers.
  • Inner classes can be nested


Types of inner classes
Class type Scope Restrictions Remarks
Regular inner class, or just "inner class" Outer class No static declarations Compiling generates a separate .class file for the inner class
Method-local inner class Method No static declarations Can only be instantiated within the method in which the class is defined. Can only access method-local variables that are declared final.
Anonymous inner class Both outer class and method Either derives from exactly one class, or implements exactly one interface


Examples:

// Just a helper
abstract class BaseClass
{
  BaseClass(String s) {}
}

// Compiling generates file OuterClass.class
class OuterClass
{
  private int outerMember = 42;
  private static int staticOuterMember = 42;
  int shadowedMember = 42;

  // Regular inner class with default access level
  // Compiling generates file OuterClass$RegularInnerClass.class
  class RegularInnerClass
  {
    private int innerMember = 42;
    int shadowedMember = 17;
    public void doIt()
    {
      outerMember = 24;                 // Access outer instance variable as if it were a member of the inner class - even if it's private!
      staticOuterMember = 24;           // Ditto for static members
      OuterClass oc = OuterClass.this;  // Access to outer class instance

      // Access to shadowed variable; works the same for shadowed methods
      shadowedMember *= 2;                  // 34; references inner class member
      this.shadowedMember *= 2;             // 68; references inner class member
      OuterClass.this.shadowedMember *= 2;  // 84; references outer class member
    }
    static int staticInnerMember = 42;  // ERROR! - no static declarations within inner class
    static void staticMethod();         // ERROR! - ditto
  }

  // Other regular inner classes, but with different access modifiers
  public class PublicRegularInnerClass {}
  protected class ProtectedRegularInnerClass {}
  private class PrivateRegularInnerClass {}

  void methodWithInnerClass()
  {
    int regularLocalVariable = 42;
    final int finalLocalVariable = 42;
    // Method-local inner class
    class MethodLocalInnerClass
    {
      MethodLocalInnerClass()
      {
        regularLocalVariable = 24;        // ERROR! - Cannot access regular variable local to the method
        int i = finalLocalVariable;       // OK - Final local variables are allowed (nobody knows why, but we are not here to ask questions :-()
        OuterClass oc = OuterClass.this;  // Access to outer class instance is still possible
      }
    }  
    MethodLocalInnerClass mlic = new MethodLocalInnerClass();       // Instantiate *AFTER* the class definition
    MethodLocalInnerClass mlic = this.new MethodLocalInnerClass();  // ERROR! - This syntax works only for regular inner classes
  }
  static void staticMethodWithInnerClass()
  {
    // Method-local inner classes can also appear in a static context
    class MethodLocalInnerClass
    {
      MethodLocalInnerClass()
      {
        OuterClass oc = OuterClass.this;  // ERROR! - Cannot access outer class instance, this inner class belongs to a static method!
      }
    }
  }
  void makeInner()
  {
    // Create inner class instance and access a member
    RegularInnerClass ric = new RegularInnerClass();
    ric.doIt();
    // Access to private inner class members is also possible
    ric.innerMember = 24;

    // Alternative method to create the inner class instance. This is only listed to illustrate
    // similarities with staticMakeInner() below where we are forced to use the weird new() syntax
    RegularInnerClass ric = this.new RegularInnerClass();

    // ERROR! - Cannot access method-local inner class defined in another method
    MethodLocalInnerClass mlic = new MethodLocalInnerClass();
  }
  static void staticMakeInner()
  {
    // Static context: First we require an outer class instance
    OuterClass oc = new OuterClass();
    // Weird new() syntax that is required so that the inner class instance is created in the correct context
    RegularInnerClass ric = oc.new RegularInnerClass();
    ric.doIt();
  }

  // Anonymous inner class type 1: Derives from a class
  Object aic_1a = new Object()  // derive from class Object
  {
    void doIt() {}
  };  // <------------------------- notice the semicolon !!!
  // Another AIC type 1: This time we demonstrate that we can use other constructors than just the non-arg constructor
  BaseClass aic_1b = new BaseClass("foo") {};
  // Anonymous inner class type 2: Implements an interface
  Runnable aic_2a = new Runnable()  // implements interface Runnable
  {
    public void run() {}
  };  // <------------------------- again, the semicolon!
  void makeAnonymousInnerClass()
  {
    Object aic_1c = new Object() {};           // Anonymous inner class type 1
    Runnable aic_2b = new Runnable() { ... };  // Anonymous inner class type 2
    runIt(new Runnable()     // Anonymous inner class type 3: Argument-defined anonymous inner class
      {
        public void run() {}
      });  // <-------------------- this time the semicolon ends the statement that invokes runIt()
  }
  void runIt(Runnable r) {}
}

class SomeOtherClass
{
  void makeInner()
  {
    // Same as static context: First we require an outer class instance
    OuterClass oc = new OuterClass();
    OuterClass.RegularInnerClass ric = oc.new RegularInnerClass();

    // Normal access level restrictions
    ric.doIt();                                                                         // OK - public method
    ric.innerMember = 24;                                                               // ERROR! - private variable
    OuterClass.PublicRegularInnerClass pric1 = oc.new PublicRegularInnerClass();        // OK
    OuterClass.ProtectedRegularInnerClass pric2 = oc.new ProtectedRegularInnerClass();  // OK
    OuterClass.PrivateRegularInnerClass pric3 = oc.new PrivateRegularInnerClass();      // ERROR!
  }
}


Static nested classes

Static nested classes, sometimes also known as "top-level nested class", are not associated with a specific instance of the outer class encapsulating them. Because of this they are technically not considered inner classes, and are therefore treated in a separate section.


An example of the very simple syntax:

class OuterClass
{
  private int outerMember = 42;
  private static int staticOuterMember = 42;
  private static int staticShadowedMember = 42;

  public static class StaticNestedClass
  {
    private int nestedMember = 42;
    private static int staticNestedMember = 17;
    public void doIt()
    {
      staticOuterMember = 24;  // Access outer class variable as if it were a member of the inner class - even if it's private!
      outerMember = 24;        // ERROR! - no access to instance variables from a static context

      // Access to shadowed variable; works the same for shadowed methods
      staticShadowedMember  *= 2;                   // 34; references inner class member
      StaticNestedClass.staticShadowedMember *= 2;  // 68; references inner class member
      OuterClass.staticShadowedMember *= 2;         // 84; references outer class member
    }
  }
  void doIt()
  {
    StaticNestedClass snc = new StaticNestedClass();
    snc.nestedMember = 24;
    snc.staticNestedMember = 24;
  }
}
class SomeOtherClass
{
  void doIt()
  {
    // Refer to the static nested class by prefixing its name with the name of the outer class
    OuterClass.StaticNestedClass snc = new OuterClass.StaticNestedClass();
    snc.nestedMember = 24;        // ERROR! - Instance variable is not visible because it is private
    snc.staticNestedMember = 24;  // ERROR! - Ditto for class variable
  }
}


Class member access (keywords: public, protected, private)

As in C++ we can declare class members using the modifiers public, protected and private. Unlike C++, though, if none of these keywords is present the member has the access control level default (in C++ it's private).

The access control level is used in two ways:

  • It influences the visibility of a member to code outside the class
  • It influences what a subclass can inherit from its superclass


The following example is especially important to understand how protected works.

package ch.herzbube.ch.example;
public class Base
{
  // Public members can be accessed by all other classes, regardless of the
  // package they live in. The same goes for inheritance: A subclass inherits
  // all public members regardless of the package it belongs to.
  public int i1;
  public void m1();
  // Protected members can be accessed by other classes only if they live in
  // the same package. It's different for inheritance: A subclass inherits
  // all protected members, regardless of the package it belongs to. Note,
  // however, that the subclass code cannot *ACCESS* the member through a
  // *REFERENCE* to the superclass. 
  protected int i2;
  protected void m2();
  // Private members are not accessible, and are not inherited, by any class.
  // They are exclusively private to the class they are declared in.
  private int i3;
  private void m3();
  // Members with default access level are almost the same as protected members,
  // with one exception: Inheritance is more restricted in that only subclasses
  // that live in the same package as the superclass inherit
  // default-access-level members.
  int i4;
  void m4();
}

package ch.herzbube.ch.example.subpackage;
import ch.herzbube.ch.example.Base;
class Sub extends Base
{
  void m17()
  {
    // m2() is protected, so we inherit it and can call it even though we are
    // in a different package 
    m2();
    // Because we live in a different package we cannot access m2() through a
    // reference to Foo. This is pretty stupid, IMHO, but unfortunately this
    // is the way how Java works :-(
    Foo f = this;
    this.m2();   // compiler error !!!
  }
}
class Foo
{
  void m42()
  {
    // m2() remains inaccessible to classes in the same package as the subclass
    // because the protected declaration happened in another package. Good!
    Sub s = new Sub();
    s.m2();
  }
}


Declaring interfaces (keyword: interface)

An interface is basically the same as a class with the following properties:

  • It is declared abstract
  • All of its methods are declared public and abstract


This means that

  • We can apply modifiers to an interface in the same way as we would to a class. For instance, public influences an interface's visibility
  • Or we can let one interface extend another
  • Or we can create inner interfaces, similar to inner classes. The only two types that are allowed, though, are regular inner interfaces, and static nested interfaces. An inner interface can be nested inside an outer class, enum or interface, and it is possible to create both an inner class and inner enum within an outer interface. Which of these combinations make sense is another matter.


Examples (without inner or outer interfaces):

// An interface is implicitly declared "abstract". We could write "public abstract ..."
// but this would be redundant and should therefore be avoided.
public interface AnInterface
{
  // An interface method is implicitly declared "public" and "abstract". We
  // could write "public abstract void ...", but this would be redundant and
  // should be avoided.
  // Because interface methods are abstract, they *cannot* be declared "final",
  // "strictfp" or "native". An interface method also cannot be declared "static"
  // since interfaces always declare instance methods.
  void doIt();
  // An interface variable is implicitly declared "public", "static" and "final".
  // In other words, it is a constant and not an instance variable! We could write
  // "public static final int ...", but this would be redundant and should be
  // be avoided.
  // Because interface variables are constants, they *must* be initialized within
  // the interface code.
  int i = 42;
}

// As with classes, an interface is not required to be public. The interface in
// this case is visible only within the same package.
interface AnotherInterface
{
  ...
}

// Not surprisingly, an interface can extend one or more other interfaces. It
// doesn't make sense (nor is it possible even to try), though, to extend something
// else than interfaces (e.g. a class) or to implement anything at all (e.g. another
// interface).
public interface YetAnotherInterface extends AnotherInterface
{
  ...
}


Declaring enumerations (keyword: enum)

Enums are new to Java 1.5. An enum is basically the same as a class with the following properties:

  • The very first thing that must be declared in an enum are its values. After that enum declarations work the same as class declarations
  • Each value has the type of the enum that it belongs to
  • The static method values() is automatically added to each enum; when invoked it returns an array with all the enum values, in the order in which they were declared
  • A value can be declared with a so-called "constant specific class body" which can be used to override certain methods of the enum


Also note:

  • Enums can be used as key in hash collections because they override equals() and hashCode(). See java.lang.Enum.


Since enums are very similar to classes, this means that

  • We can apply modifiers to an enum in the same way as we would to a class. For instance, public influences an enum's visibility
  • Or we can let an enum implement an interface. It is not possible, though, to let an enum extend anything else (either class or another enum)
  • Or we can create inner enums similar to inner classes. The only two types that are allowed, though, are regular inner enums, and static nested enums. An inner enum can be nested inside an outer class, enum or interface, and it is possible to create both an inner class and inner interface within an outer enum. Which of these combinations make sense is another matter.


An extensive example:

public enum EnumPublicFile
{
  // Enum values must be declared as the very first thing
  VAL_9(9),
  // This value has a so-called "constant specific class body". In it we
  // override getCode() so that a different code is returned for this value
  // only
  VAL_8(8)
  {
    // Return a different code for this value only
    String getCode() { return "b"; }
  },
  // Note the semicolon (";") at the end. It is used to declare that no
  // more enum values will follow
  VAL_7(7);

  // Constructor that is called by each of the value declarations above
  EnumPublicFile(int value) { this.value = value; }
  // Store and return the value
  private int value;
  int getValue() { return value; }
  // Return the default code
  String getCode() { return "a"; }
}

enum EnumDefaultFile
{
  // Note the missing semicolon; it is not required here because there is no
  // more code after the value declarations
  VAL_FOO, VAL_BAR, VAL_FOOBAR
}

class ClassWithEnums
{
  public enum EnumPublic { VAL_1, VAL_2, VAL_3 }
  protected enum EnumProtected { VAL_1, VAL_2, VAL_3 }
  private enum EnumPrivate { VAL_1, VAL_2, VAL_3 }
  enum EnumDefault { VAL_1, VAL_2, VAL_3 }

  void m1()
  {
    // Enums cannot be declared within methods
    public enum EnumInMethod { VAL_1, VAL_2, VAL_3 }  // illegal !!!

    EnumPublicFile epublicfile = EnumPublicFile.VAL_9;
    EnumDefaultFile edefaultfile = EnumDefaultFile.VAL_BAR;
    EnumPublic epublic = EnumPublic.VAL_1;
    EnumProtected eprotected = EnumProtected.VAL_1;
    EnumPrivate eprivate = EnumPrivate.VAL_1;
    EnumDefault edefault = EnumDefault.VAL_1;
  }
}

class ClassWithoutEnums
{
  void m1()
  {
    EnumPublicFile epublicfile = EnumPublicFile.VAL_9;
    EnumDefaultFile edefaultfile = EnumDefaultFile.VAL_BAR;
    ClassWithEnums.EnumPublic epublic = ClassWithEnums.EnumPublic.VAL_1;
    ClassWithEnums.EnumProtected eprotected = ClassWithEnums.EnumProtected.VAL_1;
    // Access not possible because the enum is private
    ClassWithEnums.EnumPrivate eprivate = ClassWithEnums.EnumPrivate.VAL_1;  // illegal !!!
    ClassWithEnums.EnumDefault edefault = ClassWithEnums.EnumDefault.VAL_1;

    // Get all values as an array from the built-in method values()
    EnumPublicFile[] epublicfileValues = EnumPublicFile.values();
  }
}


Extending classes and implementing interfaces (keywords: extends, implements, abstract, final)

To extend a class, or subclass it, write something like the following. Note that it is not possible to inherit from more than one base class.

class Sub extends Base

To implement one or more interfaces, write code similar to the following:

// Separate interface names with commas
class Foo implements AnInterface, AnotherInterface
{
  // *All* methods of an interface must be implemented if the class should be non-abstract.
  // If an interface method is implemented, it *must* be declared public. If the keyword were
  // missing the method's visibility would be decreased, which is not allowed.
  public void AnInterfaceMethod1() { ... }
  public void AnInterfaceMethod2() { ... }
  // The method signature must, of course, match the method declaration in the interface.
  // More precisely, the method must follow all the rules for LEGAL OVERRIDES. This means that
  // some things in the signature may change; see section on Overrides for details.
  public int AnotherInterfaceMethod1(String s) { ... }
}

// It is legal syntax to redeclare which interfaces are implemented by a subclass' base classes.
// As can be seen in this example, even partial redeclaration is allowed. IMHO this does not
// make much sense, though.
class Bar extends Foo implements AnInterface
{
}

A class cannot be subclassed if it is declared final. A class cannot be instantiated if it is declared abstract. The following declarations therefore would not make much sense (in fact it does not compile):

abstract final class Foo

A commented example to clarify abstract:

# A class *must* be declared abstract if at least one of its methods are declared abstract.
# A class *may* still be declared abstract even if all of its methods are non-abstract.
abstract class Foo
{
  // An abstract class can have members
  private int i;
  // An abstract method does not have an implementation, instead its declaration ends with a ";"
  public abstract void doThis();
  // An abstract class can have non-abstract methods
  public void doThat() { ... }
}

Another example to demonstrate how classes can gradually become "less" abstract over time:

interface AnInterface
{
  void anInterfaceMethod();
}
interface AnotherInterface
{
  void anotherInterfaceMethod();
}
// Implementing an interface does not require the class to implement the interface
// methods - as long as the class remains abstract!
abstract class Base_Abstract implements AnInterface, AnotherInterface
{
  abstract void m_abstract1(); 
  abstract void m_abstract2(); 
}
// Partial implementation 1. Notice how we do not need to repeat the declarations
// of those methods that remain abstract. We *could* repeat declarations, but it
// would be redundant and should be avoided.
abstract class Sub_Abstract1 extends Base_Abstract
{
  void m_abstract1() {} 
}
// Partial implementation 2
abstract class Sub_Abstract2 extends Sub_Abstract1
{
  public void anInterfaceMethod() {} 
}
// Only in the concrete subclass do we need to implement all the remaining
// abstract methods - be they interface methods or methods declared abstract
// in a base class.
class Sub_Concrete extends Sub_Abstract2
{
  void m_abstract2() {} 
  public void anotherInterfaceMethod() {} 
}

Note: abstract can never be combined with final, private or static.


Interface implementation "tricks":

import java.io.FileNotFoundException;
interface AnInterface
{
  void anInterfaceMethod() throws FileNotFoundException;
}
interface AnotherInterface extends AnInterface
{
  AnInterface anotherInterfaceMethod();
}
public class Foo implements AnotherInterface
{
  // OK - Override rules state that checked exceptions do not need to be redeclared
  public void anInterfaceMethod() { ... }
  public void doIt()
  {
    AnInterface ai = new Foo();
    ai.anInterfaceMethod();  // ERROR! - no surprise here, we need to catch the exception
    Foo f = new Foo();
    f.anInterfaceMethod();   // OK - this came as a bit of a surprise to me, although it's logical after the second glance
  }
  // OK - Override rules state that a subtype of the original return type is legal. This is called a covariant return.
  public AnotherInterface anotherInterfaceMethod() { ... }
}


Constructors (keyword: new)

Rules about constructors:

  • Have the same name as the class
  • Do not have a return type
  • Can have arguments (even var-args)
  • Can have the following modifiers: public, protected, private
  • Cannot have the following modifiers: static (constructors are responsible for creating an instance), final (constructors cannot be overridden), abstract (same reason)
  • The first statement in a constructor body must be
    • Either a call to a superclass constructor (e.g. super(), super(42))
    • Or a call to another overloaded constructor in the same class (e.g. this(), this(42))
    • If no explicit constructor call is present, the compiler manufactures a call to super() (which may result in a compiler error if the superclass does not have a no-arg constructor)
  • Only static variables and methods can be accessed as part of the call to super() or this()
  • If a class has no explicit constructors, the compiler inserts a default constructor which has the following properties:
    • It's a no-arg constructor
    • Its implementation consists of a single call to super()
    • The constructor's access modifier is the same as the class' access modifier (i.e. either public or default, since classes cannot have protected or private)


Constructors are invoked using the new keyword. Some examples:

// If no explicit constructor is present, the compiler manufactures a default constructor
class Foo {}
abstract class AbstractBase
{
  // An abstract class can also have a constructor.
  AbstractBase() {}
  void anAbstractMethod();
}
// This class has explicit constructors, so there is no default constructor
class Base extends AbstractBase {
{
  static final int THE_ANSWER = 42;
  static int getTheAnswer() { return Base.THE_ANSWER; }
  Base(int i)
  {
    // Explicitly invoke superclass constructor. In this case we invoke a constructor
    // of the root class Object. Without an explicit call to a superclass constructor,
    // a default call to super() is inserted by the compiler as the very first statement
    // of the constructor body.
    super()
  }
  Base(String s)
  {
    // Instead of invoking a superclass constructor, we may also invoke an overloaded
    // constructor in the same class. We may use static methods or variables for invoking
    // this() or super().
    this(Base.getTheAnswer());
  }
  void anAbstractMethod() {}
}
class Sub1 extends Base
{
  // ERROR! - Compiler inserts default constructor with implicit call to
  // no-arg superclass constructor super(), but class Base does not have
  // such a no-arg constructor
}
class Sub2 extends Base
{
  Sub2()
  {
    int i = 3;   // ERROR! - call to superclass constructor must be the first thing in constructor body
    super(42);
  }
}
class Test
{
  void doIt()
  {
    Foo f2 = Foo()           // ERROR! - constructors are not normal methods! they must be invoked using the keyword new
    Foo f1 = new Foo();      // Invoke the auto-generated default constructor
    Base b1 = new Base();    // ERROR! - no default no-arg constructor present because we declared an explicit constructor
    Base b2 = new Base(42);  // OK
  }
}


Order of constructor execution in a class hierarchy:

  • Superclass constructors are executed first
    • This means that one of the constructors of the root class Object is executed as the very first thing when a new object is created
    • If a constructor body does not explicitly invoke an overloaded constructor in the same class, or a superclass constructor, the compiler inserts a call to the no-arg superclass constructor super()
  • Before a class' constructor executes, any initialization blocks are executed
    • These blocks are executed in the order in which they appear in the class declaration
    • An instance variable initialization counts as an initialization block
  • Finally, the class' constructor(s) is (are) executed


Overriding (keyword: final)

Some rules that overriding methods must follow:

  • A method cannot be overridden if it is not inherited in the first place (because of a restrictive access level such as private). If any of the following rules is violated, but the method in question is not inherited in the first place, a compiler error is NOT generated - the rule's violation simply results in a new method being created
  • The argument list must exactly match that of the overridden method. Violating this rule does not generate a compiler error, it simply results in a new method overload.
  • The return type must be the same as, or a subtype of, the return type declared in the overridden method. Violating this rule results in a compiler error.
    • Note: A return type that is a subtype is called a covariant return and is a feature new to Java 1.5
  • The access level cannot be more restrictive than the overridden's method (violiating this rule results in a compiler error); it can be less restrictive, though
  • Overriding of final or static methods is not possible. Violating this rule results in a compiler error.
  • The overriding method must not throw checked exceptions that are new or broader than those declared by the overridden method. Violating this rule results in a compiler error.


Examples:

package ch.herzbube.example;
public class Base
{
  // Private methods are not inherited by subclasses and therefore cannot be
  // overridden by the subclass.
  private void m_private() { ... }
  // Methods with default access level are inherited only by classes that
  // live in the same package, so these methods can be overridden *sometimes*.
  void m_default() { ... }
  // Final prevents a method from being overridden
  public final void m3() { ... }
  // We use this method to demonstrate how to change visibility of
  // overridden methods
  protected void m_protected() { ... }
}

package ch.herzbube.example;
import ch.herzbube.example.Base;
class Sub extends Base
{
  // This is *NOT* overriding the m_private() method in the superclass!
  // m_private() here just *HAPPENS* to have the same name as a private
  // superclass method. As this example demonstrates, return type and
  // thrown exceptions can be different from the superclass method.
  public int m_private() throws X { ... }
  // The subclass lives in the same package, so it inherits m_default()
  // and can override it.
  void m_default() { ... }

  // ERROR! - Overriding a final method is not possible
  public void m_final() { ... }
  
  // OK - Overriding with the same access level (protected)
  protected void m_protected() { ... }
  // OK - Overriding with an increased access level (public is less restrictive than protected)
  public void m_protected() { ... }
  // ERROR! - Overriding with a decreased access level (private and default are more restrictive than protected)
  private void m_protected() { ... }
  void m_protected() { ... }
  
}

package ch.herzbube.anotherexample;
import ch.herzbube.example.Base;
class Sub extends Base
{
  // Again, this is *NOT* overriding m_default() because the subclass lives
  // in a different package than the superclass and therefore does *NOT*
  // inherit m_default()
  public int m_default() throws X { ... }
}


Exceptions and invoking the superclass version of an ovverridden method:

class Base
{
  public void doIt() throws FileNotFoundException { ... }
}

class Sub extends Base
{
  // OK - We override doIt() even though we do not declare the exception
  public void doIt()
  {
    // ERROR! - We don't declare the exception, therefore we have to catch it
    super.doIt();
  }
  public void doSomethingElse()
  {
    Base b = this;
    // ERROR! - the superclass version of doIt() declares an exception, therefore
    // we have to catch it
    b.doIt();
    // OK - the overridden version of doIt() doesn't declare an exception, therefore
    // we don't have to catch it
    doIt();
  }
}


Overloading

Overloading means reusing the same method name in a class or interface. To distinguish the overload from the original method, it must change the argument list! Optional additional changes:

  • Different return type
  • Different access modifier
  • New or broader checked exceptions


Note: Two methods with the same name but in different classes can still be considered overloaded, if the subclass inherits one version of the method and then declares another overloaded version in its class definition.


Invoking overloaded methods:

public class Overloader
{
  // Overload 1
  public int addThem(int x, int y)
  {
    return x + y;
  }
  // Overload 2
  public double addThem(double x, double y)
  {
    return x + y;
  }
  void invoker()
  {
    int i = 1;
    double d = 2.5;

    addThem(i, i);  // clearly O1
    addThem(d, d);  // clearly O2
    addThem(i, d);  // O2, because int can be widened to double, but double cannot be narrowed to int
    addThem(d, i);  // O2, same reason
  }
}


Casting

Note: Unlike C++, a cast in Java actually does something - even if the cast merely assigns one reference to another. At runtime the JVM tries to execute the cast and, if it detects that the cast is illegal, throws java.lang.ClassCastException.


Simple downcasting works like this:

Sub s = (Sub) base;
((Sub) base).doIt();

Upcasting always works, whether or not we use an explicit cast:

Base b = sub;         // implicit upcast
Base b = (Base) sub;  // explicit upcast

Casting will not work if the two classes are not in the same inheritance tree (in C++ we would have to use static_cast() for the same level of type checking):

Foo f = (Foo) bar;   // ERROR! - Foo and Bar are unrelated

Casting also works for interfaces. Since interface implementations are not bound to an inheritance tree, the compiler has much more problems catching silly programmer errors.

class Foo extends AnInterface { ... }
final class Bar extends AnInterface { ... }

AnInterface ai1 = new Foo();                            // implicit upcast, obviously works
AnInterface ai2 = new Bar();                            // implicit upcast, obviously works
AnotherInterface aoi1 = (AnotherInterface) new Foo();   // this surprisingly compiles, even though Foo does not implement AnotherInterface!
AnotherInterface aoi2 = (AnotherInterface) ai1;         // this surprisingly compiles, even though the two interfaces are unrelated!
AnotherInterface aoi3 = (AnotherInterface) new Bar();   // ERROR! - final keyword triggers more rigorous compiler checks


Shadowing

"Shadowing" is the process of declaring a local variable with the same name as a class or instance variable. Example:

class Foo
{
  int i;
  static int j;
  // Parameters shadow the instance and class variables
  void m(int i, int j)
  {
    // Use "this" to reference the instance variable
    this.i = i;
    // Use the class name to reference the class variable
    Foo.j = j;
  }
}


Non-access modifiers (keywords: strictfp, synchronized, transient, volatile, native)

The keyword strictfp expresses that the implementation of either an entire class (when used in a class declaration) or a single method (when used in a method declaration) will conform to the IEEE 754 standard rules for floating points. Without strictfp, floating points used in the methods might behave in a platform-dependent way.


The keyword synchronized can be applied only to method declarations and blocks of code. When a thread hits a method, the JVM acquires the built-in lock of the object that the method belongs to (every Java object has such a built-in lock; if the method is static, the lock of the class object is acquired). When a thread hits a code block, the JVM acquires the lock that is specified at the beginning of the code block. For instance:

synchronized void doIt1() { ... }
void doIt2()
{
  synchronized(this) { ... }
}
static void doIt3()
{
  synchronized(MyClass.class) { ... }
}


The keyword transient can be applied only to instance variables. It indicates that the variable should be ignored when the object containing it is serialized.


The keyword volatile can be applied only to instance variables. It indicates ... TODO


The keyword native ... TODO


Static variables, methods and classes (keyword: static)

class Foo
{
  static int refCount = 0;
  Foo()
  {
    // Refer to the static member (a variable) using the class name
    Foo.refCount++;
    // Or refer to the static member (a method this time) without any modifier
    int refCount = getRefCount();
  }
  void doIt()
  {
    // Refer to the static member (a variable) using the instance reference
    int refCount1 = this.refCount;
    // Or refer to the static member (a method this time) without any modifier
    int refCount2 = getRefCount();
  }
  static int getRefCount()
  {
    // In a static context, refer to the static variable either without modifier,
    // or with the class name
    return refCount;
  }
}


Modifier table

Target public protected private abstract final static strictfp native synchronized transient volatile Combinations that are not allowed Remarks
class x - - x x ? x ? - - - abstract + final
interface x - - x (implicit) - -  x ? - - -
enum
method x x x x x x x ? x - - abstract + final
abstract + private
abstract + static
Combination "final + private" is allowed, but doesn't make much sense
constructor x x x - - - x ? - - - synchronized is not allowed because the lock built-in to the Object root class is not yet present
member variable x x x - x x - ? - x x
local variable - - - - x - - ? - - -
regular inner class x x x x x x x - - - - abstract + final Combination "abstract + private" is allowed and makes sense because another regular inner class within the same outer class may extend the abstract class.
method-local inner class - - - x x - x - - - - abstract + final
anonymous inner class - - - - - - - - - - -
static nested class x x x x x x (must) x - - - - abstract + final Combination "abstract + private" is allowed and makes sense because another static nested class within the same outer class may extend the abstract class.
initialization block - - - - - x - - - - -
anonymous code block - - - - - - - - x - -


Introspection (keywords: instanceof)

Examples with instanceof operator:

interface AnInterface {}
class Foo {}
class Bar extends Foo implements AnInterface {}
class BlackKnight {}
enum AnEnum { VAL_1(1) }

Foo f = new Foo();
Bar b = new Bar();
Foo[] a = new Foo[42];
a[0] = new Foo();
int[] i = new int[42];
if (f instanceof Object)            {}  // true
if (f instanceof Foo)               {}  // true
if (f instanceof AnInterface)       {}  // false
if (f instanceof Bar)               {}  // false
if (b instanceof Foo)               {}  // true
if (b instanceof AnInterface)       {}  // true
if (a instanceof Object)            {}  // true
if (a instanceof Foo)               {}  // false
if (a[0] instanceof Foo)            {}  // true
if (i[0] instanceof int)            {}  // ERROR! - int is a primitive, not a type
if ("foo" instanceof String)        {}  // true
if (null instanceof Object)         {}  // false (always, regardless of the type tested against)
if (AnEnum.VAL_1 instanceof AnEnum) {}  // true
if (f instanceof BlackKnight)       {}  // ERROR! - Foo and BlackKnight are unrelated


Working with class objects:

Class c = Class.forName("Foo");  // Get class object for a named class
c = Foo.class;                   // Equivalent, but involves the compiler


Compiler and command line

Compiler

Compile a source file located in the current working directory, placing the resulting .class file in the same location as the source file:

javac Foo.java

Compile a source file located in a sub-folder. The .class file is also placed in the sub-folder.

javac ch/herzbube/Bar.java

Place .class files in a separate destination folder (which must exist). Note that the compiler will create a folder structure inside the destination folder that matches the package structure.

javac -d /tmp/classes ch/herzbube/Foo.java ch/herzbube/subpackage/Bar.java

If the classes that are compiled import other classes and/or packages, a classpath needs to be defined. Note that the compiler does NOT look into any folders, not even those folders that contain the source files that are being compiled!

java -classpath .:/path/to/search:/path/to/MyJar.jar Foo.java Bar.java
java -cp .:/path/to/search:/path/to/MyJar.jar Foo.java Bar.java             # same, abbreviated argument name
CLASSPATH=.:/path/to/search:/path/to/MyJar.jar java Foo.java Bar.java


Running Java programs from the command line

The JVM is invoked by specifying exactly one class file that is to be executed. The .class extension must be omitted. Some examples:

# Run a class file that is located in the current working directory
java Foo
# Running a class file in a sub-folder requires the use of classpath (classpath syntax is basically the same as with the compiler):
java -classpath ch/herzbube Foo
# This does ***NOT*** work
java ch/herzbube/Foo

.jar file examples:

# Run a class file (in the current working directory) that imports classes from a .jar file:
java -classpath /path/to/MyJar.jar Foo
# Run a class file inside a .jar file. The class file must be located at the ***TOP*** (i.e. not in a sub-folder) of the .jar file
java -classpath /path/to/MyJar.jar Foo
# Run a class file that can be anywhere inside a .jar file. This syntax requires that the .jar file's manifest file contains a
# header called "Main-Class" with a value that points to the main class to be launched. For instance:
#   Main-Class: ch.herzbube.Foo
# Note: This did not work in an experiment conducted on my Mac OS X 10.5 system which uses a Java 1.5 JVM
java -jar /path/to/MyJar.jar

Specify command line arguments:

java Foo arg1 arg2

Define a system property (can be read using java.util.Properties):

java -DmyProp=myValue Foo


Classpath notes

A few notes about the classpath, both used by the compiler and the JVM:

  • The current directory (".") is not searched by default
  • Searching stops as soon as a class is found for the first time; if it's the wrong class - bad luck!
  • Search order:
    1. Folders in the system classpath (e.g. to find stuff from the J2SE standard library)
    2. Folders in the user-defined classpath
    3. Folders in a classpath are searched left-to-right


JAR files

Create a JAR file that includes an entire package tree:

jar -cf MyJar.jar /path/to/root/package

Create a .jar file that includes a manifest file (e.g. the "Main-Class" header). Note that the order in which the "f" and "m" options appear determine the order in which .jar file name and manifest file name must be specified.

jar -cfm MyJar.jar manifest-file /path/to/root/package

Display the content of a .jar file:

jar -tf MyJar.jar

Extract content of a .jar file:

jar -xf MyJar.jar


Conventions

The conventions in this chapter are mainly taken from Sun's Java Code Conventions [11].


Naming conventions:

  • Classes and interfaces: Begin with an uppercase letter, use CamelCase. Class names should consist of nouns. Interface names should consist of adjectives ("Runnable").
  • Methods: Begin with a lowercase letter, then use CamelCase. Method names should consist of verb-noun pairs ("getBalance", "doCalculation")
  • Variables: Same as methods (begin with lowercase letter, then use CamelCase)
  • Constants: Use only uppercase letters with "_" characters as separators instead of CamelCase
  • Enum values: Same rules as for constants


Indentation + braces

  • Use braces even if a block consists of only one statement (e.g. in an if condition)


Documentation

For the moment I concentrate solely on writing source code documentation for Javadoc. A list of useful links:


A short list of basics about writing Javadoc comments:

  • A doc comment is written in HTML
  • It must precede a class, field, constructor or method declaration
  • It is made up of two parts: A description followed by block tags (e.g. @param, @return, @see)
  • The begin-comment delimiter is /**; the doc comment ends with a regular */
  • The first sentence of the description is treated specially by Javadoc (e.g. for methods, Javadoc automatically places the first sentence in the method summary table), even if the sentence is not separated/marked up in an extraordinary way. This is unlike Doxygen where I am used to write @brief descriptions.
  • The first sentence ends after the first period that is followed by a whitespace character. It is possible to use &nbsp; or an HTML comment (<!-- -->) to circumvent this.
  • Paragraphs in the description are separated with a <p> paragraph tag
  • The description and the list of block tags are separated by a blank comment line
  • The first line that begins with an "@" character (i.e. the first block tag) ends the description
  • There is only one description block per doc comment; the description cannot be continued after the block tags.
  • Package level comments can be placed into a file named package.html which resides inside the package directory


Javadoc style

  • Use the <code> HTML tag to markup keywords, names and code examples
  • When referring to all overloads of a method, don't use parantheses; e.g. refer to the add method, not the add() method
  • Use 3rd person, not 2nd person. For instance: "Gets the label."
  • Method descriptions usually start with a verb because the method does something
  • Use block tags in the following order:
    • @author
    • @version
    • @param
    • @return
    • @exception, @throws
    • @see
    • @since
    • @serial, or @serialField or @serialData
    • @deprecated


Testing

For the moment, I am focusing on JUnit 4 only. Some general things to say:

  • JUnit 4.x is a test framework which uses annotation to identify the test methods.
  • JUnit assumes is that the all test can be performed in an arbitrary order. Therefore tests should not depend other tests.


To write a test with JUnit

  • It's probably a universally accepted truth, but I have made good experiences with placing unit test code into an entirely separate source code tree, e.g.
root folder
+-- src       Normal project source code files
+-- test      Unit test source code files
+-- bin       Compiled code
+-- doc       Documentation
  • To make the new source code tree available for running the tests within, it needs to be added to the class path
  • Annotate a method with @org.JUnit.Test
  • Use a method provides by JUnit to check the expected result of the code execution versus the actual result


Eclipse and Java

JDT

JUnit

Eclipse includes a copy of JUnit. To use it, the following things need to be configured:

  • Project properties -> Java Build Path -> Libraries
  • Click the "Add Library..." button
  • Select JUnit and click the "Next" button
  • Select JUnit 4 and click the "Finish" button

To add a JUnit test case:

  • File -> New -> Source Folder; folder name = test
  • The new folder "test" should have been automatically added to the Java build path, but to make sure check that the folder is listed under "Project properties -> Java Build Path -> Source"
  • Select the new folder "test"
  • File -> New -> JUnit Test Case
  • Fill in class details, then click the "Finish" button

To run the tests:

  • Select the folder "test"
  • From the context menu, select "Run as -> JUnit Test"; this creates, and immediately launches, a Run Configuration which executes all tests below the selected folder
  • After test execution finishes, the JUnit view will open automatically and display test execution results
  • The tests can be re-run by clicking the appropriate button in the JUnit view


Javadoc

Eclipse treats Javadoc as a compiler, consequently its settings can be found in the Preferences dialog under "Java -> Compiler -> Javadoc". A restrictive configuration set is this. It basically insists that all public members are documented (except where the member is an overriding or implementing method), that comments are properly formatted and that required tags are present.

  • Process Javadoc comments = true
  • Malformed Javadoc comments = Warning
    • Only consider members as visible as = Public
    • Validate tag arguments = true
      • Report non visible references = true
      • Report deprecated references = true
    • Missing tag descriptions = Validate all standard tags
  • Missing Javadoc tags = Warning
    • Only consider members as visible as = Public
    • Ignore in overriding and implementing methods = true
  • Missing Javadoc comments = Warning
    • Only consider members as visible as = Public
    • Ignore in overriding and implementing methods = true


To actually generate the documentation, select "Project -> Generate Javadoc...", then click through the wizard

  • Select the types for which documentation should be generated. For instance, all types.
  • Create Javadoc for members with visibility = Public
  • Destination folder = /path/to/folder
  • Extra Javadoc options = -charset "UTF-8"
    • This is required because I have set my workspace to use the UTF-8 encoding by default
    • Usually the problem is my surname, which appears in the @author tag, and contains a German umlaut
  • JRE source compatibility = 1.6
  • Open generated index file in browser = true


The destination folder can later be changed in the project preferences under "Javadoc Location". Unfortunately, I have not yet found a way how to specify the folder relative to the project base directory.


Last but not least: If the Javadoc view is open, it will always display the comment block for the current member, properly formatted and all. Very neat!


Tools

TODO


References