Core Java Technologies Tech Tips Tips, Techniques, and Sample Code 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 (http://java.net). You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/JDCTechTips/2003/tt0715.html. 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 http://java.sun.com/j2se/1.4.1/docs/guide/lang/preferences.html). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 * FEEDBACK Comments? Please enter your feedback on the Tech Tips at: http://developers.sun.com/contact/feedback.jsp?category=sdn * SUBSCRIBE/UNSUBSCRIBE 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, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), choose the newsletters you want to subscribe to and click "Update". - To unsubscribe, go to the subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), uncheck the appropriate checkbox, and click "Update". - To use our one-click unsubscribe facility, see the link at the end of this email: - ARCHIVES You'll find the Core Java Technologies Tech Tips archives at: http://java.sun.com/jdc/TechTips/index.html - COPYRIGHT Copyright 2003 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, California 95054 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html Core Java Technologies Tech Tips July 15, 2003 Trademark Information: http://www.sun.com/suntrademarks/ Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.