Welcome to the Core Java Technologies Tech Tips, May 6, 2003. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).
This issue covers:
Using the native2ascii Tool
Local Classes
These tips were developed using Java 2 SDK, Standard Edition, v 1.4.
See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.
This issue of the JDC Tech Tips is written by Glen McCluskey.
USING THE NATIVE2ASCII TOOL
Imagine that you're working with a Java property file, and you decide that you'd like to use the Unicode character \u0430 (a letter) in one of your keys. So you write a property file that looks like this, in a file named propfile:
\u0430=testing
You then write some code that reads the file:
import java.io.*;
import java.util.*;
public class NativeDemo1 {
static String key = "\u0430";
public static void main(String[] args)
throws IOException {
Properties prop = new Properties();
FileInputStream fis =
new FileInputStream("propfile");
prop.load(fis);
fis.close();
String value = prop.getProperty(key);
System.out.println("value = " + value);
}
}
|
When you run this program, the output is:
value = testing
which is the expected result.
\u0430 and similar characters can be used in Java property files,
but there's an issue that comes up when you try to use them that
way. In the example above, \u0430 is not actually the Unicode
character with hex value 0x0430, but rather a representation of
that value using a Unicode escape sequence. The representation is
made up of multiple characters.
What happens if you have a Java property file with an actual
character value \u0430 in it? Let's look at this situation using
a program that generates a file with such a character:
import java.io.*;
public class NativeDemo2 {
public static void main(String[] args)
throws IOException {
OutputStream os =
new FileOutputStream("propfile");
Writer w =
new OutputStreamWriter(os, "UTF-8");
BufferedWriter bw = new BufferedWriter(w);
bw.write("\u0430=testing");
bw.newLine();
bw.close();
}
}
|
This program writes a property file using the UTF-8 encoding.
If you run NativeDemo2, and then NativeDemo1 again, the result is:
value = null
The problem is that property files are assumed to be expressed using ISO 8859-1 (Latin-1) characters and Unicode escapes (\uxxxx).
You can use the native2ascii tool to fix this problem, like this:
$ javac NativeDemo2.java
$ java NativeDemo2
$ native2ascii -encoding UTF-8 propfile outfile
$ mv outfile propfile
$ javac NativeDemo1.java
$ java NativeDemo1
|
This sequence runs the NativeDemo2 program to create a property file with an actual \u0430 in it. This file is then run through native2ascii to convert the character to its Unicode escape form. The output file is then renamed back to propfile, using the UNIX mv command. When NativeDemo1 is run again, it does the right thing -- the \u0430 character has been converted to escape form, that is, expressed using ISO 8859-1 characters. Try the sequence. You'll see that the result is once again:
value = testing
Note that you can specify other encodings besides UTF-8 to
native2ascii, and you can redirect standard input and output. The Java compiler has an option -encoding to solve a similar problem, so you don't need to use native2ascii for Java source files.
For more information, see section 3.1, Unicode, section 3.3, Unicode Escapes, and section 3.8, Identifiers in "The Java Language Specification Second Edition" by Gosling, Joy, Steele, and Bracha.
LOCAL CLASSES
Suppose that you have a list of Integer objects that you'd like to sort using Collections.sort. You need to specify your own comparator, because you want the objects sorted in descending rather than ascending order. Here's some code that illustrates how to do that:
import java.util.*;
public class LocalDemo1 {
// sort using Comparator implemented via
// anonymous class
static void sortanon(List list) {
Collections.sort(list, new Comparator() {
public int compare(
Object o1, Object o2) {
int cc = ((Integer)o1).compareTo(o2);
return (cc < 0 ? 1 : cc > 0 ? -1 : 0);
}
});
}
// sort using Comparator implemented via
// local class
static void sortlocal(List list) {
class MyComparator implements Comparator {
public int compare(
Object o1, Object o2) {
int cc = ((Integer)o1).compareTo(o2);
return (cc < 0 ? 1 : cc > 0 ? -1 : 0);
}
};
Collections.sort(list, new MyComparator());
}
public static void main(String[] args) {
List list1 = new ArrayList();
list1.add(new Integer(1));
list1.add(new Integer(2));
list1.add(new Integer(3));
sortanon(list1);
System.out.println(list1);
List list2 = new ArrayList();
list2.add(new Integer(1));
list2.add(new Integer(2));
list2.add(new Integer(3));
sortlocal(list2);
System.out.println(list2);
}
}
|
The output of the program is:
[3, 2, 1]
[3, 2, 1]
The demo illustrates two different approaches to implementing the Comparator interface. The first uses an anonymous class, and the second a local class. What's the difference?
One difference is stylistic -- an anonymous class is declared more tersely than a local class, and is in fact part of an expression:
Comparator c = new Comparator() {...};
By contrast, a local class looks very similar to a conventional class declaration of the sort you've seen many times. For example, the local class declaration uses "implements", which is implicit in the declaration of the anonymous class.
Which style is "better" depends on your point of view. An anonymous class declaration can be hard to read. But using a local class where it's not needed might give the illusion that more is going on than is really the case.
Let's look at another example, as a means of further comparing and contrasting anonymous and local classes:
import java.util.*;
public class LocalDemo2 {
// sort two lists, using instances of
// two distinct anonymous classes
static void sort1(List list1, List list2) {
Collections.sort(list1, new Comparator() {
public int compare(
Object o1, Object o2) {
int cc = ((Integer)o1).compareTo(o2);
return (cc < 0 ? 1 : cc > 0 ? -1 : 0);
}
});
Collections.sort(list2, new Comparator() {
public int compare(
Object o1, Object o2) {
int cc = ((Integer)o1).compareTo(o2);
return (cc < 0 ? 1 : cc > 0 ? -1 : 0);
}
});
}
// sort two lists, using two instances of
// a single local class
static void sort2(List list1, List list2) {
class MyComparator implements Comparator {
public int compare(
Object o1, Object o2) {
int cc = ((Integer)o1).compareTo(o2);
return (cc < 0 ? 1 : cc > 0 ? -1 : 0);
}
}
Collections.sort(list1, new MyComparator());
Collections.sort(list2, new MyComparator());
}
// sort two lists, using one instance
// of a single anonymous class
static void sort3(List list1, List list2) {
Comparator cmp = new Comparator() {
public int compare(
Object o1, Object o2) {
int cc = ((Integer)o1).compareTo(o2);
return (cc < 0 ? 1 : cc > 0 ? -1 : 0);
}
};
Collections.sort(list1, cmp);
Collections.sort(list2, cmp);
}
// sort two lists, using one instance
// of a single local class
static void sort4(List list1, List list2) {
class MyComparator implements Comparator {
public int compare(
Object o1, Object o2) {
int cc = ((Integer)o1).compareTo(o2);
return (cc < 0 ? 1 : cc > 0 ? -1 : 0);
}
}
Comparator cmp = new MyComparator();
Collections.sort(list1, cmp);
Collections.sort(list2, cmp);
}
static class AppComparator implements
Comparator {
public int compare(Object o1, Object o2) {
int cc = ((Integer)o1).compareTo(o2);
return (cc < 0 ? 1 : cc > 0 ? -1 : 0);
}
}
static Comparator appcomparator =
new AppComparator();
// sort two lists, using a comparator
// defined in the application
static void sort5(List list1, List list2) {
Collections.sort(list1, appcomparator);
Collections.sort(list2, appcomparator);
}
public static void main(String[] args) {
List list1 = new ArrayList();
list1.add(new Integer(1));
list1.add(new Integer(2));
list1.add(new Integer(3));
List list2 = new ArrayList();
list2.add(new Integer(4));
list2.add(new Integer(5));
list2.add(new Integer(6));
//sort1(list1, list2);
//sort2(list1, list2);
//sort3(list1, list2);
//sort4(list1, list2);
sort5(list1, list2);
System.out.println(list1);
System.out.println(list2);
}
}
|
The output of the program is:
[3, 2, 1]
[6, 5, 4]
All the sort methods in this program do the same thing -- sort two lists of Integer objects in descending order.
sort1 uses two anonymous classes, each of which implements the Comparator interface. The two classes are distinct from each other, and the second duplicates the logic of the first (an undesirable thing). sort1 illustrates the point that an anonymous class always has exactly one point of instantiation, and exactly one class instance is created at the point of instantiation. You can't create multiple instances of an anonymous class except by repeatedly executing the code that contains the instantiation
point.
The sort2 method uses two instances of a single local class. It's
a better approach than sort1, but not the most efficient.
The sort3 and sort4 methods each use a single instance of one class. There's no duplication of logic, and the code is efficient. sort3 is terser than sort4, but sort4 is more readable. Except for readability, using a local class in this situation doesn't do anything for you.
None of these four approaches is really right if you need to implement the same sort policy throughout an application. In that case, it would be better to hoist the comparator class (MyComparator) to be a global or nested class, or create a single instance of a comparator that can be used everywhere in the
application. sort5 is an example of this approach.
Another point about anonymous classes is that they must be based on a preexisting type (class or interface). For example, when you code:
Comparator c = new Comparator() {...};
what actually happens is that an instance of a nameless class that implements the Comparator interface is created. There is no such restriction for a local class.
A further difference between anonymous and local classes is that an anonymous class cannot have a constructor, because it has no name. It's still possible to do initialization, using {...} style initialization blocks. Here's an example:
import java.util.*;
public class LocalDemo3 {
public static void main(String[] args) {
Comparator cmp1 = new Comparator() {
{
System.out.println(
"object initialization");
}
public int compare(
Object o1, Object o2) {
int cc = ((Integer)o1).compareTo(o2);
return (cc < 0 ? 1 : cc > 0 ? -1 : 0);
};
};
class MyComparator implements Comparator {
public MyComparator(int x) {
System.out.println(
"constructor called" +
" with value " + x);
}
public int compare(
Object o1, Object o2) {
int cc = ((Integer)o1).compareTo(o2);
return (cc < 0 ? 1 : cc > 0 ? -1 : 0);
};
};
Comparator cmp2 = new MyComparator(100);
}
}
|
The output of the program is:
object initialization
constructor called with value 100
The local class has a constructor that's called in the usual way. However the anonymous class must make do with {...} to perform initialization.
Consider again the example about sorting lists. It would be possible to control the behavior of the MyComparator class by defining a constructor for the class and passing an argument to specify sorting behavior (such as ascending/descending). It's possible to pass the equivalent of a constructor argument to an anonymous class by declaring a final variable in the surrounding
method scope.
A final example illustrates an issue that comes up with anonymous and local classes alike -- accessing local variables in the enclosing method. Here's some code that illustrates the issue:
interface A {
int f();
}
public class LocalDemo4 {
static A createB() {
final int i = 100;
class B implements A {
public int f() {
return i;
}
}
return new B();
}
public static void main(String[] args) {
A aref = createB();
System.out.println(aref.f());
}
}
|
The f method for the object of local class B is called after the createB method completes. The f method accesses the local variable i after i's context (stack frame) has been destroyed. In this situation, the local variable must be declared final, that is, bound, such that its value can be preserved for later use by the f method.
Local classes fit better within the mainstream of class usage that you are familiar with. Local classes are also more readable than anonymous classes. But at the same time, the functionality of a local class is often more than you need -- an anonymous class is a better choice in these situations.
For more information, see section 5.3, Local Inner Classes, in
"The Java Programming Language Third Edition" by Arnold, Gosling, and Holmes.
IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html
Comments? Send your feedback on the Core Java Technologies Tech Tips to: jdc-webmaster@sun.com
Subscribe to other Java developer Tech Tips:
- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).
To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".
ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
Copyright 2003 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks (http://www.sun.com/suntrademarks/) of Sun Microsystems, Inc. in the United States and other countries.
|