Java Cookbook By Ian F. Darwin Unconfirmed error reports are from readers. They have not yet been approved or disproved by the author or editor and represent solely the opinion of the reader. This page was updated March 25, 2004. Here's a key to the markup: [page-number]: serious technical mistake {page-number}: minor technical mistake : important language/formatting problem (page-number): language change or minor formatting problem ?page-number?: reader question or request for clarification UNCONFIRMED errors and suggestions from readers: [N/A] javacooksrc.zip; Put in these lines in Example StringFormatTest.java public final static int JUST_LEFT = 0; public final static int JUST_CENTER = 1; public final static int JUST_RIGHT = 2; [1] javacook/RE/BookRank.java line 21; There's a compiler error when trying to compile javacook/RE/BookRank.java: unclosed string literal: public final static String QUERY = " "http://www.quickbookshops.web/cgi-bin/search?isbn="; The quote on line 21 should be removed: public final static String QUERY = "http://www.quickbookshops.web/cgi-bin/search?isbn="; [1] javacooksrc/DBM/jdbm.c; one of the malloc()s incorrectly uses the k.dsize instead of the v.dsize to allocate the size, causing the example to not work. line 97: is: v.dptr = malloc(k.dsize); // XXX should be: v.dptr = malloc(v.dsize); // XXX [1] javacook/strings/StrTok4.java line 14; The source file cannot be compiled because of a missing comma: StringTokenizer st = new StringTokenizer(line, DELIM true); should be: StringTokenizer st = new StringTokenizer(line, DELIM, true); (3) MacOS 1st paragraph 5th sentence; "Java 1.8" should be "JRE 1.1.8". (23) last paragraph; First sentence of the last paragraph should read: Compilation of this program and examination of the resulting class file reveals that the string "Hello, World " does appear, but th e conditionally printed epigram does not. (25) Second paragraph; Last sentence of the 2nd paragraph should include the period within the quotes around 'debug', like this: My Debug class also provides the string "debug." as part of the System.getProperty(), so we can... {58} Example 3-1, 3rd paragraph, instanciating the StringBuffer; There is a comma missing in this Line: StringTokenizer st = new StringTokenizer(line, DELIM true); Instead the line should be as follows: StringTokenizer st = new StringTokenizer(line, DELIM, true); {59} "Solution" for Section 3.3: "Use String concatentation: the + operator. The compiler will contstruct a StringBuffer for you and use its append() methods." It is my understanding that when Java appends two Strings together using the "+" operator, it does not use a StringBuffer. Instead it creates a new String Object which contains the contents of the Strings being concatenated. This is why String concatenation is slow. {51} GetOptDemo sample output; In the sample output printed in the book from GetOptDemo, the first input file is lost. For example, in "java GetOptDemo -n a b c" the output of GetOptDemo lists only b and c as input. File a is lost. The problem is in GetOpt (not printed in the book), which doesn't return a very useful value in getOptInd(). Sometimes this is the index of the first input file, but sometimes it is the index of the second input file. I don't see any way a calling program can tell the difference, unless the calling program re-reads the previous arguments again and duplicates a lot of the logic in GetOpt. Also, the GetOptTest on page 52 looks like it wanted to use different data in the "badArgs" array, but in the event, badArgs is identical to goodArgs, which leaves the reader wondering what the point is. Or maybe the intention was to do two runs, not three, and the middle run got inserted by accident. Anyway, run 2 and run 3 are identical, since goodArgs and badArgs are the same. [58] Example 3-1; Line is is in book: StringTokenizer st = new StringTokenizer(line, DELIM true) should have been: StringTokenizer st = new StringTokenizer(line, DELIM, true) Comment: missing comma. Bought my book two weeks ago in Pretoria (South Africa). I hope it is the latest printing. Nice book! {59} Section 3.3; The basic rule for when to use Strings and + vs. StringBuffers and append is that when a String is constructed all at once, it's easier to concatenate Strings with +. The Java compiler will automatically convert this notation into StringBuffer objects and append method calls. Therefore, the first, rather than the third, way shown in Example 3-2 is actually the concise, real-world way to form Strings. On the other hand, when a String needs to be formed little-by-little in a loop, it's much more efficient to use a StringBuffer and the append method explictly. If a String is formed using n iterations in a loop, using a String and + will take O(n^2) time, but using a StringBuffer and append will take just O(n) time. The time savings in this case justifies the awkwardness of the StringBuffer and append method. {89} Table 4-1; Jakarta-ORO and Daniel Savarese's ORO libraries are the same thing. It's just that Jakarta-ORO is the latest version. Details are at http://jakarta.apache.org/oro/ (98) 3rd part of the code sample; System.out.println(input + "--> " + r.sub(input, "daemon")); it should be System.out.println(input + "--> " + r.subst(input, "daemon")); {121} Example 5-1; Comparing the difference of two numbers with epsilon requires that the magnitude of epsilon change with the magnitude of the numbers being compared. But the whole purpose of floating point is that the point floats, that is, the programmer need not keep track of the magnitude of the numbers. Here's an equals() method that works over a wide range of magnitudes, and also works with infinities, NaNs, and positive and negative zero: public static boolean equals(double a, double b) { return a == b || Math.abs(a - b) < EPS * Math.max(Math.abs(a), Math.abs(b)); } where EPS is a small double such as 1e-12. Knuth gives a much more rigorous algorithm for approximate floating point equality in The Art of Computer Programming, but the above code should be adequate for most Java programs. [150] 2nd paragraph; Talking about the Calendar class, the introduction to "Dates and Times" states: "In the western world, our calendar epoch is the imaginary year 0, representin g the putative birth year of Jesus Christ. [..] the years before are called Bef ore Christ [..] " In the old days, when our western calendar was introduced, even non-Pascal programmers started counting with one. As a result, the "putative birth year of Jesus Christ" is 1 AD, the year before is 1 BC. No year zero at all. The Java GregorianCalendar implements this correctly. E.g.: SimpleDateFormat sdf = new SimpleDateFormat(); sdf.applyPattern("MM/dd/yyyy G"); Calendar calendar = new GregorianCalendar(1, Calendar.DECEMBER, 24); System.out.println(sdf.format(calendar.getTime()).toString()); calendar.add(Calendar.YEAR, -1); System.out.println(sdf.format(calendar.getTime()).toString()); outputs 12/24/0001 AD 12/24/0001 BC As a sidenote, you can use zero to set the year in the GregorianCalendar, as it is mapped to 1 BC. (The year -1 is mapped to 2 BC and so on.) [160] 1st paragraph; The method will fail if there's a change in daylight saving between the first and the second date. the result of the following lines should be 122: Date d1 = new GregorianCalendar(1999, 11, 31, 23, 59).getTime(); Date d2 = new GregorianCalendar(2000, 04, 01, 23, 59).getTime(); long diff = d2.getTime() - d1.getTime(); System.out.println("Difference between " + d2 + "\n" + (diff / (1000 * 60 * 60 * 24)) + " days."); The solution could be something like: TimeZone t = new SimpleTimeZone(0, "SIMPLE"); GregorianCalendar c1 = new GregorianCalendar(t); GregorianCalendar c2 = new GregorianCalendar(t); c1.set(1999, 11, 31, 23, 59); Date d1=c1.getTime(); c2.set(2000,04,01,23,59); Date d2 = c2.getTime(); [163] Discussion; The example of 6.10 is a bit misleading, as it fails to point a serious trap in the result of the Calendars get() method: while Calendar counts from one for most entries, months start at zero.. Example: Calendar calendar = new GregorianCalendar(2001,Calendar.DECEMBER,24); System.out.println("month/day/year: " +calendar.get(Calendar.MONTH) +"/" +calendar.get(Calendar.DAY_OF_MONTH)+"/" +calendar.get(Calendar.YEAR)); outputs "month/day/year: 11/24/2001". Thus Java celebrates X-mas at 11/24! (166) Bottom half of page: Solution Call System.getTimeMillis() This is not a Java function of the System class. It's used in three examples on the page... Correct function System.currentTimeMillis() it is later corrected in other examples [181] Last line of code; The line where the Iterator is created is wrong in the HashMap example. It says: Iterator it = h.values().iterator(); with results: Key Mountain View, CA; Value null Key White Plains, NY; Value null Key Mountain View, CA; Value null Key Mountain View, CA; Value null Key Redmond, WA; Value null Key Sebastopol, CA; Value null Key Los Angeles, CA; Value null It should be: Iterator it = h.keySet().iterator(); (196) paragraph under "Problem" for item 7.14; Missing a period at the end of the next-to-last sentence. [213] Section 8.4; It's rare for a clone() method to declare that it throws CloneNotSupportedException, and doing so makes it much harder for the users of the class to copy objects because they must handle the "impossible" exception. The only time doing so is necessary is if the class itself does not implement Cloneable although subclasses are allowed to implement Cloneable, or if the clone() method called on a field may throw a CloneNotSupportedException. A more cookbook way to write the clone() method is the following: public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } } or if some Cloneable fields need to be cloned: public Object clone() { ThisClass tc; try { tc = (ThisClass) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } if (field != null) tc.field = (FieldClass) field.clone(); return tc; } or if the class itself does not implement Cloneable or it clones a field that may throw CloneNotSupportedException: public Object clone() throws CloneNotSupportedException { ThisClass tc = (ThisClass) super.clone(); if (field != null) tc.field = (FieldClass) field.clone(); return tc; } {215} Section 8.5 Solution; The suggestion "use a finalizer" is very vague. A protected finalizer method that calls super.finalize() should be provided, but should not be called by anyone except the garbage collector and the subclass's finalize() method. Here's a cookbook finalize() method, assuming that cleanUp() is the method that should be called to release resources, and it can be called multiple times (once by client code, once by the finalizer). protected void finalize() throws Throwable { try { cleanUp(); } finally { super.finalize(); } } {216} Section 8.6 Discussion 1st paragraph 2nd sentence; The definition of an inner class is incorrect. A nested class (or nested interface) is one that is declared inside another class or interface. An inner class is a non-static class declared inside another class. This terminology is the one used in the latest editions of The Java Language Specification (2nd) and The Java Programming Language (3rd). [232] Streams and Readers/Writers: 1st para; The following sentence is a mis-statement: "Script-based languages like Arabic and Indian languages, and pictographic languages like Chinese, Japanese and Korean each have many more than 256 characters, the maximum that can be represented in an eight-bit byte." The Korean language is NOT an pictographic language like Chinese and Japanese--it is a phoenetic language with 11 vowels and 14 consonents. [242] Last paragraph of recipe 9.4; The paragraph refers to the methods copyTestFile() and copyDataFile(), which aren't shown in example 9-1. (260) Recipe 9.14 Seeking, 1st paragraph in section titled "Discussion": "the primary methods of interest are void(long where), which moves the position ..." should be: "the primary methods of interest are void seek(long where), which moves the position ..." {294} Delete2 code example; The sense of the check for success of the delete is reversed. Where it says if (!bkup.delete()) System.out.println("** Deleted "+fileName); the message indicates we are checking for a successful delete(). Therefore the if statement should read: if (bkup.delete()) //no ! (307) The Communications API Section, 2nd paragraph, 3rd sentence; The phrase "cast the ComPort object" should read "cast the ComPort reference". A cast applies to the reference that points to the object; the object pointed to by the reference is unaffected by the cast. [325] line 8 in the source code; NOTE: this error is also present in the downloadable source code. it reads: .... // The modem sends an extra blank line by way of a prompt. // Here we read and discard it. xyz String junk = os.readLine(); if (junk.length() != 0) { System.err.print("Warning unexpected response: "); System.err.println(junk); } .... see the line marked "xyz" - a) readLine is deprecated, so its use should be avoided b) object "os" is a PrintStream - these do not conventionally possess a "readLine" method btw - I'm using JDK 1.3, and a surprising amount of code is coming up as deprecated. {341} First sentence; The CompTest source doesn't appear to be included in the online code examples. [342] picture and program; I believe there are some problems with recipe 12.4. First, the y coordinate of Graphics.drawstring() is the coordinate of the baseline. Therefore, I don't understand why you are subtracting the leading which is just the inter-line spacing. If you want the baseline to be centered, they the y-coordinate should just be "getSize().height / 2". If you want the text to actually be centered, then I think you need something like: (getSize().height - fm.getHeight()) / 2 + fm.getLeading() + fm.getAscent() In addition, your font picture is, at best, very misleading. The "height" is equal to "ascent + decent + leading". Check out the FontMetrics javadoc. (457) Example 15-10; Use of deprecated method in example 15-10: bweigand% javac -deprecation Telnet.java Telnet.java:50: warning: readLine() in java.io.DataInputStream has been deprecated while ((line = is.readLine()) != null) { ^ Summary of method: (http://java.sun.com/products/jdk/1.2/docs/api/java/io/DataInputStream.html) readLine() Deprecated. This method does not properly convert bytes to characters. As of JDK 1.1, the preferred way to read lines of text is via the BufferedReader.readLine() method. Programs that use the DataInputStream class to read lines can be converted to use the BufferedReader class by replacing code of the form: DataInputStream d = new DataInputStream(in); with: BufferedReader d = new BufferedReader(new InputStreamReader(in)); [477] In the run method, declaration of clientSocket; The variable clientSocket is declared within the synchronized section, and it appears the scope is confined to that section as well. So when it is used outside of this section (i.e. within the next 6 lines below), the compiler flags it with a "cannot resolve symbol" error. It can be fixed by declaring clientSocket at the top of the try block, and then just assigning the variable with the synchronized block: ... try { Socket clientSocket; ... synchronized(servSock) { clientSocket = servSock.accept(); } ... {490} First line; should be