Tuesday, 18 September 2012

Puzzle 21: What's My Class, Take 2


This program does exactly what the one in the previous puzzle did, but doesn't assume that the slash character is used to separate filename components. Instead, the program uses java.io.File.separator, which is a public String field specified to contain the platform-specific filename separator. Does the program print the correct platform-specific name of the class file from which it was loaded?
package com.javapuzzlers;

import java.io.File;



public class MeToo {

   public static void main(String[] args) {

   System.out.println(MeToo.class.getName().

       replaceAll("\\.", File.separator) + ".class");

  }

}


Solution 21: What's My Class, Take 2

The program displays one of two behaviors depending on the underlying platform. If the file separator is a slash, as it is on UNIX, the program prints com/javapuzzlers/MeToo.class, which is correct. If, however, the file separator is a backslash, as it is on Windows, the program prints something like this:
Exception in thread "main"

StringIndexOutOfBoundsException: String index out of range: 1

 at java.lang.String.charAt(String.java:558)

 at java.util.regex.Matcher.appendReplacement(Matcher.java:696)

 at java.util.regex.Matcher.replaceAll(Matcher.java:806)

 at java.lang.String.replaceAll(String.java:2000)

 at com.javapuzzlers.MeToo.main(MeToo.java:6)


Although this behavior is platform dependent, it isn't exactly what we were looking for. What went wrong on Windows? It turns out that the second parameter of String.replaceAll is a not an ordinary string but a replacement string, as defined in the java.util.regex specification [Java-API]. A backslash appearing in a replacement string escapes the following character, causing it to be treated literally. When you run the program on Windows, the replacement string is a lone backslash character, which is invalid. Admittedly, the exception could be a little more informative.
So how do you solve this problem? Release 5.0 provides not one but two new methods that solve it. One is java.util.regex.Matcher.quoteReplacement, which translates a string into the corresponding replacement string. Here is how to fix the program by using this method:
System.out.println(MeToo.class.getName().replaceAll(

    "\\.",  Matcher.quoteReplacement(File.separator))

            + ".class");


The second method introduced in release 5.0 provides an even better solution. This method, String.replace(CharSequence, CharSequence), does the same thing as String.replaceAll, but treats both the pattern and the replacement as literal strings. Here is how to fix the program by using this method:
System.out.println(MeToo.class.getName().

    replace(".", File.separator) + ".class");


But what if you are using an earlier Java release? Unfortunately, there is no easy way to generate the replacement string. It is easier to dispense with regular expressions entirely and to use String.replace(char, char):
System.out.println(MeToo.class.getName().

    replace('.', File.separatorChar) + ".class");


The main lesson of this puzzle and the previous one is: Be careful when using unfamiliar library methods. When in doubt, consult the Javadoc. Also, regular expressions are tricky: Problems tend to show up at run time rather than compile time.
For API designers, it is important to use a method-naming scheme that distinguishes methods whose behavior differs in significant ways. Java's String class is not perfect in this regard. For many programmers, it is not easy to remember which string-replacement methods use literal strings and which ones use regular expressions or replacement strings.

No comments:

Post a Comment

Your comments are welcome!