|
Tech Tips Archive
July 23, 2002
WELCOME to the Core Java Technologies Tech Tips (formerly the Java Developer Connection (JDC) Tech Tips), July 23, 2002. Here you'll get tips on using core Java technologies and APIs,
such as those in Java 2 Platform, Standard Edition (J2SE).
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 John Zukowski,
president of JZ Ventures, Inc.
CONVERTING NUMERIC ENTRIES
Numbers entered from the console into a computer are not entered
as their decimal value. Internally, a number entered from the
keyboard is represented by a Unicode value of the numerical
representation for the locale-specific character of that number.
In other words, if an American user enters the number 1 on an
English language system, the computer represents it as the
Unicode value 49. The Unicode value is the the American-English,
numerical representation of the character 1. This numerical
representation then maps to a physical representation of the
number 1, based on the font being used. At no point is this
character 1 thought of as the numerical value of 1.
There are two mechanisms in the standard Java libraries that you
can use to convert a user's "number" input into actual numeric
values. Which mechanism you use depends on your personal
preferences and desired feature support. For example, if you want
to convert the user entry "3.14" to the value 3.14, there is a
way to directly convert the string. Converting locale-specific
input, such as "$5,000.12" to the value 5000.12, requires a
little more work. You'll learn how to do both types of
conversions in this tip.
To convert from a string to a number, use the wrapper classes.
These classes offer parsing methods that do the conversion. Which
method you use depends on the data type of the intended result:
- String to byte --
Byte.parseByte(string)
- String to short --
Short.parseShort(string)
- String to int --
Integer.parseInt(string)
- String to long --
Long.parseLong(string)
- String to float --
Float.parseFloat(string)
- String to double --
Double.parseDouble(string)
The integer types (byte, short, int, and long) also offer an
ovloaded version of the associated parse method. The overloaded
version includes an input base as a second argument to the method.
So, for example, if the input is in base 2, you don't have to
manually calculate that "100" is 4.
- String to byte --
Byte.parseByte(string, base)
- String to short --
Short.parseShort(string, base)
- String to int --
Integer.parseInt(string, base)
- String to long --
Long.parseLong(string, base)
The following program demonstrates these two conversion
mechanisms. The program prompts for two numbers. When you run the
program, and respond to the prompts, the first number you enter
is converted to a double datatype. Be sure to try entering input
with scientific notation, too, as in "1.3E4" to indicate 13000.
The second prompt is for a hex value. Here, you can enter a hex
value such as "FFFFFF" to indicate 16777215 in base 10.
import java.io.*;
public class Input {
public static void main(String args[])
throws IOException {
InputStreamReader isr =
new InputStreamReader(System.in);
BufferedReader input = new BufferedReader(isr);
System.out.println("Enter a double: ");
String doubleString = input.readLine();
if (doubleString != null) {
try {
double d = Double.parseDouble(doubleString);
System.out.println("Double: " + d);
} catch (NumberFormatException e) {
System.err.println("Bad input: " +
doubleString);
}
}
System.out.println("Enter a hex value: ");
String hexString = input.readLine();
if (hexString != null) {
try {
long l = Long.parseLong(hexString, 16);
System.out.println("Converted Hex: " + l);
} catch (NumberFormatException e) {
System.err.println("Bad input: " + hexString);
}
}
}
}
|
Note: The previous example is for demonstration purposes only.
Under no circumstances should a properly created
internationalized program accept input from the user without
using the NumberFormat capabilities described below. Also, in a
similar way, under no circumstances should a text message be
hard-coded into a println statement. You should use a
ResourceBundle for maintaining locale-specific messages.
When you run the program, the interaction should look something
like this:
Enter a double:
1.3E4
Double: 13000.0
Enter a hex value:
FFFFFF
Hex: 16777215
|
If the input cannot be converted, the program throws a
NumberFormatException (which is indicated in this program
by "Bad input:").
To convert formatted input, use the NumberFormat class of the
java.text package. NumberFormat allows you to accept input in a
locale-specific way, while dealing with such things as currency
symbols, grouping separators (for example, the comma in the US or
the period in Germany), decimal separators (for example, the
period in the US or the comma in Germany), and percentages.
The NumberFormat class is abstract. It works by using the Factory
design pattern. You provide the details of the format you want.
NumberFormat then returns an instance of a subclass that
implements the desired input pattern. By using the returned
instance of NumberFormat, you can translate the requested input
format.
NumberFormat provides a number of methods that return a specific
format for the current default locale:
- Numbers --
NumberFormat.getNumberInstance()
- Currency --
NumberFormat.getCurrencyInstance()
- Percentages --
NumberFormat.getPercentInstance()
- Integers --
NumberFormat.getIntegerInstance()
If these methods don't return the format you want, you can define
the format yourself by creating an instance of the DecimalFormat
class.
If you want to support a locale other then the default, simply
pass the locale as an argument to the overloaded versions:
- Numbers --
NumberFormat.getNumberInstance(locale)
- Currency --
NumberFormat.getCurrencyInstance(locale)
- Percentages --
NumberFormat.getPercentInstance(locale)
- Integers --
NumberFormat.getIntegerInstance(locale)
After you get the necessary instance, you call the parse method
to translate the string to a number.
NumberFormat format =
NumberFormat.getNumberInstance();
Number number = format.parse(string);
You can also use NumberFormat to format how you want to display
numbers to users. Instead of using the parse method, you use
the format method.
NumberFormat format =
NumberFormat.getCurrencyInstance();
Number number = format.parse(string);
System.out.println(format.format(number));
To demonstrate, the following program prompts for a double value
and a currency amount. For the double value, you can enter an
integer, floating point number, or use scientific notation. For
the currency amount, and for the US locale used in the example
program, you need to start with a currency symbol (such as $).
However, the currency amount doesn't require a grouping separator
or decimal point. These characters are displayed in the output in
the appropriate places.
import java.io.*;
import java.text.*;
import java.util.*;
public class InputFormat {
public static void main(String args[])
throws IOException {
InputStreamReader isr =
new InputStreamReader(System.in);
BufferedReader input = new BufferedReader(isr);
System.out.println("Enter a double: ");
String doubleString = input.readLine();
if (doubleString != null) {
try {
NumberFormat format =
NumberFormat.getNumberInstance(Locale.US);
Number number = format.parse(doubleString);
System.out.println("Double: " +
number.doubleValue());
} catch (ParseException e) {
System.err.println("Bad input: " +
doubleString);
}
}
System.out.println("Enter a currency value: ");
String currencyString = input.readLine();
if (currencyString != null) {
try {
NumberFormat format =
NumberFormat.getCurrencyInstance(Locale.US);
Number number = format.parse(currencyString);
System.out.println("Currency: " +
format.format(number));
} catch (ParseException e) {
System.err.println("Bad input: " +
currencyString);
}
}
}
}
|
Although the Input and InputFormat programs function similarly,
there are some differences. The first difference is that parsing
with NumberFormat throws a ParseException instead of a
NumberFormatException. Another, and bigger, difference has to do
with the way parsing is done. With the different wrapper methods,
such as Integer.parseInt, parsing is attempted on the entire
string. If the string as a whole is invalid, the parsing fails.
That isn't how NumberFormat.parse works though. With NumberFormat, parsing fails (and a ParseException is thrown) only if there is an invalid leading character in the input.
To demonstrate this difference, enter "3.a" in response to the
prompt for a double value. The Input program will throw an
exception. By comparison, the InputFormat program will indicate
that the input was 3.0.
java Input
Enter a double:
3.a
Bad input: 3.a
java InputFormat
Enter a double:
3.a
Double: 3.0
|
You can use NumberFormat to progressively parse through pieces of
a string. There is a second version of NumberFormat.parse that
accepts a second parameter indicating a parse position. You can
use this version of the parse method to loop through a long
string and pull out individual numbers from it. The position is
indicated by the ParsePosition class, which automatically
positions itself at the point where the parsing ends. If parsing
fails due to an invalid character, the starting and ending
position will be the same. There is no ParseException thrown by
this form of the parse method.
The following program demonstrates progressively parsing through
a string. The program prompts for set of numbers. Each number in
the set must be separated by a single non-numeric character. The
program then parses through the set, and displays the individual
numbers:
import java.io.*;
import java.text.*;
public class InputPieces {
public static void main(String args[])
throws IOException {
InputStreamReader isr =
new InputStreamReader(System.in);
BufferedReader input = new BufferedReader(isr);
System.out.println(
"Enter space delimited set of numbers: ");
String bulkString = input.readLine();
if (bulkString != null) {
NumberFormat format =
NumberFormat.getNumberInstance();
ParsePosition position = new ParsePosition(0);
int beginIndex;
while ((beginIndex = position.getIndex())
< bulkString.length()) {
Number number =
format.parse(bulkString, position);
System.out.println(
"Number: " + number.doubleValue());
int endIndex = position.getIndex();
if (beginIndex == endIndex) {
System.err.println(
"Parsing error, position: " + endIndex);
} else {
position.setIndex(endIndex+1);
}
}
}
}
}
|
For input of "1e54,456/3E4 8" (where the separators are the e, /,
and space) the output is:
Number: 1.0
Number: 54456.0
Number: 30000.0
Number: 8.0
Although the getXXXInstance methods of NumberFormat can meet most
needs for formats to accept or display, it doesn't meet every
format need. For those cases, where the getXXXInstance methods of
NumberFormat can't meet the need, you can use the DecimalFormat
class. When you call a method such as
NumberFormat.getCurrencyInstance(), it creates a locale-specific
version of DecimalFormat based on the type you request. Using
DecimalFormat, you can also create a custom NumberFormat.
In general, creating a custom NumberFormat with DecimalFormat is
done for output purposes. For example, suppose you want to
display numbers with grouping separators (commas in the United
States) every two positions instead of every three, and always
with four digits after the decimal place. That would be a format
of "#,#0.0000", where each '0' indicates that a number should be
shown at the position. Each '#' also indicates that a number
should be shown at the position, but not to show anything if the
value is zero. The grouping separator automatically repeats to the
left, so a display format of "##,##,#0.0000" is not necessary.
Here's a program that demonstrates using the DecimalFormat class
to create a custom Number Format. The program prompts for a number
and an output format. Try running the program with different
numbers and different formatting characteristics.
import java.io.*;
import java.text.*;
public class InputPrompt {
public static void main(String args[])
throws IOException {
InputStreamReader isr =
new InputStreamReader(System.in);
BufferedReader input = new BufferedReader(isr);
System.out.println("Enter number: ");
String doubleString = input.readLine();
if (doubleString != null) {
try {
NumberFormat format =
NumberFormat.getNumberInstance();
Number number = format.parse(doubleString);
System.out.println("Enter format: ");
String formatString = input.readLine();
if (formatString != null) {
NumberFormat outputFormat =
new DecimalFormat(formatString);;
System.out.println("Output: " +
outputFormat.format(number));
}
} catch (ParseException e) {
System.err.println("Bad input: " +
doubleString);
}
}
}
}
|
Here's an example of what you should see:
Enter number:
1234.56
Enter format:
#,#0.0000
Output: 12,34.5600
|
For more information about using NumberFormat, see the
Using Predefined Formats section of the Java Tutorial. Also, see the Customizing Formats section of the Java Tutorial for more information on using DecimalFormat.
DISPLAYING MULTILINE TEXT
Suppose you want to create a label (either Swing JLabel or AWT
Label) that displays multiple lines of text in a GUI. How would
you create it? One way to do it is to create multiple labels,
each containing one line of text, and group the labels in a
vertical panel. Here's a program that does that:
import java.awt.*;
import javax.swing.*;
public class Multi1 extends JFrame {
public Multi1() {
super("Vertical JLabel Set");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Container content = getContentPane();
BoxLayout layout =
new BoxLayout(content, BoxLayout.Y_AXIS);
content.setLayout(layout);
String lines[] = {"Line 1", "Line 2" ,
"Line 3", "Line 4" , "Line 5"};
for (int i=0, n=lines.length; i<n; i++) {
content.add(new JLabel(lines[i]));
}
setSize(300, 200);
}
public static void main(String args[]) {
JFrame frame = new Multi1();
frame.show();
}
}
|
If you run the Multi1 program, you should see the following:
An easier approach to displaying multiline text in a Swing label
is to use the HTML feature of Swing text components. By prefixing
the text label of the component with <HTML>, the label switches
to HTML display mode. Then, by using HTML tags, such as <BR> and
<P>, or by simply using regular wordwrap at the end of a line,
you can easily display a Swing label with multiple lines of text.
import java.awt.*;
import javax.swing.*;
public class Multi2 extends JFrame {
public Multi2() {
super("HTML JLabel");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Container content = getContentPane();
String input =
"<HTML><P ALIGN=\"CENTER\">" +
"You're A Grand Old Flag,<BR>" +
"You're a high fly-ing flag,<BR>" +
"And for-ev-er, in peace, may you wave.<BR>" +
"You're the em-blem of<BR>" +
"the land I love,<BR>" +
"The home of the free and the brave.";
JLabel label = new JLabel(input);
content.add(label, BorderLayout.CENTER);
setSize(300, 200);
}
public static void main(String args[]) {
JFrame frame = new Multi2();
frame.show();
}
}
|
If you run the Multi2 program, you should see the following:
Probably a simpler way to display multiple lines of text in a GUI
is not with a label. Instead use a text area component. A text
area component is specifically designed to support the display of
multiple lines of text. Here's a program that uses a text area
component to display multiple lines of text:
import java.awt.*;
import javax.swing.*;
public class Multi3 extends JFrame {
public Multi3() {
super("Read-only JTextArea");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Container content = getContentPane();
String input =
"I pledge allegiance to the flag of " +
"the United States of America, and " +
"to the republic for which it stands, " +
"one nation, under God, indivisible " +
"with liberty and justice for all.";
JTextArea text = new JTextArea(input);
text.setEditable(false);
text.setLineWrap(true);
text.setWrapStyleWord(true);
/* text.setBackground(
(Color)UIManager.get("Label.background"));
text.setForeground(
(Color)UIManager.get("Label.foreground"));
text.setFont(
(Font)UIManager.get("Label.font"));
*/ JScrollPane pane = new JScrollPane(text);
content.add(pane, BorderLayout.CENTER);
setSize(300, 200);
}
public static void main(String args[]) {
JFrame frame = new Multi3();
frame.show();
}
}
|
If you run the Multi3 program, you should see the following:
Notice that both line wrap and word wrap style are enabled in the
Multi3 program. By enabling both, you don't have to manually
calculate what constitutes a line or worry about what font will
be used by the HTML viewer.
There are advantages and disadvantages to using a JTextArea (or
TextArea) for the display of multiline text. One disadvantage is
that the display of the read-only text component looks different
than the display of the label control. This is a minor
disadvantage, and it's something you can fix. Uncomment the six
lines of code in the middle of the Multi3 program to copy the
foreground, background, and font setting over to the JTextArea.
Recompile. Then run the program and see the difference. The
JTextArea will now look more like a JLabel.
An advantage of using a text area component is that you do not
have to calculate where each line breaks. Another advantage of
the text area component approach, is that you can select the
displayed message and copy the text to the system clipboard for
further processing. This is something that you can't easily do
with a label.
Swing components are usually sufficient for dealing with the
display of multiline text, but not always. For example, if you
want to create a custom editor, you'll probably need to draw
the multiline output yourself. In cases like this, there are
five classes available to assist you:
AttributedString
AttributedCharacterIterator
FontRenderContext
LineBreakMeasurer
TextLayout
These classes let you describe the text to draw, and measure how
much text will fit on each line. Use the AttributedString class
to contain the text. It allows different characters of the
text string to have different attributes. However in the simplest
case, you simply pass the quoted string to the constructor.
AttributedString attributedString =
new AttributedString("...");
To configure an attribute for the entire string, use the
addAttribute method. For instance, to change the font for an
entire string, specify:
Font font = ...
attributedString.addAttribute(
TextAttribute.FONT, font);
To configure an attribute for just a part of the content, you
pass in a beginning and ending index:
attributedString.addAttribute(
TextAttribute.FONT, font, startPosition,
endPosition);
The TextAttribute class of the java.awt.font package includes
constants for the different attributes you can set. These include
foreground and background colors, font, superscript, underline,
justification, and many others.
You only need to configure the attributes of the string once.
Then you need to draw the string. That's where the other classes
and interfaces help.
When you need to draw the text, you fetch an
AttributedCharacterIterator from the AttributedString so that the
system can pull out the individual characters of the string.
AttributedCharacterIterator characterIterator =
attributedString.getIterator();
FontRenderContext is used to ensure that the proper text sizes
are used in the drawing. You obtain the FontRenderContext from
the Graphics2D object passed into the paint method. You could
call the constructor for FontRenderContext, however doing so
wouldn't provide the correct measurements for the current graphic
display.
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
...
FontRenderContext fontRenderContext =
g2d.getFontRenderContext();
|
The LineBreakMeasurer class is used to determine how much text
fits on a line. More specifically, given an
AttributedCharacterIterator and FontRenderContext,
LineBreakMeasurer measures how much of the AttributedString fits
on each line. Each line is represented by a TextLayout.
int screenWidth = ...;
LineBreakMeasurer measurer =
new LineBreakMeasurer(characterIterator,
fontRenderContext);
while (measurer.getPosition() <
characterIterator.getEndIndex()) {
// Get line
TextLayout textLayout =
measurer.nextLayout(screenWidth);
...
}
|
That just leaves drawing the TextLayout, and there is a draw
method for just that. Between lines, you just need to increase
the y position to move down the screen.
textLayout.draw(g2d, x, y);
Putting all the pieces together produces the following program:
import java.awt.*;
import javax.swing.*;
import java.awt.font.*;
import java.text.*;
public class Multi4 extends JFrame {
String input =
"EDWARD by the grace of God, King of " +
"England, Lord of Ireland, and Duke of " +
"Guyan, to all Archbishops, Bishops, etc. " +
"We have seen the Great Charter of the " +
"Lord HENRY, sometimes King of England, " +
"our father, of the Liberties of England, " +
"in these words: ";
AttributedString attributedString;
public Multi4() {
super("Manual Painting");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Font font = (Font)UIManager.get("Label.font");
attributedString = new AttributedString(input);
attributedString.addAttribute(
TextAttribute.FONT, font);
Color color =
(Color)UIManager.get("Label.foreground");
attributedString.addAttribute(
TextAttribute.FOREGROUND, color);
setSize(300, 200);
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D)g;
// Watch the margins
Insets insets = getInsets();
int width = getSize().width -
insets.right - insets.left;
// Set the starting position to draw
int x = insets.left;
int y = insets.top;
// Get iterator for string
AttributedCharacterIterator characterIterator =
attributedString.getIterator();
// Get font context from graphics
FontRenderContext fontRenderContext =
g2d.getFontRenderContext();
// Create measurer
LineBreakMeasurer measurer =
new LineBreakMeasurer(characterIterator,
fontRenderContext);
while (measurer.getPosition() <
characterIterator.getEndIndex()) {
// Get line
TextLayout textLayout =
measurer.nextLayout(width);
// Move down to baseline
y += textLayout.getAscent();
// Draw line
textLayout.draw(g2d, x, y);
// Move down to top of next line
y += textLayout.getDescent() +
textLayout.getLeading();
}
}
public static void main(String args[]) {
JFrame frame = new Multi4();
frame.show();
}
}
|
If you run the Multi4 program, you should see the following:
For information about drawing more than text with the Java 2D API,
see the tutorial Displaying Graphics with Graphics2D.
IMPORTANT: Please read our Terms of Use and Privacy policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html
FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:
jdc-webmaster@sun.com
SUBSCRIBE/UNSUBSCRIBE
- To subscribe, go to the subscriptions 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 JDC Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
- COPYRIGHT
Copyright 2002 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
Core Java Technologies Tech Tips
July 23, 2002
Sun, Sun Microsystems, Java, Java Developer Connection, J2SE,
J2EE, and J2ME are trademarks or registered trademarks of Sun
Microsystems, Inc. in the United States and other countries.
|