Welcome to the Core Java Technologies Tech Tips, July 15, 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 Preferences API
Interfaces and Constants
These tips were developed using Java 2 SDK, Standard Edition, v 1.4.
This issue of the Core Java Technologies Tech Tips is written by Daniel Steinberg, Director of Java Offerings for Dim Sum Thinking, Inc, and editor-in-chief for java.net.
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.
USING THE PREFERENCES API
There are many times when you need to persist configuration
information for an application. In the past, you might have used
java.util.Properties to save details about the preferred date
format for an application, the preferred window size or location,
or the previous time that a user logged into an application. The
Properties class required you to write to and read from streams.
This, in turn, meant that you had to manage where the properties
files were stored in your file system. In other words, much of
your effort was spent managing details that had little to do with
the information being managed.
The Preferences API introduced in J2SE 1.4 provides a convenient
framework for storing and recalling data about user-specific
preferences or system-wide preferences. In this tip, you'll learn
how to use the Preferences API to store and recall user-specific
preferences.
First, here's a little background about how preferences data is
organized. Data about user and system preferences is organized
into hierarchical collections called nodes. Multiple nodes exist
in a tree. There are two types of trees: one for user preferences
and one for system preferences. There is a separate preference
tree for each user. But there is only one system preference tree
for the entire system. Note that even though data about
user-specific preferences and system-wide preferences are in
different trees, the techniques for storing and recalling system
preferences are the same as those shown in this tip.
This first step in using the Preferences API is to get a handle
to a Preferences object for this class. To do this, pass the
factory method useNodeForPackage() an object of type Class.
import java.util.prefs.Preferences;
import java.awt.Dimension;
public class UserPreferences {
private Preferences userPrefs;
public UserPreferences(){
userPrefs = Preferences.userNodeForPackage(
UserPreferences.class);
}
}
|
This gives you an instance of a Preferences object that you can
use to read from and write to the stored data. For this tip,
let's use the Preferences instance to store and retrieve the
dimensions of a JFrame. This is shown in the following code:
import java.util.prefs.Preferences;
import java.awt.Dimension;
import javax.swing.JFrame;
public class UserPreferences extends JFrame{
private Preferences userPrefs;
public UserPreferences(){
userPrefs = Preferences.userNodeForPackage(
UserPreferences.class);
setToPreferredSize();
}
public void setToPreferredSize(){
int width = userPrefs.getInt("width", 100);
int height = userPrefs.getInt("height", 200);
setSize(width, height);
}
public Dimension getDimensions(){
int width = userPrefs.getInt("width", 100);
int height = userPrefs.getInt("height", 200);
return new Dimension(width, height);
}
public void putDimensions( Dimension dimension){
userPrefs.putInt(
"width", (int)dimension.getWidth());
userPrefs.putInt(
"height", (int)dimension.getHeight());
}
}
|
In this class, the setToPreferredSize() method is used to
retrieve the height and width, and set the JFrame's size to these dimensions. The putDimensions() method is used to persist int values for the height and width. These methods, in turn, call the getInt() and putInt() methods of the Preferences class.
The getInt() method in the Preferences class takes two arguments. The first is a String that acts as the key for the property being stored. The second argument is an int that is used as the default value. If the property has not yet been set, then the default value is used. Similarly the putInt() method takes two arguments.
The first is the key, and the second is the value of the property
being set.
Now let's exercise the code. The following program creates a
JFrame, setting the size based on data read from the Preferences object. The dimensions in the Preferences object are initially
width 100 and height 200. In the program, they're reset to width
200 and height 300. A print statement and a call to setVisible() have been added so you can see the effects.
import java.util.prefs.Preferences;
import java.awt.Dimension;
import javax.swing.JFrame;
public class UserPreferences extends JFrame{
private Preferences userPrefs;
public UserPreferences(){
userPrefs = Preferences.userNodeForPackage(
UserPreferences.class);
setToPreferredSize();
setVisible(true);
}
public void setToPreferredSize(){
int width = userPrefs.getInt("width", 100);
int height = userPrefs.getInt("height", 200);
setSize(width, height);
System.out.println("Width = "+ getWidth() +
" Height = " + getHeight());
}
public Dimension getDimensions(){
int width = userPrefs.getInt("width", 100);
int height = userPrefs.getInt("height", 200);
return new Dimension(width, height);
}
public void putDimensions( Dimension dimension){
userPrefs.putInt(
"width", (int)dimension.getWidth());
userPrefs.putInt(
"height", (int)dimension.getHeight());
}
public static void main(String[] args){
new UserPreferences().putDimensions(
new Dimension(200,300));
}
}
|
Run this program, and you should get the following results:
Width = 100 Height = 200
Run the program a second time, and you should get the following
results:
Width = 200 Height = 300
The width and height have been successfully saved, and so the
values are saved between runs of the program.
If you want to add the ability to clear the values from memory,
add the following method to UserPreferences.java.
public void clearPreferences(){
try {
userPrefs.clear();
} catch (BackingStoreException e) {
e.printStackTrace();
}
}
|
Also add the import statement for BackingStoreException. A call to the clearPreferences() method will result in the use of the default values when you call the setToPreferredSize() method.
You can also implement a listener, specifically
PreferenceChangeListener, for changes in preferences. For
example, let's set up the JFrame to implement
PreferenceChangeListener, and so respond to changes in the
preferences. In addition to extending JFrame, UserPreferences can also implement PreferenceChangeListener. The only requirement is that you implement PreferenceChangeListener's one method: preferenceChange(). When a change to the preferred
dimensions is discovered, the appropriate action is to change the
dimensions of the JFrame by calling setToPreferredSize().
public void preferenceChange(PreferenceChangeEvent e){
setToPreferredSize();
}
Other changes you can make to the code include adding methods to
select and set the random ints that are stored as preferences,
and adding a call to the resetManyTimes() method in main(). The revised code looks like this.
import javax.swing.JFrame;
import java.util.prefs.Preferences;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.PreferenceChangeEvent;
public class UserPreferences extends JFrame
implements PreferenceChangeListener{
private Preferences userPrefs;
public UserPreferences(){
userPrefs = Preferences.userNodeForPackage(
UserPreferences.class);
userPrefs.addPreferenceChangeListener(this);
setToPreferredSize();
setVisible(true);
}
public void setToPreferredSize(){
int width = userPrefs.getInt("width", 100);
int height = userPrefs.getInt("height", 200);
setSize(width, height);
System.out.println("Width = "+ getWidth() +
" Height = "+ getHeight());
}
public void resetDimensionsManyTimes(){
for (int i = 0; i<10;i++){
putRandomDimensions();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void putRandomDimensions(){
userPrefs.putInt("width", getRandomInt());
userPrefs.putInt("height", getRandomInt());
}
private int getRandomInt(){
return (int)(Math.random()*300+100);
}
public void preferenceChange(PreferenceChangeEvent e){
setToPreferredSize();
}
public static void main(String[] args){
new UserPreferences().resetDimensionsManyTimes();
}
}
|
Run the application and you should see the JFrame change size ten times.
For more information on the Preferences APIs, see the its
description in the J2SE 1.4.1 documentation.
INTERFACES AND CONSTANTS
When you want to use global constants in an application, it is
often tempting to define the constants in an interface and then
implement the interface in the class using the constants. For
example, suppose you have the following interface that contains
approximations for the numbers pi and e.
public interface TranscendentalConstants {
public static final double PI = 3.14159;
public static final double E = 2.71828;
}
Suppose too that you have a second collection of constants that
contains approximations for the square roots of two and three.
public interface IrrationalConstants {
public static final double SQRT_TWO = 1.414;
public static final double SQRT_THREE = 1.732;
}
A common strategy for using these constants is to create a class
like the following.
public class BadUseOfConstants implements
TranscendentalConstants, IrrationalConstants {
public double sinPiOverFour(){
return SQRT_TWO / 2;
}
public double sinPiOverThree(){
return SQRT_THREE / 2;
}
private void outputResults() {
System.out.println("Pi is approximately " + PI);
System.out.println(
"The sin of Pi/4 is approximately " +
sinPiOverFour());
System.out.println(
"The sin of Pi/3 is approximately " +
sinPiOverThree());
}
public static void main(String[] args) {
new BadUseOfConstants().outputResults();
}
}
|
Even though this code runs, it is an improper use of interfaces.
That's because BadUseOfConstants is not an extension of the type
TranscendentalConstants. Instead, BadUseOfConstants is a consumer
of TranscendentalConstants. There are two reasons why using
interfaces in this way is so attractive. First, you can easily
use constants defined in two different interfaces. If the
constants were used in different classes instead, multiple
inheritance would be required to perform the same task. Second,
by implementing the interfaces, you can refer to the constants
without qualifying them as SQRT_TWO instead of as
IrrationalConstants.SQRT_TWO.
You can address this second problem of having to qualify constants
by caching the variables locally like this.
public class OKUseOfConstants {
private double PI = TranscendentalConstants.PI;
private double SQRT_TWO =
IrrationalConstants.SQRT_TWO;
private double SQRT_THREE =
IrrationalConstants.SQRT_THREE;
public double sinPiOverFour() {
return SQRT_TWO / 2;
}
public double sinPiOverThree() {
return SQRT_THREE / 2;
}
public void outputResults() {
System.out.println("Pi is approximately " + PI);
System.out.println(
"The sin of Pi/4 is approximately " +
sinPiOverFour());
System.out.println(
"The sin of Pi/3 is approximately " +
sinPiOverThree());
}
public static void main(String[] args) {
new OKUseOfConstants().outputResults();
}
}
|
When J2SE 1.5 is released, a new mechanism for importing specific
constants will be introduced that will address both of these
issues. In the meantime, you might want to introduce an inner
class or a different class that collects all of the interfaces
containing the constants you want to use. Here's an example. In
the BetterUseOfConstants program that follows, the inner class
ConstantCollector is used to collect the constants declared in
TranscendentalConstants and IrrationalConstants.
public class BetterUseOfConstants {
private ConstantCollector constants =
new ConstantCollector();
public double sinPiOverFour(){
return constants.SQRT_TWO / 2;
}
public double sinPiOverThree(){
return constants.SQRT_THREE / 2;
}
private void outputResults() {
System.out.println(
"Pi is approximately " +
constants.PI);
System.out.println(
"The sin of Pi/4 is approximately " +
sinPiOverFour());
System.out.println(
"The sin of Pi/3 is approximately " +
sinPiOverThree());
}
public static void main(String[] args) {
new BetterUseOfConstants().outputResults();
}
private class ConstantCollector implements
TranscendentalConstants, IrrationalConstants{}
}
|
Running this code should produce the following results:
Pi is approximately 3.14159
The sin of Pi/4 is approximately 0.707
The sin of Pi/3 is approximately 0.866
In this case ConstantCollector is a type extension of the two
interfaces.
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: http://developers.sun.com/contact/feedback.jsp?category=sdn
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.
|