LearningJavaAPI
About this page
This page contains my notes from when I started to learn about the Java API as preparation for the SCJP.
The root class: java.lang.Object
All classes in Java are descendants of the root class Object
even though they do not explicitly extend that class. Inheriting from Object
provides a certain set of capabilities to all Java classes:
getClass
method- Returns a
Class
object that represents the object's class at runtime. Great for introspection! :-) equals
method- Indicates whether two objects are "equal". The default implementation returns true only if the two objects being compared are the same instance. The meaning of "equality" depends on the objects' types: With primitive wrappers the case is pretty obvious, but what about
java.io.File
? hashCode
method- Returns a hash code value for the object. This is used mostly when an object is stored in a collection that uses hashing (e.g.
Hashtable
). Note: Ifequals()
returns true for two objects, their hash code value must also be the same. If two objects are not equal, their hash code value may be different (but is not necessarily so). clone
method- Creates and returns a copy of this object.
toString
method- Returns a string representation of the object.
notify
method- TODO
wait
method- TODO
finalize()
method- This method is invoked just before an object is deleted by the Garbage Collector. It is therefore possible to override this method and implement code that performs some last-minute cleanup. In practice, it is not recommended to do this because there is no guarantee for any specific object that it will ever be garbage collected, so there is no guarantee that the code in
finalize()
will run, either. Note 1: It is possible that the actions taken infinalize()
will prevent the object from being deleted. Note 2:finalize()
is never called more than once per object. In other words, iffinalize()
is run and prevents the object from being deleted, and later on the object becomes eligible for deletion a second time, the GC will not runfinalize()
a second time.
Note: The API docs for java.lang.Object
have additional interesting information not replicated here.
Strings
java.lang.String
Strings are not primitives, they are objects of type
java.lang.String
A String literal is a sequence of characters between double quotes:
String s = "Foo";
A few more notes about strings:
- String objects are immutable
- When a String object is created from a string literal, the JVM places the object into the so-called "String constant pool". If the same literal is encountered again, instead of creating a new object the JVM reuses the String object from the pool. Note: This does not happen if a String is created like this:
new String("abc")
.
Examples for string handling
String s1 = "ab"; // New String object placed in the String constant pool String s2 = "ab"; // Reuse String object from the String constant pool String s3 = new String("ab"); // New String object, *NOT* placed in the String constant pool assert(s1 == s2); // == tests the object reference assert(s1 != s3); assert(s1.equals(s3)); // equals() tests the String content, not the object reference String s4 = s1 + "cd"; // New String object contains "abcd" s4 += "ef"; // New String object contains "abcdef"; old String object "abcd" is lost s4.concat("gh"); // No change to s4; new String object is created but lost immediately String s5 = s4.concat("gh"); // New String object contains "abcdefgh" String s6 = s5.toUpperCase(); // New String object contains "ABCDEFGH" assert(s5.equalsIgnoreCase(s6)); String s7 = s6.toLowerCase(); // "abcdefgh" s7 += "b"; // "abcdefghb" s7 = s7.replace('b', 'B'); // "aBcdefghB" char c = s7.charAt(1); // 'B' int index = s7.indexOf(c); // 1; search forward from beginning index = s7.indexOf(c, 2); // 8; search forward from index position 2 index = s7.lastIndexOf(c); // 8; search backward from end index = s7.lastIndexOf(c, 7); // 1; search backward from index position 7 index = s7.indexOf("cd"); // 2; searching also works for Strings String s8 = s7.substring(5); // "efghB" String s9 = s7.substring(5, 6); // "e"; ARGH! Who is the imbecile who declared that the 2nd parameter shall not be zero-based? int length = s7.length(); // 9 assert(s5.startsWith("ab"); assert(s5.endsWith("gh"); String s10 = s7.toString() // "aBcdefghB", duh! toString() is inherited from Object String s11 = " x ".trim(); // "x"; removes leading *AND* trailing spaces
java.lang.StringBuffer
and java.lang.StringBuilder
StringBuilder
is new to Java 1.5. It has exactly the same API as the StringBuffer
class, except StringBuilder
is not thread safe.
Examples:
StringBuffer sb1 = new StringBuffer("ab"); sb1.append("cd"); // "abcd" sb1.delete(1, 3); // "ad"; ARGH! Again the imbecile is at work - the 2nd parameter is not zero-based! sb1.insert(1, "bc"); // "abcd" sb1.reverse(); // "dcba" String s1 = sb1.toString(); // toString() is inherited from Object // Each method returns a reference to the StringBuffer object, therefore we can use chaining like this. The // method calls are the same as in the above example, the result therefore is "dcba". StringBuffer sb2 = new StringBuffer("ab").append("cd").delete(1, 3).insert(1, "bc").reverse();
Generics
Generics are a new feature of Java 1.5. It works similar to templates in C++, e.g.
List<String> sList = new ArrayList<String>(); String s = "foo"; sList.add(s); sList.add("bar"); sList.add(42); // ERROR! s = sList.get(0); // no cast
Polymorphism examples:
class Foo { void overload_1(List list) {} void overload_1(List<Object> list) {} // ERROR! - Compiler cannot distinguish from overload above void method_2a(List list) {} void method_2b(List<Object> list) {} void method_2c(List<String> list) {} // This is "wildcard syntax". It instructs the compiler that the generic type // can be varied, but the method implementation must *NOT* add any objects // We are saying: "You can give us anything that IS-A Object; we will treat // it like that and will not attempt to add anything to the list that is not // an Object." void method_3a(List<? extends Object> list) { list.add(new Object()); // ERROR! - No objects must be added, even if they match the generic type } // Again the wildcard syntax, but unlike with the keyword "extends" this time // the compiler allows to add objects *IF* they are of the proper type. // We are saying: "You can give us a list with anything from String up until Object; we // might add something to the list, and if we do it will be a String (or any subtype thereof, // which of course is impossible since String is final)." void method_3b(List<? super String> list) { list.add(new String()); // OK list.add(new Object()); // ERROR! - Only objects of the subtype may be added list.add(new SubString()); // OK - SubString is a theoretical subclass of String } // Identical to List<? extends Object> void method_3c(List<?> list) {} // Use keywords "extends" and "super" even if the supertype or subtype is an interface void method_3d(List<? extends Serializable> list) {} void method_3d(List<? super Serializable> list) {} void doIt() { List<String> sList = new ArrayList<String>(); // OK - Normal polymorphism rules at work here: ArrayList is a subtype of List List<Object> oList = new ArrayList<Object>(); ArrayList<String> sArray = new ArrayList<String>(); method_2a(sList); // OK - Works because of special backward compatibility method_2b(sList); // ERROR! - If both parameter and argument use generics, the generic type *MUST* match method_2c(sArray); // OK - Again, normal polymorphism rules at work here method_3a(sList); // OK - Works because of wildcard syntax method_3a(oList); // ditto method_3b(sList); // ditto method_3b(oList); // ditto } }
Declare new generic types and methods (instead of using premade collection types):
public class Foo<T1, T2> // "T1" and "T2" can be any legal identifiers { private List<T1> entryList; private T1 entry; private T1[] entryArray; private int i; // can still use non-generic members private T2 somethingElse; public Foo(List<T1> list) { entryList = list; T1 entry = new T1(); // ERROR! - Instances cannot be created } public T1 getEntry() { return entryList.get(0); } public addEntry(T1 entry) { return entryList.add(entry); } } // Interfaces can also be defined using generics public interface AnInterface<T> {} // Define a generic method inside a normal class public class Bar { <T> void doIt(T entry) {} <T> T doWhat(T entry) { return entry; } <T> T doThat() { return new T(); } // ERROR! - Instances cannot be created public <T> Bar(T entry) {} // OK - A generic constructor (weird!) public <Bar> Bar(Bar entry) {} // OK - But perverted: The parameter "entry" has the generic type, not type Bar } // Restrict the types that can be used. Note: "super" *CANNOT* be used! public class Foo<T extends Serializable> {} // ERROR! - Wildcard syntax doesn't make sense here! public class Foo<? extends Serializable> {}
Collections
Overview
Type | Class or interface | Extends or implements | Collection flavour | Ordering | Remark |
---|---|---|---|---|---|
Collection |
Interface | - | |||
List |
Interface | Collection |
Access elements by index | ||
Queue |
Interface | Collection |
Access elements at the front or end (typically FIFO) | ||
Set |
Interface | Collection |
Elements must be unique (equals() )
| ||
SortedSet |
Interface | Set |
|||
NavigableSet |
Interface | SortedSet |
|||
Map |
Interface | - | Key/value pairs, where the key must be unique (equals() )
| ||
SortedMap |
Interface | Map |
|||
NavigableMap |
Interface | SortedMap |
|||
HashSet |
Class | Set |
Set | Unordered | |
LinkedHashSet |
Class | HashSet |
Set | Ordered by insertion time | Like HashSet except for the ordering.
|
TreeSet |
Class | NavigableSet |
Set | Sorted | Like HashSet except for the sorting.
|
HashMap |
Class | Map |
Map | Unordered | |
Hashtable |
Class | Map |
Map | Unordered | Like HashMap , but access is synchronized and no null key/values are allowed
|
LinkedHashMap |
Class | HashMap |
Map | Ordered by insertion time | Like HashMap except for the ordering
|
TreeMap |
Class | NavigableMap |
Map | Sorted | Like HashMap except for the sorting
|
ArrayList |
Class | List |
List | Ordered by index | Like a regular array, but this one can change its size. List changes are not very fast. |
Vector |
Class | List |
List | Ordered by index | Like ArrayList , but access is synchronized
|
LinkedList |
Class | List and Queue |
List | Ordered by index | Like ArrayList , but elements are doubly-linked. List changes are faster, but access is slower.
|
PriorityQueue |
Class | Queue |
Queue | Sorted | Sorting affects which elements are at the front or end of the queue. The head of this queue is the least element with respect to the specified ordering. |
Arrays |
Class | Object |
Utility | Manipulate arrays. Most of the stuff in this class is static. | |
Collections |
Class | Object |
Utility | Manipulate collections. Most of the stuff in this class is static. |
A few basic examples:
// List stuff List<String> list1 = new ArrayList<String>(); String s = "foo"; list1.add("foo"); // Append list1.add(s, 0); // Insert at index position System.out.println(list1.size()); // 2 System.out.println(list1.contains(42)); // false; works even though the argument is *NOT* a string! System.out.println(list1.indexOf(42)); // -1 (or an index position if the element exists) list1.get(0); // Access via index position list1.remove("foo"); // removes the first occurrence (i.e. the one with lowest index) System.out.println(list1.size()); // 1 for (String sss : list1) {} // for-each can also be used on collections (not only arrays) List<Integer> list2 = new ArrayList<Integer>(); list2.add(42); // Autoboxing adds an Integer // Set stuff HashSet<Integer> hashSet1 = new HashSet<Integer>(); assert(hashSet1.add(42)); // true if the element was added successfully assert(! hashSet1.add(42)); // false if the element was not added (in this case because it already exists) // Map stuff HashMap<Integer, String> hashMap1 = new HashMap<Integer, String>(); Integer key1 = 42; String value1 = "24"; hashMap1.put(key1, value1); // Add entry to map; an old entry for the same key is replaced hashMap1.containsKey(key1); hashMap1.containsValue(value1); hashMap1.get(key1); String value2 = hashMap1.remove(key1); // Removes an entry, returning the value that was associated with the key hashMap1.remove("foo"); // No exception, even though the key does not exist and is not an Integer Set<Integer> keySet1 = hashMap1.keySet(); // The set contains all keys in the map
Iterators
Instead of using for-each
, it is still possible to use iterators for List
and Set
:
List<Foo> fooList = new ArrayList<Foo>(); // populate list... Iterator<Foo> it = fooList.iterator(); // Make an iterator; will traverse the collection in ASCENDING order while (it.hasNext()) { Foo f = it.next(); // Cast not required because the iterator is declared with generics syntax }
Converting between Lists and Arrays
Convert to List:
String[] sa = {"one", "two", "three", "four"}; List sList = Arrays.asList(sa); // The list is connected to the array, i.e. changes to one will also affect the other! sList.set(3, "six"); // Change List; this also changes the array sa[1] = "five"; // Change array; this also changes the List // At this point, both the array and the List contain "one", "five", "three", "six"
Convert to array:
List<Integer> iList = new ArrayList<Integer>(); for (int x=0; x < 3; x++) { iList.add(x); } Object[] oa1 = iList.toArray(); // Create an Object array Integer[] ia1 = new Integer[3]; iList.toArray(ia1); // The array passed as an argument is filled with the list's element because // it is of sufficient capacity Integer[] ia2 = new Integer[2]; iList.toArray(ia2); // The array passed as an argument is *NOT* of sufficient capacity -> the list's // element are filled into a *NEWLY CREATED* array -> since the return value is // not stored, the array is lost ia2 = iList.toArray(ia2); // Same case, but this time the result is stored; ia2 now contains a *NEW* array Integer[] ia3 = iList.toArray(new Integer[0]); // Same case, but this time we don't even bother to pass an argument Object[] oa2 = iList.toArray(new Object[0]); // Functionally equivalent to the initial Object array example
Sorting and searching
Classes that implement the Comparable
interface can be sorted inside a collection.
class Foo implements Comparable<Foo> { String bar; String somethingCompletelyDifferent; String getBar() { return bar; } String getSomethingCompletelyDifferent() { return somethingCompletelyDifferent; } // <0 - thisObject < anotherObject // =0 - thisObject == anotherObject // >0 - thisObject > anotherObject int compareTo(Foo anotherObject) { // Delegate comparison to String return bar.compareTo(anotherObject.getBar()); } public static void main(Strings[] args) { ArrayList<Foo> fooList = new ArrayList<Foo>(); Foo[] fooArray = new Foo[42]; // populate list and array Collections.sort(fooList); Arrays.sort(fooArray); } }
Alternatively, a class may implement the interface Comparator
:
class FooSort implements Comparable<Foo> { // See above for the meaning of the return valuee public int compare(Foo one, Foo two) { return one.getSomethingCompletelyDifferent().compareTo( two.getSomethingCompletelyDifferent()); } public static void main(Strings[] args) { ArrayList<Foo> fooList = new ArrayList<Foo>(); Foo[] fooArray = new Foo[42]; // populate list and array Collections.sort(fooList); // sort by foo Collections.sort(fooList, new FooSort()); // sort by somethingCompletelyDifferent Collections.reverse(fooList); // reverse sort order (only works with List) Arrays.sort(fooArray); // sort by foo Arrays.sort(fooArray, new FooSort()); // sort by somethingCompletelyDifferent // Create a comparator object that reverses the natural ordering of Comparable objects, // i.e. the order imposed by compareTo() is reversed Comparator comp = Collections.reverseOrder(); Collections.sort(fooList, comp); Arrays.sort(fooArray, comp); // we can also use the comparator for arrays // Create a comparator object that reverses the ordering imposed by FooSort (or whatever // comparator is passed as an argument) comp = Collections.reverseOrder(new FooSort()); comp = Collections.reverseOrder(comp); // quite pointless: the same order as FooSort } }
List collections can be searched using the binarySearch()
method implemented both in the Arrays
and Collections
utility classes. The rules for searching are:
- A successful search returns the index position within the list where the element was found
- An unsuccessful search returns a negative number that indicates the index position where the element would be inserted to keep the collection properly sorted. To get the actual insert position:
(-(insert position) - 1)
(e.g. -3 is actually the insert position 2) - The collection must be sorted before a search is attempted
- The search must be made in the same manner in which the sort was performed, i.e. if a
Comparator
was used for sorting, the sameComparator
must be used for searching, or if the collection was sorted in natural order, the search must also be made in natural order
Examples for Arrays
and Collections
:
import java.util.Arrays; import java.util.Collections; Foo f = new Foo(); // Search arrays int[] intArray = new int[3]; // Primitive array Foo[] fooArray = new Foo[3]; // Object array fooArray[0] = f; Arrays.binarySearch(intArray, 42); Arrays.binarySearch(fooArray, f); Arrays.binarySearch(fooArray, f, new FooSort()); // Use a comparator for searching // Search lists List<Foo> fooList = new ArrayList<Foo>(); fooList.add(f); Collections.binarySearch(fooList, f);
More bits and bytes:
Arrays.equals(intArray, intArray); // Compare two entire arrays Arrays.equals(fooArray, fooArray); Arrays.toString(intArray); // Create a string representation of an entire array Arrays.toString(fooArray);
Default sort order rules for strings:
- Digits
- Spaces
- Uppercase characters
- Lowercase characters
- Uppercase umlaut characters
- Lowercase umlaut characters
- For instance: "99" -> " f" -> "FF" -> "f " -> "ff" -> "ÄÄ" -> "ää"
TreeSet
and TreeMap
TreeSet<Integer> set = new TreeSet<Integer>(); TreeMap<String, Integer> map = new TreeMap<String, Integer>(); // populate... Integer i = 42; set.descendingSet(); // Returns the set in reverse order set.ceiling(i); // Returns lowest element >= 42 set.higher(i); // Returns lowest element > 42 set.floor(i); // Returns highest element <= 42 set.lower(i); // Returns highest element < 42 set.pollFirst(); // Returns first set element set.pollLast(); // Returns last set element String s = "42"; map.descendingMap(); // See above map.ceilingKey(s); map.higherKey(s); map.floorKey(s); map.lowerKey(s); map.pollFirstEntry(); map.pollLastEntry();
Views (aka "backed collections"):
TreeSet<Integer> set = new TreeSet<Integer>(); TreeMap<String, Integer> map = new TreeMap<String, Integer>(); // populate... boolean inclusive = true; Integer i1 = 17; Integer i2 = 42; SortedSet<Integer> view1; NavigableSet<Integer> view2; view1 = set.headSet(i1); // All elements from the beginning of the set, up to but excluding i1 view2 = set.headSet(i1, inclusive); // ditto, but including i1 view1 = set.tailSet(i1); // All elements starting with i1 (*INCLUSIVE*), up to the end of the set view2 = set.tailSet(i1, inclusive); // ditto view1 = set.subSet(i1, i2); // All elements starting with i1 (inclusive), up to but excluding i2 view2 = set.subSet(i1, inclusive, // ditto, but including i2 i2, inclusive); String s1 = "17"; String s2 = "42"; SortedMap<String, Integer> view3; NavigableMap<String, Integer> view4; view3 = map.headMap(s1); // See above view4 = map.headMap(s1, inclusive); view3 = map.tailMap(s1); view4 = map.tailMap(s1, inclusive); view3 = map.subMap(s1, s2); view4 = map.subMap(s1, inclusive, s2, inclusive); // Assume set has the following elements: 1, 18, 24, 41, 128 // Assume view1 includes 18, 24, 41, with borders set to 17 and 42 (inclusive) set.add(16); set.add(17); // set: 1, 16, 17, 18, 24, 41, 128 // view1: 17, 18, 24, 41 view1.add(42); // set: 1, 16, 17, 18, 24, 41, 42, 128 // view1: 17, 18, 24, 41, 42 view1.add(43); // RUNTIME ERROR! - Element added is outside the view's range view1.remove(17); // set: 1, 16, 18, 24, 41, 42, 128 // view1: 18, 24, 41, 42
PriorityQueue
PriorityQueue<Integer> pq = new PriorityQueue<Integer>(); pq.offer(3); // Populate in arbitrary order pq.offer(1); // Other queue implementations may impose a size limit, in which case offer() returns pq.offer(7); // false if the element cannot be added pq.size(); // 3 pq.peek(); // 1 (element with the least value in the ordering scheme, i.e. the highest priority) pq.size(); // still 3 pq.poll(); // 1 (element with the highest priority) pq.size(); // 2 pq.poll(); // 3 pq.poll(); // 7 pq.size(); // 0 (queue is empty) pq.poll(); // null (if queue is empty) pq.peek(); // null (same as poll())
File I/O
Overview
Class | Description | Extends | Remark |
---|---|---|---|
java.io.File |
An abstract representation of a file or directory using the file/directory pathname. File/directory contents are handled by reader and writer classes. | Object | |
java.io.FileReader |
Reads content from character files. Rather low-level access to single characters or an entire stream. Usually wrapped by higher-level objects such as BufferedReader . |
Reader | |
java.io.BufferedReader |
Makes lower-level classes such as FileReader more efficient and convenient to use. |
Reader | |
java.io.FileInputStream |
? | InputStream | |
java.io.ObjectInputStream |
Read data from a stream and de-serialize an object | InputStream | |
java.io.FileWriter |
Writes content to character files. Rather low-level access for writing single characters or an entire string. Usually wrapped by higher-level objects such as BufferedWriter . |
Writer | |
java.io.BufferedWriter |
Makes lower-level classes such as FileWriter more efficient and convenient to use. |
Writer | |
java.io.PrintWriter |
Prints formatted representations of objects to a text-output stream. | Writer | |
java.io.FileOutputStream |
? | OutputStream | |
java.io.ObjectOutputStream |
Serialize an object and write the data to a stream | OutputStream | |
java.io.Console |
Java 1.6 convenience class to read from and write to the console. | ? |
java.io.File
import java.io.*; try { File file = new File("foo"); // The file or directory does not exist yet assert(! file.exists()); boolean success = file.createNewFile(); // Try to create a file if (success) { assert(file.exists()); assert(file.isFile()); assert(! file.isDirectory()); file.renameTo(new File("bar")); success = file.delete(); } else { // do something } File dir = new File("foo"); success = dir.mkdir(); // Try to create a directory if (success) { assert(dir.exists()); assert(! dir.isFile()); assert(dir.isDirectory()); dir.renameTo(new File("bar")); // Create new file *inside* the directory File anotherFile = new File(dir, "foobar"); anotherFile.createNewFile(); // Returns both files and subdirectories inside the directory String[] contents = dir.list(); for (String s : contents) System.out.println(s); success = dir.delete(); // Will fail e.g. if directory is not empty } } catch(IOException e) { // do stuff }
java.io.FileReader
, java.io.FileWriter
and java.io.PrintWriter
import java.io.*; try { File file1 = new File("foo.txt"); // The files do not exist yet File file2 = new File("bar.txt"); assert(! file1.exists()); assert(! file2.exists()); FileWriter fw = new FileWriter(file1); // Create the actual files at the same time as the writer objects. Note: The PrintWriter pw = new PrintWriter(file2); // parent directories must exist! assert(file1.exists()); assert(file2.exists()); // Very simple FileWriter fw.write("howdy\nfolks\n"); // OUCH! - Platform specific line separators fw.flush(); // *ALWAYS* flush before closing fw.close(); // *ALWAYS* close // Slightly more sophisticated PrintWriter pw.println("howdy"); pw.println("folks"); pw.flush(); // *ALWAYS* flush before closing pw.close(); // *ALWAYS* close FileReader fr = new FileReader(file); char[] in = new char[50]; // OUCH! - Have to know the size *before* we read. We could read character // by character, but this would also not be very satisfying... int size = fr.read(in); // Read the whole file System.out.print(size + " "); // How many bytes have been read? for(char c : in) System.out.print(c); // NOTE! - no flush() required on a reader fr.close(); // *ALWAYS* close } catch(IOException e) { // do stuff }
java.io.BufferedReader
and java.io.BufferedWriter
import java.io.*; try { File file = new File("foo.txt"); // The file does not exist yet PrintWriter pw = new PrintWriter(file); // We still need the intermediate PrintWriter BufferedWriter bw = new BufferedWriter(file); // Wrap it with a high-level BufferedWriter // TODO: Is this really how BufferedWriter is used? bw.write("howdy"); bw.newline(); bw.write("folks"); bw.newline(); bw.flush(); bw.close(); FileReader fr = new FileReader(file); // We still need the intermediate FileReader BufferedReader br = new BufferedReader(fr); // Wrap it with a high-level BufferedReader String s; while((s = br.readLine()) != null) // Read a single line. Note: The string does *NOT* contain the line separator System.out.println(s); // NOTE! - no flush() required on a reader br.close(); } catch(IOException e) { // do stuff }
java.io.Console
import java.io.*; Console c = System.console(); if (c == null) // Check that the environment actually has a console return; // Get a String String s = c.readLine("%s", "input: "); // Write back the String input c.format("output: %s \n", s); // Get a character array, not a String. Reason: A String might be placed in a pool somewhere in memory // Note: Input is not echoed char[] pw = c.readPassword("%s", "pw: "); // Write back the character input for(char ch: pw) c.format("%c ", ch); c.format("\n");
Serialization
Rules:
- Static variables are not included in the serialization process
- Inside a serializable class, instance variables marked as
transient
are initialized to their default values when an object of the class is de-serialized (e.g. primitive values are set to 0, 0.0 etc., reference variables arenull
) - When an object is constructed by de-serializing it, its construction does not happen by running the normal cycle of constructors and/or init-blocks. Instead it works like this:
- The class hierarchy is examined starting with the root class
Object
- Constructors and/or init-blocks are executed up to the last class that is not serializable
- The first class that implements
Serializable
will be constructed using the de-serialization mechanism - Example:
class A extends class B implements Serializable extends class C extends Object
. Here constructors are run for classesObject
and C, while from class B upwards no constructors are run. Class A inherits the serialization capability from B.
- The class hierarchy is examined starting with the root class
- When serializing arrays and collections, all elements must be serializable, otherwise serialization will fail (probably with a runtime error)
import java.io.*; class Foo implements Serializable { int i = 42; // Straightforward: The integer value in this variable gets serialized } class Bar // Is *NOT* serializable { int j = 17; } class Foobar implements Serializable { Foo f = new Foo(); // Works only if Foo is also Serializable; if not we get a NotSerializableException! transient Bar b = new Bar(); // Exempt Bar from serialization. On de-serialization this will be null. // Optional callback method that is invoked on serialization private void writeObject(ObjectOutputStream oos) { try { // Order of serialization is important! oos.defaultWriteObject(); // First perform the normal serialization process oos.writeInt(b.j); // Afterwards serialize additional state } catch(Exception e) { // handle exception } } // Optional callback method that is invoked on de-serialization private void readObject(ObjectInputStream ois) { try { // Order of de-serialization must match order of serialization! ois.defaultReadObject(); // First perform the normal de-serialization process b = new Bar(); b.j = ois.readInt(); // Afterwards de-serialize additional state } catch(Exception e) { // handle exception } } } class SerializableTester { void doIt() { try { Foobar fb = new Foobar(); FileOutputStream fos = new FileOutputStream("testSer.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(fb); oos.close(); } catch (Exception e) { // do something } try { FileInputStream fis = new FileInputStream("testSer.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Foobar fb = (Foobar) ois.readObject(); // Creates a new object ois.close(); } catch (Exception e) { // do something } } }
i18n
java.util.Locale
The java.util.Locale
class is the basic block of internationalization (i18n). An instance of this class is often combined with other objects that are related to i18n such as java.util.Calendar
.
How to create a locale object:
import java.util.* String language = "de"; // Lower-case 2-letter ISO 639 language code String country = "CH"; // Upper-case 2-letter ISO 3166 country code Locale loc1 = new Locale(language); Locale loc2 = new Locale(language, country); String s1 = loc2.getDisplayCountry(); // Country name using the default locale (e.g. "Switzerland") String s2 = loc2.getDisplayLanguage(); // Language name using the default locale (e.g. "German") s1 = loc2.getDisplayCountry(loc2); // "Schweiz" s2 = loc2.getDisplayLanguage(loc2); // "Deutsch" // Working with the default locale Locale defaultLocale = Locale.getDefault(); Locale.setDefault(loc2);
Dates and calendars
Important notes:
java.util.Date
represents a single date/time. Internally it stores a single primitivelong
value. The value holds the number of milliseconds since the epoch (GMT 01.01.1970-00:00:00.000).- Large parts of the
java.util.Date
class API are deprecated. The class is still used to bridge betweenjava.util.Calendar
andjava.text.DateFormat
java.util.Calendar
is more "modern" thanDate
Date & time operations, quick & dirty style:
import java.util.* Date now = new Date(); // Get current date/time Date d = new Date(1000000L); // Get date/time 1 million milliseconds after the epoch d.setTime(d.getTime() + 3600000); // Add 1 hour String s = d.toString();
Using Calendar
:
import java.util.* Date d = new Date(HUGE_NUMBER); // Assume a number that refers to 07.12.2009, 17:00:00 Calendar c = Calendar.getInstance(); // Invoke factory method to get an instance that refers to NOW // and uses the default time zone and locale c.setTime(d); // Set the calendar to a specific time (no longer NOW) int dow = c.get(Calendar.DAY_OF_WEEK); // Get value of calendar field "day of week" c.add(Calendar.MONTH, 3); // Add 3 months, including rollover of year; c now refers to 07.03.2010, 17:00:00 c.roll(Calendar.MONTH, 10); // Add 10 months, without rollover of year; c now refers to 07.01.2010, 17:00:00 d = c.getTime(); // Get a Date object that refers to the calendar's time value
Formatting dates & times for a specific locale:
import java.util.* import java.text.* Locale loc = new Locale(...); int style = DateFormat.LONG; DateFormat df = DateFormat.getDateInstance(style, loc); Calendar c = Calendar.getInstance(); // Calendar object refers to NOW and uses default time zone and locale Date d = c.getTime(); String s = df.format(d); // Convert into a string that is formatted according to the proper style and locale try { d = df.parse(s); // Convert from string. We *LOSE* time information here because the // DateFormat object was created using getDateInstance() DateFormat df_withtime = DateFormat.getDateTimeInstance(style, loc); d = df_withtime.parse(s); // Again, convert from string, but this time *RETAIN* time information } catch(ParseException e) { ... }
Numbers and Currency
Rather than DateFormat
, we use NumberFormat
for working with numbers:
import java.util.* import java.text.* float f1 = 123.456789f; Locale loc = new Locale("de", "CH"); NumberFormat nf1 = NumberFormat.getInstance(loc); NumberFormat nf2 = NumberFormat.getCurrencyInstance(loc); String s1 = nf1.format(f1); // "123.457", rounded to 3 fraction digits (the default for this locale) String s2 = nf2.format(f1); // "SFr. 123.46", rounded to 2 fraction digits (the currency default for this locale) nf1.setMaximumFractionDigits(5); s1 = nf1.format(f1); // "123.45679", now rounded to 5 fraction digits try { nf1.setMaximumFractionDigits(2); Number n = nf1.parse(s1); // Convert from string; n is 123.45679, because setMaximumFractionDigits() only has // an effect for formatting, not for parsing nf1.setParseIntegerOnly(true); n = nf1.parse(s1); // n is now 123; this time all fractions are lost because setParseIntegerOnly() does // influence parsing (but not formatting) } catch(ParseException e) { ... }
Regexp, tokenizing, and formatting
Regexp
A few regexp expressions that I didn't know about yet:
\d
- Any numeric digit. An alternative is
[0-9]
\s
- Any whitespace character
\w
- Any word character (letters, digits and "_")
\b
- Backspace
?
,*
,+
- Greedy as usual
??
,*?
,+?
- Non-greedy (= reluctant)
import java.util.regex.* Pattern p1 = Pattern.compile(".*?xx"); // The pattern; note: uses non-greedy operator Matcher m1 = p.matcher("yyxxxyxx"); // The string to match while (m1.find()) { System.out.println(m1.start() + " " m1.group()); // Results in two lines: "0 yyxx" and "4 xyxx" }
Tokenizing
Very basic tokenizing uses String.split()
:
String s = "ab,cd5b,6x,,z4"; String delimiter = ","; String[] tokens = s.split(delimiter); // Results in 5 tokens: "ab", "cd5b", "6x", "" (empty string), "z4"
java.util.Scanner
is the mammoth tokenizer. It has the following advantages:
- Possible sources: Files, streams, or strings
- Tokenizing is performed within a loop so that you can exit the process at any point
- Tokens can be converted to their appropriate primitive types automatically
An example:
String input = "1 true 34 hi"; Scanner s1 = new Scanner(input); // Use default delimiter, which is whitespace // hasNext(), hasNextInt(), etc. all test 1) if there is a next token, and 2) if the token matches // the requested type. The methods themselves do not change the input stream. while (s1.hasNext()) { // next(), nextInt(), etc. all get the next token. If there is no next token, or if it is not of // the requested type (e.g. an int), the function throws an exception String token = s1.next(); // Results in 4 tokens: "1", "true", "34", "hi" } Scanner s2 = new Scanner(input); s2.useDelimiter("\\s"); // Explicitly set whitespace as delimiter. Note: We could also specify a compiled regex Pattern. while (s2.hasNext()) { if (s2.hasNextInt()) { int i = s2.nextInt(); // Two tokens: "1", "34" } else if (s2.hasNextBoolean()) { boolean b = s2.nextBoolean(); // One token: "true" } else { String s = s2.next(); // One token: "hi" } }
Formatting
Notes:
- This chapter is about the
format()
andprintf()
methods in thejava.io.PrintStream
class - Both methods behave exactly the same way
- They use the
java.util.Formatter
class behind the scenes; if in doubt, the class API has all the details - Specs for a format string
%[arg_index$][flags][width][.precision]conversion char
- Flags
- "-" = Left justify
- "+" = Include the sign
- "0" = Pad with zeroes
- "," = Use locale-specific grouping separators (e.g. the "." for Swiss currency)
- "(" = Enclose negative numbers in parantheses
Examples:
System.out.printf("%2$d + %1$d", 123, 456); // Result: "456 + 123" System.out.format("%d", 12.3); // ERROR! - Runtime exception
Threads
Basic stuff
Initial notes:
- A thread in Java has two meanings:
- An instance of class
java.lang.Thread
- A thread of execution
- An instance of class
- The thread that runs the
main()
method is the main thread - Each thread has its own call stack; each call stack lives in its own thread
- There are two types of threads:
- User threads: When all user threads have finished running, the JVM exits the application, even if one or more daemon threads are still running
- Daemon threads: Threads of this type do not have an influence on when the JVM exits the application
- A thread begins execution in its
run()
method (no arguments, return typevoid
) - The
run()
method must be placed into a class that does one of the following:- Extend the class
java.lang.Thread
. This is not the recommended approach, unless the application needs more specialized thread-specific behaviour - Implement the interface
java.lang.Runnable
. This is the recommended approach; the implementing class can be more lightweight, and it also leaves the class free to extend something else
- Extend the class
- Thread state can be queried using
Thread.getState()
; possible values from the enumThread.State
are- NEW: Thread object has been created but the thread has not been started yet. Thread is not yet alive.
- RUNNABLE: Thread is executing, or is ready to execute. Thread is alive.
- BLOCKED: Thread is blocked because a resource is not available (e.g. reading from a stream).
- WAITING
- TIMED_WAITING
- TERMINATED: Thread's target
run()
method has finished. Thread is dead, i.e. not alive anymore.
- Every object in Java has a built-in lock that is acquired when a thread starts to execute code that is marked as
synchronized
. The built-in lock is recursive, i.e. calling a synchronized method from another synchronized method does not result in a deadlock
Examples:
class MyThread extends Thread { public void run() { System.out.println("Job running in MyThread"); System.out.println("Job is run by Thread " + Thread.currentThread().getName()); } } class MyRunnable implements Runnable { public void run() { System.out.println("Job running in MyRunnable"); System.out.println("Job is run by Thread " + Thread.currentThread().getName()); } } class Invoker { void invoke() { Thread t1 = new MyThread(); // Thread is not yet running assert(! t1.isAlive()); // Thread is not yet alive Thread.State s = t1.getState(); assert(s == Thread.State.NEW); // Thread state is "new" t1.start(); // Start the thread; eventually run() will be called in a different execution thread assert(t1.isAlive()); // Thread is alive from the moment onwards when start() is invoked t1.start(); // RUNTIME ERROR! - Thread cannot be started a second time // wait until thread is finished, // i.e. until the run() method of // the thread's target has finished s = t1.getState(); assert(s == Thread.State.TERMINATED); // Thread final state is "terminated" t1.start(); // RUNTIME ERROR! - Thread cannot be started even after it has finished running Thread t2 = new MyThread("foo"); // Give the thread a name Runnable r1 = new MyRunnable(); Thread t3 = new Thread(r1); // Make the Runnable the *TARGET* of the thread t3.start(); // Thread invokes run() on the Runnable Thread t4 = new Thread(r1); t4.start(); // Run the same job a second time in a different thread Thread t5 = new Thread(t2); // Silly but legal: Thread implements Runnable, therefore we can pass a Thread instance to a Thread constructor t5.start(); Thread t6 = new Thread(r1, "bar"); // Give the thread a name t6.setName("no, its foo"); // Change the name t6.start(); t6.run(); // Invokes r1.run() because that's how Thread.run() is implemented; invocation does *NOT* happen in a new thread context } }
Mechanisms to influence thread scheduling
class MyRunnable implements Runnable { public void run() { try { int milliseconds = 1000; Thread.sleep(milliseconds); // Sleep *AT LEAST* this long; thread may *NOT* run for some time even after the specified time has expired Thread.yield(); // Voluntarily give up control so that another thread *MAY* get a chance at running; this is *NOT* guaranteed } catch (InterruptedException ex) // This gets hit if sleep() is interrupted { } } } class Invoker { void invoke() { Runnable r1 = new MyRunnable(); Thread t1 = new Thread(r1); t1.setPriority(7); // Usually the priority is between 1 and 10 (higher numbers = higher priority), but this depends on the JVM t1.setPriority(Thread.MIN_PRIORITY); // 1 t1.setPriority(Thread.NORM_PRIORITY); // 5 t1.setPriority(Thread.MAX_PRIORITY); // 10 t1.start(); try { int milliseconds = 1000; t1.join(milliseconds); // Wait until t1 has finished running (i.e. becomes dead), then go on with the current thread; wake up earlier // if t1 takes longer than the specified amount of time to finish t1.join(); // Wait unconditionally } catch (InterruptedException ex) // This gets hit if join() is interrupted { } } }
Locks and other fancy pancies
Usage of synchronized
:
class Account { private int balance = 50; public int getBalance() { return balance; } public void withdraw(int amount) { balance -= amount; } } class MyRunnable implements Runnable { private Account a = new Account(); public void run() { // do stuff } // Marking a method as synchronized causes the object's built-in lock to be acquired private synchronized void makeWithdrawal(int amount) { if (a.getBalance() >= amount) { a.withdraw(amount); } } private void makeWithdrawal2(int amount) { // Synchronize a code block instead of an entire method synchronized(this) { if (a.getBalance() >= amount) { a.withdraw(amount); } } } }
Usage of wait()
and notify()
:
class Foo { public static void main(String [] args) { Runnable r = new MyRunnable(); Thread b = new Thread(r); b.start(); synchronized(r) // Acquire lock *BEFORE* we enter the wait condition { try { System.out.println("Waiting for runnable to complete..."); r.wait(); // Enter wait condition *AND* release the lock; the thread wakes up when it gets a notification *AND* can re-acquire the lock int milliseconds = 1000; r.wait(milliseconds); // Doesn't make sense here, just for illustration purposes that we can also use a timeout } catch (InterruptedException e) {} System.out.println("Total is: " + r.total); } r.wait(); // RUNTIME ERROR! - We don't have the lock on object r } } class MyRunnable implements Runnable { int total; public void run() { synchronized(this) { for (int i = 0; i < 100; i++) { total += i; } // NOTE: Both notify() and notifyAll() require a lock on the object on which they are invoked !!! notify(); // Notify *ONE* of the waiting threads; that thread will then try to wake up but will be blocked because we still have the lock notifyAll(); // Notify *ALL* waiting threads } // *NOW* one of the notified but blocked threads will wake up because we have released our lock } }
Mixed pickles
System properties
System properties can be set, for instance, when the Java program is launched:
java -DmyProp=myValue Foo
A few examples how to work with system properties:
import java.util.Properties; Properties p = System.getProperties(); String value1 = p.getProperty("myProp"); // Returns "myValue" (assuming the property has been set when the Java program was launched) String value2 = p.getProperty("yourProp"); // Returns null value2 = p.getProperty("yourProp", "default"); // Returns the default value "default" p.setProperty("myProp", "yourValue"); p.list(System.out);
Garbage collection
Rules:
- An object is eligible for garbage collection (GC) when no live thread can access it
- There is no guarantee when, or if, an object eligible for GC is actually collected
- There is no guarantee when GC is run; this wholly depends on the implementation of the JVM
- GC cannot be forced. A request to run GC can be submitted to the JVM, but there is no guarantee when, or if, the request is granted
To make an object unreachable to live threads, hence make it eligible for GC:
- Set variables that reference the object to some other value, e.g. set them to
null
, or to a reference to a different object - Isolated cross-references are usually discovered when GC is run
JavaBeans API
JavaBeans are classes that conform to a certain API. Among the rules are
- JavaBeans have properties
- Properties are accessed using getter and setter methods (I like to call these accessors)
- The property name is inferred from the name of the accessors. There is no need for having an actual instance variable for a property
- The getter must be prefixed with "is" or "get" if the property is a boolean. The getter must always be prefixed with "get" if the property is not a boolean
- The setter must always be prefixed with "set"
- Setter method signatures must be marked
public
, with avoid
return type and an argument that represents the property type - Getter method signatures must be marked
public
, take no arguments, and have a return type that matches the argument type of the setter method for that property
- JavaBeans have events
- For each event type it is possible to register one or more so-called listeners; a listener is what I would call an observer: an object that is notified when the event occurs
- Methods used to manage event listeners must be prefixed with "add" or "remove", and end with the listener type. Note that the type must always end with the word "Listener"
- For instance:
addFooListener
andremoveFooListener
- Both methods take exactly one argument: The object that should be registered or unregistered as a listener. The argument must have the type that is embedded in the method name, e.g.
FooListener
in the example above - It is not yet clear what methods are invoked on a listener object to notify it that an event occurred