LearningJava
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 more tricky C++ slicing! (
- 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 existsfinal
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
ortry
- 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;
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;
)
- Either an explicit cast (e.g.
- 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 afloat
is 9.223372e18). - Further investigation would be needed for an exact statement, i.e. how exactly is precision lost
- Loss of precision (e.g. the result of assigning 2147483647 (0x7fffffff, maxint) to a
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
- Things that can be tested for equality:
- 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 (?).
- I always have trouble remembering the sequence: Is it
- 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
- The bitwise operators can be used in boolean expressions such as
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
- Example:
- Instance method
xxxValue
- Example:
byteValue()
- Purpose: Return a primitive value that corresponds to the value encapsulated by the wrapper object
- Example:
- Instance method
toString()
- Purpose: Return a
String
object that represents the value encapsulated by the wrapper object
- Purpose: Return a
Integer
andLong
only: Static methodtoString()
- 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
- Example:
Integer
andLong
only: Static methodtoXxxString()
- Example:
toHexString()
- Purpose: Similar to
toString()
, but the method name specifies the radix
- Example:
- Static method
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
- A class is called an exception if it is, directly or indirectly, a subclass of
- 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 onecatch
orfinally
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 } } }
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
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
andabstract
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()
andhashCode()
. Seejava.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)
- Either a call to a superclass constructor (e.g.
- Only static variables and methods can be accessed as part of the call to
super()
orthis()
- 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
ordefault
, since classes cannot haveprotected
orprivate
)
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
orstatic
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:
- Folders in the system classpath (e.g. to find stuff from the J2SE standard library)
- Folders in the user-defined classpath
- 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:
- Main page with Javadoc resources
- How to Write Doc Comments for the Javadoc Tool
- Javadoc FAQ
- Javadoc Reference (for Javadoc 1.5)
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
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 theadd()
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
- ↑ Canonical Java website: http://java.sun.com/
- ↑ List of FAQs: http://java.sun.com/reference/faqs/index.html
- ↑ Learning the Java Language: http://java.sun.com/docs/books/tutorial/java/TOC.html
- ↑ The Java Language Specification: http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html
- ↑ The Java Tutorials: http://java.sun.com/docs/books/tutorial/index.html ("Learning the Java Language" is one of these tutorials)
- ↑ Sun's Java glossary: http://java.sun.com/docs/glossary.html
- ↑ AWT Wikpedia article
- ↑ Swing Wikipedia article
- ↑ SWT Wikipedia article
- ↑ JNI Wikipedia article
- ↑ Sun's Java Code Conventions: http://java.sun.com/docs/codeconv/CodeConventions.pdf