|
Welcome to the Core Java Technologies Tech Tips for May 4, 2004. 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:
Formatting Text Input With JFormattedTextField
When Are Two Strings Equal?
These tips were developed using Java 2 SDK, Standard Edition, v 1.4.2.
This issue of the Core Java Technologies Tech Tips is written by Daniel H. 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.
For more Java technology content, visit these sites:
java.sun.com - The latest Java platform releases, tutorials, and
newsletters.
java.net - A web forum for collaborating and building solutions
together.
java.com - The marketplace for Java technology, applications and
services.
FORMATTING TEXT INPUT WITH JFORMATTEDTEXTFIELD
Formatting the contents of text fields became easier in J2SE 1.4 with the introduction of javax.swing.JFormattedTextField. It is now easier to create a text field that contains things like a decimal number with exactly three digits to the right of the decimal point, or a string that is entirely upper case. In this tip, you will examine the content and behavior of a JFormattedTextField. Specifically, you will apply different content formats, experiment with a mask for the user input, and alter the behavior of whether or not the input is committed when the JFormattedTextField loses focus.
You can format the contents of a JFormattedTextField in a variety of ways. You can use subclasses of the abstract base class java.text.format such as NumberFormat, MessageFormat, and DateFormat. NumberFormat is itself an abstract class. You get the formatter you want using one of the static factory methods. The getInstance() method returns the default number format for the default locale. If you plan to use the JFormattedTextField for percents, you can use the getPercentInstance(). You can similarly specify integers with getIntegerInstance(), or currency with getCurrencyInstance(). In any of these cases, the default locale is used. If it is important to specify the locale, pass in an object of type Locale as a parameter for any of the above methods.
In the example that follows, you first obtain a concrete instance of type NumberFormat. Then you can customize the instance by specifying the minimum or maximum number of digits to appear after the decimal point.
NumberFormat format2 = NumberFormat.getInstance();
format2.setMinimumFractionDigits(1);
format2.setMaximumFractionDigits(4);
Here is a simple Swing application that contains two JFormattedTextFields that differ slightly in their formatting rules.
import javax.swing.JPanel;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.text.NumberFormat;
import java.awt.GridLayout;
public class FormatEx1 extends JPanel {
private JFormattedTextField field1;
private JFormattedTextField field2;
public FormatEx1() {
super(new GridLayout(2, 2));
setUpField1();
setUpField2();
displayFields();
}
private void displayFields() {
add(new JLabel("field 1:"));
add(field1);
add(new JLabel("field 2:"));
add(field2);
}
private void setUpField1() {
NumberFormat format1;
format1 = NumberFormat.getInstance();
format1.setMaximumFractionDigits(2);
field1 = new JFormattedTextField(format1);
}
private void setUpField2() {
NumberFormat format2;
format2 = NumberFormat.getInstance();
format2.setMinimumFractionDigits(1);
format2.setMaximumFractionDigits(4);
field2 = new JFormattedTextField(format2);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Number Format Demo");
frame.setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
frame.getContentPane().add(new FormatEx1());
frame.setVisible(true);
}
}
Compile and run the FormatEx1 application. Then enter 123 in the top field (field 1). There are two different properties for every JFormattedTextField: the text and the value. When you typed 123, you changed the value of the text property. In this example, the value property is set when the text field loses focus. It is the value that will be formatted and altered to be properly displayed.
The point is that you should only care about the value property and ignore the text property. As you will see in the next example, you can specify when the text value should be converted to the value property by way of setFocusLostBehavior() method and setCommitsOnValidEdit() method in the DefaultFormatter object. The value property points to an instance of Object. In this case, you are working with a Double which can be set directly like this:
setValue(new Double( someDouble));
Back to the example... you have entered 123 in the top field. Tab to the next field (field 2). Nothing should change in the number you entered. Now enter 456 in field 2, and tab to field 1. The 456 should now be displayed as 456.0 because you specified that the minimum number of digits after the decimal point is one.
Change the number in field1 to 0.123, and tab to the bottom field. The displayed number changes to 0.12 because in the application you specified that the maximum number of digits to appear after the decimal point is two. Now enter 0.45003 in field 2, and tab. The display value in field 2 is now 0.45. The rule you set in the application is that only four decimal places can be shown. The formatter has removed the two trailing zeros in 0.4500.
What happens if you type a nonsensical value into either of the fields? Try entering the String abcd in field 1. If you then tab or do anything that makes the field lose focus, the value displayed in field 1 reverts to the previous value of 0.12. You can alter this behavior with the setFocusLostBehavior() method in the JFormattedTextField class. You can also initialize the fields with a specified value.
In the next example, you set field 1 to 12.34 and field2 to 56.78. Also, you set field 1 to REVERT to its previous value, even if the new value is valid.
import javax.swing.JPanel;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.text.NumberFormat;
import java.awt.GridLayout;
public class FormatEx2 extends JPanel {
private JFormattedTextField field1;
private JFormattedTextField field2;
public FormatEx2() {
super(new GridLayout(2, 2));
setUpField1();
setUpField2();
displayFields();
}
private void displayFields() {
add(new JLabel("field 1:"));
add(field1);
add(new JLabel("field 2:"));
add(field2);
}
private void setUpField1() {
NumberFormat format1 =
NumberFormat.getInstance();
field1 = new JFormattedTextField(format1);
field1.setValue(new Double(12.34));
field1.setFocusLostBehavior
(JFormattedTextField.REVERT);
}
private void setUpField2() {
NumberFormat format2 =
NumberFormat.getInstance();
field2 = new JFormattedTextField(format2);
field2.setValue(new Double(56.78));
field2.setFocusLostBehavior
(JFormattedTextField.COMMIT);
}
public static void main(String[] args) {
JFrame frame = new JFrame
("Number Format Demo");
frame.setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
frame.getContentPane().add(new FormatEx2());
frame.setVisible(true);
}
}
Compile and run the FormatEx2 application. Enter 98.76 in field 1, and press the tab key. The field reverts to 12.34. The second field, field 2, is set to COMMIT, so the commitEdit() method is invoked. In this case, even if you enter the string abcd in field 2 and press tab, the string is displayed as the new value of field 2. The default behavior used in the previous example (FormatEx1) is COMMIT_OR_REVERT. This commits the formatted value only if the text is a valid input.
There might be times when you want to restrict the input in a particular way. Perhaps you want the user to only enter digits or letters of the alphabet. The MaskFormatter class allows you to specify the format for string input. You can configure a new MaskFormatter when you instantiate it like this:
MaskFormatter formatter1 = new MaskFormatter("##.#%");
The argument to the constructor is the mask. Here are the characters that can be specified in the mask, and what they mean:
# any valid number
' the escape character
? any character
A anycharacter or number
* anything
U any character, but all lowercase letters become uppercase
L maps uppercase letters to lower case.
H specifies a hex character.
If you want to restrict input within a category, use the methods setValidCharacters() or setInvalidCharacters(). In the MaskFormatter example above, as the user enters numbers, the first two digits will appear to the left of the decimal point, and the third will appear to the right. Subsequent digits will not be visible. So if you enter 1234, you will see 12.3%.
Here is an example that uses this field masked by "##.#%", and another field in which a six-character string is converted so that successive characters go from uppercase to lowercase and back to uppercase.
import javax.swing.JPanel;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.text.MaskFormatter;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class FormatEx3 extends JPanel {
private JFormattedTextField field1;
private JFormattedTextField field2;
public FormatEx3() {
super(new GridLayout(2, 2));
setUpField1();
setUpField2();
displayFields();
}
private void displayFields() {
add(new JLabel("field 1 (00.0 - 99.9%:"));
add(field1);
add(new JLabel("field 2: (6-alphas)"));
add(field2);
}
private void setUpField1() {
MaskFormatter formatter1 = null;
try {
formatter1 = new MaskFormatter("##.#%");
} catch (java.text.ParseException exc) {
// need to handle exception
}
field1 = new JFormattedTextField(formatter1);
}
private void setUpField2() {
MaskFormatter formatter2 = null;
try {
formatter2 = new MaskFormatter("ULULUL");
} catch (java.text.ParseException exc) {
// need to handle exception
}
field2 = new JFormattedTextField(formatter2);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Number Format Demo");
frame.setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
frame.getContentPane().add(new FormatEx3());
frame.setVisible(true);
}
class FormatListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("Hi");
}
}
}
For more information on using JFormattedTextField see Formatted Text Fields.
WHEN ARE TWO STRINGS EQUAL?
Strings are treated differently by Java developers than other first class objects. You can initialize a new String using new:
String string = new String("hello"); //not recommended
You can also use the following syntax to accomplish almost the same thing:
String string = "hello";
This tip addresses the question "when are two Strings equal?" Because they are objects, you can always compare the values of two Strings using the equals() method. If s1 and s2 are two Strings with the same value, then s1.equals(s2) is true. The tricky part comes when you try to use == to compare s1 and s2. In this tip you will see when s1 == s2 should return true.
To start with, create two objects of type Double, two primitives of type double, and two objects of type String. Use the s1 = "hello" syntax for initializing the Strings.
class Equals {
public static void main(String[] args) {
Double object1 = new Double("7.2");
Double object2 = new Double("7.2");
System.out.println
("For Double objects both 7.2");
System.out.print("\t object1 == object2 is " );
System.out.println(object1 == object2);
System.out.print("\t object1.equals(object2) is ");
System.out.println(object1.equals(object2));
double prim1 = 7.2;
double prim2 = 7.2;
System.out.println
("For double primitives both 7.2");
System.out.print("\t prim1 == prim2 is " );
System.out.println(prim1 == prim2);
String string1 = "7.2";
String string2 = "7.2";
System.out.println("For Strings both 7.2");
System.out.print("\t string1 == string2 is " );
System.out.println(string1 == string2);
System.out.print("\t string1.equals(string2) is ");
System.out.println(string1.equals(string2));
}
}
When your run Equals, you get the following results:
For Double objects both 7.2
object1 == object2 is false
object1.equals(object2) is true
For double primitives both 7.2
prim1 == prim2 is true
For Strings both 7.2
string1 == string2 is true
string1.equals(string2) is true
As you would expect, object1.equals(object2) is true because they have the same value but object1 == object2 is false because they are different objects. Also, prim1 == prim2 is true because they have the same value (note that you cannot use equals() to compare two primitives). For the objects string1 and string 2, string1.equals(string2) is true because their values are the same. Perhaps it is a surprise that string1 == string2 is true. The Java Language Specification explains that "Literal strings within the same class in the same package represent references to the same String object."
The Language Specification explains that "String literals - or, more generally, strings that are the values of constant expressions - are 'interned' so as to share unique instances using the method String.intern."
On the surface, the next example seems to highlight the differences between initializing a String as you would initialize an object, and initializing a String as you would initialize a primitive. It actually demonstrates the difference between obtaining a String by using a String literal directly, and by calling a String constructor.
class NewEquals {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = "hello";
System.out.println("For new Strings s1 and s2");
System.out.print("\t s1 == s2 is ");
if (s1 == s2)
System.out.println("true");
else
System.out.println("false");
System.out.print("\t " + "s1.equals(s2) is ");
if (s1.equals(s2))
System.out.println("true");
else
System.out.println("false");
System.out.println("For Strings s1 and s3");
System.out.print("\t s1 == s3 is ");
if (s1 == s3)
System.out.println("true");
else
System.out.println("false");
System.out.print("\t " + "s1.equals(s3) is ");
if (s1.equals(s3))
System.out.println("true");
else
System.out.println("false");
}
}
Run NewEquals, and you will see these results:
For new Strings s1 and s2
s1 == s2 is false
s1.equals(s2) is true
For Strings s1 and s3
s1 == s3 is false
s1.equals(s3) is true
All three Strings are created with the same value, so equals() returns true for both comparisons. However, s1 and s2 are different objects. Despite being constructed with the same value, they are as different as the two Double objects were in the first example. This is why s1 == s2 is false. Similarly, s1 == s3 is also false.
You probably wouldn't be surprised by this result if it were not for the first example (Equals) where the two Strings returned true when compared with the == operator. It seems that these objects have the behavior expected of objects. The Equals example showed you that there is a pool for Strings. Multiple String variables can refer to the same String object. If they are set to equal string literals, they are guaranteed to do so.
The Language Specification guarantees that calling new will create a new object. That object has never been passed to String.intern. However, the value of a String literal is a String that has been passed String.intern, so it is different. The Language Specification does say that you can force a String into the common String pool using the intern() method, as shown in the following example:
class NewInternString {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = "hello";
s1 = s1.intern(); //this changes everything
System.out.println("For Strings s1 and s2");
System.out.print("\t s1 == s2 is " );
if (s1 == s2)
System.out.println("true");
else System.out.println("false");
System.out.print("\t " + "s1.equals(s2) is ");
if (s1.equals(s2))
System.out.println("true");
else System.out.println("false");
System.out.println("For Strings s1 and s3");
System.out.print("\t s1 == s3 is " );
if (s1 == s3)
System.out.println("true");
else System.out.println("false");
System.out.print("\t " + "s1.equals(s3) is ");
if (s1.equals(s3))
System.out.println("true");
else System.out.println("false");
}
}
Here are the results of running NewInternString:
For new Strings s1 and s2
s1 == s2 is false
s1.equals(s2) is true
For Strings s1 and s3
s1 == s3 is true
s1.equals(s3) is true
You can see that again the values are being reported as equal. That's because the equals() method returns true in both cases. After you force s1 into the constants pool you find that s1 == s3 is true. However, s2 is still not in the constants pool, so s1 == s2 is false.
In this tip you saw that the only safe way to test that Strings have the same value is with the equals() method. If you work with Strings that have the same value, then it might be worth interning them into the constants pool. That way you can make a quicker check using the == operator. In any case, you can see that you have to be careful before deciding to use == to check equivalence.
For more information on String comparisons see section 9.2 "String Comparisons" in The Java Programming Language Third Edition by Arnold, Gosling, and Holmes.
|
|
 |
 |
|
|
 |
 |
IMPORTANT: Please read our Licensing, Terms of Use, and Privacy policies:
http://developer.java.sun.com/berkeley_license.html
http://www.sun.com/share/text/termsofuse.html
Privacy Statement: Sun respects your online time and privacy (http://sun.com/privacy). You have received this based on your email preferences. If you would prefer not to receive this information, please follow the steps at the bottom of this message to unsubscribe.
Comments? Send your feedback on the Core Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet
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/developer/JDCTechTips/index.html
Copyright 2004 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/developer/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.
|