LearningJavaAPI

From HerzbubeWiki
Jump to navigation Jump to search

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: If equals() 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 in finalize() will prevent the object from being deleted. Note 2: finalize() is never called more than once per object. In other words, if finalize() 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 run finalize() 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

Collection interfaces and classes (all from java.util)
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 same Comparator 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 are null)
  • 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 classes Object and C, while from class B upwards no constructors are run. Class A inherits the serialization capability from B.
  • 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 primitive long 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 between java.util.Calendar and java.text.DateFormat
  • java.util.Calendar is more "modern" than Date


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() and printf() methods in the java.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:
    1. An instance of class java.lang.Thread
    2. A thread of execution
  • 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:
    1. User threads: When all user threads have finished running, the JVM exits the application, even if one or more daemon threads are still running
    2. 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 type void)
  • The run() method must be placed into a class that does one of the following:
    1. Extend the class java.lang.Thread. This is not the recommended approach, unless the application needs more specialized thread-specific behaviour
    2. 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
  • Thread state can be queried using Thread.getState(); possible values from the enum Thread.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 a void 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 and removeFooListener
    • 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