Collected Java Practices
Collected Java Practices
Consolidated Topics
This compilation is intended for offline use, and for printing. It presents all topics on a single web page.
The content of the topics on javapractices.com is updated continually. Generated on Fri Sep 20 12:39:58
ADT 2013. See javapractices.com for the latest updates, and for further information.
Servlets and JSPs | Swing | Exceptions | Input-Output | Databases | Collections | Overriding Object Methods | Common Tasks |
Common Design Patterns | Common Practices | Inheritance | More Common Practices | Assertions | Constructors | Serialization |
Threads
Swing
A Swing App - StocksMonitor
Align menu items
Consider JGoodies Forms for layouts
Don't subclass JDialog or JFrame
Indicate table sort
Launch other applications
Look and Feel guidelines
Observers and listeners
Actions
Change theme
Don't bury dialogs
Filter table rows
Input dialogs
Layout Managers
Minimal Swing application
Preferences dialogs
1
Exceptions
Avoid @throws in javadoc
Be specific in throws clause
Checked versus unchecked exceptions
Exceptions and control flow
Javadoc all exceptions
Stack trace as String
Input-Output
Always close streams
Console input
Reading and writing binary files
Reading and writing text files
Databases
Business identifiers as String
Consider data layer tools
Consider wrapper classes for optional data
Data exception wrapping
Don't perform basic SQL tasks in code
Keep SQL out of code
Reduce database code duplication
Simplify database operations
Use template for transactions
Connection pools
Consider using standard SQL
Data access objects
Data is king
Encapsulate connections
Prefer PreparedStatement
Remember the basics of database design
Try pseudo-persistence for mock ups
Collections
Choosing the right Collection
Iterate without an index
Two ways of using Iterator
Use interface references to Collections
Encapsulate collections
Prefer Collections over older classes
Use for-each liberally
Use standard Collections
Implementing compareTo
Implementing hashCode
Never rely on finalize
Common Tasks
2
Arrays as String
Command line operations
Copy an array
Examine bytecode
Generate random numbers
Get size of object in memory
Internationalization
Measure application performance
Open file in native directory
Pattern-match lines of a file
Recovering resources
Replace a substring
Send an email
Command objects
Immutable objects
Model Objects
Private constructor
Template method
Wrapper (Decorator)
Common Practices
Avoid basic style errors
Beware of instanceof operator
Construct classes from the outside in
Don't declare local variables before use
Interface for constants
Minimize ripple effects
Nest classes with care
Separate public and private members
Tag or marker interfaces
Use @Override liberally
Use javadoc liberally
Use System.exit with care
Validate method arguments
Inheritance
Consider composition instead of subclassing
Overridable methods need special care
Clarifying method
Compile regular expressions once
Consider code generators
Conventional name for return value
Design by Contract
Extra space in argument list
Include 'from', exclude 'to'
Overloading can be tricky
Passwords never in clear text
Quote dynamic text when logging
Structs are occasionally useful
Try alternatives to ResourceBundle
Use a testing framework (JUnit)
Use boxing with care
Validate state with class invariants
Coding conventions
Conditional compile
Consider immutable forms for dates
Defensive copying
Don't use tab characters
Generating unique IDs
Multiple return statements
Package by feature, not layer
Prefer empty items to null ones
Self-encapsulate fields
Test using main method
Use a fake system clock
Use Ant for build scripts
Use enums to restrict arguments
Validation belongs in a Model Object
Assertions
Assert is for private arguments only
Assertions in general
Constructors
Avoid JavaBeans style of construction
Construct Object using class name
Constructors shouldn't call overridables
Copy constructors
Initializing fields to 0-false-null is redundant
Serialization
Implementing Serializable
Some classes need readResolve
Threads
Always shut down an ExecutorService
Data integrity first
Dump thread information
Launch thread is just another user thread
Perform N tasks in parallel
Query host for the number of processors
Remember the types of intrinsic lock
Stop threads through cooperation
Synchronized is implementation detail
Use finally to unlock
Avoid ThreadGroup
Document thread safety
Handle InterruptedException
Objects communicating across threads
Prefer modern libraries for concurrency
Read-write locks
Schedule periodic tasks
Synchronize access to mutable fields
Thread priorities are not portable
Here, the server detects the desired operation by simply examining the value of theoperation parameter.
However, this style doesn't work well when the application is multilingual, since thevalue attribute
varies according to language.
A simple workaround for this problem is to use a different name for eachSUBMIT control:
<input type='submit' name='operation_Add' value='Add'>
<input type='submit' name='operation_Change' value='Change'>
<input type='submit' name='operation_Delete' value='Delete'>
Here, the server will detect the desired operation by looking for the presence of certain parameter names,
and not by examining the value of a singleoperation parameter.
An application might define a naming convention to ease the extraction of the desired operation. Here's
an example method where request parameters whose names start withoperation_ are treated as
operation indicators. This method may be placed in a base action class, or in a utility class:
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import java.util.logging.*;
public final class SubmitOperation {
/**
* Determine the desired operation.
*
* Returns the name of the single submitted operation_X parameter, without
* the leading 'operation_'. For example, if a single parameter named
* operation_Add is present in aRequest, then this method will return
* 'Add'.
*
* This method depends on two conventions :
* <ul>
* <li>a naming convention, whereby the names of request parameters which
* denote an operation are in the style : 'operation_blah', 'operation_XYZ',
* and so on. That is, the parameter name starts with 'operation_'
* <li>at most one operation_X parameter can appear in aRequest
* (it may have none; in that case this method will return null).
* </ul>
*
* This method is needed only when the app is multilingual, and
* multiple SUBMIT buttons appear on a single page.
*/
String getOperation(HttpServletRequest aRequest){
String result = null;
String OPERATION = "operation_";
List<String> operationParamNames = new ArrayList<>();
Enumeration<String> allParamNamesEnum = aRequest.getParameterNames();
while (allParamNamesEnum.hasMoreElements()) {
String paramName = allParamNamesEnum.nextElement();
if (paramName.startsWith(OPERATION)) {
operationParamNames.add(paramName);
}
}
if (! operationParamNames.isEmpty()) {
if (operationParamNames.size() > 1) {
throw new IllegalArgumentException(
"Expecting at most one parameter starting with '" + OPERATION +
"'. Actually found : " + operationParamNames
);
}
String operationParamName = operationParamNames.get(0);
result = operationParamName.substring(OPERATION.length());
}
fLogger.fine("Operation : " + result);
return result;
}
// PRIVATE
private static final Logger fLogger = Logger.getLogger(
6
SubmitOperation.class.getName()
);
}
See Also :
Understand details of FORM tags
2000
Submit
When the user POSTs this form, the data should always be validated as strongly as possible by the
server. For instance, the Movie Decade should be checked against a fixed set of values, to ensure that
7
form has not been altered by a malicious user. If the Movie Decade is indeed checked in such a manner,
then it's not possible for the user to enter an arbitrary script as a value for the decade. If such strong
checks are performed, then no special precautions against XSS attacks are needed.
The Movie Title is a different story, however. Since it's free-form user input, it's not possible to tightly
constrain the Movie Title . The server cannot perform tight validation checks on its content, since there
is no 'whitelist' of allowed values.
In this case, the user may indeed enter a script. To prevent the script from being activated when rendered
in a page as regular data, special care must be taken: any special characters in free-form user input must
be escaped. The Open Web App Security Project recommends that you escape these 12 characters :
Character Encoding
<
<
>
>
&
&
"
"
'
'
(
)
#
%
;
+
-
for operations without any side-effects (listings, reports, search operations), use either a form with
method='GET' or a link
for each user log-in, create a random, hard-to-guess token, and store it in the session. This token
(also called a nonce) is injected as a hidden parameter into each form served by your application.
For each POST ed form, the server verifies that the hidden parameter is present, and has the expected
value for the current session. All of this has the intent of answering a simple question: 'Did this
POSTed form really come originally from the legitimate server, or from some unknown third
party?'
specify the content-type of the response as an HTTP header. When using form tokens, the
content-type lets tools know when you are serving HTML - if your are serving plain text or
XML (which doesn't contain a form), then there's no need to inject form tokens.
Your web app framework should assist you in defending against CSRF attacks. With servlets, it's
common to provide a Filter to generate and validate the special form token.
See Also :
Manage sessions closely
Beware of doubly escaped ampersands
Prefer PreparedStatement
independence of browser settings - if the user's browser has cookies disabled, then the session
will be implemented using URL rewriting, as a backup, if desired.
If you decide to use a Cookie directly, then care should be exercised that:
it doesn't represent a security risk by exposing sensitive user data
the case of disabled cookies is acceptable in some way
Note as well that the Open Web App Security Project says that 'Remember Me' cookies are a security
risk.
See Also :
Manage sessions closely
According to the Open Web App Security Project, URL rewriting has significant security risks. The
general idea is that since the session id appears in the URL, it may be easily seen by third parties:
end users often copy and paste such links without knowing the attached session id compromises
their security
server log files usually record the 'Referer' header, which will record session ids in the log
Third-party access to session id's simply means that private user information is wide open to attack. Thus,
many argue that URL rewriting is a dangerous practice, and should be avoided. If cookies are used
instead, then the session id does not appear in the URL.
It's possible that some web sites may use cookies to track user browsing patterns. As a result, some users
turn off cookies in an attempt to protect their privacy. However, given the seriousness of the above
security issue, many would argue that turning off cookies is actually much worse for user privacy. That is,
the risk of compromising personal data through session hijacking seems to far outweigh concerns about
tracking personal browsing patterns.
Options for managing URL rewriting include :
disabling them at the server level.
disabling them at the application level. An attractive option is a Servlet filter. The filter wraps the
response object with an alternate version, which changes response.encodeURL(String) and
related methods into no-operations. (The WEB4J tool includes such a filter.)
In the case of public web sites, you will need to decide if requiring browsers to keep cookies enabled is
acceptable in each case.
See Also :
Emit flexible URLs
Always maintain HttpSessions
Manage sessions closely
11
In the case of a temporary redirection, another style can be used instead, if desired:
aResponse.sendRedirect("www.javapractices.com/source/SourceAction.do");
static page can indeed perform a redirect. However, that style seems to be slower. As well, it forces the
quick display of an intermediate page before the "real" one, which seems to be a less pleasing experience
for the user.
The javapractices.com site uses a redirection Controller to serve its home page. Here are the relevant
entries in its web.xml:
<servlet>
<servlet-name>RedirectWelcomeFile</servlet-name>
<description>
Redirects directory requests to the home page Action.
</description>
<servlet-class>hirondelle.jp.redirect.WelcomeFileController</servlet-class>
<init-param>
<param-name>Destination</param-name>
<param-value>http://www.javapractices.com/home/HomeAction.do</param-value>
<description>
The URL of the home page, as an Action.
</description>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>RedirectWelcomeFile</servlet-name>
<url-pattern>/welcome</url-pattern>
</servlet-mapping>
<!-- the welcome-file points to the servlet, not to a file -->
<!-- note the slash does not appear here -->
<welcome-file-list>
<welcome-file>welcome</welcome-file>
</welcome-file-list>
The actual Controller class is fairly simple. Note that it reads in the Destination setting from web.xml :
package hirondelle.jp.redirect;
import
import
import
import
import
import
import
import
hirondelle.web4j.util.Util;
java.io.IOException;
java.util.logging.Logger;
javax.servlet.ServletConfig;
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
/**
* Redirect requests for the home page to a specific Action.
*
* <P>Configuration in web.xml :<br>
* This Controller servlet is mapped to '/welcome', and a corresponding welcome-file
entry
* is listed as 'welcome', without the leading slash.
*/
public final class WelcomeFileController extends HttpServlet {
@Override public void init(ServletConfig aConfig) throws ServletException {
super.init(aConfig);
fLogger.config("WelcomeFile Controller - starting.");
fDestination = aConfig.getInitParameter("Destination");
if ( ! Util.textHasContent(fDestination) ) {
fLogger.severe("Destination URL needed, but not configured in web.xml.");
}
}
@Override public void destroy(){
fLogger.config("WelcomeFile Controller - ending.");
}
@Override protected void doGet(
HttpServletRequest aRequest, HttpServletResponse aResponse
) throws ServletException, IOException {
fLogger.fine("Redirecting directory request to new location : " + fDestination);
aResponse.setContentType("text/html");
aResponse.sendRedirect(fDestination);
13
}
// PRIVATE //
private String fDestination;
private static final Logger fLogger = Util.getLogger(WelcomeFileController.class);
}
See Also :
Forward versus redirect
See Also :
Always maintain HttpSessions
Prefer JSTL tags
Manage sessions closely
Beware of URL rewriting
14
java.net.URLEncoder;
java.io.UnsupportedEncodingException;
java.text.CharacterIterator;
java.text.StringCharacterIterator;
java.util.regex.Pattern;
java.util.regex.Matcher;
import
import
import
import
import
import
hirondelle.web4j.security.SafeText;
hirondelle.web4j.ui.translate.Text;
hirondelle.web4j.ui.translate.Tooltips;
hirondelle.web4j.ui.translate.TextFlow;
hirondelle.web4j.ui.tag.Populate;
hirondelle.web4j.database.Report;
/**
Convenience methods for escaping special characters related to HTML, XML,
and regular expressions.
<P>To keep
characters
don't need
shouldn't
15
}
else if (character == '\t') {
addCharEntity(9, result);
}
else if (character == '!') {
addCharEntity(33, result);
}
else if (character == '#') {
addCharEntity(35, result);
}
else if (character == '$') {
addCharEntity(36, result);
}
else if (character == '%') {
addCharEntity(37, result);
}
else if (character == '\'') {
addCharEntity(39, result);
}
else if (character == '(') {
addCharEntity(40, result);
}
else if (character == ')') {
addCharEntity(41, result);
}
else if (character == '*') {
addCharEntity(42, result);
}
else if (character == '+') {
addCharEntity(43, result);
}
else if (character == ',') {
addCharEntity(44, result);
}
else if (character == '-') {
addCharEntity(45, result);
}
else if (character == '.') {
addCharEntity(46, result);
}
else if (character == '/') {
addCharEntity(47, result);
}
else if (character == ':') {
addCharEntity(58, result);
}
else if (character == ';') {
addCharEntity(59, result);
}
else if (character == '=') {
addCharEntity(61, result);
}
else if (character == '?') {
addCharEntity(63, result);
}
else if (character == '@') {
addCharEntity(64, result);
}
else if (character == '[') {
addCharEntity(91, result);
}
else if (character == '\\') {
addCharEntity(92, result);
}
else if (character == ']') {
addCharEntity(93, result);
}
else if (character == '^') {
addCharEntity(94, result);
}
else if (character == '_') {
addCharEntity(95, result);
}
else if (character == '`') {
addCharEntity(96, result);
}
else if (character == '{') {
addCharEntity(123, result);
17
}
else if (character == '|') {
addCharEntity(124, result);
}
else if (character == '}') {
addCharEntity(125, result);
}
else if (character == '~') {
addCharEntity(126, result);
}
else {
//the char is not a special one
//add it to the result as is
result.append(character);
}
character = iterator.next();
}
return result.toString();
}
/**
Escape all ampersand characters in a URL.
<P>Replaces all <tt>'&'</tt> characters with <tt>'&'</tt>.
<P>An ampersand character may appear in the query string of a URL.
The ampersand character is indeed valid in a URL.
<em>However, URLs usually appear as an <tt>HREF</tt> attribute, and
such attributes have the additional constraint that ampersands
must be escaped.</em>
<P>The JSTL <c:url> tag does indeed perform proper URL encoding of
query parameters. But it does not, in general, produce text which
is valid as an <tt>HREF</tt> attribute, simply because it does
not escape the ampersand character. This is a nuisance when
multiple query parameters appear in the URL, since it requires a little
extra work.
*/
public static String forHrefAmpersand(String aURL){
return aURL.replace("&", "&");
}
/**
Synonym for <tt>URLEncoder.encode(String, "UTF-8")</tt>.
<P>Used to ensure that HTTP query strings are in proper form, by escaping
special characters such as spaces.
<P>It is important to note that if a query string appears in an <tt>HREF</tt>
attribute, then there are two issues - ensuring the query string is valid HTTP
(it is URL-encoded), and ensuring it is valid HTML (ensuring the
ampersand is escaped).
*/
public static String forURL(String aURLFragment){
String result = null;
try {
result = URLEncoder.encode(aURLFragment, "UTF-8");
}
catch (UnsupportedEncodingException ex){
throw new RuntimeException("UTF-8 not supported", ex);
}
return result;
}
/**
Escape characters for text appearing as XML data, between tags.
<P>The following characters are replaced with corresponding character entities :
<table border='1' cellpadding='3' cellspacing='0'>
<tr><th> Character </th><th> Encoding </th></tr>
<tr><td> < </td><td> < </td></tr>
<tr><td> > </td><td> > </td></tr>
<tr><td> & </td><td> & </td></tr>
<tr><td> " </td><td> "</td></tr>
<tr><td> ' </td><td> '</td></tr>
</table>
<P>Note that JSTL's {@code <c:out>} escapes the exact same set of
18
result.append("\\r");
}
else if(character == '\t'){
result.append("\\t");
}
else {
//the char is not a special one
//add it to the result as is
result.append(character);
}
character = iterator.next();
}
return result.toString();
}
/**
Return <tt>aText</tt> with all <tt>'<'</tt> and <tt>'>'</tt> characters
replaced by their escaped equivalents.
*/
public static String toDisableTags(String aText){
final StringBuilder result = new StringBuilder();
final StringCharacterIterator iterator = new StringCharacterIterator(aText);
char character = iterator.current();
while (character != CharacterIterator.DONE ){
if (character == '<') {
result.append("<");
}
else if (character == '>') {
result.append(">");
}
else {
//the char is not a special one
//add it to the result as is
result.append(character);
}
character = iterator.next();
}
return result.toString();
}
/**
Replace characters having special meaning in regular expressions
with their escaped equivalents, preceded by a '\' character.
<P>The escaped characters include :
<ul>
<li>.
<li>\
<li>?, * , and +
<li>&
<li>:
<li>{ and }
<li>[ and ]
<li>( and )
<li>^ and $
</ul>
*/
public static String forRegex(String aRegexFragment){
final StringBuilder result = new StringBuilder();
final StringCharacterIterator iterator =
new StringCharacterIterator(aRegexFragment)
;
char character = iterator.current();
while (character != CharacterIterator.DONE ){
/*
All literals need to have backslashes doubled.
*/
if (character == '.') {
result.append("\\.");
}
else if (character == '\\') {
result.append("\\\\");
}
else if (character == '?') {
result.append("\\?");
}
else if (character == '*') {
20
result.append("\\*");
}
else if (character == '+') {
result.append("\\+");
}
else if (character == '&') {
result.append("\\&");
}
else if (character == ':') {
result.append("\\:");
}
else if (character == '{') {
result.append("\\{");
}
else if (character == '}') {
result.append("\\}");
}
else if (character == '[') {
result.append("\\[");
}
else if (character == ']') {
result.append("\\]");
}
else if (character == '(') {
result.append("\\(");
}
else if (character == ')') {
result.append("\\)");
}
else if (character == '^') {
result.append("\\^");
}
else if (character == '$') {
result.append("\\$");
}
else {
//the char is not a special one
//add it to the result as is
result.append(character);
}
character = iterator.next();
}
return result.toString();
}
/**
Escape <tt>'$'</tt> and <tt>'\'</tt> characters in replacement strings.
<P>Synonym for <tt>Matcher.quoteReplacement(String)</tt>.
<P>The following methods use replacement strings which treat
<tt>'$'</tt> and <tt>'\'</tt> as special characters:
<ul>
<li><tt>String.replaceAll(String, String)</tt>
<li><tt>String.replaceFirst(String, String)</tt>
<li><tt>Matcher.appendReplacement(StringBuffer, String)</tt>
</ul>
<P>If replacement text can contain arbitrary characters, then you
will usually need to escape that text, to ensure special characters
are interpreted literally.
*/
public static String forReplacementString(String aInput){
return Matcher.quoteReplacement(aInput);
}
/**
Disable all <tt><SCRIPT></tt> tags in <tt>aText</tt>.
<P>Insensitive to case.
*/
public static String forScriptTagsOnly(String aText){
String result = null;
Matcher matcher = SCRIPT.matcher(aText);
result = matcher.replaceAll("<SCRIPT>");
matcher = SCRIPT_END.matcher(result);
result = matcher.replaceAll("</SCRIPT>");
return result;
}
21
// PRIVATE //
private EscapeChars(){
//empty - prevent construction
}
private static final Pattern SCRIPT = Pattern.compile(
"<SCRIPT>", Pattern.CASE_INSENSITIVE
);
private static final Pattern SCRIPT_END = Pattern.compile(
"</SCRIPT>", Pattern.CASE_INSENSITIVE
);
private static void addCharEntity(Integer aIdx, StringBuilder aBuilder){
String padding = "";
if( aIdx <= 9 ){
padding = "00";
}
else if( aIdx <= 99 ){
padding = "0";
}
else {
//no prefix
}
String number = padding + aIdx.toString();
aBuilder.append("&#" + number + ";");
}
}
See Also :
Replace a substring
Beware of doubly escaped ampersands
22
In general, a forward should be used if the operation can be safely repeated upon a browser reload of the
resulting web page; otherwise, redirect must be used. Typically, if the operation performs an edit on the
datastore, then a redirect, not a forward, is required. This is simply to avoid the possibility of
inadvertently duplicating an edit to the database.
More explicitly (in terms of common SQL operations) :
for SELECT operations, use a forward
for INSERT, UPDATE, or DELETE operations, use a redirect
In HTML, a <FORM> tag can either GET or POST its data. In this context, a GET corresponds to a SELECTthen-forward, and a POST corresponds to an edit-then-redirect.
It's strongly recommended that forms for the input of search criteria should use GET , while forms for
editing database records should use POST .
The most common symptom of not using forward/redirect properly is a warning message in a browser,
asking the user if they really wish to POST their form data a second time.
Example
This example is after the style of the WEB4J Controller class. The important methods of the Servlet API
are:
ServletRequest.getRequestDispatcher(String)
RequestDispatcher.forward(request, response)
HttpServletResponse.sendRedirect(String)
import
import
import
import
import
import
import
java.io.IOException;
javax.servlet.*;
javax.servlet.http.*;
hirondelle.web4j.action.Action;
hirondelle.web4j.action.ResponsePage;
hirondelle.web4j.request.RequestParser;
hirondelle.web4j.model.BadRequestException;
}
else {
forward(responsePage, aRequest, aResponse);
}
}
catch (BadRequestException ex){
//..elided
//use Response.sendError()
}
catch (Throwable ex) {
//..elided
//use Response.sendError()
}
}
// PRIVATE //
private void redirect(
ResponsePage aDestinationPage, HttpServletResponse aResponse
) throws IOException {
String urlWithSessionID = aResponse.encodeRedirectURL(aDestinationPage.toString());
aResponse.sendRedirect( urlWithSessionID );
}
private void forward(
ResponsePage aResponsePage, HttpServletRequest aRequest, HttpServletResponse
aResponse
) throws ServletException, IOException {
RequestDispatcher dispatcher =
aRequest.getRequestDispatcher(aResponsePage.toString());
dispatcher.forward(aRequest, aResponse);
}
}
See Also :
Command objects
A Web App Framework - WEB4J
Consider Controllers for redirects
24
See Also :
Use Model-View-Controller framework
Package by feature, not layer
Validation belongs in a Model Object
See Also :
25
See Also :
Web usability guidelines
Measure web app performance
Strings into more meaningful objects in the problem domain. Typically, this means performing two kinds
of operations:
translating Strings into Integer, BigDecimal , and so on
grouping such translated items into complete Model Objects
These operations will vary depending on the framework you're using.
Example
This example is taken from an application built with the WEB4J framework.
/** Build a Model Object from request parameters. */
public final class RestoAction extends ActionTemplateListAndEdit {
public static final RequestParameter RESTO_ID =
RequestParameter.withLengthCheck("Id");
public static final RequestParameter NAME = RequestParameter.withLengthCheck("Name");
public static final RequestParameter LOCATION =
RequestParameter.withLengthCheck("Location");
public static final RequestParameter PRICE =
RequestParameter.withLengthCheck("Price");
public static final RequestParameter COMMENT =
RequestParameter.withLengthCheck("Comment");
protected void validateUserInput() {
try {
ModelFromRequest builder = new ModelFromRequest(getRequestParser());
fResto = builder.build(Resto.class, RESTO_ID, NAME, LOCATION, PRICE, COMMENT);
}
catch (ModelCtorException ex){
//displays error message to end user
addError(ex);
}
}
//..many items elided
//target Model Object, built from user input
private Resto fResto;
}
In this case, the parsing of raw HTTP request parameters is shared between these classes:
RequestParameter represents the raw underlying parameters accepted by the Action
RequestParser defines default policies for translating single request parameters into String, Date ,
Boolean , Integer , and BigDecimal objects, and Collection s thereof.
ModelFromRequest translates sets of request parameters into Model Objects. While a
RequestParser returns single "building block" objects ( Integer , Date , etc.), ModelFromRequest
goes a step further, and returns an entire Model Object, which more or less encapsulates a number
of "building block" objects.
Validation and parsing of HTTP request parameters are related topics. See the Repel invalid requests
topic for further information.
See Also :
Repel invalid requests
Command objects
Model Objects
A Web App Framework - WEB4J
27
Pre-populate forms
Pre-populating form elements is a very common and important task in a web application. If prepopulation is implemented effectively, then a web app becomes both simpler and more robust.
Forms are used for two kinds of operations: editing the database, and defining search criteria.
When editing the database, there are two use cases for forms:
the "add" use case, where a new record is added to the datastore - here, input items are initially
blank, or have default initial values set in their static HTML.
the "change" use case, where an already existing record is to be edited by the user - here, input
items are not initially blank, but are dynamically pre-populated with the current values fetched
from the database.
In both cases, the user makes entries in the form, and then submits it for processing, using a POST . If an
error is detected, then there is a "return to sender" operation, and the HTML form is redisplayed, with all
its items dynamically pre-populated, showing their most recently entered values. Appropriate error
messages are also displayed.
(For forms used for input of search criteria, the behavior is similar to the "add" use case described above,
with the important difference that the form uses GET instead of POST .)
How can this behavior be implemented most effectively?
One method uses a single custom Populate tag, and is guided by the idea that adding pre-population to a
form should have an absolutely minimal affect on the markup used in the case of a regular, static HTML
form. This seems very effective, since
web page authors do not need to learn anything new, since forms remain almost exactly as they are
in the static case
it radically minimizes the effort needed to implement pre-population
it reuses request parameters which are already present and already in the form needed for ultimate
display
it couples HTML forms to the details of a domain object only through a simple naming convention,
which maps the names of input controls to the getXXX methods of the domain object
implementations of domain classes need to handle only the case of valid data, so they become
smaller and simpler, a class invariant is easy to define, and they can be immutable objects
An inferior style, commonly seen in frameworks such as Struts and Java Server Faces, replaces the
standard HTML tags such as <INPUT> and <SELECT> with a parallel collection of custom tags. Such a
style has these disadvantages:
the form needs radical alteration from the style used in a simple, static HTML form
any prototype written in HTML must be either discarded or altered radically
the web page author is forced to learn the details of multiple custom tags, which are different for
each framework
if the framework is changed, then another set of custom tags must be learned
pre-population is more of a programming task than a rendering task, but in this style the page
author is mainly responsible for its implementation
Example
28
The web4j framework implements such a Populate tag. Here, a JSP uses the <w:populate> tag to wrap
the controls of a <FORM> tag that may need dynamic population. Note how the <w:populate> tag
simply wraps the body of the form, and represents an absolutely minimal change to the style used in a
simple, static HTML form.
<w:populate using="itemForEdit">
<form action='MemberAction.do' method="post">
<input name="Id" type="hidden">
<table align="center">
<tr>
<td><label>Name</label> *</td>
<td><input name="Name" type="text"></td>
</tr>
<tr>
<td><label>Is Active?</label></td>
<td><input name="Is Active" type="checkbox" value="true"></td>
</tr>
<tr>
<td><label>Disposition</label></td>
<td>
<select name="Disposition">
<option> </option>
<c:forEach var="item" items="${dispositions}">
<option value="${item.id}">${item}</option>
</c:forEach>
</select>
</td>
</tr>
<tr>
<td align="center" colspan=2>
<input type="submit" value="add.edit.button">
</td>
</tr>
</table>
<tags:hiddenOperationParam/>
</form>
</w:populate>
See Also :
Prefer JSTL tags
A Web App Framework - WEB4J
<td>${restaurant.price}</td>
<td align="center">${restaurant.comment}</td>
</tr>
</c:forEach>
...
System Properties (${fn:length(systemProperties)})
<c:forEach var="entry" items="${systemProperties}">
<b>${entry.key}</b> = ${entry.value}
</c:forEach>
<jsp:useBean id="now" class="java.util.Date" />
Copyright <fmt:formatDate value="${now}" pattern="yyyy" />
See Also :
Emit flexible URLs
Always maintain HttpSessions
As usual, such a policy should be defined in one place, if possible (for example, in a template page).
Server
In principle, the browser should respond to the server by including the character encoding it has already
received from the server. In practice, however, browsers do not do a very good job at this. So, even
though a JSP has indicated the character encoding, it's likely a good practice to "reset" the character
encoding of the request, using, for example :
30
request.setCharacterEncoding("UTF-8");
AController could perform this for every incoming request, perhaps using a value configured
inweb.xml. This method must be called early in processing, before any parameter values are retrieved.
For reference, the servlet API has these methods for managing character encoding :
ServletRequest.getCharacterEncoding
ServletRequest.setCharacterEncoding
ServletResponse.setLocale
ServletResponse.setContentType
Database
The database has a character encoding as well. Please consult your database documentation for further
information.
Stated Encoding Must Match Actual Encoding
It's important to note that such encoding settings don't define the encoding as such, in the sense that they
don't define or change how the text is actually represented as bytes. Rather, they act simply as advice to
tools, by stating what the encoding is supposed to be. Of course, it's an error to advise a tool that the
encoding is X when it's actually Y.
A good example of this is the encoding of Java Server Pages. If a JSP is saved as ISO-8859-1, and its
page directive states that its encoding is UTF-8, then that's a mistake. Such mistakes are very easy to
make, since the error is not detected until later, when you see weird characters in your web page. Thus,
you need to pay attention to which encoding is in effect when you save such files on your system.
Unfortunately, many systems are not set up to use UTF-8 as the default.
See Also :
Beware of Byte Order Marks
Prevent self-linking
It's often the case that a web page links to itself. This can be frustrating for the user, especially if the page
takes a long time to load.
Instead of self-linking, a page can suppress links to itself, and at the same time highlight the link text in
some way, to clearly indicate the current page. The user is able to painlessly answer the common question
"Where am I on this site?", at the same time as they are prevented from following a link that is usually
useless. Such a style clearly has more compassion for the user's experience.
There are two ways to avoid self-linking in a set of navigation links :
copy and paste the set of navigation links across all pages, and manually alter each possible
occurrence of self-linking.
define the set of navigation links in one place, and create some means of dynamically suppressing
self-linking when generating each page. This is the preferred style, since it defines the menu in a
single place.
For the dynamic style, there is no standard way to define how such links are suppressed. An
implementation might rely on a naming convention, for example, to identify self-linking items that
31
See Also :
Web usability guidelines
A Web App Framework - WEB4J
Example
Here is part of the Controller from the WEB4J framework. It delegates most of its work to:
a RequestParser , a higher level wrapper around the underlying request
an implementation of an Action interface
a ResponsePage, to represent the final page served to the user
an ApplicationFirewall , to verify basic sanity of the request
a TroubleTicket , to gather diagnostic information, and email it to the webmaster
/**
* Controller that delegates to various framework classes.
*/
public class Controller extends HttpServlet {
//..elided
/** Call {@link #processRequest}. */
@Override public final void doGet(
HttpServletRequest aRequest, HttpServletResponse aResponse
) throws ServletException, IOException {
processRequest(aRequest, aResponse);
}
/** Call {@link #processRequest}. */
@Override public final void doPost(
HttpServletRequest aRequest, HttpServletResponse aResponse
) throws ServletException, IOException {
processRequest(aRequest, aResponse);
}
/**
* Handle all HTTP <tt>GET</tt> and <tt>POST</tt> requests.
*
* <P>This method can be overridden, if desired. Most applications will not need
* to override this method.
*
* <P>Operations include :
* <ul>
* <li>set the request character encoding to the value configured in <tt>web.xml</tt>
* <li>get an instance of {@link RequestParser}
* <li>get its {@link Action}, and execute it
* <li>perform either a forward or a redirect to its
* {@link hirondelle.web4j.action.ResponsePage}
* <li>if an unexpected problem occurs, create a {@link TroubleTicket}, log it, and
* email it to the webmaster email address configured in <tt>web.xml</tt>
* <li>if the response time exceeds a configured threshold, build a
* {@link TroubleTicket}, log it, and email it to the webmaster address configured
* in <tt>web.xml</tt>
* </ul>
*/
protected void processRequest(
HttpServletRequest aRequest, HttpServletResponse aResponse
) throws ServletException, IOException {
Stopwatch stopwatch = new Stopwatch();
stopwatch.start();
RequestParser requestParser = RequestParser.getInstance(aRequest, aResponse);
try {
Action action = requestParser.getWebAction();
ApplicationFirewall appFirewall = BuildImpl.forApplicationFirewall();
appFirewall.doHardValidation(action, requestParser);
ResponsePage responsePage = action.execute();
if ( responsePage.getIsRedirect() ) {
redirect(responsePage, aResponse);
}
else {
forward(responsePage, aRequest, aResponse);
}
}
catch (BadRequestException ex){
33
if( Util.textHasContent(ex.getErrorMessage()) ){
aResponse.sendError(ex.getStatusCode(), ex.getErrorMessage());
}
else {
aResponse.sendError(ex.getStatusCode());
}
}
catch (Throwable ex) {
//Bugs OR rare conditions, for example datastore failure
logAndEmailSeriousProblem(ex, aRequest);
aResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
stopwatch.stop();
if ( stopwatch.toValue() >= fPOOR_PERFORMANCE_THRESHOLD ) {
logAndEmailPerformanceProblem(stopwatch.toValue(), aRequest);
}
}
/**
* Inform the webmaster of an unexpected problem with the deployed application.
*
* <P>Typically called when an unexpected <tt>Exception</tt> occurs in
* {@link #processRequest}. Uses {@link TroubleTicket#mailToWebmaster}.
*
* <P>Also, stores the trouble ticket in application scope, for possible
* later examination.
*/
protected final void logAndEmailSeriousProblem (
Throwable ex, HttpServletRequest aRequest
) throws AppException {
TroubleTicket troubleTicket = new TroubleTicket(ex, aRequest);
fLogger.severe("TOP LEVEL CATCHING Throwable");
fLogger.severe( troubleTicket.toString() );
log("SERIOUS PROBLEM OCCURRED.");
log( troubleTicket.toString() );
aRequest.getSession().getServletContext().setAttribute(
MOST_RECENT_TROUBLE_TICKET, troubleTicket
);
troubleTicket.mailToWebmaster();
}
/**
* Inform the webmaster of a performance problem.
*
* <P>Called only when the response time of a request is above the threshold
* value configured in <tt>web.xml</tt>.
*
* <P>Builds a <tt>Throwable</tt> with a description of the problem, then creates and
34
See Also :
Use Model-View-Controller framework
Parse parameters into domain objects
Command objects
Send trouble-ticket emails
A Web App Framework - WEB4J
35
It's useful to note that you don't have to specify two different pages.
For example:
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/Login.jsp</form-login-page>
<form-error-page>/Login.jsp?Retry=True</form-error-page>
</form-login-config>
</login-config>
That is, the same page can be reused for login errors. In the presence of the Retry parameter, the
Login.jsp will display a simple error message.
Here is a snippet from a Login.jsp which uses this style:
<form method="POST" action='<%= response.encodeURL("j_security_check") %>'>
<table align="center">
<c:if test='${not empty param["Retry"]}'>
<tr>
<td colspan='2' align='center'><b>Please try again.</b></td>
</tr>
<tr>
<td> </td>
</tr>
</c:if>
<tr>
<td><label>Name</label></td>
<td><input type="text" name="j_username"></td>
</tr>
<tr>
<td><label>Password</label></td>
<td><input type="password" name="j_password"></td>
</tr>
<tr align="center">
<td colspan="2"><input type="submit" value="Login"></td>
</tr>
</table>
</form>
For security reasons, many recommend not giving specific error information. For example, stating
explicitly that the password is incorrect is undesirable, since that information is useful to hackers.
For similar reasons, when displaying an error it's likely best not to repeat the user's original input, and to
leave the login form blank.
Example
Here's an example of a utility class which sends simple emails. It can also validate an email address. It
uses an entry in web.xml for an SMTP host name.
import javax.servlet.ServletConfig;
import java.util.Properties;
import
import
import
import
import
import
import
javax.mail.Session;
javax.mail.Transport;
javax.mail.Message;
javax.mail.MessagingException;
javax.mail.internet.InternetAddress;
javax.mail.internet.AddressException;
javax.mail.internet.MimeMessage;
/**
* Send simple email from the webmaster to a receiver.
*
* <P>The emails sent by this class include sender, receiver, subject, and
* body. No features such as attachments are supported by this class. If more
* flexibity is needed, please use {@link javax.mail} directly.
*/
public final class Mailer {
/**
* Called upon startup, to allow this class to extract mail server
* config information from <code>web.xml</code>.
*/
public static void init(ServletConfig aConfig){
fConfig = aConfig;
}
/**
* Validate the form of an email address.
*
* <P>Return <code>true</code> only if
*<ul>
* <li> <code>aEmailAddress</code> can successfully construct an
* {@link javax.mail.internet.InternetAddress}
* <li> when parsed with a "@" delimiter, <code>aEmailAddress</code> contains
* two tokens which have content.
*</ul>
*
*<P> The second condition arises since local email addresses, simply of the form
* "<tt>albert</tt>", for example, are valid but almost always undesired.
*/
public static boolean isValidEmailAddress(String aEmailAddress){
if (aEmailAddress == null) return false;
boolean result = true;
try {
InternetAddress emailAddr = new InternetAddress(aEmailAddress);
if (! hasNameAndDomain(aEmailAddress)) {
result = false;
}
}
catch (AddressException ex){
result = false;
}
return result;
}
/**
* Send an email to a single recipient, using a hard-coded Webmaster email address.
*
* @param aToAddress satisfies {@link #isValidEmailAddress}.
* @param aSubject has content.
* @param aBody has content.
*/
public void send(String aToAddress, String aSubject, String aBody){
if (!textHasContent(aSubject) || !textHasContent(aBody)) {
throw new IllegalArgumentException("Must have content.");
}
if (isMailDisabled()){
37
log("Mailing is disabled.");
return;
}
Session session = Session.getDefaultInstance( getMailServerConfig(), null );
MimeMessage message = new MimeMessage( session );
try {
message.setFrom(new InternetAddress("admin@blah.com"));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(aToAddress));
message.setSubject(aSubject);
message.setText(aBody);
Transport.send(message); //thread-safe?
}
catch (MessagingException ex){
log("CANNOT SEND EMAIL." + ex);
}
log("Mail is sent.");
}
// PRIVATE
private static ServletConfig fConfig;
/**
* Identifies the item in web.xml which contains the configured
* mail server.
*/
private static final String fMAIL_SERVER = "MailServer";
/**
* Identifies the lack of any configured mail server in web.xml.
* If fMAIL_SERVER takes this value, then mailing is disabled, and
* this class will abort all attempts to send email.
*/
private static final String fNONE = "NONE";
private String getMailServer(){
return fConfig.getServletContext().getInitParameter(fMAIL_SERVER);
}
private boolean isMailDisabled(){
return getMailServer().equalsIgnoreCase(fNONE);
}
/**
* Return the configured mail server in the form of a Properties object.
*/
private Properties getMailServerConfig(){
Properties result = new Properties();
result.put("mail.host", getMailServer());
return result;
}
private static boolean hasNameAndDomain(String aEmailAddress){
String[] tokens = aEmailAddress.split("@");
return
tokens.length == 2 &&
textHasContent(tokens[0]) &&
textHasContent(tokens[1])
;
}
private static void log(Object aObject){
System.out.println(String.valueOf(aObject));
}
private static boolean textHasContent(String aText){
return aText != null && aText.trim().length()>0;
}
}
A common type of email is a "trouble ticket", sent to the webmaster when a problem occurs in a
deployed application. Such an email provides a detailed listing of information which may help to resolve
38
the problem. A TroubleTicket class would gather together such information, and a class similar to the
above Mailer would be used to send it to the webmaster.
Example
Here, a Controller creates and sends a TroubleTicket if
an unexpected Exception is thrown (a bug occurs)
the response time exceeds some configured level
This implementation uses a setting in web.xml for throttling down on excessive numbers of similar
trouble tickets.
/**
* Servlet Controller.
* Sends email to webmaster when problem occurs, or when response time
* exceeds a configured value.
*/
public class Controller extends HttpServlet {
@Override public final void init(ServletConfig aConfig) throws ServletException {
//..elided
TroubleTicket.init(aConfig, appInfo);
}
/** Call {@link #processRequest}. */
@Override public final void doGet(
HttpServletRequest aRequest, HttpServletResponse aResponse
) throws ServletException, IOException {
processRequest(aRequest, aResponse);
}
/** Call {@link #processRequest}. */
@Override public final void doPost(
HttpServletRequest aRequest, HttpServletResponse aResponse
) throws ServletException, IOException {
processRequest(aRequest, aResponse);
}
/**
* Handle all HTTP <tt>GET</tt> and <tt>POST</tt> requests.
*
* <P>Operations include :
* <ul>
* <li>if an unexpected problem occurs, create a {@link TroubleTicket}, log it, and
* email it to the webmaster email address configured in <tt>web.xml</tt>
* <li>if the response time exceeds a configured threshold, build a
* {@link TroubleTicket}, log it, and email it to the webmaster address configured
* in <tt>web.xml</tt>
* </ul>
*/
protected void processRequest(
HttpServletRequest aRequest, HttpServletResponse aResponse
) throws ServletException, IOException {
Stopwatch stopwatch = new Stopwatch();
stopwatch.start();
RequestParser requestParser = RequestParser.getInstance(aRequest, aResponse);
try {
Action action = requestParser.getWebAction();
ResponsePage responsePage = action.execute();
//..elided
}
catch (BadRequestException ex){
//..elided
}
catch (Throwable ex) {
//Bugs OR rare conditions, for example datastore failure
logAndEmailSeriousProblem(ex, aRequest);
aResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
stopwatch.stop();
39
See Also :
Refactor large Controllers
A Web App Framework - WEB4J
Launch other applications
Servlets usually serve some form of text - HTML, XML, JSON data, plain text, and so on. Servlets can
also serve binary content, such as images or .pdf files.
Here's an example of a simple servlet that serves binary content. It serves a .pdf file of a fixed name,
located in the web application's root directory.
import java.io.*;
import
import
import
import
import
javax.servlet.ServletException;
javax.servlet.ServletOutputStream;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
/**
Servlet that serves binary data, not text.
<P>This servlet serves a static .pdf file, but the idea is the same with other
forms of data.
*/
public class ServeBinary extends HttpServlet {
@Override protected void doGet(
HttpServletRequest aRequest, HttpServletResponse aResponse
) throws ServletException, IOException {
serveBinary(aRequest, aResponse);
}
@Override protected void doPost(
HttpServletRequest aRequest, HttpServletResponse aResponse
) throws ServletException, IOException {
serveBinary(aRequest, aResponse);
}
// PRIVATE
//elided...
private void serveBinary(
HttpServletRequest aRequest, HttpServletResponse aResponse
) throws IOException {
log("ServeBinary servlet.");
//IMPORTANT: set the MIME type of the response stream
aResponse.setContentType("application/pdf");
//serve a fixed file, located in the root folder of this web app
try (
ServletOutputStream output = aResponse.getOutputStream();
InputStream input = getServletContext().getResourceAsStream("test.pdf");
){
//transfer input stream to output stream, via a buffer
byte[] buffer = new byte[2048];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
}
log("Done.");
}
}
Although not required, it's sometimes helpful if objects placed in session scope implement
Serializable . Some would do this only if necessary, since implementing Serializable is not trivial.
This allows the servlet container to either store the contents of a session on disk, or to transfer session
contents over the network to another server.
In the case of a restart, the web container may attempt to implement a "failover" strategy by attempting to
serialize all data stored in session scope, in order to recover the data after the restart has completed - this
will work only if such data implementsSerializable.
See Also :
Implementing Serializable
Loss of data during restart
validations. Thus, the Model Object should typically encapsulate both data and validations. In Struts 1,
however, data validation is completely separated from the Model Object. Why? Why have the creators
of Struts 1 lost sight of such a fundamental, basic idea in object programming? It does not seem to have
any justification.
Standard HTML forms are not used.
In Struts 1, forms usually depart widely from standard HTML, and are usually implemented with a
specific set of custom tags. This is neither necessary nor desirable. It's not necessary since other tools
have demonstrated that dynamically populated forms can simply reuse standard HTML forms as part of
their implementation. It's not desirable since the page author cannot reuse their knowledge of standard
HTML, but is instead forced to learn a new set of custom tags specific to Struts 1. This is unproductive.
Actions must be thread-safe.
Actions are always cached and reused by Struts 1. This is both a performance optimization and a
constraint, since it requires Actions to be safe for use in a multi-threaded environment.
As a performance optimization, this policy is of dubious quality. The stack traces of a web container are
large. If an Action is "just another object", it's of very little benefit to cache and reuse it. On the other
hand, designs in which an Action holds a reference to an expensive object (such as a database
connection) are often inferior.
As a constraint, Joshua Bloch explains clearly in Effective Java that ensuring a class is usable in a multithreaded environment is a non-trivial task. Forcing all Action implementations to be thread-safe seems
an onerous requirement having doubtful benefit. For example, in a Struts 1 Action, data shared between
methods cannot be stored in a field; rather, it must be passed as a parameter.
Struts 1 is an unimpressive implementation of the Command pattern.
The execute method has four arguments, which must always be passed in, regardless of whether or not
the argument is actually relevant to a particular action. In the Design Patterns book, the number of
arguments to the execute method is exactly zero. There is a good reason for this, and it's illustrated by
almost every design pattern in the Gang Of Four book: the central methods of any design are free of
implementation details. Data needed by the implementation of an abstraction is almost always passed to a
constructor. This keeps the abstractions clean, and places data specific to the implementation in a place
where inheritance and polymorphism do not apply (the constructor).
In addition, Struts 1 always requires a cast to be performed in an Action, to get access to a specific
ActionForm . Casts are something to be avoided unless absolutely necessary, since they lead to runtime
errors.
The web4j tool was built out of strong dissatisfaction with the Struts 1 front end framework.
See Also :
Parse parameters into domain objects
Command objects
Pre-populate forms
A Web App Framework - WEB4J
In a web application, you should verify that the following items are safe for operation in a multi-threaded
environment:
servlet classes
servlet filter classes
items placed in application scope
items placed in session scope
Many applications use a framework. Frameworks usually define the servlet class, and the thread-safety of
those servlets will be explicitly stated by a well-documented framework. Many frameworks require the
application developer to define actions for implementing features. In this case, the framework owns the
servlet, and the servlet in turn uses your action objects. A well-designed framework will create your
action objects on the fly, on the single thread assigned to handle a single request. This in turn means that
your actions will be confined to a single thread; thus, in this case, your action classes will have no need
for thread-safety.
The most glaring example of a framework which violates this rule is Struts 1. In Struts 1, your actions
must be designed for operation in a multi-threaded environment. This is a poor design for a framework,
since ensuring a class is thread-safe is both non-trivial to implement and easy to forget. This is an
onerous and unnecessary burden for an application developer.
In the case of servlet filters, a framework is not usually used when defining them. In this case, you have
to be careful that your servlet filter class is indeed safe for operation in a multi-threaded environment.
For objects placed in either application scope or session scope, the simplest design is to ensure that the
object is immutable, such that external synchronization is never necessary.
It's clear to most people that objects placed in application scope will be accessed by more than one
thread. However, even objects placed in session scope can also be used by more than one thread. From
the Servlet Specification:
"Multiple servlets executing request threads may have active access to a single session object at the same
time. The Developer has the responsibility for synchronizing access to session resources as appropriate."
Here are some ways in which multiple threads might access the same session:
web apps containing multiple servlets can share access to the same session
Ajax requests from a single client
multiple browser windows on a single client
repeated clicks on a submit button
using HTML frames to request multiple pages at the same time
See Also :
Immutable objects
Document thread safety
When building web applications, you should have an excellent understanding of how <FORM> tags work.
In particular, there are a number of quirky behaviors that should be noted. When in doubt, you can verify
form behavior in various browsers. Since these quirks are often related to browser implementations, and
not to the HTML specfication itself, these quirks may change over time.
SELECT Controls
Even if the user has made no selection at all in a <SELECT> control, browsers will automatically submit
the first <OPTION> appearing in the body of a <SELECT> . As a workaround, it's often useful to ensure that
the first <OPTION> is a blank value, instead of a 'real' one.
File Upload Controls
Curiously, there is apparently no attribute to specify the text of the button used for file upload controls.
The text is apparently deduced by the browser from language settings on the client, and not by settings in
HTML or HTTP headers.
Reminder: if a form includes a file upload control, then it must have :
method='POST'
enctype='multipart/form-data'
Reminder: the Servlet API 2.5 has poor support for file upload requests. As soon as a form includes a file
upload control, the format of the underlying request is completely changed. In particular, if no special
measures are taken, none of the form's data will be available from request.getParameter(String) ; this
applies to all of the form's data, both file upload controls and regular controls.
Tools such as Apache Commons FileUpload are often used to handle such forms. Also note that support
for file upload requests has been added to version 3.0 of the Servlet Specification.
Submit Controls
It's often desirable to distinguish between the appearance of a control, as seen by the end user, and the
actual underlying value submitted to the server. (This is particularly true in multilingual applications.)
This is indeed possible for all controls, except for the Submit control, whose value attribute is used both
for its visual appearance and for its submitted value.
This is a problem in multilingual applications where forms have more than one submit button (further
discussion).
Checkbox Controls
When a checkbox control is checked, its name and value are submitted to the server. If the checkbox
control is not checked, then it is not submitted at all. That is, the server has two possible tasks: if the
request parameter is present, then access its value. If the request parameter is not submitted at all, then
the server must assume some default value such as 'null' or 'false'.
Disabled Controls and Read-Only Controls
The readonly and disabled attributes are distinct, and have different behavior. The most important
distinction is that readonly items are submitted, while disabled items are not.
45
When rendering reports or listings that include boolean values, it is tempting to use a disabled checkbox.
However, some feel that such disabled checkboxes don't have a very pleasing appearance. One alternative
is to use an image instead of a checkbox control, to present the boolean value. (One might even use an
image of a checkbox.)
Hitting Enter to Submit
Forms are usually submitted by hitting a Submit button. However, forms can often be submitted just by
hitting the 'Enter' key. This behavior is commonly used for web site search boxes.
Be careful that the server can handle both styles of submission - Enter key and Submit button.
TEXTAREA Controls
You often need to exercise care regarding how new lines are handled by your <TEXTAREA> controls. The
non-standard wrap attribute can often be added to a TEXTAREA to define a desired policy for new line
characters. Its values are :
wrap=(virtual|physical|off)
name style sheets with a .css extension, to ensure they work with tools which may incorrectly
require that extension
if using pseudo classes for links, use a:link and a:visited as a pair
for font sizes, usually prefer em or % (relative), over ex and px (absolute)
the font-size styles of xx-small , x-small , and so on, are not consistent across browers, and
should likely be avoided, as a kind of absolute measure.
when referring to specific fonts, always include a generic font family to be used as an alternative:
serif , sans-serif , monospace
some recommend that !important should be avoided
favor techniques which minimize the amount of markup
use the CSS Specification as reference, and use the CSS Validator to verify your work
Reminders:
. denotes a class
# denotes an id
: denotes a pseudo-class, as in a:visited
[] denotes attribute selectors
}
.highlight {
background-color: #ffff66;
}
.sidebar{
background-color: rgb(85%, 85%, 85%);
border-top: 1px solid rgb(50%,50%,50%);
border-left: 5px solid rgb(50%,50%,50%);
border-right: 5px solid rgb(50%,50%,50%);
border-bottom: 1px solid rgb(50%,50%,50%);
}
/* End of the color scheme. */
body {
margin: 0;
padding:0;
font: 1.0em Verdana, Arial, Helvetica, sans-serif;
}
a {
font-weight: bold;
text-decoration: none;
}
h2{
font: bold 20px Verdana, Arial, Helvetica, sans-serif;
border-bottom: 1px solid;
}
h3{
font: bold 16px Verdana, Arial, Helvetica, sans-serif;
}
h4{
font: bold 16px Verdana, Arial, Helvetica, sans-serif;
}
blockquote.abstract{
padding: 0.3em;
}
ul {
list-style-type: square;
}
tr {
vertical-align: top
}
/*
Tables used for user input.
*/
form.user-input table {
background-color: rgb(83%, 83%, 83%);
border-style: solid;
border-width: 2px;
border-color: rgb(45%,45%,45%);
padding: 1.0em;
}
/* improves alignment of form controls */
form.user-input input {
margin: 0;
}
/*
REPORTS
Here, reports are implemented with tables, and refer to any kind of listing.
*/
table.report {
background-color: rgb(83%, 83%, 83%);
border-style: solid;
border-width: 2px;
border-color: rgb(45%,45%,45%);
border-collapse: collapse;
empty-cells: show;
caption-side: bottom;
}
table.report td, th {
/*white-space: nowrap;*/
48
margin-right: 0px;
}
#nav-menu{
padding-top:18px;
padding-left:15px;
width:120px;
float:left;
display:block;
}
#nav-menu ul {
list-style-type: none;
display:block;
padding: 0;
margin: 0;
}
#nav-menu li {
text-decoration: none;
display:block;
}
#nav-menu li a {
padding-left: 7px;
padding-top: 6px;
height: 22px;
/* width: 113px; */
width: 70px;
display: block;
text-decoration: none;
font-weight: bold;
}
/* Search form in menu */
#nav-menu li form {
padding-left: 7px;
padding-top: 6px;
height: 22px;
width: 113px;
font-weight: bold;
}
#content{
/* margin-left: 135px; */
margin-left: 100px;
padding-left:10px;
padding-right:10px;
padding-top:1px;
}
#contentdisabled{
margin: 10px;
padding:10px;
}
.maincopy a:hover {
text-decoration: underline;
}
#textcontent{
height:100%;
padding-left:0;
padding-right:15px;
}
div.author {
font-style: italic;
padding-top: 0em;
margin-top: 0.25em;
text-align: right;
}
.opening-quote{
margin: 10px;
padding: 10px;
}
.sidebar{
margin: 10px;
padding: 10px;
50
}
#footer{
text-align:center;
height:24px;
padding-top: 10px;
padding-left: 0px;
font: 11px Verdana, Arial, Helvetica, sans-serif;
padding-right: 10px;
border-right-width: 0px;
border-bottom-width: 0px;
border-left-width: 0px;
}
.small-graphic{
float:left;
margin-right:0.2em;
}
/* sometimes footer is too high on page */
div.spacer {
height: 14.0em;
}
div.smallspacer {
height: 7.0em;
}
div.bigspacer {
height: 30.0em;
}
/* Styles for print. */
@media print {
* {
color: black !important;
background: white !important;
}
body {
font-family: "Times New Roman", serif;
font-size: 12pt;
}
a {
text-decoration: none;
}
img.photo {
display: none;
}
div#nav-menu{
display: none;
}
div#header{
display: none;
}
}
There is an alternative to using <http-method>: use the extension appearing in the URL. In this case,
URLs take the form:
.../Account.list
.../Account.add
.../Account.delete
.../Account.fetch?Id=45
When thinking of security, it's natural to think in terms of nouns and verbs:
what is being operated on - the noun
what exactly is being done to it - the verb
In the above example, Account is the noun, while the extension ( .list , .add , and so on) is the verb.
With this style, any degree of granularity for security constraints can be implemented. One can mix and
match the nouns and the verbs independently of each other, in a natural way.
Example 1
Only a manager can perform this specific delete operation :
<security-constraint>
<web-resource-collection>
<web-resource-name>Deleting Members</web-resource-name>
<url-pattern>/main/member/MemberAction.delete</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>manager</role-name>
</auth-constraint>
</security-constraint>
Example 2
A reader can read, but not write to the database :
<security-constraint>
<web-resource-collection>
<web-resource-name>View Operations</web-resource-name>
<url-pattern>*.list</url-pattern>
<url-pattern>*.fetch</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>reader</role-name>
</auth-constraint>
</security-constraint>
Example 3
An editor can both read and write to a database :
<security-constraint>
<web-resource-collection>
<web-resource-name>Edit Operations</web-resource-name>
<url-pattern>*.list</url-pattern>
<url-pattern>*.fetch</url-pattern>
<url-pattern>*.add</url-pattern>
<url-pattern>*.change</url-pattern>
<url-pattern>*.delete</url-pattern>
<url-pattern>*.fetchForChange</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>editor</role-name>
</auth-constraint>
</security-constraint>
Example 4
52
Example 5
Only an megawebmaster can access /webmaster/Logs.delete :
<security-constraint>
<web-resource-collection>
<web-resource-name>Log Deletion</web-resource-name>
<url-pattern>/webmaster/Logs.delete</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>megawebmaster</role-name>
</auth-constraint>
</security-constraint>
Here's a table showing whether access is granted in various cases, given the above constraints:
For User With Role(s)
Accessing URL
Allow Access?
editor
../Account.list
editor
../Account.delete
reader
../Account.list
reader
../Account.delete
editor
/main/member/MemberAction.delete
reader, manager
/main/member/MemberAction.delete
reader
/webmaster/Logs.list
webmaster
/webmaster/Logs.list
webmaster
/webmaster/Logs.delete
delegate).
See Also :
Refactor large Controllers
JSPs should contain only presentation logic
Model Objects
of the JSP which supplies all page content not otherwise defined
in the template.
The <tags:xxx/> items refer to .tag files, that contain small JSP snippets.
<%@ include file="/JspHeader.jsp" %>
<
!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd"
>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=uTF-8">
<%-- TTitle is a request parameter for the templated page title. --%>
<title>
Fish and Chips Club - ${param.TTitle} </title>
<link rel="stylesheet" type="text/css" href="../../stylesheet.css" media="all">
</head>
<body>
<div align="center">
<img class="no-margin" src="../../images/logo.jpg" alt="Fish And Chips Club">
</div>
<div class="header">
"The Total Fish & Chips Dining Experience"
</div>
<div class="menu-bar">
<w:highlightCurrentPage styleClass='highlight'>
<c:url value="/main/home/HomePageAction.do" var="homeURL"/>
<A href='${homeURL}'>Home</a>
<c:url value="/main/rsvp/RsvpShow.do" var="showRsvpURL"/>
<A href='${showRsvpURL}'>Rsvp</a>
<c:url value="/all/logoff/LogoffAction.do" var="logoffURL"/>
<A href='${logoffURL}'>Log Off</a>
</w:highlightCurrentPage>
</div>
54
See Also :
Always maintain HttpSessions
Prefer JSTL tags
Prevent self-linking
A Web App Framework - WEB4J
java.util.regex.*;
javax.mail.internet.AddressException;
javax.mail.internet.InternetAddress;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.servlet.http.HttpSession;
/**
* Static convenience methods for common web-related tasks.
*/
public final class WebUtil {
/**
* Validate the form of an email address.
*
* <P>Return <tt>true</tt> only if
*<ul>
* <li> <tt>aEmailAddress</tt> can successfully construct an
* {@link javax.mail.internet.InternetAddress}
* <li> when parsed with "@" as delimiter, <tt>aEmailAddress</tt> contains
* two tokens which satisfy {@link hirondelle.web4j.util.Util#textHasContent}.
*</ul>
55
*
*<P> The second condition arises since local email addresses, simply of the form
* "<tt>albert</tt>", for example, are valid for
* {@link javax.mail.internet.InternetAddress}, but almost always undesired.
*/
public static boolean isValidEmailAddress(String aEmailAddress){
if (aEmailAddress == null) return false;
boolean result = true;
try {
InternetAddress emailAddr = new InternetAddress(aEmailAddress);
if (! hasNameAndDomain(aEmailAddress)) {
result = false;
}
}
catch (AddressException ex){
result = false;
}
return result;
}
private static boolean hasNameAndDomain(String aEmailAddress){
String[] tokens = aEmailAddress.split("@");
return
tokens.length == 2 &&
Util.textHasContent(tokens[0]) &&
Util.textHasContent(tokens[1])
;
}
//..elided
}
See Also :
Send trouble-ticket emails
A Web App Framework - WEB4J
Launch other applications
keep download times as short as possible; enabling zip compression on your server is almost
always a good idea
Rendering:
don't hard-code font size
don't disable the browser's ability to change font size
don't hard-code absolute pixel sizes
rendering must be reasonable for a wide range of screen resolutions
if layout is based on tables, use several tables instead of one large one - the first table will be
rendered while the others are still downloading
if using tables, use the COLGROUP and COL tags to define both the number of columns and their
relative percentage widths - this allows a smart browser to start rendering a single table before it
has received all of its content
include width and height for images - allows faster rendering
include ALT text for images
break up blocks of text with lots of white space
Navigation:
use standard colours for links
avoid horizontal scrolling
don't use frames
half the users will go directly to the search box, without examining the structure of the site in any
way
store the text entered by users into the search box, for later examination
the attention of many users will initially go to the center of a page, not the top
high quality page titles and metadata are important for bookmarks and search engines
See Also :
Minimize site response time
Prevent self-linking
One technique for handling file upload requests uses a wrapper for the underlying request, such that
request.getParameter(String) and related methods may be used in the usual way.
57
Example
The following wrapper uses the Apache Commons FileUpload tool to parse the request into both regular
parameters and file upload parameters. An action class may use this class as it would any request, with
one exception: to access the methods related specifically to files, a cast is necessary.
import
import
import
import
import
import
import
import
java.util.*;
java.io.*;
javax.servlet.http.HttpServletRequestWrapper;
javax.servlet.http.HttpServletRequest;
org.apache.commons.fileupload.FileUploadException;
org.apache.commons.fileupload.servlet.ServletFileUpload;
org.apache.commons.fileupload.disk.DiskFileItemFactory;
org.apache.commons.fileupload.FileItem;
/**
* Wrapper for a file upload request (before Servlet 3.0).
*
* <P>This class uses the Apache Commons
* <a href='http://commons.apache.org/fileupload/'>File Upload tool</a>.
* The generous Apache License will very likely allow you to use it in your
* applications as well.
*/
public class FileUploadWrapper extends HttpServletRequestWrapper {
/** Constructor. */
public FileUploadWrapper(HttpServletRequest aRequest) throws IOException {
super(aRequest);
ServletFileUpload upload = new ServletFileUpload( new DiskFileItemFactory());
try {
List<FileItem> fileItems = upload.parseRequest(aRequest);
convertToMaps(fileItems);
}
catch(FileUploadException ex){
throw new IOException("Cannot parse underlying request: " + ex.toString());
}
}
/**
* Return all request parameter names, for both regular controls and file upload
* controls.
*/
@Override public Enumeration<String> getParameterNames() {
Set<String> allNames = new LinkedHashSet<>();
allNames.addAll(fRegularParams.keySet());
allNames.addAll(fFileParams.keySet());
return Collections.enumeration(allNames);
}
/**
* Return the parameter value. Applies only to regular parameters, not to
* file upload parameters.
*
* <P>If the parameter is not present in the underlying request,
* then <tt>null</tt> is returned.
* <P>If the parameter is present, but has no associated value,
* then an empty string is returned.
* <P>If the parameter is multivalued, return the first value that
* appears in the request.
*/
@Override public String getParameter(String aName) {
String result = null;
List<String> values = fRegularParams.get(aName);
if(values == null){
//you might try the wrappee, to see if it has a value
}
else if (values.isEmpty()) {
//param name known, but no values present
result = "";
}
else {
//return first value in list
result = values.get(FIRST_VALUE);
}
return result;
58
}
/**
* Return the parameter values. Applies only to regular parameters,
* not to file upload parameters.
*/
@Override public String[] getParameterValues(String aName) {
String[] result = null;
List<String> values = fRegularParams.get(aName);
if(values != null) {
result = values.toArray(new String[values.size()]);
}
return result;
}
/**
* Return a {@code Map<String, List<String>>} for all regular parameters.
* Does not return any file upload parameters at all.
*/
@Override public Map<String, List<String>> getParameterMap() {
return Collections.unmodifiableMap(fRegularParams);
}
/**
* Return a {@code List<FileItem>}, in the same order as they appear
* in the underlying request.
*/
public List<FileItem> getFileItems(){
return new ArrayList<FileItem>(fFileParams.values());
}
/**
* Return the {@link FileItem} of the given name.
* <P>If the name is unknown, then return <tt>null</tt>.
*/
public FileItem getFileItem(String aFieldName){
return fFileParams.get(aFieldName);
}
// PRIVATE
/** Store regular params only. May be multivalued (hence the List). */
private final Map<String, List<String>> fRegularParams = new LinkedHashMap<>();
/** Store file params only. */
private final Map<String, FileItem> fFileParams = new LinkedHashMap<>();
private static final int FIRST_VALUE = 0;
private void convertToMaps(List<FileItem> aFileItems){
for(FileItem item: aFileItems) {
if ( isFileUploadField(item) ) {
fFileParams.put(item.getFieldName(), item);
}
else {
if( alreadyHasValue(item) ){
addMultivaluedItem(item);
}
else {
addSingleValueItem(item);
}
}
}
}
private boolean isFileUploadField(FileItem aFileItem){
return ! aFileItem.isFormField();
}
private boolean alreadyHasValue(FileItem aItem){
return fRegularParams.get(aItem.getFieldName()) != null;
}
private void addSingleValueItem(FileItem aItem){
List<String> list = new ArrayList<>();
list.add(aItem.getString());
fRegularParams.put(aItem.getFieldName(), list);
}
59
java.io.*;
javax.servlet.Filter;
javax.servlet.FilterChain;
javax.servlet.FilterConfig;
javax.servlet.ServletException;
javax.servlet.ServletRequest;
javax.servlet.ServletResponse;
javax.servlet.http.HttpServletRequest;
/**
* Filter that wraps an underlying file upload request (before Servlet 3.0).
*
* <P>This filter should be configured only for those operations that use a
* file upload request.
*/
public final class FileUploadFilter implements Filter {
public void init(FilterConfig aConfig) throws ServletException {
//do nothing
}
public void destroy() {
//do nothing
}
public void doFilter(
ServletRequest aRequest, ServletResponse aResponse, FilterChain aChain
) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) aRequest;
if ( isFileUploadRequest(request) ) {
FileUploadWrapper wrapper = new FileUploadWrapper(request);
aChain.doFilter(wrapper, aResponse);
}
else {
aChain.doFilter(aRequest, aResponse);
}
}
private boolean isFileUploadRequest(HttpServletRequest aRequest){
return
aRequest.getMethod().equalsIgnoreCase("POST") &&
aRequest.getContentType().startsWith("multipart/form-data")
;
}
}
Finally, for a second example of a file upload request wrapper, see this article by Jason Hunter.
See Also :
Understand details of FORM tags
StocksMonitor
61
See Also :
A Web App Framework - WEB4J
Actions
The Command pattern is described in Design Patterns, where menu items are used as an example
implementation.
In Swing, the Action interface and the AbstractAction base class implement the Command pattern.
They eliminate duplication of code, and separate tasks from the details of their invocation:
the same Action object can be invoked in multiple ways, usually through a JMenuItem and
JToolBar button ( JButton and JTextField can also be used)
text, mnemonic, accelerator, tooltip, command String, and icon are all defined once in an
implementation of the Action, and are all shared by all associated graphical elements
the enabled/disabled state of an Action is always reflected by all associated graphical elements
Action objects can be passed directly to the constructor of JMenuItem and JButton , and also to
JToolBar.add
Example
A JMenuItem corresponding to File->Save can be created using this Action:
package hirondelle.stocks.file;
import
import
import
import
import
import
import
import
java.awt.event.*;
javax.swing.*;
java.util.*;
hirondelle.stocks.portfolio.PortfolioDAO;
hirondelle.stocks.portfolio.CurrentPortfolio;
hirondelle.stocks.util.ui.UiUtil;
java.util.logging.Logger;
hirondelle.stocks.util.Util;
/**
* Save the edits performed on the {@link CurrentPortfolio}, and update the display
62
See Also :
Align menu items
import java.awt.Component;
import java.awt.Graphics;
import hirondelle.stocks.util.Args;
/**
* Represents a square icon having no graphical content.
*
* <P>Intended for use with <tt>Action</tt> and <tt>JMenuItem</tt>.
* Alignment of text is poor when the same menu mixes menu items without an icon with
* menu items having an icon. In such cases, items without an icon can use
* an <tt>EmptyIcon</tt> to take up the proper amount of space, and allow
* for alignment of all text in the menu.
*/
final class EmptyIcon implements Icon {
/**
* Convenience object for small icons, whose size matches the size of
* small icons in Sun's graphics repository.
*/
static final EmptyIcon SIZE_16 = new EmptyIcon(16);
/**
* Convenience object for large icons, whose size matches the size of
* large icons in Sun's graphics repository.
*/
static final EmptyIcon SIZE_24 = new EmptyIcon(24);
/**
* EmptyIcon objects are always square, having identical height and width.
*
* @param aSize length of any side of the icon in pixels, must
* be in the range 1..100 (inclusive).
*/
EmptyIcon(int aSize) {
Args.checkForRange(aSize, 1, 100);
fSize = aSize;
}
/**
* Return the icon size (width is same as height).
*/
@Override public int getIconWidth() {
return fSize;
}
/**
* Return the icon size (width is same as height).
*/
@Override public int getIconHeight() {
return fSize;
}
/**
* This implementation is empty, and paints nothing.
*/
@Override public void paintIcon(Component c, Graphics g, int x, int y) {
//empty
}
// PRIVATE
private int fSize;
}
See Also :
Actions
Change theme
64
The default Java look-and-feel has a theme mechanism, whereby custom colors and fonts may be
defined. This is particularly useful for users with poor vision.
Here's an illustration of a theme created for people with low vision:
Defining Themes
Here, the Theme class defines the set of MetalTheme objects available to the application.
package hirondelle.stocks.util.ui;
import
import
import
import
import
import
import
javax.swing.plaf.metal.*;
javax.swing.plaf.basic.BasicBorders;
javax.swing.border.*;
javax.swing.plaf.*;
javax.swing.*;
java.awt.*;
java.util.*;
/**
* Defines all themes which can allow the user to customize the Java Look and Feel.
65
*
* <P>This application uses only the cross-platform Java Look-and-Feel, and never
* attempts to adapt to the native look-and-feel (Windows, Metal, Mac).
*/
public final class Theme {
/*
* Implementation Note:
* This item has not been converted to an enum class, since it
* contains so many data-related settings.
*/
/**
* A theme identical to the default Java look-and-feel, but whose name
* is "Default" instead of the cryptic "Steel", and which provides a
* <tt>toString</tt> method (required if <tt>Theme</tt> objects
* passed to a combo box). Used as the base class for all other themes
* used in this application.
*/
public static final MetalTheme DEFAULT = new Default();
/**
* Much like {@link #DEFAULT}, but uses some blue-green colors.
*/
public static final MetalTheme AQUA = new Aqua();
/**
* Differs from {@link #DEFAULT} only in font sizes.
*/
public static final MetalTheme LARGE_FONT = new LargeFont();
/**
* Large fonts, and high contrast black and white colors.
*
* <P>This is an amalgam of two example themes from the JDK swing examples ; there
* is apparently no recommended standard for a low-vision theme.
*/
public static final MetalTheme LOW_VISION = new LowVision();
/**
* Convert <tt>aText</tt> into its corresponding <tt>Theme</tt> object,
* if possible.
*
* @param aText possibly-null text which may map to a Theme.
* @return null if <tt>aText</tt> is null, else try to match to a
* known <tt>Theme</tt>.
* @throws IllegalArgumentException if <tt>aText</tt> cannot be
* matched to a known theme.
*/
public static MetalTheme valueOf(String aText) {
if (aText == null) return null;
for(MetalTheme theme: VALUES){
if (aText.endsWith(theme.getName())){
return theme;
}
}
throw new IllegalArgumentException("Cannot parse into Theme object:" + aText);
}
/**
* Return true if <tt>aTheme</tt> uses a larger font than the default; this is the
* case only for <tt>LARGE_FONT</tt> and <tt>LOW_VISION</tt>.
*
* <P>Themes with large font sizes need particular care, as their use may
* require changes outside those provided through
* <tt>SwingUtilities.updateComponentTreeUI</tt>.
*/
public static boolean hasLargeFont(MetalTheme aTheme) {
return aTheme == LARGE_FONT || aTheme == LOW_VISION;
}
private static final MetalTheme[] fValues = {
DEFAULT,
AQUA,
LARGE_FONT,
LOW_VISION
};
/**Allows user to iterate over all elements of this enumeration.
66
*/
static
static
static
static
final
final
final
final
String
String
String
String
fDEFAULT_NAME = "Default";
fAQUA_NAME = "Aqua";
fLARGE_FONT_NAME = "Large Font";
fLOW_VISION_NAME = "Low Vision";
/*
* All items below are private nested classes which define the various
* themes.
*/
private static class Default extends DefaultMetalTheme {
public String getName(){
return fName;
}
/**
* This override is provided such that Theme objects can
* be directly passed to JComboBox, instead of Strings. (This would
* not be necessary if getName had been named toString instead).
*/
@Override public final String toString() {
return getName();
}
private final String fName = fDEFAULT_NAME;
}
private static class Aqua extends Default {
public String getName(){ return fName; }
protected ColorUIResource getPrimary1() {
protected ColorUIResource getPrimary2() {
protected ColorUIResource getPrimary3() {
private final String fName = fAQUA_NAME;
private final ColorUIResource fPrimary1 =
private final ColorUIResource fPrimary2 =
private final ColorUIResource fPrimary3 =
}
return fPrimary1; }
return fPrimary2; }
return fPrimary3; }
new ColorUIResource(102, 153, 153);
new ColorUIResource(128, 192, 192);
new ColorUIResource(159, 235, 235);
See Also :
Look and Feel guidelines
always pleasing to the eye. Another nice feature is its debug mode, whereby a colored grid is laid over
the form, so you may easily verify the x-y coordinates of its cells.
See Also :
Layout Managers
See Also :
Standardized dialogs
69
hirondelle.movies.util.ui.OnClose;
hirondelle.movies.util.Edit;
hirondelle.movies.util.Util;
hirondelle.movies.util.ui.UiUtil;
hirondelle.movies.util.ui.StandardDialog;
import
import
import
import
import
java.util.logging.Logger;
java.util.*;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
javax.swing.*;
/**
Dialog allowing user to input {@link Movie} information.
<P>This view can be used to either add a new Movie, or to change an existing one.
<P>It's important to note that validation of user entries is <em>not</em>
performed by this class. Rather, it's performed by the {@link Movie} class.
*/
final class MovieView {
/**
Constructor.
<P>Called when adding a new {@link Movie}.
*/
MovieView(JFrame aParent) {
fEdit = Edit.ADD;
buildGui(aParent, "Add Movie");
fStandardDialog.display();
}
/**
Constructor.
<P>Called when editing an existing {@link Movie}. The text fields are simply
prepopulated with the text taken from the currently selected row of the table.
*/
MovieView(JFrame aParent, Movie aSelectedMovie) {
fLogger.fine("Editing selected movie:" + aSelectedMovie);
fEdit = Edit.CHANGE;
fId = aSelectedMovie.getId();
buildGui(aParent, "Edit Movie");
populateFields(aSelectedMovie);
fStandardDialog.display();
70
}
/**
Return the movie id. The id is used by the database, but is never shown to the
user,
nor is it ever edited by the end user. This method is supplied since it's
convenient to carry the id with the other information related to a movie, and the
{@link MovieDAO} needs a way to uniquely identify records.
*/
String getId() {
return fId;
}
/** The title of the movie, as entered by the user. */
String getTitle() {
return fTitle.getText();
}
/** The date the movie was viewed, as entered by the user. */
String getDateViewed() {
return fDateViewed.getText();
}
/** The movie rating, as entered by the user. */
String getRating() {
return fRating.getText();
}
/** The comment on the movie, as entered by the user. */
String getComment() {
return fComment.getText();
}
/** Close the view. */
void closeDialog() {
fStandardDialog.dispose();
}
/** Return the underlying dialog. */
JDialog getDialog() {
return fStandardDialog.getDialog();
}
// PRIVATE
private StandardDialog fStandardDialog;
private Edit fEdit;
private String fId;
private JTextField fTitle = new JTextField();
private JTextField fDateViewed = new JTextField();
private JTextField fRating = new JTextField();
private JTextField fComment = new JTextField();
private JButton fEditButton;
private static final Logger fLogger = Util.getLogger(MovieView.class);
/** Populate the GUI with data from the movie. */
private void populateFields(Movie aSelectedMovie) {
fTitle.setText(Util.format(aSelectedMovie.getTitle()));
fDateViewed.setText(Util.format(aSelectedMovie.getDateViewed()));
fRating.setText(Util.format(aSelectedMovie.getRating()));
fComment.setText(aSelectedMovie.getComment());
}
private void buildGui(JFrame aParent, String aDialogTitle) {
fStandardDialog = new StandardDialog(
aParent, aDialogTitle, true, OnClose.DISPOSE, getUserInputArea(), getButtons()
);
fStandardDialog.setDefaultButton(fEditButton);
}
private JPanel getUserInputArea() {
JPanel result = new JPanel();
result.setLayout(new BoxLayout(result, BoxLayout.Y_AXIS));
addTextField(fTitle, "Title", result);
addTextField(fDateViewed, "Date Viewed", result);
addTextField(fRating, "Rating", result);
addTextField(fComment, "Comment", result);
UiUtil.alignAllX(result, UiUtil.AlignX.LEFT);
return result;
71
}
private void addTextField(JTextField aTextField, String aLabel, JPanel aPanel) {
JLabel label = new JLabel(aLabel);
aPanel.add(label);
aPanel.add(aTextField);
aTextField.setColumns(15);
}
private java.util.List<JButton> getButtons() {
java.util.List<JButton> result = new ArrayList<>();
fEditButton = new JButton(fEdit.toString());
fEditButton.addActionListener(new MovieController(this, fEdit));
result.add(fEditButton);
JButton cancel = new JButton("Cancel");
cancel.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) {
closeDialog();
}
});
result.add(cancel);
return result;
}
}
See Also :
Standardized dialogs
package hirondelle.stocks.table;
import hirondelle.stocks.quotes.Quote;
import java.util.*;
/**
* Allows collections of {@link Quote} objects
* to be filtered according to a criterion defined by implementors.
* JDK less than 6.
*/
public abstract class QuoteFilter {
72
/**
* Defines the criteria by which <tt>aQuote</tt> is accepted or rejected
* by this filter.
*/
abstract public boolean isAcceptable(Quote aQuote);
/**
* Return a <tt>List</tt> which has the same
* iteration order as <tt>aQuotes</tt>, but which includes only those elements
* which satisfy {@link #isAcceptable}.
*/
public final List<Quote> sift(Collection<Quote> aQuotes ){
/*
* This is an example of a template method : the general outline is
* defined here in this abstract base class, but the implementation of
* specific steps (in this case the method isAcceptable) is left to
* concrete subclasses.
*
* Note as well that this method is final, so that no subclass can override this
* implementation.
*/
List<Quote> result = new ArrayList<>();
for(Quote quote : aQuotes){
if (isAcceptable(quote)) {
result.add(quote);
}
}
return result;
}
}
to
/**
* Graphical component which allows the end user to select a
* {@link QuoteFilter}, and informs its listeners of changes
* to this selection.
* JDK less than 6.
*/
public final class QuoteFilterFactory extends JScrollPane implements Observer {
/**
* Constructor.
*
* @param aCurrentPortfolio is observed by this class, since the list of
* possible filters displayed by this class depends on its content.
*/
public QuoteFilterFactory (CurrentPortfolio aCurrentPortfolio){
fCurrentPortfolio = aCurrentPortfolio;
//bad practice - 'this' is not fully defined until the constructor has returned:
fCurrentPortfolio.addObserver(this);
fSelectedFilter = NO_SELECTION_FILTER;
initGui();
}
/**
* Return the <tt>QuoteFilter</tt>
* attached to the currently selected item. If no selection is present,
* then return a <tt>QuoteFilter</tt> which accepts all {@link Quote} objects.
*/
public QuoteFilter getSelectedFilter(){
73
* {@link Quote} objects are accepted only if todays price change is negative.
*/
static private final class QuoteFilterLosers extends QuoteFilter {
@Override public boolean isAcceptable(Quote aQuote){
return aQuote.getChange().doubleValue() < 0.0;
}
@Override public String toString(){
return LOSERS;
}
}
/** {@link Quote} objects are accepted only if it is an index. */
static private final class QuoteFilterIndex extends QuoteFilter {
@Override public boolean isAcceptable(Quote aQuote){
return aQuote.getStock().isIndex();
}
@Override public String toString(){
return INDEX;
}
}
/** {@link Quote} objects are accepted only if it is not an index. */
static private final class QuoteFilterNonIndex extends QuoteFilter {
@Override public boolean isAcceptable(Quote aQuote){
return !aQuote.getStock().isIndex();
}
@Override public String toString(){
return NON_INDEX;
}
}
}
See Also :
Observers and listeners
Sort table rows
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
java.util.Collections;
java.util.List;
import
import
import
import
import
import
javax.swing.JButton;
javax.swing.JFrame;
javax.swing.JPanel;
javax.swing.JScrollPane;
javax.swing.JTable;
javax.swing.RowSorter;
app.buildAndDisplayGui();
}
// PRIVATE
private void buildAndDisplayGui(){
JFrame frame = new JFrame("Test Generic Table Sort");
addSortableTableTo(frame);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private void addSortableTableTo(JFrame aFrame){
JPanel panel = new JPanel();
Object[][] data = { {1,"T"},
{2,"B"}, {3,"A"},
String[] cols = {"One", "Two"};
final JTable table = new JTable(data, cols);
{4, "F"}};
To unsort a table, and revert back to its original state, you need to make a (rather cryptic) call to
table.getRowSorter().setSortKeys(null)
This is done in the above example. The null value is required, and here acts as a flag to reverse any
sorting.
Custom Sorting
The default sorting shown above acts on a single column. In some cases, you may want more control over
sorting. For example, you may need to implement a sort that depends on more than one column (a
common requirement). In that case, one alternative is outlined here.
First, define the different Comparator s you need, perhaps directly in your Model Object. (These
Comparator s know nothing about the user interface, but they are ultimately the classes that do the actual
sorting.) Then it becomes a case of wiring together clicks on the column header to the proper
Comparator , sorting the data, and then refreshing the screen.
More precisely:
attach a MouseAdapter to the JTableHeader, to listen for clicks on the table's header.
translate the clicks into a column index.
instruct your TableModel to sort itself on the given column index; these sorts are implemented by
the N different Comparator s that you've defined for this purpose.
refresh the display by calling fireTableDateChanged() in the TableModel .
Here's a snippet for the first steps:
76
myTable.getTableHeader().addMouseListener(new MySorter());
private final class MySorter extends MouseAdapter {
@Override public void mouseClicked(MouseEvent aEvent) {
int colIdx = myTable.getColumnModel().getColumnIndexAtX(aEvent.getX());
myTableModel.sortByColumn(colIdx);
}
}
The above technique is used by the Movies app, by the following classes:
Movie - the Model Object, that defines the needed Comparator s
MainWindow - wires the clicks on the table header a method call on the table model (here called
sortByColumn )
MovieTableModel - uses the Comparator s to sort its list of Model Objects
A disadvantage in the above technique is that there's no visual indicator for the selected sort.
Input dialogs
Input dialogs allows users to input new items, or to change old ones. It's often helpful to implement a
class which can handle both cases, since their data validation requirements are almost always identical.
Example
Here, the StockEditor class can either add a new Stock object, or change a Stock which already exists.
When a change is performed, all input fields are pre-populated with current values. When an add is
performed, all fields are either blank or pre-populated with default values.
Internally, StockEditor uses a standard dialog. It also uses a RegexInputVerifier for all text validations,
using regular expressions. Text validation proceeds as follows:
the user enters text into a JTextField
when focus leaves the JTextField , the input is verified versus a specified regular expression
if invalid, the text of the JTextField is altered to explicitly show what is invalid, and the system
beeps
the validation mechanism does not force the focus to remain in the offending JTextField
For example, here is the result of inputing an invalid Quantity , which must be an integer:
77
The "INVALID" text is absent from the start of all text fields
Stock.isValidInput returns true (this is a second, independent check)
If there is an error, the user is informed by a JOptionPane message.
package hirondelle.stocks.portfolio;
import
import
import
import
import
import
java.util.*;
java.util.regex.Pattern;
java.math.BigDecimal;
javax.swing.*;
java.awt.*;
java.awt.event.*;
import
import
import
import
import
import
import
hirondelle.stocks.quotes.Stock;
hirondelle.stocks.quotes.Exchange;
hirondelle.stocks.util.Args;
hirondelle.stocks.util.Util;
hirondelle.stocks.util.Consts;
hirondelle.stocks.util.ui.StandardEditor;
hirondelle.stocks.util.ui.UiUtil;
/**
* Dialog allows user to either add new a {@link Stock} to the
* {@link CurrentPortfolio}, or to change the parameters of a <tt>Stock</tt>
* which is already in the <tt>CurrentPortfolio</tt>.
*/
final class StockEditor {
/**
* Constructor.
*
* @param aFrame parent to which dialogs of this class are attached.
*/
StockEditor(JFrame aFrame) {
Args.checkForNull(aFrame);
fFrame = aFrame;
}
/**
* Return the {@link Stock} representing a new
* item which the user has input, or <tt>null</tt> if the user cancels the dialog.
*/
Stock addStock(){
showDialog("Add New Stock", null);
return fNewStock;
}
/**
* Return the possibly-edited version of <tt>aOldStock</tt>, representing
* the desired changes, or <tt>null</tt> if the user cancels the dialog.
*
* @param aOldStock {@link Stock} which the end user wishes to change.
*/
Stock changeStock(Stock aOldStock){
Args.checkForNull(aOldStock);
showDialog("Change Stock", aOldStock);
return fNewStock;
}
// PRIVATE
/**
* Always returned to the caller, and is null if the user cancels the
* dialog.
*/
private Stock fNewStock;
private
private
private
private
private
JTextField fNameField;
JTextField fTickerField;
JComboBox<Exchange> fExchangeField;
JTextField fQuantityField;
JTextField fAveragePriceField;
new
aEditor.dispose();
}
else {
Object[] message = errorMessages.toArray();
JOptionPane.showMessageDialog(
fFrame, message, "Invalid Input", JOptionPane.INFORMATION_MESSAGE
);
}
}
/**
* Return true only if at least one of the text input fields contains
* an error message.
*/
private boolean hasErrorMessage(){
return
fNameField.getText().startsWith(RegexInputVerifier.ERROR_MESSAGE_START) ||
fTickerField.getText().startsWith(RegexInputVerifier.ERROR_MESSAGE_START) ||
fQuantityField.getText().startsWith(RegexInputVerifier.ERROR_MESSAGE_START) ||
fAveragePriceField.getText().startsWith(RegexInputVerifier.ERROR_MESSAGE_START
);
}
private void addCompanyName(JPanel aContent, Stock aInitialValue) {
fNameField = UiUtil.addSimpleEntryField(
aContent,
"Company Name:",
(aInitialValue == null ? null : aInitialValue.getName()),
KeyEvent.VK_C,
UiUtil.getConstraints(0,0),
"No spaces on the ends, and at least one character"
);
fNameField.setInputVerifier(RegexInputVerifier.TEXT);
}
private void addTickerField(JPanel aContent, Stock aInitialValue) {
fTickerField = UiUtil.addSimpleEntryField(
aContent,
"Ticker:",
(aInitialValue == null ? null : aInitialValue.getTicker()),
KeyEvent.VK_T,
UiUtil.getConstraints(1,0),
"No spaces on the ends, 1-20 characters: " +
"letters, periods, underscore and ^"
);
String regex = "(\\^)?([A-Za-z._]){1,20}";
RegexInputVerifier tickerVerifier =
new RegexInputVerifier(Pattern.compile(regex),
RegexInputVerifier.UseToolTip.FALSE
);
fTickerField.setInputVerifier( tickerVerifier );
}
private void addExchange(JPanel aContent, Stock aInitialValue) {
JLabel exchange = new JLabel("Exchange:");
exchange.setDisplayedMnemonic(KeyEvent.VK_X);
aContent.add( exchange, UiUtil.getConstraints(2,0) );
DefaultComboBoxModel<Exchange> exchangesModel =
new DefaultComboBoxModel<>(Exchange.VALUES.toArray(new Exchange[0]))
;
fExchangeField = new JComboBox<Exchange>(exchangesModel);
if (aInitialValue != null) {
fExchangeField.setSelectedItem( aInitialValue.getExchange() );
}
exchange.setLabelFor(fExchangeField);
GridBagConstraints constraints = UiUtil.getConstraints(2,1);
constraints.fill = GridBagConstraints.HORIZONTAL;
aContent.add(fExchangeField, constraints);
}
private void addQuantity(JPanel aContent, Stock aInitialValue) {
fQuantityField = UiUtil.addSimpleEntryField(
aContent,
"Quantity:",
(aInitialValue == null ? null :
aInitialValue.getNumShares().toString()),
KeyEvent.VK_Q,
UiUtil.getConstraints(3,0),
80
See Also :
Standardized dialogs
Verify input with regular expressions
Verify input with Model Objects
Desktop
open a file for editing or viewing (text file, graphics file, or whatever)
print a file with an application's print command
The Desktop class has a pleasingly simple API. Here's an example of its use. Note that when you create
an email, you often need to pay attention to encoding issues (see below).
import
import
import
import
import
import
import
import
import
import
import
java.awt.Desktop;
java.io.IOException;
java.io.UnsupportedEncodingException;
java.net.URI;
java.net.URISyntaxException;
java.nio.file.Path;
java.nio.file.Paths;
java.text.CharacterIterator;
java.text.StringCharacterIterator;
java.util.regex.Matcher;
java.util.regex.Pattern;
81
}
private String getEmailBody(){
StringBuilder result = new StringBuilder();
String NL = System.getProperty("line.separator");
result.append("Hello,");
result.append(NL);
result.append(NL);
//exercises a range of common characters :
result.append("Testing 1 2 3. This is a 'quote', _yes_? ($100.00!) 5*3");
return encodeUnusualChars(result.toString());
}
/**
This is needed to handle special characters.
This method hasn't been tested with non-Latin character sets.
Encodes all text except characters matching [a-zA-Z0-9].
All other characters are hex-encoded. The encoding is '%' plus the hex
representation of the character in UTF-8.
<P>See also :
http://tools.ietf.org/html/rfc2368 - mailto
http://tools.ietf.org/html/rfc1738 - URLs
*/
private String encodeUnusualChars(String aText){
StringBuilder result = new StringBuilder();
CharacterIterator iter = new StringCharacterIterator(aText);
for(char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
char[] chars = {c};
String character = new String(chars);
if(isSimpleCharacter(character)){
result.append(c);
}
else {
hexEncode(character, "UTF-8", result);
}
}
return result.toString();
}
private boolean isSimpleCharacter(String aCharacter){
Matcher matcher = SIMPLE_CHARS.matcher(aCharacter);
return matcher.matches();
}
/**
For the given character and encoding, appends one or more hex-encoded characters.
For double-byte characters, two hex-encoded items will be appended.
*/
private static void hexEncode(String aCharacter, String aEncoding, StringBuilder
aOut) {
try {
String HEX_DIGITS = "0123456789ABCDEF";
byte[] bytes = aCharacter.getBytes(aEncoding);
for (int idx = 0; idx < bytes.length; idx++) {
aOut.append('%');
aOut.append(HEX_DIGITS.charAt((bytes[idx] & 0xf0) >> 4));
aOut.append(HEX_DIGITS.charAt(bytes[idx] & 0xf));
}
}
catch (UnsupportedEncodingException ex) {
log(ex.getStackTrace());
}
}
}
See Also :
Send an email
Send trouble-ticket emails
83
Layout Managers
Layout Managers should almost always be used. Hard coding positions and sizes is usually not
appropriate.
Most common layouts:
GridBagLayout - excellent when many
BoxLayout (and associated Box class) -
components involved
"glue" and "struts" (defined in Box , not BoxLayout ),
combined with proper alignment and equalization of sizes, produce results pleasing to the eye
BorderLayout - often suitable for a top level container
Least common layouts:
FlowLayout - items can appear on different rows, according
GridLayout - forces all components to be the same size
SpringLayout - added in JDK 1.4
to size of container
Layout Managers are often mixed together in a single frame or dialog, where a top level container has its
own Layout Manager (often a BoxLayout or BorderLayout), and smaller parts use their own layout,
completely independent of the others.
Please see the topic on JGoodies Forms for an interesting alternative to the standard JDK classes for
layouts.
Example 1
Here's an example of a dialog box which uses four nested BoxLayout objects to arrange its elements. The
brightly colored areas group elements which share the same BoxLayout :
Red - (vertical layout) "struts" provide uniform spacing around each JButton, while "glue" takes up the
remaining extra space below the Delete button
Blue - (horizontal layout) again, struts provide uniform spacing around each JButton, and glue takes up
the remaining extra space to the left of the OK button.
JTable
Add-Change-Delete
84
and the
buttons
Yellow - (vertical layout) the top level container for Black and Blue
Yellow and Blue form a standardized dialog, in which top level layout and the OK, Cancel buttons are
defined by an abstract base class. Concrete subclasses define both the contents of Black and the action
taken by the OK button.
The sizing and alignment of JButton objects in BoxLayout is not automatic, unfortunately. See Common
utility tasks for these related utility methods :
getCommandRow
getCommandColumn
equalizeSizes
alignAllX , alignAllY
Example 2
Here's a typical data entry dialog :
See Also :
Standardized dialogs
Change theme
Input dialogs
import
import
import
import
import
import
java.awt.event.ActionListener;
javax.swing.JButton;
javax.swing.JFrame;
javax.swing.JLabel;
javax.swing.JPanel;
javax.swing.JOptionPane;
/**
Simple harness for testing GUI code.
<P>To use this class, edit the code to suit your needs.
*/
public final class MinimalSwingApplication {
/**
Build and display minimal GUI.
<P>The GUI has a label and an OK button.
The OK button launches a simple message dialog.
No menu is included.
*/
public static void main(String... aArgs){
MinimalSwingApplication app = new MinimalSwingApplication();
app.buildAndDisplayGui();
}
// PRIVATE
private void buildAndDisplayGui(){
JFrame frame = new JFrame("Test Frame");
buildContent(frame);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private void buildContent(JFrame aFrame){
JPanel panel = new JPanel();
panel.add(new JLabel("Hello"));
JButton ok = new JButton("OK");
ok.addActionListener(new ShowDialog(aFrame));
panel.add(ok);
aFrame.getContentPane().add(panel);
}
private static final class ShowDialog implements ActionListener {
/** Defining the dialog's owner JFrame is highly recommended. */
ShowDialog(JFrame aFrame){
fFrame = aFrame;
}
@Override public void actionPerformed(ActionEvent aEvent) {
JOptionPane.showMessageDialog(fFrame, "This is a dialog");
}
private JFrame fFrame;
}
}
which do not change the state of the publisher). Interfaces are used to ensure that publishers and
subscribers have only minimal knowledge of each other.
When using Swing, there is a choice among using Observable and Observer , or one of the many
XXXListener interfaces which apply to GUI components.
Although Observer is indeed an interface, Observable is neither an interface nor an abstract base class:
it's a concrete class. This is very unusual. Almost all implementations of Design Patterns define the
important items as either interfaces or abstract base classes, not as concrete classes.
Example
The CurrentPortfolio class is a non-graphical class which encapsulates data regarding a set of stocks a stock portfolio. It's the central abstraction of its application, and a number of other classes need to
interact with it, both as active and passive listeners. Since it's a non-graphical class, it extends
Observable . Note how it calls:
Observable.setChanged()
whenever its underlying data changes, informing its listeners of changes to the portfolio.
The FileSaveAction class is an "active" Observer of the CurrentPortfolio: it both reacts to changes
in the CurrentPortfolio, and changes it. It reacts to CurrentPortfolio in its update method, where it
enables the action only when the CurrentPortfolio has unsaved edits. When FileSaveAction itself is
executed, on the other hand, its actionPerformed method acts upon CurrentPortfolio by changing its
state to "no unsaved edits".
package hirondelle.stocks.file;
import
import
import
import
import
import
import
import
java.awt.event.*;
javax.swing.*;
java.util.*;
hirondelle.stocks.portfolio.PortfolioDAO;
hirondelle.stocks.portfolio.CurrentPortfolio;
hirondelle.stocks.util.ui.UiUtil;
java.util.logging.Logger;
hirondelle.stocks.util.Util;
/**
* Save the edits performed on the {@link CurrentPortfolio}, and update the display
* to show that the <tt>CurrentPortfolio</tt> no longer needs a save.
*/
public final class FileSaveAction extends AbstractAction implements Observer {
/**
* Constructor.
*
* @param aCurrentPortfolio is to be saved by this action.
*/
public FileSaveAction(CurrentPortfolio aCurrentPortfolio) {
super("Save", UiUtil.getImageIcon("/toolbarButtonGraphics/general/Save"));
fCurrentPortfolio = aCurrentPortfolio;
fCurrentPortfolio.addObserver(this);
putValue(SHORT_DESCRIPTION, "Save edits to the current portfolio");
putValue(
ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK)
);
putValue(LONG_DESCRIPTION, "Save edits to the current portfolio");
putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_S));
}
@Override public void actionPerformed(ActionEvent e) {
fLogger.info("Saving edits to the current portfolio.");
PortfolioDAO portfolioDAO = new PortfolioDAO();
portfolioDAO.save(fCurrentPortfolio.getPortfolio());
fCurrentPortfolio.setNeedsSave(false);
fCurrentPortfolio.notifyObservers();
}
88
/**
* Synchronize the state of this object with the state of the
* <tt>CurrentPortfolio</tt> passed to the constructor.
*
* This action is enabled only when the <tt>CurrentPortfolio</tt> is titled and
* needs a save.
*/
@Override public void update(Observable aPublisher, Object aData) {
setEnabled(fCurrentPortfolio.getNeedsSave() && !fCurrentPortfolio.isUntitled());
}
// PRIVATE
private CurrentPortfolio fCurrentPortfolio;
private static final Logger fLogger = Util.getLogger(FileSaveAction.class);
}
See Also :
Filter table rows
Preferences dialogs
Settings for user preferences are used in most graphical applications. These settings control the operation
and appearance of a program, and need to be available to many classes. On the other hand, the dialogs
for user preferences are usually quite extensive, and can take a long time to build and display to the user.
An advantageous design would allow quick programmatic access to preferences, but construct GUIs only
if necessary.
Example
A common style is to use a JTabbedPane, with each pane corresponding to a set of related preferences:
89
The getUI method of this interface defines the graphical aspect of a PreferencesEditor implementations do not extend a JComponent , but rather return a JComponent from the getUI method.
This allows callers to access preference values from implementations of PreferencesEditor without
necessarily constructing a GUI.
Here's an example implementation for the preferences shown above. Note the addition of these methods,
specific to this class, which allow programmatic, read-only access to stored preference values (and not
the preference values as currently displayed in the GUI):
hasShowToolBar
hasLargeIcons
getTheme
This implementation extends Observable , such that interested classes may register their interest in
changes to this set of preferences:
package hirondelle.stocks.preferences;
import
import
import
import
import
import
import
java.util.*;
java.util.logging.*;
java.awt.*;
java.awt.event.*;
javax.swing.*;
java.util.prefs.*;
javax.swing.plaf.metal.MetalTheme;
import
import
import
import
hirondelle.stocks.util.ui.Theme;
hirondelle.stocks.util.ui.UiConsts;
hirondelle.stocks.util.ui.UiUtil;
hirondelle.stocks.util.Util;
/**
* Allows editing of user preferences related to the general
* appearance of the application, such as font size, toolbar icon size, theme,
* and the like.
*
* <P>Also allows programmatic read-only access to the current stored preferences
* for these items.
*/
public final class GeneralLookPreferencesEditor extends Observable
implements PreferencesEditor {
@Override public JComponent getUI(){
JPanel content = new JPanel();
GridBagLayout gridbag = new GridBagLayout();
content.setLayout(gridbag);
addShowToolbarAndLargeIcons(content);
addTheme(content);
addRestoreDefaults(content);
UiUtil.addVerticalGridGlue(content, 4);
matchGuiToStoredPrefs();
return content;
}
@Override public String getTitle() {
return TITLE;
}
@Override public int getMnemonic() {
return MNEMONIC;
}
savePreferences(){
91
aContent.add(iconSize, getConstraints(1,0));
fLargeIcons = new JCheckBox("Use Large Icons");
fLargeIcons.setMnemonic(KeyEvent.VK_U);
iconSize.setLabelFor(fLargeIcons);
aContent.add(fLargeIcons, getConstraints(1,1));
}
private void addTheme(JPanel aContent) {
JLabel theme = new JLabel("Theme:");
theme.setDisplayedMnemonic(KeyEvent.VK_T);
aContent.add(theme, getConstraints(2,0));
DefaultComboBoxModel<MetalTheme> themesModel = new
DefaultComboBoxModel<MetalTheme>(
Theme.VALUES.toArray(new MetalTheme[0])
);
fThemes = new JComboBox<MetalTheme>(themesModel);
theme.setLabelFor(fThemes);
aContent.add(fThemes, getConstraints(2,1));
}
private void addRestoreDefaults(JPanel aContent) {
JButton restore = new JButton("Restore Defaults");
restore.setMnemonic(KeyEvent.VK_D);
restore.addActionListener( new ActionListener() {
@Override public void actionPerformed(ActionEvent event) {
matchGuiToDefaultPreferences();
}
});
GridBagConstraints constraints = UiUtil.getConstraints(3,1);
constraints.insets = new Insets(UiConsts.ONE_SPACE, 0,0,0);
aContent.add( restore, constraints );
}
private GridBagConstraints getConstraints(int aY, int aX){
GridBagConstraints result = UiUtil.getConstraints(aY, aX);
result.insets = new Insets(0, 0, UiConsts.ONE_SPACE, UiConsts.ONE_SPACE);
return result;
}
}
See Also :
Standardized dialogs
Using preferences
In Swing applications, the default behavior still prints the stack trace to the console, but the Event
Dispatch Thread (the main Swing thread) is not terminated. The Event Dispatch Thread always remains
alive, in spite of any RuntimeExceptions thrown by your program.
However, simply printing a stack trace to the console in a Swing application is not very informative for
the average user. Instead, you should likely override Java's default handling of uncaught exceptions. The
benefits are:
the end user is kept informed of problems more effectively
new behavior can be added, such as logging or sending emails to support staff
If you're using JDK 5+, then you may define your own UncaughtExceptionHandler . They can be
defined at 3 different levels. From the highest level to the lowest level, they are:
for the whole Java Runtime, call Thread.setDefaultUncaughtExceptionHandler upon startup
for a ThreadGroup, override ThreadGroup.uncaughtException
for a single Thread, call Thread.setUncaughtExceptionHandler
Example
Here's an example of such a handler class.
package hirondelle.movies.exception;
import hirondelle.movies.util.Util;
import hirondelle.movies.util.ui.UiUtil;
import
import
import
import
java.io.PrintWriter;
java.io.StringWriter;
java.io.Writer;
java.util.logging.Logger;
import javax.swing.JOptionPane;
/**
Custom handler for any uncaught exceptions.
<P>By default, a Swing app will handle uncaught exceptions simply by
printing a stack trace to {@link System#err}. However, the end user will
not usually see that, and if they do, they will not likely understand it.
This class addresses that problem, by showing the end user a
simple error message in a modal dialog. (The dialog's owner is the
currently active frame.)
*/
public final class ExceptionHandler implements Thread.UncaughtExceptionHandler {
/**
Custom handler for uncaught exceptions.
<P>Displays a simple model dialog to the user, showing that an error has
occured. The text of the error includes {@link Throwable#toString()}.
The stack trace is logged at a SEVERE level.
*/
@Override public void uncaughtException(Thread aThread, Throwable aThrowable) {
fLogger.severe(getStackTrace(aThrowable));
JOptionPane.showMessageDialog(
UiUtil.getActiveFrame(), "Error: " + aThrowable.toString(),
"Error", JOptionPane.ERROR_MESSAGE
);
}
// PRIVATE
private static final Logger fLogger = Util.getLogger(ExceptionHandler.class);
private String getStackTrace(Throwable aThrowable) {
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
aThrowable.printStackTrace(printWriter);
return result.toString();
}
94
For JDK 1.4, the simplest way of overriding the default handler for uncaught exceptions is to use the
following undocumented feature (which has actually been removed from JDK 7):
create a class with a no-argument constructor
add to it a method public void handle(Throwable aThrowable){...}
upon startup, add a System property named 'sun.awt.exception.handler', whose value is the
fully qualified class name of this new handler class
See Also :
Stack trace as String
Example 1
The default action of setValue is to place the result of toString into a cell. This value may not be
appropriate.
Here, Stock.toString would place unwanted data in the cell. The setValue method is overridden to
customize both the text placed in a cell, and its associated tooltip:
package hirondelle.stocks.table;
95
import javax.swing.table.*;
import hirondelle.stocks.quotes.Stock;
import hirondelle.stocks.util.Util;
/**
* Display a {@link Stock} in a table cell by placing the
* full name in the cell, and by providing its Yahoo ticker
* (including suffix for the {@link hirondelle.stocks.quotes.Exchange}) as tooltip.
*/
final class RenderStockName extends DefaultTableCellRenderer {
@Override public void setValue(Object aValue) {
Object result = aValue;
if ((aValue != null) && (aValue instanceof Stock)) {
Stock stock = (Stock) aValue;
result = stock.getName();
setTooltip(stock);
}
super.setValue(result);
}
private void setTooltip(Stock aStock) {
StringBuilder tooltip = new StringBuilder("Yahoo Ticker: ");
tooltip.append(aStock.getTicker());
String suffix = aStock.getExchange().getTickerSuffix();
if (Util.textHasContent(suffix)) {
tooltip.append(".");
tooltip.append(suffix);
}
setToolTipText(tooltip.toString());
}
}
Example 2
Use the current locale to render a Number as a currency:
package hirondelle.stocks.table;
import javax.swing.table.*;
import javax.swing.*;
import java.text.NumberFormat;
/**
* Display a <tt>Number</tt> in a table cell in the format defined by
* {@link NumberFormat#getCurrencyInstance()}, and aligned to the right.
*/
final class RenderPrice extends DefaultTableCellRenderer {
RenderPrice() {
setHorizontalAlignment(SwingConstants.RIGHT);
}
@Override public void setValue(Object aValue) {
Object result = aValue;
if ((aValue != null) && (aValue instanceof Number)) {
Number numberValue = (Number)aValue;
NumberFormat formatter = NumberFormat.getCurrencyInstance();
result = formatter.format(numberValue.doubleValue());
}
super.setValue(result);
}
}
Example 3
Render as red or green, according to the sign of a Number. The implementation overrides
getTableCellRendererComponent instead of setValue , and returns a this reference.
96
package hirondelle.stocks.table;
import javax.swing.table.*;
import javax.swing.*;
import java.awt.*;
/**
* Display a <tt>Number</tt> in a table cell as either red (for negative values)
* or green (for non-negative values), and aligned on the right.
*
* <P>Note that this class will work with any <tt>Number</tt> * <tt>Double</tt>, <tt>BigDecimal</tt>, etc.
*/
final class RenderRedGreen extends DefaultTableCellRenderer {
RenderRedGreen () {
setHorizontalAlignment(SwingConstants.RIGHT);
}
@Override public Component getTableCellRendererComponent(
JTable aTable, Object aNumberValue, boolean aIsSelected,
boolean aHasFocus, int aRow, int aColumn
) {
/*
* Implementation Note :
* It is important that no 'new' objects be present in this
* implementation (excluding exceptions):
* if the table is large, then a large number of objects would be
* created during rendering.
*/
if (aNumberValue == null) return this;
Component renderer = super.getTableCellRendererComponent(
aTable, aNumberValue, aIsSelected, aHasFocus, aRow, aColumn
);
Number value = (Number)aNumberValue;
if (value.doubleValue() < 0) {
renderer.setForeground(Color.red);
}
else {
renderer.setForeground(fDarkGreen);
}
return this;
}
// PRIVATE
//the default green is too bright and illegible
private Color fDarkGreen = Color.green.darker();
}
97
See Also :
Sort table rows
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
java.util.Collections;
java.util.List;
import
import
import
import
import
import
javax.swing.JButton;
javax.swing.JFrame;
javax.swing.JPanel;
javax.swing.JScrollPane;
javax.swing.JTable;
javax.swing.RowSorter;
{4, "F"}};
The above example unsorts the table when a button is clicked. To unsort a table, and revert it back to its
original state, you simply pass a null or empty List to this method:
table.getRowSorter().setSortKeys(List)
java.awt.event.MouseAdapter;
java.awt.event.MouseEvent;
java.util.ArrayList;
java.util.List;
import
import
import
import
import
import
import
import
javax.swing.JFrame;
javax.swing.JPanel;
javax.swing.JScrollPane;
javax.swing.JTable;
javax.swing.RowSorter;
javax.swing.SortOrder;
javax.swing.table.TableModel;
javax.swing.table.TableRowSorter;
frame.pack();
frame.setVisible(true);
}
private void addSortableTableTo(JFrame aFrame){
JPanel panel = new JPanel();
Object[][] data = { {1,"T"},
{2,"B"},
String[] cols = {"One", "Two"};
fTable = new JTable(data, cols);
{3,"A"},
{4, "F"}};
100
The above technique is implemented by the Movies app, using the following classes:
Movie - the Model Object, that defines the
MainWindow - wires the clicks on the table
sortByColumn )
MovieTableModel - uses the Comparator s
needed Comparator s
header to a method call on the table model (here called
to sort its list of Model Objects
See Also :
Type-Safe Enumerations
Filter table rows
Splash screen
Splash screens are simple graphics which load quickly upon startup, and assure the user that the
application is loading promptly. The SplashScreen class was added to JSE 6, and should be used if
available.
If JSE 6 is not available, then the following technique is provided as an alternative.
Various policies for controlling the appearance and disappearance of the splash screen are possible. Here,
the splash screen disappears when a main window has finished loading.
This example is also of interest since it demonstrates clearly that the launch thread is unusual - it's distinct
from the event dispatch thread, where most of the work of a Swing application takes place:
upon launch, the launch thread shows a splash screen, which activates the event dispatch thread
once the splash screen is showing (or, more precisely, is "realized"), the launch thread acts as a
"worker thread", and can only close the splash screen indirectly, using EventQueue.invokeLater
to properly inject an event into the event dispatch thread
package hirondelle.stocks.main;
import java.awt.*;
import java.net.URL;
import java.awt.image.ImageObserver;
/**
* Present a simple graphic to the user upon launch of the application, to
* provide a faster initial response than is possible with the main window.
*
* <P>Adapted from an
* <a href=http://developer.java.sun.com/developer/qow/archive/24/index.html>item</a>
* on Sun's Java Developer Connection.
*
* <P>This splash screen appears within about 2.5 seconds on a development
* machine. The main screen takes about 6.0 seconds to load, so use of a splash
* screen cuts down the initial display delay by about 55 percent.
*
* <P>When JDK 6+ is available, its java.awt.SplashScreen class should be used instead
* of this class.
*/
final class SplashScreen extends Frame {
/**
* Construct using an image for the splash screen.
101
*
* @param aImageId must have content, and is used by
* {@link Class#getResource(java.lang.String)} to retrieve the splash screen image.
*/
SplashScreen(String aImageId) {
/*
* Implementation Note
* Args.checkForContent is not called here, in an attempt to minimize
* class loading.
*/
if (aImageId == null || aImageId.trim().length() == 0){
throw new IllegalArgumentException("Image Id does not have content.");
}
fImageId = aImageId;
}
/**
* Show the splash screen to the end user.
*
* <P>Once this method returns, the splash screen is realized, which means
* that almost all work on the splash screen should proceed through the event
* dispatch thread. In particular, any call to <tt>dispose</tt> for the
* splash screen must be performed in the event dispatch thread.
*/
void splash(){
initImageAndTracker();
setSize(fImage.getWidth(NO_OBSERVER), fImage.getHeight(NO_OBSERVER));
center();
fMediaTracker.addImage(fImage, IMAGE_ID);
try {
fMediaTracker.waitForID(IMAGE_ID);
}
catch(InterruptedException ex){
System.out.println("Cannot track image load.");
}
SplashWindow splashWindow = new SplashWindow(this,fImage);
}
// PRIVATE
private final String fImageId;
private MediaTracker fMediaTracker;
private Image fImage;
private static final ImageObserver NO_OBSERVER = null;
private static final int IMAGE_ID = 0;
private void initImageAndTracker(){
fMediaTracker = new MediaTracker(this);
URL imageURL = SplashScreen.class.getResource(fImageId);
fImage = Toolkit.getDefaultToolkit().getImage(imageURL);
}
/**
* Centers the frame on the screen.
*
*<P>This centering service is more or less in {@link
hirondelle.stocks.util.ui.UiUtil};
* this duplication is justified only because the use of
* {@link hirondelle.stocks.util.ui.UiUtil} would entail more class loading, which is
graphics.drawImage(fImage,0,0,this);
}
}
private Image fImage;
}
/**
* Developer test harness shows the splash screen for a fixed length of
* time, without launching the full application.
*/
private static void main(String... aArgs){
SplashScreen splashScreen = new SplashScreen("StocksMonitor.gif");
splashScreen.splash();
try {
Thread.sleep(2000);
}
catch(InterruptedException ex) {
System.out.println(ex);
}
System.exit(0);
}
}
Here's an example of a class which launches an application using the above SplashScreen:
package hirondelle.stocks.main;
import
import
import
import
java.util.logging.*;
java.awt.EventQueue;
hirondelle.stocks.util.Util;
hirondelle.stocks.util.Consts;
/**
* Launch the application using an older version of a splash screen.
*
*<P>Perform tasks in this order :
*<ul>
* <li>log basic system information
* <li>promptly show a splash screen upon startup
* <li>show the main screen
* <li>remove the splash screen once the main screen is shown
*</ul>
*
* These tasks are performed in a thread-safe manner.
*/
public final class Launcher {
/**
* Launch the application and display the main window.
*
* @param aArgs are ignored by this application, and may take any value.
*/
public static void main (String... aArgs) {
/*
* Implementation Note:
*
* Note that the launch thread of any GUI application is in effect an initial
* worker thread - it is not the event dispatch thread, where the bulk of
processing
* takes place. Thus, once the launch thread realizes a window, then the launch
* thread should almost always manipulate such a window through
* EventQueue.invokeLater. (This is done for closing the splash
* screen, for example.)
*/
//verifies that assertions are on:
// assert(false) : "Test";
logBasicSystemInfo();
showSplashScreen();
showMainWindow();
EventQueue.invokeLater(new SplashScreenCloser());
fLogger.info("Launch thread now exiting...");
}
103
// PRIVATE
private static SplashScreen fSplashScreen;
private static final Logger fLogger = Util.getLogger(Launcher.class);
private static final String SPLASH_IMAGE = "StocksMonitor.gif";
/**
* Show a simple graphical splash screen, as a quick preliminary to the main screen.
*/
private static void showSplashScreen(){
fLogger.info("Showing the splash screen.");
fSplashScreen = new SplashScreen(SPLASH_IMAGE);
fSplashScreen.splash();
}
/**
* Display the main window of the application to the user.
*/
private static void showMainWindow(){
fLogger.info("Showing the main window.");
StocksMonitorMainWindow mainWindow = new StocksMonitorMainWindow();
}
/**
* Removes the splash screen.
*
* Invoke this <tt>Runnable</tt> using
* <tt>EventQueue.invokeLater</tt>, in order to remove the splash screen
* in a thread-safe manner.
*/
private static final class SplashScreenCloser implements Runnable {
@Override public void run(){
fLogger.fine("Closing the splash screen.'");
fSplashScreen.dispose();
}
}
private static void logBasicSystemInfo() {
fLogger.info("Launching the application...");
fLogger.config(
"Operating System: " + System.getProperty("os.name") + " " +
System.getProperty("os.version")
);
fLogger.config("JRE: " + System.getProperty("java.version"));
fLogger.info("Java Launched From: " + System.getProperty("java.home"));
fLogger.config("Class Path: " + System.getProperty("java.class.path"));
fLogger.config("Library Path: " + System.getProperty("java.library.path"));
fLogger.config("Application Name: " + Consts.APP_NAME + "/" + Consts.APP_VERSION);
fLogger.config("User Home Directory: " + System.getProperty("user.home"));
fLogger.config("User Working Directory: " + System.getProperty("user.dir"));
fLogger.info("Test INFO logging.");
fLogger.fine("Test FINE logging.");
fLogger.finest("Test FINEST logging.");
}
}
See Also :
Swing threads
Standardized dialogs
Inconsistencies in a user interface usually decrease the perceived quality of an application. So, it's
probably a good idea to standardize the layout, appearance, and behavior of all the dialogs in your
application.
104
Example
Here's a class which acts as a template for all dialogs havingOK andCancel buttons. It standardizes these
items:
the form of the dialog title
the dialog icon
the layout and spacing of principal elements
the centering behavior
the resizing behavior
Note the two abstract methods that must be implemented by its subclasses: getEditorUI and okAction .
The other methods are declared as final.
The callers of this class don't usually need to subclass it directly, at the top level. Rather, a private, nested
class can be used instead.
package hirondelle.stocks.util.ui;
import java.util.*;
import javax.swing.*;
import java.awt.event.*;
import hirondelle.stocks.util.Args;
/**
* Abstract Base Class for a dialog with standard layout, buttons, and behavior.
* (The name <tt>StandardEditor</tt> was chosen since almost all non-trivial
* dialogs allow the user to edit data in some way.)
*
* <P>Use of this class will apply a standard appearance to
* dialogs in the application.
*
* <P> Subclasses implement the body of the dialog (wherein business objects
* are manipulated), and the action taken by the <tt>OK</tt> button.
*
* <P>Services of a <tt>StandardEditor</tt> include:
*<ul>
* <li>centering on the parent frame
* <li>reusing the parent's icon
* <li>standard layout and border spacing, based on Java Look and Feel guidelines.
* <li>uniform naming style for dialog title, with the application name appearing first
* <li><tt>OK</tt> and <tt>Cancel</tt> buttons at the bottom of the dialog * <tt>OK</tt> is the default, and the <tt>Escape</tt> key activates
* <tt>Cancel</tt> (the latter works only if the dialog receives the escape
* keystroke, and not one of its components)
* <li>disabling of resizing
*</ul>
*
* <P>The <tt>Escape</tt> key does not always work (for example, when a
* <tt>JTable</tt> row has the focus)
*/
public abstract class StandardEditor {
/**
* Constructor.
* @param aTitle text which appears in the title
* the application; must have content.
* @param aParent window to which this dialog is
* @param aCloseAction sets the behaviour of the
*/
protected StandardEditor (String aTitle, JFrame
Args.checkForContent(aTitle);
Args.checkForNull(aParent);
fTitle = aTitle;
fParent = aParent;
fCloseAction = aCloseAction.getValue();
}
/**
* Forces calls to constructor to have greater clarity, by using an
* enumeration instead of integers.
105
*/
protected enum CloseAction {
DISPOSE(JDialog.DISPOSE_ON_CLOSE),
HIDE(JDialog.HIDE_ON_CLOSE);
int getValue(){
return fAction;
}
private final int fAction;
private CloseAction(int aAction){
fAction = aAction;
}
}
/**
* Display this <tt>StandardEditor</tt> to the user.
*
* <P>Follows the Java Look and Feel guidelines for spacing elements.
*/
public final void showDialog(){
boolean isModal = true;
fDialog = new JDialog(fParent, UiUtil.getDialogTitle(fTitle), isModal);
fDialog.setDefaultCloseOperation(fCloseAction);
fDialog.setResizable(false);
addCancelByEscapeKey();
JPanel standardLayout = new JPanel();
standardLayout.setLayout(new BoxLayout(standardLayout, BoxLayout.Y_AXIS));
standardLayout.setBorder(UiUtil.getStandardBorder());
standardLayout.add(getEditorUI());
standardLayout.add(getCommandRow());
fDialog.getContentPane().add(standardLayout);
UiUtil.centerOnParentAndShow(fDialog);
}
/** Close the editor dialog.
public final void dispose(){
fDialog.dispose();
}
*/
/**
* Return the GUI which allows the user to manipulate the business
* objects related to this dialog; this GUI will be placed above the
* <tt>OK</tt> and <tt>Cancel</tt> buttons, in a standard manner.
*/
protected abstract JComponent getEditorUI();
/**
* The action taken when the user hits the <tt>OK</tt> button.
*/
protected abstract void okAction();
// PRIVATE
private final String fTitle;
private final JFrame fParent;
private JDialog fDialog;
private final int fCloseAction;
/**
* Return a standardized row of command buttons, right-justified and
* all of the same size, with OK as the default button, and no mnemonics used,
* as per the Java Look and Feel guidelines.
*/
private JComponent getCommandRow() {
JButton ok = new JButton("OK");
ok.addActionListener( new ActionListener() {
@Override public void actionPerformed(ActionEvent event) {
okAction();
}
});
fDialog.getRootPane().setDefaultButton( ok );
JButton cancel = new JButton("Cancel");
cancel.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent event) {
closeDialog();
}
});
106
See Also :
Overridable methods need special care
Look and Feel guidelines
Swing utility tasks
Input dialogs
Don't subclass JDialog or JFrame
Swing in general
Swing is a large and complex part of the Java libraries. Perhaps not unexpectedly, it's also challenging to
learn and use. It also has some bugs.
Useful tools from Sun include:
the graphics repository of commonly used icons
Java Web Start for deploying and updating applications over a network
JavaHelp for creating HTML help systems
the Look and Feel Design Guidelines (also discussed here)
the Swing Tutorial
Long methods
There is a widespread tendency for GUI code - in particular initialization code - to be implemented with
very long methods. This is probably a bad idea, for the same reason that all excessively long methods are
107
Swing threads
Event Dispatch Thread
In a Swing application, most of the processing takes place in a single, special thread called the event
dispatch thread (EDT).
This thread becomes active after a component becomes realized: either pack , show , or
setVisible(true) has been called. When a top level window is realized, all of its components are also
realized. Swing is mostly single-threaded: almost all calls to realized components should execute in the
event dispatch thread. The thread-safe exceptions are:
some methods of JComponent : repaint, revalidate , invalidate
all addXXXListener and removeXXXListener methods
all methods explicitly documented as thread-safe
Worker Threads Keep GUIs Responsive
If a task needs a relatively long time to complete, then performing that task in the event dispatch thread
will cause the user interface to become unresponsive for the duration of the task - the GUI becomes
"locked". Since this is undesirable, such tasks are usually performed outside the event dispatch thread, on
what is commonly referred to as a worker thread.
When a worker thread completes its task, it needs a special mechanism for updating realized GUI
components, since, as stated above, realized components almost always need to be updated only from the
event dispatch thread.
With modern JDK's, the most common way of doing this is the SwingWorker class, introduced in JSE 6.
108
JDK < 6
Other techniques are needed when using older versions of the JDK. Two methods of the EventQueue
class, invokeLater and invokeAndWait , are provided for this purpose ( SwingUtilities has synonymous
methods as well). Sun recommends using invokeLater as the usual preferred style.
When using threads in this way, it is usually a good idea to use daemon threads, not user threads,
whenever possible: daemon threads will not prevent an application from terminating. Since threads are
user threads by default, an explicit call to Thread.setDaemon(true) is required.
The remaining examples use JSE 1.5.
Example 2
The Splash Screen topic (and in particular its Launcher class) is a good example of using a worker
thread. Here, the status of the launch thread as a worker thread is exploited to show a splash screen to the
user, but only until the main window has finished loading.
Example 3
The ColorTip class, shown below, changes the background color of a component for a short, fixed
interval of time, as a simple way of calling attention to that component.
Its worker thread does not work very hard - it sleeps a lot. The calls to sleep do not cause the GUI to
become unresponsive, however, since these calls do not take place in the event dispatch thread.
ColorTip
of a target component
back to its original color
package hirondelle.stocks.quotes;
import hirondelle.stocks.util.Args;
import hirondelle.stocks.util.Consts;
import hirondelle.stocks.util.Util;
import java.awt.Color;
import java.awt.EventQueue;
import java.util.logging.Logger;
import javax.swing.JComponent;
/**
* Calls user's attention to an aspect of the GUI (much like a
* <tt>ToolTip</tt>) by changing the background color of a
* component (typically a <tt>JLabel</tt>) for a few seconds;
* the component will always revert to its original background color
* after a short time has passed. This is done once, without repeating.
*
* <p>Example use case:
<pre>
//no initial delay, and show the new color for 2 seconds only
ColorTip tip = new ColorTip(0, 2, someLabel, temporaryColor);
tip.start();
</pre>
110
*
* Uses a daemon thread, so this class will not prevent a program from
* terminating. Will not lock the GUI.
*/
final class ColorTip {
/**
* Constructor.
*
* @param aInitialDelay number of seconds to wait before changing the
* background color of <tt>aComponent</tt>, and must be in range 0..60 (inclusive).
* @param aActivationInterval number of seconds to display <tt>aTempColor</tt>,
* and must be in range 1..60 (inclusive).
* @param aComponent GUI item whose background color will be changed.
* @param aTempColor background color which <tt>aComponent</tt> will take for
* <tt>aActivationInterval</tt> seconds.
*/
ColorTip (
int aInitialDelay, int aActivationInterval, JComponent aComponent, Color
aTempColor
) {
Args.checkForRange(aInitialDelay, 0, Consts.SECONDS_PER_MINUTE);
Args.checkForRange(aActivationInterval, 1, Consts.SECONDS_PER_MINUTE);
Args.checkForNull(aTempColor);
fInitialDelay = aInitialDelay;
fActivationInterval = aActivationInterval;
fComponent = aComponent;
fTemporaryColor = aTempColor;
fOriginalColor = aComponent.getBackground();
fOriginalOpacity = aComponent.isOpaque();
}
/**
* Temporarily change the background color of the component, without interfering with
* the user's control of the gui, and without preventing program termination.
*
* <P>If the target temporary color is the same as the current background color, then
int fInitialDelay;
int fActivationInterval;
JComponent fComponent;
Color fTemporaryColor;
Color fOriginalColor;
int fCONVERSION_FACTOR = Consts.MILLISECONDS_PER_SECOND;
/**
* Stores the original value of the opaque property of fComponent.
*
* Changes to the background color of a component
* take effect only if the component is in charge of drawing its background.
* This is defined by the opaque property, which needs to be true for these
* changes to take effect.
*
* <P>If fComponent is not opaque, then this property is temporarily
* changed by this class in order to change the background color.
*/
private final boolean fOriginalOpacity;
private static final Logger fLogger = Util.getLogger(ColorTip.class);
111
/**
* Return true only if fTemporaryColor is the same as the fOriginalColor.
*/
private boolean isSameColor(){
return fTemporaryColor.equals(fOriginalColor);
}
/**
The sleeping done by this class is NOT done on the Event Dispatch Thread;
that would lock the GUI.
*/
private final class Worker implements Runnable {
@Override public void run(){
try {
fLogger.fine("Initial Sleeping...");
Thread.sleep(fCONVERSION_FACTOR * fInitialDelay);
EventQueue.invokeLater(new ChangeColor());
fLogger.fine("Activation Sleeping...");
Thread.sleep(fCONVERSION_FACTOR * fActivationInterval);
EventQueue.invokeLater(new RevertColor());
}
catch (InterruptedException ex) {
fLogger.severe("Cannot sleep.");
}
fLogger.fine("Color worker done.");
}
}
private final class ChangeColor implements Runnable {
@Override public void run(){
if (! fOriginalOpacity) {
fComponent.setOpaque(true);
}
fComponent.setBackground(fTemporaryColor);
}
}
private final class RevertColor implements Runnable {
@Override public void run(){
fComponent.setBackground(fOriginalColor);
fComponent.setOpaque(fOriginalOpacity);
}
}
}
See Also :
Splash screen
Timers
See Also :
Look and Feel guidelines
Layout Managers
Align menu items
Timers
Timers are used to perform actions periodically, after an initial delay, or both. Also, actions can be
performed only once, if desired.
Here are some reminders regarding the two flavors of timers, as used in Swing applications:
javax.swing.Timer
suitable for simpler cases, using low numbers of timers (say less than a dozen)
runs ActionListener objects on the event dispatch thread
if the task completes quickly (say under 300 milliseconds), then it may be run directly on the event
dispatch thread, without locking the GUI.
if the task takes a long time to complete, then it should do its work on a background thread, usually
with a SwingWorker
java.util.Timer
*/
public final class AboutAction extends AbstractAction {
//elided...
@Override public void actionPerformed(ActionEvent e) {
fLogger.info("Showing the about box.");
showAboutBox();
}
// PRIVATE
//elided...
/** Displays the size of the object heap. */
private JLabel fObjectHeapSize;
/** Periodically updates the display of <tt>fObjectHeapSize</tt>. */
private javax.swing.Timer fTimer;
private ActionListener fHeapSizeUpdater;
private static final int UPDATE_FREQ = 2 * Consts.MILLISECONDS_PER_SECOND;
private static final long SLEEP_INTERVAL = 100;
private void showAboutBox(){
JTabbedPane aboutPane = new JTabbedPane();
aboutPane.addTab( "About" , getAboutPanel() );
aboutPane.setMnemonicAt(0, KeyEvent.VK_A);
aboutPane.addTab( "System Info" , getSystemInfoPanel() );
aboutPane.setMnemonicAt(1, KeyEvent.VK_S);
startHeapSizeTimer();
Icon image = UiUtil.getImageIcon("xray-small.jpg", this.getClass()) ;
String title = UiUtil.getDialogTitle("About");
JOptionPane.showMessageDialog(
fFrame, aboutPane, title, JOptionPane.OK_OPTION, image
);
stopHeapSizeTimer();
}
/** Periodically update the display of object heap size. */
private void startHeapSizeTimer(){
//SwingWorker isn't used here, since the action happens more than once,
//and the task doesn't take very long
fHeapSizeUpdater = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
//this returns quickly; it won't lock up the GUI
updateHeapSizeDisplay();
}
};
fTimer = new javax.swing.Timer(UPDATE_FREQ, fHeapSizeUpdater);
fTimer.start();
fLogger.fine("Starting timer...");
}
/**
* Must be called when the About Box is closed - otherwise the timer will continue
* to operate.
*/
private void stopHeapSizeTimer(){
fLogger.fine("Stopping timer...");
fTimer.stop(); //stops notifying registered listeners
fTimer.removeActionListener(fHeapSizeUpdater); //removes the one registered
listener
fHeapSizeUpdater = null;
fTimer = null;
}
private void updateHeapSizeDisplay(){
fLogger.fine("Updating heap size...");
fObjectHeapSize.setText(getHeapSize());
}
/** Return a measure of the current heap size in kilobytes.*/
private String getHeapSize(){
long totalMemory = Runtime.getRuntime().totalMemory();
long freeMemory = Runtime.getRuntime().freeMemory();
Long memoryUseKB = new Long( (totalMemory - freeMemory)/Consts.ONE_KILOBYTE );
StringBuilder result = new StringBuilder();
result.append(UiUtil.getLocalizedInteger(memoryUseKB));
114
result.append(" KB");
return result.toString();
}
}
Example 2
The FetchQuotesAction class uses a javax.swing.Timer to perform a task which is not short lived.
This is acceptable, however, since in this case the Timer is combined with a worker thread, and most of
the task is not performed on the event dispatching thread.
This is an example of a common requirement - that of performing an intensive, periodic task, using both
a timer and worker thread. Another example would be a graphical network management application,
which polls the state of many devices every few minutes, and then updates the user interface with the
results.
Here are the pertinent parts of the code:
/**
* Fetch current quote data for the {@link CurrentPortfolio} from a data
* source on the web.
*
* <P>This class performs most of its work in a background thread,
* using a javax.swing.Timer. The user interface remains responsive,
* regardless of the time taken for its work to complete.
*/
public final class FetchQuotesAction extends AbstractAction implements Observer {
//elided...
/**
* Start an internal Timer, which in turn calls {@link
#actionPerformed(ActionEvent)}.
*
* <P>This method must be called immediately after calling the constructor.
* (Since this operation uses a 'this' reference, it shouldn't be included in the
* constructor itself.)
*/
public void startTimer(){
fQuoteTablePrefEditor.addObserver(this);
fCurrentPortfolio.addObserver(this);
fTimer = new javax.swing.Timer(fUpdateFreq * CONVERSION_FACTOR, this);
fTimer.start();
}
/**
Fetch quotes from the web for the <tt>CurrentPortfolio</tt>.
This is called either explicitly, or periodically, by a Timer.
*/
@Override public void actionPerformed(ActionEvent e) {
fLogger.info("Fetching quotes from web.");
fSummaryView.showStatusMessage("Fetching quotes...");
SwingWorker<List<Quote>, Void> hardWorker = new HardWorker();
hardWorker.execute();
}
// PRIVATE
/**
* The set of {@link Stock} objects in which the user
* is currently interested.
*/
private CurrentPortfolio fCurrentPortfolio;
/**
* Periodically fetches quote data.
*
* <P>Use of a Swing Timer is acceptable here, in spite of the fact that the task
* takes a long time to complete, since the task does <em>not</em> in fact get
* executed on the event-dispatch thread, but on a separate worker thread.
*/
private javax.swing.Timer fTimer;
115
Example 3
For a simple example using java.util.Timer and java.util.TimerTask in a non-Swing context,
please see this related topic.
See Also :
Schedule periodic tasks
Swing threads
Using JavaHelp
Sun's JavaHelp tool creates help systems for graphical applications, using HTML topic files and XML to
define the topic hierarchy. Developers access its services through the javax.help package.
116
Here's an example of using javax.help to launch a JavaHelp system from a standard Help menu.
package hirondelle.stocks.help;
import
import
import
import
import
import
import
java.awt.event.*;
javax.swing.*;
java.util.logging.*;
javax.help.*;
java.net.URL;
hirondelle.stocks.util.Args;
hirondelle.stocks.util.Util;
/**
* Display the help system for the application.
*
* <P>Display one of table of contents, index, or search tab, according
* to argument passed to the constructor. This implementation uses
* Sun's <a href=http://java.sun.com/products/javahelp/>JavaHelp</a> tool.
*
* <P>This action activates the Help key (often <tt>F1</tt>) for this application.
* When the help key is pressed, the help system's table of contents is displayed.
*
* <P>This action is unusual in that it corresponds to more than one menu item
* (Contents, Index, and Search).
*
* <P>Note: the displayed JavaHelp screen is not centered; it's left as is,
* since the JavaHelp GUI is often cut off at the bottom anyway, and centering would
* make this problem worse.
*/
public final class HelpAction extends AbstractAction {
/**
* Constructor.
*
* @param aFrame parent window to which the help window is attached
* @param aText name of the menu item for this help action
* @param aMnemonicKeyEvent mnemonic for <tt>aText</tt>
* @param aIcon possibly-null graphic to be displayed alongside the text, or
* in a toolbar
* @param aView determines which help window is to be displayed: Contents, Index,
* or Search
*/
public HelpAction(
JFrame aFrame, String aText, int aMnemonicKeyEvent, Icon aIcon, View aView
) {
super(aText, aIcon);
Args.checkForNull(aFrame);
Args.checkForNull(aText);
Args.checkForNull(aView);
fFrame = aFrame;
fView = aView;
putValue(SHORT_DESCRIPTION, "StocksMonitor Help");
putValue(LONG_DESCRIPTION, "Displays JavaHelp for StocksMonitor.");
putValue(MNEMONIC_KEY, new Integer(aMnemonicKeyEvent) );
initHelpSystem();
}
@Override public void actionPerformed(ActionEvent event) {
fLogger.info("Showing help system.");
fHelpBroker.setCurrentView( fView.toString() );
fDisplayHelp.actionPerformed( event );
}
/** Enumeration for the style of presentation of the the Help system. */
public enum View {
SEARCH("Search"),
CONTENTS("TOC"),
INDEX("Index");
@Override public String toString(){
return fName;
}
private View(String aName){
fName = aName;
}
private String fName;
}
// PRIVATE
117
Using preferences
The Preferences API is not part of Swing, but is often used in graphical applications. It provides a
lightweight and simple way of storing application and user preferences, without directly accessing the file
system or using JNDI.
Example
Here, the Preferences API is used to store and access user preferences related to the general look and feel
of an application:
toolbar visiblilty
icon size
colors and fonts
Note the field fPrefs, and the pairs of keys and default values for each preference.
package hirondelle.stocks.preferences;
import
import
import
import
import
import
import
java.util.*;
java.util.logging.*;
java.awt.*;
java.awt.event.*;
javax.swing.*;
java.util.prefs.*;
javax.swing.plaf.metal.MetalTheme;
118
import
import
import
import
hirondelle.stocks.util.ui.Theme;
hirondelle.stocks.util.ui.UiConsts;
hirondelle.stocks.util.ui.UiUtil;
hirondelle.stocks.util.Util;
/**
* Allows editing of user preferences related to the general
* appearance of the application, such as font size, toolbar icon size, theme,
* and the like.
*
* <P>Also allows programmatic read-only access to the current stored preferences
* for these items.
*/
public final class GeneralLookPreferencesEditor extends Observable
implements PreferencesEditor {
@Override public JComponent getUI(){
JPanel content = new JPanel();
GridBagLayout gridbag = new GridBagLayout();
content.setLayout(gridbag);
addShowToolbarAndLargeIcons(content);
addTheme(content);
addRestoreDefaults(content);
UiUtil.addVerticalGridGlue(content, 4);
matchGuiToStoredPrefs();
return content;
}
@Override public String getTitle() {
return TITLE;
}
@Override public int getMnemonic() {
return MNEMONIC;
}
@Override public void savePreferences(){
fLogger.fine("Updating general preferences.");
fPrefs.putBoolean(SHOW_TOOL_BAR_KEY, fShowToolBar.isSelected());
fPrefs.putBoolean(USE_LARGE_ICONS, fLargeIcons.isSelected());
fPrefs.put(THEME_NAME_KEY, fThemes.getSelectedItem().toString());
setChanged();
notifyObservers();
}
@Override public void matchGuiToDefaultPreferences(){
fShowToolBar.setSelected(SHOW_TOOLBAR_DEFAULT);
fLargeIcons.setSelected(USE_LARGE_ICONS_DEFAULT);
fThemes.setSelectedItem(Theme.valueOf(THEME_NAME_DEFAULT));
}
/**
* Return the stored user preference for hiding or showing the toolbar.
*/
public boolean hasShowToolBar(){
return fPrefs.getBoolean(SHOW_TOOL_BAR_KEY, SHOW_TOOLBAR_DEFAULT);
}
/**
* Return the stored user preference for using large icons.
*/
public boolean hasLargeIcons(){
return fPrefs.getBoolean(USE_LARGE_ICONS, USE_LARGE_ICONS_DEFAULT);
}
/**
* Return the stored user preference for the theme to be applied to the Java
* look-and-feel.
*/
public MetalTheme getTheme(){
String themeName = fPrefs.get(THEME_NAME_KEY, THEME_NAME_DEFAULT);
return Theme.valueOf(themeName);
}
// PRIVATE
private static final String GENERAL_LOOK_NODE = "stocksmonitor/ui/prefs/GeneralLook";
private Preferences fPrefs = Preferences.userRoot().node(GENERAL_LOOK_NODE);
119
120
See Also :
Preferences dialogs
<em>fail</em>,
then the user is shown an error message. If N such errors are present in user
input,
then N <em>separate</em> message will be presented for each failure, one by one.
<P>If all such parse operations <em>succeed</em>, then the "regular" constructor
{@link #Movie(String, String, Date, BigDecimal, String)}
will then be called. It's important to note that this call to the second
constructor
can in turn result in <em>another</em> error message being shown to the
user (just one this time).
*/
Movie(
String aId, String aTitle, String aDateViewed, String aRating, String aComment
) throws InvalidInputException {
this(
aId, aTitle, Util.parseDate(aDateViewed, "Date Viewed"),
Util.parseBigDecimal(aRating, "Rating"), aComment
);
}
String getId(){ return fId; }
String getTitle(){ return fTitle; }
Date getDateViewed(){ return fDateViewed; }
BigDecimal getRating(){ return fRating; }
String getComment(){ return fComment; }
//elided...
// PRIVATE
private String fId;
private final String fTitle;
private final Date fDateViewed;
private final BigDecimal fRating;
private final String fComment;
private static final BigDecimal TEN = new BigDecimal("10.0");
private static final int EQUAL = 0;
private static final int DESCENDING = -1;
//elided...
private void validateState() throws InvalidInputException {
InvalidInputException ex = new InvalidInputException();
if(! Util.textHasContent(fTitle)) {
ex.add("Title must have content");
}
if (fRating != null){
if (fRating.compareTo(BigDecimal.ZERO) < 0) {
ex.add("Rating cannot be less than 0.");
}
if (fRating.compareTo(TEN) > 0) {
ex.add("Rating cannot be greater than 10.");
}
}
if (ex.hasErrors()) {
throw ex;
}
}
}
The user edits Movie data using a dialog (not listed here). When the user hits a button, execution passes
to the following MovieController class. The actionPerformed method first attempts to build a Movie
object from user input. If a problem is detected, then an error message is displayed to the user.
package hirondelle.movies.edit;
import
import
import
import
hirondelle.movies.exception.InvalidInputException;
hirondelle.movies.main.MainWindow;
hirondelle.movies.util.Edit;
hirondelle.movies.util.Util;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Logger;
122
import javax.swing.JOptionPane;
/**
Add a new {@link Movie} to the database, or change an existing one.
<P>It's important to note that this class uses most of the other classes in
this feature to get its job done (it doesn't use the <tt>Action</tt> classes):
<ul>
<li>it gets user input from the view - {@link MovieView}
<li>it validates user input using the model - {@link Movie}
<li>it persists the data using the Data Access Object - {@link MovieDAO}
</ul>
*/
final class MovieController implements ActionListener {
/**
Constructor.
@param aView user interface
@param aEdit identifies what type of edit - add or change
*/
MovieController(MovieView aView, Edit aEdit){
fView = aView;
fEdit = aEdit;
}
/**
Attempt to add a new {@link Movie}, or edit an existing one.
<P>If the input is invalid, then inform the user of the problem(s).
If the input is valid, then add or change the <tt>Movie</tt>, close the dialog,
and update the main window's display.
*/
@Override public void actionPerformed(ActionEvent aEvent){
fLogger.fine("Editing movie " + fView.getTitle());
try {
createValidMovieFromUserInput();
}
catch(InvalidInputException ex){
informUserOfProblems(ex);
}
if ( isUserInputValid() ){
if( Edit.ADD == fEdit ) {
fLogger.fine("Add operation.");
fDAO.add(fMovie);
}
else if (Edit.CHANGE == fEdit) {
fLogger.fine("Change operation.");
fDAO.change(fMovie);
}
else {
throw new AssertionError();
}
fView.closeDialog();
MainWindow.getInstance().refreshView();
}
}
// PRIVATE
private final MovieView fView;
private Movie fMovie;
private Edit fEdit;
private MovieDAO fDAO = new MovieDAO();
private static final Logger fLogger = Util.getLogger(MovieController.class);
private void createValidMovieFromUserInput() throws InvalidInputException {
fMovie = new Movie(
fView.getId(), fView.getTitle(), fView.getDateViewed(),
fView.getRating(), fView.getComment()
);
}
private boolean isUserInputValid(){
return fMovie != null;
}
private void informUserOfProblems(InvalidInputException aException) {
Object[] messages = aException.getErrorMessages().toArray();
JOptionPane.showMessageDialog(
123
fView.getDialog(), messages,
"Movie cannot be saved", JOptionPane.ERROR_MESSAGE
);
}
}
See Also :
Immutable objects
Use a testing framework (JUnit)
Verify input with regular expressions
Input dialogs
Validation belongs in a Model Object
Example
uses regular expressions to verify input. Verification is performed when focus
moves from an associated JTextField ; if an error is found, the system beeps and an error message is
placed in the JTextField .
RegexInputVerifier
Items to note:
and verify are defined originally in InputVerifier
the constructor uses a type-safe enumeration ( UseToolTip ) as an argument, instead of a primitive
boolean value. This forces constructor calls to have greater clarity.
there are several static convenience objects provided at the end of this class, which are useful for
many common cases
shouldYieldFocus
/**
* Verifies user input into a {@link javax.swing.text.JTextComponent} versus a
* regular expression.
*
*<P> Upon detection of invalid input, this class takes the following actions :
*<ul>
* <li> emit a beep
* <li> overwrite the <tt>JTextComponent</tt> to display the following:
* INVALID: " (input data) "
* <li> optionally, append the tooltip text to the content of the INVALID message; this
* is useful only if the tooltip contains helpful information regarding input.
* Warning : appending the tooltip text may cause the error
* text to be too long for the corresponding text field.
*</ul>
*
*<P> The user of this class is encouraged to always place conditions on data entry
* in the tooltip for the corresponding field.
*/
final class RegexInputVerifier extends InputVerifier {
/**
* Constructor.
*
124
* @param aPattern regular expression against which all user input will
* be verified; <tt>aPattern.pattern</tt> satisfies
* {@link Util#textHasContent}.
* @param aUseToolTip indicates if the tooltip text should be appended to
* error messages displayed to the user.
*/
RegexInputVerifier(Pattern aPattern, UseToolTip aUseToolTip){
Args.checkForContent( aPattern.pattern() );
fMatcher = aPattern.matcher(Consts.EMPTY_STRING);
fUseToolTip = aUseToolTip.getValue();
}
/** Enumeration compels the caller to use a style which reads clearly. */
enum UseToolTip {
TRUE(true),
FALSE(false);
boolean getValue(){
return fToggle;
}
private boolean fToggle;
private UseToolTip(boolean aToggle){
fToggle = aToggle;
}
}
/**
* Always returns <tt>true</tt>, in this implementation, such that focus can
* always transfer to another component whenever the validation fails.
*
* <P>If <tt>super.shouldYieldFocus</tt> returns <tt>false</tt>, then
* notify the user of an error.
*
* @param aComponent is a <tt>JTextComponent</tt>.
*/
@Override public boolean shouldYieldFocus(JComponent aComponent){
boolean isValid = super.shouldYieldFocus(aComponent);
if (isValid){
//do nothing
}
else {
JTextComponent textComponent = (JTextComponent)aComponent;
notifyUserOfError(textComponent);
}
return true;
}
/**
* Return <tt>true</tt> only if the untrimmed user input matches the
* regular expression provided to the constructor.
*
* @param aComponent must be a <tt>JTextComponent</tt>.
*/
@Override public boolean verify(JComponent aComponent) {
boolean result = false;
JTextComponent textComponent = (JTextComponent)aComponent;
fMatcher.reset(textComponent.getText());
if (fMatcher.matches()) {
result = true;
}
return result;
}
/**
* The text which begins all error messages.
*
* The caller may examine their text fields for the presence of
* <tt>ERROR_MESSAGE_START</tt>, before processing input.
*/
static final String ERROR_MESSAGE_START = "INVALID: ";
/**
* Matches user input against a regular expression.
*/
private Matcher fMatcher;
/**
* Indicates if the JTextField's tooltip text is to be appended to
* error messages, as a second way of reminding the user.
*/
125
See Also :
Type-Safe Enumerations
Parse text
Pattern-match lines of a file
Input dialogs
Use enums to restrict arguments
The first constructor includes several @throws tags in its javadoc. However, aside from the type of the
unchecked exception, all of these @throws tags are logically equivalent to some previous statement in a
@param tag. They add nothing to the contract.
The second constructor follows a different style. It has a single parameter, and the conditions on this
parameter are stated once (and once only) in its @param tag.
public final class BasketBall {
/**
* @param aManufacturer non-null and has visible content.
* @param aDiameter in centimeters, in the range 1..50.
* @throws IllegalArgumentException if aDiameter not in given range.
* @throws IllegalArgumentException if aManufacturer has no visible content.
* @throws NullPointerException if aManufacturer is null.
*/
BasketBall(String aManufacturer, int aDiameter){
//..elided
}
/**
* @param aDiameter in centimeters, in the range 1..50.
*/
BasketBall(int aDiameter){
//..elided
}
128
// PRIVATE
private String fManufacturer;
private int fDiameter;
}
See Also :
Javadoc all exceptions
Design by Contract
*/
In general, when a exception occurs, it can be thrown up to the caller, or it can be caught in a catch
block. When catching an exception, some options include:
inform the user (strongly recommended)
log the problem, using the JDK logging services, or similar tool
send an email describing the problem to an administrator
Deciding what exactly to do seems to depend on the nature of the problem. If there's an actual bug in the
program - a defect that needs to be fixed - then one might do all three of the above. In this case, the end
user should likely be shown a generic "Sorry, we goofed" message, not a stack trace. It's usually
considered bad form to display a stack trace to a non-technical end user, or if exposing a stack trace may
129
be a security risk.
If the exception does not represent a bug, then different behavior may be appropriate. For example, if a
problem with user input is detected and an exception is thrown as a result, then merely informing the user
of the problem might be all that is required.
See Also :
Logging messages
Avoid basic style errors
See Also :
Exception translation
130
See Also :
Data exception wrapping
131
hirondelle.web4j.model.ModelCtorException;
hirondelle.web4j.model.ModelUtil;
hirondelle.web4j.model.Id;
hirondelle.web4j.security.SafeText;
hirondelle.web4j.model.Decimal;
static hirondelle.web4j.model.Decimal.ZERO;
hirondelle.web4j.model.Check;
hirondelle.web4j.model.Validator;
static hirondelle.web4j.util.Consts.FAILS;
132
/**
Full constructor.
@param aId underlying database internal identifier (optional) 1..50 characters
@param aName of the restaurant (required), 2..50 characters
@param aLocation street address of the restaurant (optional), 2..50 characters
@param aPrice of the fish and chips meal (optional) $0.00..$100.00
@param aComment on the restaurant in general (optional) 2..50 characters
*/
public Resto(
Id aId, SafeText aName, SafeText aLocation, Decimal aPrice, SafeText aComment
) throws ModelCtorException {
fId = aId;
fName = aName;
fLocation = aLocation;
fPrice = aPrice;
fComment = aComment;
validateState();
}
public
public
public
public
public
Example 2
is a convenient utility class. It performs common validations on method arguments. If a validation
fails, then it throws an unchecked exception. It is suitable for checking the internal consistency of
program, but not for checking arbitrary user input.
Args
package hirondelle.web4j.util;
import java.util.regex.*;
/**
Utility methods for common argument validations.
<P>Replaces <tt>if</tt> statements at the start of a method with
more compact method calls.
<P>Example use case.
<P>Instead of :
<PRE>
public void doThis(String aText){
if (!Util.textHasContent(aText)){
throw new IllegalArgumentException();
}
//..main body elided
}
</PRE>
<P>One may instead write :
<PRE>
public void doThis(String aText){
Args.checkForContent(aText);
//..main body elided
}
</PRE>
*/
public final class Args {
/**
If <code>aText</code> does not satisfy {@link Util#textHasContent}, then
throw an <code>IllegalArgumentException</code>.
<P>Most text used in an application is meaningful only if it has visible content.
*/
public static void checkForContent(String aText){
if( ! Util.textHasContent(aText) ){
throw new IllegalArgumentException("Text has no visible content");
}
}
/**
If {@link Util#isInRange} returns <code>false</code>, then
throw an <code>IllegalArgumentException</code>.
@param aLow is less than or equal to <code>aHigh</code>.
*/
public static void checkForRange(int aNumber, int aLow, int aHigh) {
if ( ! Util.isInRange(aNumber, aLow, aHigh) ) {
throw new IllegalArgumentException(aNumber + " not in range " + aLow + ".." +
aHigh);
}
}
/**
If <tt>aNumber</tt> is less than <tt>1</tt>, then throw an
<tt>IllegalArgumentException</tt>.
*/
public static void checkForPositive(int aNumber) {
if (aNumber < 1) {
134
See Also :
Model Objects
Exception translation
Occasionally, it's appropriate to translate one type of exception into another.
The data layer, for example, can profit from this technique. Here, the data layer seeks to hide almost all
of its implementation details from other parts of the program. It even seeks to hide the basic persistence
mechanism - whether or not a database or an ad hoc file scheme is used, for example.
135
However, every persistence style has specific exceptions - SQLException for databases, and
IOException for files, for example. If the rest of the program is to remain truly ignorant of the
persistence mechanism, then these exceptions cannot be allowed to propagate outside the data layer, and
must be translated into some higher level abstraction - DataAccessException , say.
See the data exception wrapping topic for an extended example.
See Also :
Be specific in throws clause
Data exception wrapping
Case
try throws no exception
try throws a handled exception
try throws an unhandled exception
try
y
y
y
catch
y
-
finally
y
y
y
bottom
y
y
-
Here, "bottom" refers simply to any code which follows the finally block, as shown here :
final class Bottom {
void doStuff() {
try {
//..elided
}
catch( Exception ex ) {
//..elided
}
finally {
//..elided
}
//any code appearing here, after the finally
//block, is "bottom" code
}
}
There is a misconception - especially common among C programmers migrating to Java - that exceptions
can be used to define ordinary control flow. This is a misuse of the idea of exceptions, which are meant
only for defects or for items outside the direct control of the program.
See Also :
136
Style 2
If a method handles all of the checked exceptions that may be thrown by its implementation, then an
interesting variation is to nest a try..finally within a try..catch . This style is particularly useful
137
when the finally block throws the same exceptions as the rest of the code (which is common with
java.io operations.) Although this style may seem slightly complex, it appears to be superior to
alternative styles:
import java.io.*;
import java.util.logging.*;
/** Before JDK 7. */
public final class NestedFinally {
public static void main(String... aArgs) {
nestedFinally("C:\\Temp\\test.txt");
}
private static void nestedFinally(String aFileName) {
try {
//If the constructor throws an exception, the finally block will NOT execute
BufferedReader reader = new BufferedReader(new FileReader(aFileName));
try {
String line = null;
while ((line = reader.readLine()) != null) {
//process the line...
}
}
finally {
//no need to check for null
//any exceptions thrown here will be caught by
//the outer catch block
reader.close();
}
}
catch(IOException ex){
fLogger.severe("Problem occured : " + ex.getMessage());
}
}
private static final Logger fLogger =
Logger.getLogger(NestedFinally.class.getPackage().getName())
;
}
Style 3
A more verbose style places a catch within the finally. This style is likely the least desirable, since it
has the most blocks:
import
import
import
import
java.io.BufferedReader;
java.io.FileReader;
java.io.IOException;
java.util.logging.Logger;
finally {
try {
//need to check for null
if ( reader != null ) {
reader.close();
}
}
catch(IOException ex){
fLogger.severe("Problem occured. Cannot close reader : " + ex.getMessage());
}
}
}
private static final Logger fLogger =
Logger.getLogger(CatchInsideFinally.class.getPackage().getName())
;
}
See Also :
Always close streams
Recovering resources
Use finally to unlock
See Also :
Validate method arguments
Avoid @throws in javadoc
140
/**
* Return <tt>true</tt> only if <tt>aNumber</tt> is in the range
* <tt>aLow..aHigh</tt> (inclusive).
*
* @param <tt>aLow</tt> less than or equal to <tt>aHigh</tt>.
*/
static public boolean isInRange(int aNumber, int aLow, int aHigh){
if (aLow > aHigh) {
throw new IllegalArgumentException("Low:" + aLow + " greater than High:" +
aHigh);
}
return (aLow <= aNumber && aNumber <= aHigh);
}
}
*/
141
See Also :
Dump thread information
142
Some java.io classes (apparently just the output classes) include a flush method. When a close
method is called on a such a class, it automatically performs a flush . There is no need to explicitly call
flush before calling close .
One stream can be chained to another by passing it to the constructor of some second stream. When this
second stream is closed, then it automatically closes the original underlying stream as well.
If multiple streams are chained together, then closing the one which was the last to be constructed, and is
thus at the highest level of abstraction, will automatically close all the underlying streams. So, one only
has to call close on one stream in order to close (and flush, if applicable) an entire series of related
streams.
Example
import java.io.*;
import java.util.*;
import java.util.logging.*;
/** JDK before version 7. */
public class ExerciseSerializable {
public static void main(String... aArguments) {
//create a Serializable List
List<String> quarks = Arrays.asList(
"up", "down", "strange", "charm", "top", "bottom"
);
//serialize the List
//note the use of abstract base class references
try{
//use buffering
OutputStream file = new FileOutputStream("quarks.ser");
OutputStream buffer = new BufferedOutputStream(file);
ObjectOutput output = new ObjectOutputStream(buffer);
try{
output.writeObject(quarks);
}
finally{
output.close();
}
}
catch(IOException ex){
fLogger.log(Level.SEVERE, "Cannot perform output.", ex);
}
//deserialize the quarks.ser file
//note the use of abstract base class references
try{
//use buffering
InputStream file = new FileInputStream("quarks.ser");
InputStream buffer = new BufferedInputStream(file);
ObjectInput input = new ObjectInputStream (buffer);
try{
//deserialize the List
List<String> recoveredQuarks = (List<String>)input.readObject();
//display its data
for(String quark: recoveredQuarks){
System.out.println("Recovered Quark: " + quark);
}
}
finally{
input.close();
}
}
catch(ClassNotFoundException ex){
fLogger.log(Level.SEVERE, "Cannot perform input. Class not found.", ex);
}
catch(IOException ex){
fLogger.log(Level.SEVERE, "Cannot perform input.", ex);
}
}
143
// PRIVATE
//Use Java's logging facilities to record exceptions.
//The behavior of the logger can be configured through a
//text file, or programmatically through the logging API.
private static final Logger fLogger =
Logger.getLogger(ExerciseSerializable.class.getPackage().getName())
;
}
See Also :
Finally and catch
Reading and writing text files
Recovering resources
int data = 0;
while ((data = input.read()) != -1){
//do nothing
}
}
finally {
input.close();
}
}
catch (IOException ex){
ex.printStackTrace();
}
}
/**
* @param aFile is an existing file which can be read.
*/
static public void readWithoutBuffer(File aFile) {
try {
Reader input = new FileReader(aFile);
try {
//do not use buffering
int data = 0;
while ((data = input.read()) != -1){
//do nothing
}
}
finally {
input.close();
}
}
catch (IOException ex){
ex.printStackTrace();
}
}
private static void verifyFile(File aFile) {
if (aFile == null) {
throw new IllegalArgumentException("File should not be null.");
}
if (!aFile.exists()) {
throw new IllegalArgumentException ("File does not exist: " + aFile);
}
if (!aFile.isFile()) {
throw new IllegalArgumentException("Should not be a directory: " + aFile);
}
if (!aFile.canWrite()) {
throw new IllegalArgumentException("File cannot be written: " + aFile);
}
}
}
See Also :
Time execution speed
Console input
The Console class allows the user to interact with a simple console application, using textual commands
that you define.
Here's a simple example of its use:
import java.util.Arrays;
import java.io.Console;
145
/**
* Simple interactive console application.
* Uses the java.io.Console class of Java 6.
*/
public final class Console6 {
public static final
Console console =
//read user name,
String username =
An example run:
>java Console6
User Name? john
Password?
Welcome, john.
Please enter a package-qualified class name:java.util.ArrayList
The inheritance tree:
class java.util.ArrayList
class java.util.AbstractList
class java.util.AbstractCollection
class java.lang.Object
Bye.
JDK < 6
The Console class was added in Java 6. The following is an extended example of using an older version
of the JDK. Here, input is read from the console in a continuous loop. As well, it has separated the
146
problem into several parts, such that some parts can be reused in other console applications.
As in the previous example, the user inputs a package-qualified class name, and the corresponding
inheritance tree is displayed.
An example run:
>java -cp . Console InheritanceInterpreter
Please enter a class name>as;k
Invalid. Example:"java.lang.String">java.lang.String
The inheritance tree:
class java.lang.String
class java.lang.Object
Please enter a class name>
Invalid. Example:"java.lang.String">
Invalid. Example:"java.lang.String">....
Invalid. Example:"java.lang.String">a;lskf
Invalid. Example:"java.lang.String">java.sql.SQLWarning
The inheritance tree:
class java.sql.SQLWarning
class java.sql.SQLException
class java.lang.Exception
class java.lang.Throwable
class java.lang.Object
Please enter a class name>java.util.GregorianCalendar
The inheritance tree:
class java.util.GregorianCalendar
class java.util.Calendar
class java.lang.Object
Please enter a class name>exit
Bye.
import
import
import
import
import
import
java.io.BufferedReader;
java.io.IOException;
java.io.InputStreamReader;
java.io.Reader;
java.util.ArrayList;
java.util.List;
/**
* Sends text back and forth between the command line and an
* Interpreter. JDK less than 6.
*/
public final class Console {
/**
* Build and launch a specific <code>Interpreter</code>, whose
* package-qualified name is passed in on the command line.
*/
public static void main(String... aArguments) {
try {
Class theClass = Class.forName(aArguments[0]);
Interpreter interpreter = (Interpreter)theClass.newInstance();
Console console = new Console(interpreter);
console.run();
}
catch (ClassNotFoundException ex){
System.err.println(ex + " Interpreter class must be in class path.");
}
catch(InstantiationException ex){
System.err.println(ex + " Interpreter class must be concrete.");
}
catch(IllegalAccessException ex){
147
148
import java.util.*;
/**
* Parse a line of text and return a result.
*/
public interface Interpreter {
/**
* @param aLine is non-null.
* @param aResult is a non-null, empty List which acts as an "out"
* parameter; when returned, aResult must contain a non-null, non-empty
* List of items which all have a <code>toString</code> method, to be used
* for displaying a result to the user.
*
* @return true if the user has requested to quit the Interpreter.
* @exception IllegalArgumentException if a param does not comply.
*/
boolean parseInput(String aLine, List<Object> aResult);
/**
* Return the text to be displayed upon start-up of the Interpreter.
*/
String getHelloPrompt();
}
import java.util.*;
/**
* Given a package-qualified class name, return the names of the classes in
* the inheritance tree.
*/
public final class InheritanceInterpreter implements Interpreter {
/**
* @param aLine is a non-null name of a class.
* @param aResult is a non-null, empty List which acts as an "out"
* parameter; when returned, aResult must contain a non-null, non-empty
* List of class names which form the inheritance tree of the input class.
*
* @return true if the user has requeseted to quit the Interpreter.
* @exception IllegalArgumentException if a param does not comply.
*/
public boolean parseInput (String aLine, final List aResult) {
if (aResult == null) {
throw new IllegalArgumentException("Result param cannot be null.");
}
if (!aResult.isEmpty()){
throw new IllegalArgumentException("Result param must be empty.");
}
if (aLine == null) {
throw new IllegalArgumentException("Line must not be null.");
}
boolean hasRequestedQuit = aLine.trim().equalsIgnoreCase(fQUIT) ||
aLine.trim().equalsIgnoreCase(fEXIT);
if (hasRequestedQuit) {
aResult.add(fNEW_LINE);
}
else {
try {
Class theClass = Class.forName(aLine);
StringBuilder superclasses = new StringBuilder();
superclasses.append(fHEADER);
superclasses.append(fNEW_LINE);
while (theClass != null) {
superclasses.append(theClass);
superclasses.append(fNEW_LINE);
theClass = theClass.getSuperclass();
}
aResult.add(superclasses);
aResult.add(fDEFAULT_PROMPT);
149
}
catch (ClassNotFoundException ex){
//recover by asking the user for corrected input
aResult.clear();
aResult.add(fERROR_PROMPT);
}
}
assert !aResult.isEmpty(): "Result must be non-empty.";
return hasRequestedQuit;
}
/**
* Return the text to be displayed upon start-up of the Interpreter.
*/
public String getHelloPrompt() {
return fHELLO_PROMPT;
}
// PRIVATE
private static final String fHELLO_PROMPT = "Please enter a class name>";
private static final String fDEFAULT_PROMPT = "Please enter a class name>";
private static final String fERROR_PROMPT = "Invalid.
Example:\"java.lang.String\">";
private static final String fHEADER = "The inheritance tree:";
private static final String fQUIT = "quit";
private static final String fEXIT = "exit";
private static final String fNEW_LINE = System.getProperty("line.separator");
}
See Also :
Get size of object in memory
Construct Object using class name
Copy a file
In JDK 7+, copying a file is a simple operation, involving a single call to File.copy.
Example
import
import
import
import
import
import
java.io.IOException;
java.nio.file.CopyOption;
java.nio.file.Files;
java.nio.file.Path;
java.nio.file.Paths;
java.nio.file.StandardCopyOption;
150
In older JDKs, however, copying a file involves a lot more code. It can be done either with
FileChannels or with basic streams. The FileChannel technique is usually faster.
Here's an example showing both techniques.
import
import
import
import
import
import
import
import
import
import
java.io.BufferedInputStream;
java.io.BufferedOutputStream;
java.io.File;
java.io.FileInputStream;
java.io.FileNotFoundException;
java.io.FileOutputStream;
java.io.IOException;
java.io.InputStream;
java.io.OutputStream;
java.nio.channels.FileChannel;
/**
Copy files, using two techniques, FileChannels and streams.
Using FileChannels is usually faster than using streams.
JDK 6-.
*/
public final class CopyFiles {
/* Change these settings before running this class. */
/** The file to be copied. */
public static final String INPUT_FILE = "C:\\TEMP\\cottage.jpg";
/**
The name of the copy to be created by this class.
If this file doesn't exist, it will be created, along with any
needed parent directories.
*/
public static final String COPY_FILE_TO = "C:\\TEMP10\\cottage_2.jpg";
/** Run the example. */
public static void main(String... aArgs) throws IOException{
File source = new File(INPUT_FILE);
File target = new File(COPY_FILE_TO);
CopyFiles test = new CopyFiles();
test.copyWithChannels(source, target, false);
//test.copyWithStreams(source, target, false);
log("Done.");
}
/** This may fail for VERY large files. */
private void copyWithChannels(File aSourceFile, File aTargetFile, boolean aAppend) {
log("Copying files with channels.");
ensureTargetDirectoryExists(aTargetFile.getParentFile());
FileChannel inChannel = null;
FileChannel outChannel = null;
FileInputStream inStream = null;
FileOutputStream outStream = null;
try{
try {
inStream = new FileInputStream(aSourceFile);
inChannel = inStream.getChannel();
outStream = new FileOutputStream(aTargetFile, aAppend);
outChannel = outStream.getChannel();
long bytesTransferred = 0;
//defensive loop - there's usually only a single iteration :
while(bytesTransferred < inChannel.size()){
bytesTransferred += inChannel.transferTo(0, inChannel.size(), outChannel);
}
}
finally {
//being defensive about closing all channels and streams
if (inChannel != null) inChannel.close();
if (outChannel != null) outChannel.close();
if (inStream != null) inStream.close();
if (outStream != null) outStream.close();
}
}
catch (FileNotFoundException ex){
151
See Also :
Always close streams
Reading and writing binary files
In addition, the following classes are also commonly used with binary files, for both JDK 7 and earlier
152
versions:
Input
Output
FileInputStream
BufferedInputStream
ByteArrayInputStream
DataInput
FileOutputStream
BufferedOutputStream
ByteArrayOutputStream
DataOutput
java.io.IOException;
java.nio.file.Files;
java.nio.file.Path;
java.nio.file.Paths;
If JDK 7's try-with-resources isn't available to you, then, you need to be careful with the close method:
it usually needs to be called, or else resources will leak.
for streams that don't use the disk or network, such as the ByteArrayXXX streams, the close
operation is a no-operation. In these cases, you don't need to call close .
close will automatically flush the stream, if necessary.
calling close on a "wrapper" stream will automatically call close on its underlying stream.
closing a stream a second time is a no-operation.
Example - JDK < 7
153
This example reads and writes binary data, moving it from disk to memory, and then back again.
import
import
import
import
import
import
import
import
import
import
java.io.BufferedInputStream;
java.io.BufferedOutputStream;
java.io.ByteArrayOutputStream;
java.io.File;
java.io.FileInputStream;
java.io.FileNotFoundException;
java.io.FileOutputStream;
java.io.IOException;
java.io.InputStream;
java.io.OutputStream;
/**
Converting binary data into different forms.
<P>Reads binary data into memory, and writes it back out.
(If your're actually copying a file, there are better ways to do this.)
<P>Buffering is used when reading and writing files, to minimize the number
of interactions with the disk.
*/
public final class BytesStreamsAndFiles {
/** Change these settings before running this class. */
private static final String INPUT_FILE_NAME = "C:\\TEMP\\cottage.jpg";
private static final String OUTPUT_FILE_NAME = "C:\\TEMP\\cottage_copy.jpg";
/** Run the example. */
public static void main(String... aArgs) {
BytesStreamsAndFiles test = new BytesStreamsAndFiles();
//read in the bytes
byte[] fileContents = test.read(INPUT_FILE_NAME);
//test.readAlternateImpl(INPUT_FILE_NAME);
//write it back out to a different file name
test.write(fileContents, OUTPUT_FILE_NAME);
}
/** Read the given binary file, and return its contents as a byte array.*/
byte[] read(String aInputFileName){
log("Reading in binary file named : " + aInputFileName);
File file = new File(aInputFileName);
log("File size: " + file.length());
byte[] result = new byte[(int)file.length()];
try {
InputStream input = null;
try {
int totalBytesRead = 0;
input = new BufferedInputStream(new FileInputStream(file));
while(totalBytesRead < result.length){
int bytesRemaining = result.length - totalBytesRead;
//input.read() returns -1, 0, or more :
int bytesRead = input.read(result, totalBytesRead, bytesRemaining);
if (bytesRead > 0){
totalBytesRead = totalBytesRead + bytesRead;
}
}
/*
the above style is a bit tricky: it places bytes into the 'result' array;
'result' is an output parameter;
the while loop usually has a single iteration only.
*/
log("Num bytes read: " + totalBytesRead);
}
finally {
log("Closing input stream.");
input.close();
}
}
catch (FileNotFoundException ex) {
log("File not found.");
}
catch (IOException ex) {
log(ex);
}
return result;
}
154
/**
Write a byte array to the given file.
Writing binary data is significantly simpler than reading it.
*/
void write(byte[] aInput, String aOutputFileName){
log("Writing binary file...");
try {
OutputStream output = null;
try {
output = new BufferedOutputStream(new FileOutputStream(aOutputFileName));
output.write(aInput);
}
finally {
output.close();
}
}
catch(FileNotFoundException ex){
log("File not found.");
}
catch(IOException ex){
log(ex);
}
}
/** Read the given binary file, and return its contents as a byte array.*/
byte[] readAlternateImpl(String aInputFileName){
log("Reading in binary file named : " + aInputFileName);
File file = new File(aInputFileName);
log("File size: " + file.length());
byte[] result = null;
try {
InputStream input = new BufferedInputStream(new FileInputStream(file));
result = readAndClose(input);
}
catch (FileNotFoundException ex){
log(ex);
}
return result;
}
/**
Read an input stream, and return it as a byte array.
Sometimes the source of bytes is an input stream instead of a file.
This implementation closes aInput after it's read.
*/
byte[] readAndClose(InputStream aInput){
//carries the data from input to output :
byte[] bucket = new byte[32*1024];
ByteArrayOutputStream result = null;
try {
try {
//Use buffering? No. Buffering avoids costly access to disk or network;
//buffering to an in-memory stream makes no sense.
result = new ByteArrayOutputStream(bucket.length);
int bytesRead = 0;
while(bytesRead != -1){
//aInput.read() returns -1, 0, or more :
bytesRead = aInput.read(bucket);
if(bytesRead > 0){
result.write(bucket, 0, bytesRead);
}
}
}
finally {
aInput.close();
//result.close(); this is a no-operation for ByteArrayOutputStream
}
}
catch (IOException ex){
log(ex);
}
return result.toByteArray();
}
private static void log(Object aThing){
System.out.println(String.valueOf(aThing));
}
155
See Also :
Always close streams
Reading and writing text files
Minimize ripple effects
Copy a file
Example
Here, a List of String objects is serialized and then deserialized.
import java.io.*;
import java.util.*;
import java.util.logging.*;
/**
Uses buffering, and abstract base classes.
JDK 7+.
*/
public class ExerciseSerializableNew {
public static void main(String... aArguments) {
//create a Serializable List
List<String> quarks = Arrays.asList(
"up", "down", "strange", "charm", "top", "bottom"
);
//serialize the List
try (
OutputStream file = new FileOutputStream("quarks.ser");
OutputStream buffer = new BufferedOutputStream(file);
ObjectOutput output = new ObjectOutputStream(buffer);
){
output.writeObject(quarks);
}
catch(IOException ex){
fLogger.log(Level.SEVERE, "Cannot perform output.", ex);
}
//deserialize the quarks.ser file
try(
InputStream file = new FileInputStream("quarks.ser");
InputStream buffer = new BufferedInputStream(file);
ObjectInput input = new ObjectInputStream (buffer);
){
//deserialize the List
List<String> recoveredQuarks = (List<String>)input.readObject();
//display its data
for(String quark: recoveredQuarks){
System.out.println("Recovered Quark: " + quark);
}
}
156
catch(ClassNotFoundException ex){
fLogger.log(Level.SEVERE, "Cannot perform input. Class not found.", ex);
}
catch(IOException ex){
fLogger.log(Level.SEVERE, "Cannot perform input.", ex);
}
}
// PRIVATE
private static final Logger fLogger =
Logger.getLogger(ExerciseSerializableNew.class.getPackage().getName())
;
}
Example - JDK 6If try-with-resources is not available (JDK 6-), then you must be careful with the close method:
it always needs to be called, or else resources will leak
it will automatically flush the stream, if necessary
calling close on a "wrapper" stream will automatically call close on its underlying stream
closing a stream a second time has no consequence
Example
Here's the same example as above, but using JDK 6-.
import java.io.*;
import java.util.*;
import java.util.logging.*;
/** JDK before version 7. */
public class ExerciseSerializable {
public static void main(String... aArguments) {
//create a Serializable List
List<String> quarks = Arrays.asList(
"up", "down", "strange", "charm", "top", "bottom"
);
//serialize the List
//note the use of abstract base class references
try{
//use buffering
OutputStream file = new FileOutputStream("quarks.ser");
OutputStream buffer = new BufferedOutputStream(file);
ObjectOutput output = new ObjectOutputStream(buffer);
try{
output.writeObject(quarks);
}
finally{
output.close();
}
}
catch(IOException ex){
fLogger.log(Level.SEVERE, "Cannot perform output.", ex);
}
//deserialize the quarks.ser file
//note the use of abstract base class references
try{
//use buffering
InputStream file = new FileInputStream("quarks.ser");
InputStream buffer = new BufferedInputStream(file);
ObjectInput input = new ObjectInputStream (buffer);
try{
//deserialize the List
157
See Also :
Always close streams
Finally and catch
Recovering resources
java.nio API.
In addition, the following classes are also commonly used with text files, for both JDK 7 and earlier
versions:
Scanner - allows reading files in a compact
BufferedReader - readLine
BufferedWriter - write + newLine
way
In order to correctly read and write text files, you need to understand that those read/write operations
always use an implicit character encoding to translate raw bytes - the 1s and 0s - into text. When a text
file is saved, the tool that saves it must always use a character encoding (UTF-8 is recommended).
There's a problem, however. The character encoding is not, in general, explicit: it's not saved as part of
the file itself. Thus, a program that consumes a text file should know beforehand what its encoding is. If
it doesn't, then the best it can do is make an assumption. Problems with encoding usually show up as
weird characters in a tool that has read the file.
The FileReader and FileWriter classes are a bit tricky, since they implicitly use the system's default
character encoding. If this default is not appropriate, the recommended alternatives are, for example:
FileInputStream fis = new FileInputStream("test.txt");
InputStreamReader in = new InputStreamReader(fis, "UTF-8");
FileOutputStream fos = new FileOutputStream("test.txt");
OutputStreamWriter out = new OutputStreamWriter(fos, "UTF-8");
Scanner scanner = new Scanner(file, "UTF-8");
Example 1 - JDK 7+
import
import
import
import
import
import
import
import
import
import
import
java.io.BufferedReader;
java.io.BufferedWriter;
java.io.IOException;
java.nio.charset.Charset;
java.nio.charset.StandardCharsets;
java.nio.file.Files;
java.nio.file.Path;
java.nio.file.Paths;
java.util.Arrays;
java.util.List;
java.util.Scanner;
159
Example 2 - JDK 7+
This example demonstrates using Scanner to read a file containing lines of structured data. One Scanner
is used to read in each line, and a second Scanner is used to parse each line into a simple name-value
pair. The Scanner class is only used for reading, not for writing.
import
import
import
import
import
import
java.io.IOException;
java.nio.charset.Charset;
java.nio.charset.StandardCharsets;
java.nio.file.Path;
java.nio.file.Paths;
java.util.Scanner;
while (scanner.hasNextLine()){
processLine(scanner.nextLine());
}
}
}
/**
Overridable method for processing lines in different ways.
<P>This simple default implementation expects simple name-value pairs, separated by
an
'=' sign. Examples of valid input:
<tt>height = 167cm</tt>
<tt>mass = 65kg</tt>
<tt>disposition = "grumpy"</tt>
<tt>this is the name = this is the value</tt>
*/
protected void processLine(String aLine){
//use a second Scanner to parse the content of each line
Scanner scanner = new Scanner(aLine);
scanner.useDelimiter("=");
if (scanner.hasNext()){
//assumes the line has a certain structure
String name = scanner.next();
String value = scanner.next();
log("Name is : " + quote(name.trim()) + ", and Value is : " +
quote(value.trim()));
}
else {
log("Empty or invalid line. Unable to process.");
}
}
// PRIVATE
private final Path fFilePath;
private final static Charset ENCODING = StandardCharsets.UTF_8;
private static void log(Object aObject){
System.out.println(String.valueOf(aObject));
}
private String quote(String aText){
String QUOTE = "'";
return QUOTE + aText + QUOTE;
}
}
:
:
:
:
encoding. If you remove all references to encoding from this class, it will still work -- the system's
default encoding will simply be used instead.
import java.io.*;
import java.util.Scanner;
/**
Read and write a file using an explicit encoding.
JDK 1.5.
Removing the encoding from this code will simply cause the
system's default encoding to be used instead.
*/
public final class ReadWriteTextFileWithEncoding {
/** Requires two arguments - the file name, and the encoding to use. */
public static void main(String... aArgs) throws IOException {
String fileName = aArgs[0];
String encoding = aArgs[1];
ReadWriteTextFileWithEncoding test = new ReadWriteTextFileWithEncoding(
fileName, encoding
);
test.write();
test.read();
}
/** Constructor. */
ReadWriteTextFileWithEncoding(String aFileName, String aEncoding){
fEncoding = aEncoding;
fFileName = aFileName;
}
/** Write fixed content to the given file. */
void write() throws IOException {
log("Writing to file named " + fFileName + ". Encoding: " + fEncoding);
Writer out = new OutputStreamWriter(new FileOutputStream(fFileName), fEncoding);
try {
out.write(FIXED_TEXT);
}
finally {
out.close();
}
}
/** Read the contents of the given file. */
void read() throws IOException {
log("Reading from file.");
StringBuilder text = new StringBuilder();
String NL = System.getProperty("line.separator");
Scanner scanner = new Scanner(new FileInputStream(fFileName), fEncoding);
try {
while (scanner.hasNextLine()){
text.append(scanner.nextLine() + NL);
}
}
finally{
scanner.close();
}
log("Text read in: " + text);
}
// PRIVATE
private final String fFileName;
private final String fEncoding;
private final String FIXED_TEXT = "But soft! what code in yonder program breaks?";
private void log(String aMessage){
System.out.println(aMessage);
}
}
also uses buffering. To make this example compatible with JDK 1.4, just change StringBuilder to
StringBuffer :
import java.io.*;
/** JDK 6 or before. */
public class ReadWriteTextFile {
/**
* Fetch the entire contents of a text file, and return it in a String.
* This style of implementation does not throw Exceptions to the caller.
*
* @param aFile is a file which already exists and can be read.
*/
static public String getContents(File aFile) {
//...checks on aFile are elided
StringBuilder contents = new StringBuilder();
try {
//use buffering, reading one line at a time
//FileReader always assumes default encoding is OK!
BufferedReader input = new BufferedReader(new FileReader(aFile));
try {
String line = null; //not declared within while loop
/*
* readLine is a bit quirky :
* it returns the content of a line MINUS the newline.
* it returns null only for the END of the stream.
* it returns an empty String if two newlines appear in a row.
*/
while (( line = input.readLine()) != null){
contents.append(line);
contents.append(System.getProperty("line.separator"));
}
}
finally {
input.close();
}
}
catch (IOException ex){
ex.printStackTrace();
}
return contents.toString();
}
/**
* Change the contents of text file in its entirety, overwriting any
* existing text.
*
* This style of implementation throws all exceptions to the caller.
*
* @param aFile is an existing file which can be written to.
* @throws IllegalArgumentException if param does not comply.
* @throws FileNotFoundException if the file does not exist.
* @throws IOException if problem encountered during write.
*/
static public void setContents(File aFile, String aContents)
throws FileNotFoundException, IOException {
if (aFile == null) {
throw new IllegalArgumentException("File should not be null.");
}
if (!aFile.exists()) {
throw new FileNotFoundException ("File does not exist: " + aFile);
}
if (!aFile.isFile()) {
throw new IllegalArgumentException("Should not be a directory: " + aFile);
}
if (!aFile.canWrite()) {
throw new IllegalArgumentException("File cannot be written: " + aFile);
}
//use buffering
Writer output = new BufferedWriter(new FileWriter(aFile));
try {
//FileWriter always assumes default encoding is OK!
output.write( aContents );
163
}
finally {
output.close();
}
}
/** Simple test harness.
*/
public static void main (String... aArguments) throws IOException {
File testFile = new File("C:\\Temp\\blah.txt");
System.out.println("Original file contents: " + getContents(testFile));
setContents(testFile, "The content of this file has been overwritten...");
System.out.println("New file contents: " + getContents(testFile));
}
}
See Also :
Always close streams
Parse text
Minimize ripple effects
Reading and writing binary files
Open file in native directory
Connection pools
Database connections:
sometimes take a relatively long time to create.
are always limited in number (most medium- and low-end servers have a maximum of ~100
connections).
are resources which must be systematically released when no longer in use.
The last point is critical: connections must always be properly released when no longer needed. Failure to
do so will eventually cause the application to "hang", and fail to respond to user actions. All code in your
application regarding connections should be treated with special care.
To ensure that database connections are used as efficiently as possible, many applications make use of a
connection pool, which can sometimes improve performance in a distributed application. An application
will use a connection pool as follows:
upon startup, the application creates a pool of database connections (or, the application's
environment may already include a connection pool).
during normal operation, the application simply fetches a connection from the pool every time a
database operation is performed. This is often done in a "just-in-time" manner. That is, the
application does not keep a long-lived reference to its connection objects, but will rather fetch, use,
and then close each connection in a single method.
when the calling code is finished with the connection, it calls a close method, but the underlying
connection is not actually closed - rather, it's simply returned to the connection pool.
when the application shuts down, the connection pool it created upon startup (if any) is shut down
as well.
A database driver may contain a connection pool as part of its services, through its implementation of the
javax.sql.DataSource interface. DataSource objects are used with JNDI.
The popular Tomcat tool implements JNDI, and connection pooling is described in its documentation. It's
not difficult to use.
See Also :
Get database connection
Encapsulate connections
Entries in the above .sql file are referenced in code using SqlId objects, such as FETCH_PREFERENCES:
import
import
import
import
import
static hirondelle.predict.main.preferences.PreferencesAction.CHANGE_PREFERENCES;
static hirondelle.predict.main.preferences.PreferencesAction.FETCH_PREFERENCES;
hirondelle.web4j.database.DAOException;
hirondelle.web4j.database.Db;
hirondelle.web4j.security.SafeText;
/**
Data Access Object for user {@link Preferences}.
*/
public final class PreferencesDAO {
/** Fetch the {@link Preferences} for a given user. */
public Preferences fetch(SafeText aUserId) throws DAOException {
return Db.fetch(Preferences.class, FETCH_PREFERENCES, aUserId);
}
/** Change the {@link Preferences} for a given user. */
void change(Preferences aPreferences) throws DAOException {
Db.edit(
CHANGE_PREFERENCES,
aPreferences.getScreenName(),
aPreferences.getLoginName()
);
}
}
166
See Also :
Simplify database operations
See Also :
Keep SQL out of code
This is the recommended style. Create an ordinary object in the usual way, and use its services.
Style 2 - variation on Style 1
List<Message> messages = new MessageDAO().fetchRecentMessages();
The DAO is created and used on the same line. This style seems a bit less legible than Style 1.
Style 3 - static methods
List<Message> messages = MessageDAO.fetchRecentMessages();
Probably the least desirable. One must exercise care that such classes are thread-safe. This issue usually
doesn't exist in Styles 1 and 2, if the DAO has no static members, is short-lived, and there is no
possibility of sharing data between threads.
Full DAO Design Pattern
It's common to describe Data Access Objects as an extended design pattern. That design pattern centers
on allowing wholesale changes to a datastore mechanism - for example, changing from a relational
database to using the file system, or some other means of data storage. It is only required when multiple
storage mechanisms must coexist during production, or when the details of how data is to be stored is
decided during deployment.
It must be stressed that most applications do not need the full DAO design pattern as usually described.
Most business applications are deployed with a very particular database in mind. In such cases, allowing
a deployer to configure various types of datastore seems entirely superfluous. If your application won't
benefit from it, then the full DAO pattern is clearly of dubious benefit. Rather, implementing each DAO
as a single, ordinary class seems more appropriate and effective.
See Also :
Data exception wrapping
Keep SQL out of code
Minimize ripple effects
Abstract Factory
Model Objects
Try pseudo-persistence for mock ups
Example
When building applications with the WEB4J tool, DAOException is the only checked exception that
should be emitted by an application's data access layer. All occurrences of checked exceptions are either
handled within the data layer, or, if they are thrown, translated into a DAOException.
In the following example, ConnectionSrc encapulates how an application creates or accesses
Connection s. When an SQLException or a NamingException occurs in its implementation, it is always
wrapped in a DAOException, and then re-thrown to the caller.
This example is particulary appropriate because of the NamingException . If the NamingException was
not translated into an DAOException, and thrown as is, then it would be visible directly to the caller. That
is, the caller would know that the data layer is implemented using JNDI - which is likely inappropriate.
import
import
import
import
java.util.*;
java.util.logging.*;
java.sql.Connection;
java.sql.SQLException;
import
import
import
import
import
import
import
javax.naming.Context;
javax.naming.InitialContext;
javax.naming.NamingException;
javax.sql.DataSource;
hirondelle.web4j.database.DAOException;
hirondelle.web4j.database.ConnectionSource;
hirondelle.web4j.util.Util;
/**
* Fetch a <tt>Connection</tt> from a pool managed by the container.
*/
public final class ConnectionSrc implements ConnectionSource {
//..elided
private Connection getConnectionByName(String aDbName) throws DAOException {
Connection result = null;
String dbConnString = (String)MAP_NAME_TO_CONN_STRING.get(aDbName);
if( ! Util.textHasContent(dbConnString) ){
throw new IllegalArgumentException("Unknown db name : " + Util.quote(aDbName));
}
try {
Context initialContext = new InitialContext();
if ( initialContext == null ) {
fLogger.severe("InitialContext is null. Db : " + Util.quote(dbConnString));
}
169
See Also :
Data access objects
Reduce database code duplication
Exception translation
A Web App Framework - WEB4J
Beware of unknown root causes
Data is king
In most organizations, the data is significantly more important than the applications which use it - data is
king. The fundamental reason is simply that data almost always outlives the applications that create it.
Data is quasi-permanent, whereas the applications that use the data usually change over time. For
example, it's common for an end-user application to be completely replaced after a certain number of
years, using a more modern set of tools. In this case, the database may:
remain untouched
be upgraded a more modern version of the same database
be changed to improve its design
be converted into an entirely different database altogether
Persistent data is just that - persistent - it stays around for a long, long time.
If data is king, then the most important tasks for a developer are those involving the database. Any effort
put into improving the database, or how it's used, will have a lasting, long-term benefit. In particular, the
application programmer should make sure that:
the database structure is robust, well-defined and (usually) normalized. If the application
programmer is not experienced in database design, then they should seek help.
the data is always validated before being entered into the database (this cannot be stressed enough)
actions that consist of more than one SQL statement are properly encapsulated in a transaction, and
rolled back correctly when a failure occurs. In general, the integrity of the data should always be
170
preserved.
applications should never assume that they "own" the data. Databases are independent processes,
and are built to interact with many clients applications, not just one. For example, many databases
are loaded initially using a data load tool. When data is loaded with a script, it bypasses all
validation logic implemented in code. Thus, it's incorrect for a calling application to make
assumptions regarding the validity of the data.
Encapsulate connections
A database connection can be obtained in different ways. One should hide this design decision in a utility
class, such that changes to this decision will have minimal ripple effects. Another important benefit is
elimination of code repetition.
Example
This is taken from the WEB4J example application. Here, a Connection is obtained from a JNDI
DataSource configured on the server. An alternate implementation might obtain a Connection directly
from the JDBC driver. (Some modern drivers include a built-in connection pool.)
171
This implementation is slightly unusual in that it can return a Connection for more than one database.
package hirondelle.web4j.config;
import
import
import
import
import
import
import
import
import
import
import
import
java.util.*;
java.util.logging.*;
java.sql.Connection;
java.sql.SQLException;
javax.naming.Context;
javax.naming.InitialContext;
javax.naming.NamingException;
javax.sql.DataSource;
hirondelle.web4j.database.DAOException;
hirondelle.web4j.database.ConnectionSource;
hirondelle.web4j.util.Util;
javax.servlet.ServletConfig;
/**
Implementation of {@link ConnectionSource}, required by WEB4J.
<P>This implementation uses a <tt>Connection</tt> pool managed by the container.
This class is non-final only since it is convenient for
{@link hirondelle.fish.test.doubles.FakeConnectionSrc}.
Only one method can be overridden - {@link #getConnectionByName(String)}.
*/
public class ConnectionSrc implements ConnectionSource {
/** Read in connection strings from <tt>web.xml</tt>. */
public final void init(ServletConfig aConfig){
fDefaultDbConnString = aConfig.getInitParameter(DEFAULT_CONN_STRING);
fAccessControlDbConnectionString =
aConfig.getInitParameter(ACCESS_CONTROL_CONN_STRING)
;
fTranslationDbConnectionString = aConfig.getInitParameter(TRANSLATION_CONN_STRING);
ensureAllSettingsPresent();
fMapNameToConnectionString = new LinkedHashMap<String, String>();
fMapNameToConnectionString.put(DEFAULT, fDefaultDbConnString);
fMapNameToConnectionString.put(ACCESS_CONTROL, fAccessControlDbConnectionString);
fMapNameToConnectionString.put(TRANSLATION, fTranslationDbConnectionString);
fLogger.config(
"Connection strings : " + Util.logOnePerLine(fMapNameToConnectionString)
);
}
/**
Return value contains only {@link #DEFAULT}, {@link #ACCESS_CONTROL},
and {@link #TRANSLATION}.
*/
public final Set<String> getDatabaseNames(){
return Collections.unmodifiableSet(fMapNameToConnectionString.keySet());
}
/**
Return a {@link Connection} for the default database.
*/
public final Connection getConnection() throws DAOException {
return getConnectionByName(DEFAULT);
}
/**
Return a {@link Connection} for the identified database.
@param aDatabaseName one of the values {@link #DEFAULT},
{@link #TRANSLATION}, or {@link #ACCESS_CONTROL}.
*/
public final Connection getConnection(String aDatabaseName) throws DAOException {
return getConnectionByName(aDatabaseName);
}
/**
Name used to identify the default database. The default database is
the main database, carrying core business data. It is the data that is
most often accessed.
*/
public static final String DEFAULT = "DEFAULT";
/** Name used to identify the access control database (users, roles, etc.).*/
172
if( ! Util.textHasContent(fDefaultDbConnString) ) {
logError(DEFAULT_CONN_STRING);
}
if( ! Util.textHasContent(fTranslationDbConnectionString) ) {
logError(TRANSLATION_CONN_STRING);
}
if ( ! Util.textHasContent(fAccessControlDbConnectionString) ) {
logError(ACCESS_CONTROL_CONN_STRING);
}
}
private static void logError(String aSettingName){
fLogger.severe("Web.xml missing init-param setting for " + aSettingName);
}
}
See Also :
Connection pools
A Web App Framework - WEB4J
Here, simply fetch a String from some textual source (such as a properties file), and pass it to the
constructor of a Statement or PreparedStatement. This form is likely relatively rare, since most SQL
statements seem to have at least one parameter.
Parameterized, with a single set of values :
INSERT INTO Board
(Id, Title, TimeZone, Language, CreationDate)
VALUES ('quark','High Energy Physics','GMT','english',NOW());
Here, fetch a String having '?' as place holders from some textual source :
INSERT INTO Board
(Id, Title, TimeZone, Language, CreationDate)
VALUES (?,?,?,?,NOW());
This String is then passed to the constructor of a PreparedStatement, and parameters are inserted using
the methods of the PreparedStatement class. Note that PreparedStatement has significant advantages
over an ordinary Statement .
Parameterized, with multiple sets of parameters :
INSERT INTO
VALUES
('RASB', 3,
('RASB', 2,
('RASB', 1,
('RASB', 4,
Vote
'Y',
'Y',
'Y',
'?',
NOW()),
NOW()),
NOW()),
NOW());
Using such a statement to add many records is advantageous, since it involves only one trip to the
database. However, the PreparedStatement parameter mechanism cannot do this in one statement, since
the number of added records is arbitrary. The usual style in this case is to use batched statements, in
which a number of individual INSERT statements are collected together in a batch, and then executed as
a unit.
Search Operations
There are cases in which it remains preferable to construct an SQL statement (or at least parts of it) in
code. This is particularly true for many search operations, where the end user enters various search
criteria into a form used to construct WHERE and ORDER BY clauses. It's often the case that a very large
number of possible WHERE and ORDER BY clauses can be built from such user input. Manually
enumerating all such possibilities is often undesirable. In such cases, building the SQL statement
dynamically from user input seems preferable.
When constructing statements dynamically, however, you must guard against SQL injection attacks.
Here's a simple technique for doing so:
start with a base SQL statement defined in a text file, as above.
build the dynamic parts of the SQL statement in code (usually the WHERE and ORDER BY clauses),
but, at this stage, do not insert the data parameter values yet. That is, build a String containing '?'
placeholders for data, in the usual form expected by a PreparedStatement.
175
next, create a PreparedStatement with the given SQL string. This will verify that the SQL
statement is of valid form. As always, using PreparedStatement in this way will protect against
SQL injection attacks.
finally, inject the data for the '?' placeholders in the usual way.
With the above technique, user input of search criteria is used for two distinct purposes: first dynamically
building the core SQL with the desired WHERE and ORDER BY clauses (containing '?' placeholders), and
secondly populating the '?' placeholders with data in the usual way.
Example
Here's an example of a .sql file which contains the SQL statements related to a single feature. All
parameterized statements are expressed in a style suitable for a PreparedStatement, to avoid using any
quoting conventions. It's taken from one of the WEB4J example applications.
LIST_RESTOS {
SELECT Id, Name, Location, Price, Comment
FROM Resto
ORDER BY Name
}
FETCH_RESTO {
SELECT Id, Name, Location, Price, Comment
FROM Resto
WHERE Id =?
}
ADD_RESTO {
-- Id is an autoincrement field, populated automagically by the database.
INSERT INTO Resto (Name, Location, Price, Comment) VALUES (?,?,?,?)
}
CHANGE_RESTO {
UPDATE Resto SET Name=?, Location=?, Price=?, Comment=? WHERE Id=?
}
DELETE_RESTO {
DELETE FROM RESTO WHERE Id=?
}
In the case of web applications, such files are safely placed in the WEB-INF directory, where they are not
accessible to the user, and can be retrieved using ServletContext.getResourceAsStream .
The WEB4J framework places all SQL statements in one or more such .sql files, located anywhere
under the WEB-INF directory. (Please see the javadoc for its data layer for more information.)
See Also :
Data access objects
Reduce database code duplication
Consider using standard SQL
A Web App Framework - WEB4J
Prefer PreparedStatement
Prefer PreparedStatement
PreparedStatement
in general, it's more secure. When a Statement is constructed dynamically from user input, it's
vulnerable to SQL injection attacks. PreparedStatement is less vulnerable in this way (see below).
176
See Also :
Data exception wrapping
177
See Also :
Connection pools
Read-write locks
Business identifiers as String
}
Id add(Member aMember) throws DAOException, DuplicateException {
return Db.add(MEMBER_ADD, baseParamsFrom(aMember));
}
boolean change(Member aMember) throws DAOException, DuplicateException {
Object[] params = Db.addIdTo(baseParamsFrom(aMember), aMember.getId());
return Util.isSuccess(Db.edit(MEMBER_CHANGE, params));
}
int delete(Id aMemberId) throws DAOException {
return Db.delete(MEMBER_DELETE, aMemberId);
}
// PRIVATE
private Object[] baseParamsFrom(Member aMember){
return new Object[]{
aMember.getName(), aMember.getIsActive(), aMember.getDisposition().getId()
};
}
}
The answer is that the list and fetch methods repeat the same piece of data - the class literal
Member.class . Like any repeated literal, it should likely be placed in a private static final field.
Since that would be a common occurence, it would likely be beneficial to make a habit of doing so, and
of giving a conventional name to the field as well, such as MODEL_CLASS.
See Also :
Reduce database code duplication
Keep SQL out of code
Consider data layer tools
Encapsulate connections
A Web App Framework - WEB4J
Use template for transactions
import java.util.*;
import myapp.business.Person;
/**
* Provides "pseudo-persistance", by storing objects as static data attached to this
* class. Such data is lost when the application is shut down, but is retained
* as long as the application is running.
*
* <P>The important thing is to match the desired style of the true DAO
* into which this pseudo-DAO may evolve.
*
* <P>Note that <tt>DataAccessException</tt> (a checked exception) can and should
* be declared in the throws clause, even when the underlying implementation for
* this pseudo-DAO never throws such an exception. Again, the idea is to provide
* a stub which, from the point of view of the caller, is as realistic as possible.
*/
public final class PersonDAO {
/**
* Add <tt>aPerson</tt> to the datastore, returning an indicator of
* success or failure.
*
* If <tt>aPerson</tt> already exists in the datastore, then return false.
*/
public boolean add(Person aPerson) throws DataAccessException {
boolean result = false;
synchronized(fPersons) {
if (! fPersons.containsKey(aPerson.getName())) {
fPersons.put(aPerson.getName(), aPerson);
result = true;
log("Persons after add: " + fPersons);
}
}
return result;
}
/**
* Fetch a person from the datastore, using <tt>aName</tt> as a unique
* identifier.
*
* <P>If no <tt>Person</tt> is found, return null.
*/
public Person fetch(String aName) throws DataAccessException {
Person result = null;
synchronized(fPersons){
if(fPersons.containsKey(aName)){
result = fPersons.get(aName);
}
log("Persons during fetch: " + fPersons);
}
return result;
}
/**
* Update the data associated with <tt>aPersons</tt>, returning an
* indicator of success or failure.
*
* <P>If <tt>aPerson</tt> is not found in the datastore, then return false.
*/
public boolean change(Person aPerson) throws DataAccessException {
boolean result = false;
synchronized(fPersons){
if (fPersons.containsKey(aPerson.getName())) {
fPersons.put(aPerson.getName(), aPerson); //replaces old entry
result = true;
log("Persons after change: " + fPersons);
}
}
return result;
}
/**
* Delete the <tt>Person</tt> identified uniquely by <tt>aName</tt>,
* returning an indicator of success or failure.
*
* <P>If <tt>aName</tt> is not found in the datastore, then return false.
*/
public boolean delete(String aName) throws DataAccessException {
180
See Also :
Data access objects
package hirondelle.web4j.database;
/**
Execute a database transaction.
<P>Should be applied only to operations involving more than one SQL statement.
*/
public interface Tx {
/**
Execute a database transaction, and return the number of edited records.
*/
int executeTx() throws DAOException;
}
181
import java.sql.*;
import java.util.logging.*;
import hirondelle.web4j.BuildImpl;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.util.Consts;
/**
* Template for executing a local, non-distributed transaction versus a
* single database, using a single connection.
*
* <P>This abstract base class implements the template method design pattern.
*/
public abstract class TxTemplate implements Tx {
//..elided
/**
* <b>Template</b> method calls the abstract method {@link #executeMultipleSqls}.
* <P>Returns the same value as <tt>executeMultipleSqls</tt>.
*
* <P>A <tt>rollback</tt> is performed if <tt>executeMultipleSqls</tt> fails.
*/
public final int executeTx() throws DAOException {
int result = 0;
fLogger.fine(
"Editing within a local transaction, with isolation level : " +
fTxIsolationLevel
);
ConnectionSource connSource = BuildImpl.forConnectionSource();
if(Util.textHasContent(fDatabaseName)){
fConnection = connSource.getConnection(fDatabaseName);
}
else {
fConnection = connSource.getConnection();
}
try {
TxIsolationLevel.set(fTxIsolationLevel, fConnection);
startTx();
result = executeMultipleSqls(fConnection);
endTx(result);
}
catch(SQLException rootCause){
fLogger.fine("Transaction throws SQLException.");
rollbackTx();
String message =
"Cannot execute edit. ErrorId code : " + rootCause.getErrorCode() +
Consts.SPACE + rootCause
;
if (rootCause.getErrorCode() ==
DbConfig.getErrorCodeForDuplicateKey().intValue()){
throw new DuplicateException(message, rootCause);
}
throw new DAOException(message, rootCause);
}
catch (DAOException ex){
fLogger.fine("Transaction throws DAOException.");
rollbackTx();
throw ex;
}
finally {
DbUtil.logWarnings(fConnection);
DbUtil.close(fConnection);
}
fLogger.fine("Total number of edited records: " + result);
return result;
}
182
/**
* Execute multiple SQL operations in a single local transaction.
*
* <P>This method returns the number of records edited.
*/
public abstract int executeMultipleSqls(
Connection aConnection
) throws SQLException, DAOException;
// PRIVATE
private Connection fConnection;
private String fDatabaseName;
private final TxIsolationLevel fTxIsolationLevel;
private static final boolean fOFF = false;
private static final boolean fON = true;
private static final Logger fLogger = Util.getLogger(TxTemplate.class);
private void startTx() throws SQLException {
fConnection.setAutoCommit(fOFF);
}
private void endTx(int aNumEdits) throws SQLException, DAOException {
if ( BUSINESS_RULE_FAILURE == aNumEdits ) {
fLogger.severe("Business rule failure occured. Cannot commit transaction.");
rollbackTx();
}
else {
fLogger.fine("Commiting transaction.");
fConnection.commit();
fConnection.setAutoCommit(fON);
}
}
private void rollbackTx() throws DAOException {
fLogger.severe("ROLLING BACK TRANSACTION.");
try {
fConnection.rollback();
}
catch(SQLException ex){
throw new DAOException("Cannot rollback transaction", ex);
}
}
}
Here's a concrete implementation named RoleDAO. It has a change method which updates the roles
attached to a user. The roles are stored in a cross-reference table. In this case, an update is implemented
as 'delete all old, then add all new'. Note the lack of try..catch blocks in this class.
final class RoleDAO {
//..elided
/**
* Update all roles attached to a user.
*
* <P>This implementation will treat all edits to user roles as
* '<tt>DELETE-ALL</tt>, then <tt>ADD-ALL</tt>' operations.
*/
boolean change(UserRole aUserRole) throws DAOException {
Tx update = new UpdateTransaction(aUserRole);
return Util.isSuccess(update.executeTx());
}
// PRIVATE //
/** Cannot be a {@link hirondelle.web4j.database.TxSimple}, since there is looping.
*/
private static final class UpdateTransaction extends TxTemplate {
UpdateTransaction(UserRole aUserRole){
super(ConnectionSrc.ACCESS_CONTROL);
183
fUserRole = aUserRole;
}
public int executeMultipleSqls(
Connection aConnection
) throws SQLException, DAOException {
int result = 0;
//perform edits using a shared connection
result = result + DbTx.edit(aConnection, ROLES_DELETE, fUserRole.getUserName());
for(Id roleId : fUserRole.getRoles()){
result = result +
DbTx.edit(aConnection,ROLES_ADD,fUserRole.getUserName(),roleId);
}
return result;
}
private UserRole fUserRole;
}
}
See Also :
Template method
Simplify database operations
A Web App Framework - WEB4J
Use template for repeated try-catch
Interface HasDuplicates?
Implementations
Historical
LinkedHashSet
Set
HashSet
TreeSet
no
...
...
...
ArrayList
List
LinkedList
Vector, Stack
yes
...
...
...
no duplicate
Hashtable,
LinkedHashMap
Map
HashMap
TreeMap Properties
...
...
keys
Principal features of non-primary implementations:
HashMap has slightly better performance than LinkedHashMap , but its iteration order is undefined
HashSet has slightly better performance than LinkedHashSet , but its iteration order is undefined
TreeSet is ordered and sorted, but slower
TreeMap is ordered and sorted, but slower
LinkedList has fast adding to the start of the list, and fast deletion from the interior via iteration
184
For LinkedHashSet and LinkedHashMap , the re-insertion of an item does not affect insertion order.
For LinkedHashMap , 'access order' is from the least recent access to the most recent access. In this
context, only calls to get , put , and putAll constitute an access, and only calls to these methods affect
access order.
While being used in a Map or Set , these items must not change state (hence, it's recommended that these
items be immutable objects):
keys of a Map
items in a Set
Sorting requires either that:
the stored items implement Comparable
a Comparator for the stored objects be defined
To retain the order of a ResultSet as specified in an ORDER BY clause, insert the records into a List
or a LinkedHashMap .
See Also :
Use standard Collections
Implementing compareTo
Immutable objects
Prefer Collections over older classes
Encapsulate collections
In general, Collections are not immutable objects. As such, one must often exercise care that collection
fields are not unintentionally exposed to the caller.
One technique is to define a set of related methods which prevent the caller from directly using the
underlying collection, such as:
addThing(Thing)
removeThing(Thing)
getThings() - return an
unmodifiable Collection
Example 1
185
import java.util.*;
public final class SoccerTeam {
public SoccerTeam(String aTeamName, String aHeadCoachName){
//..elided
}
public void addPlayer(Player aPlayer){
fPlayers.add(aPlayer);
}
public void removePlayer(Player aPlayer){
fPlayers.remove(aPlayer);
}
public Set<Player> getPlayers(){
return Collections.unmodifiableSet(fPlayers);
}
//..elided
// PRIVATE
private Set<Player> fPlayers = new LinkedHashSet<>();
private String fTeamName;
private String fHeadCoachName;
}
Example 2
is an example of exposing the collection directly to the caller. This is not necessarily an
incorrect design, but it's riskier, since the contents of the collection can be directly changed by both
BaseballTeam and its caller:
BaseballTeam
import java.util.*;
public final class BaseballTeam {
public BaseballTeam(String aTeamName, String aHeadCoachName){
//..elided
}
public void setPlayers(Set<Player> aPlayers){
fPlayers = aPlayers;
}
public Set<Player> getPlayers(){
return fPlayers;
}
//..elided
// PRIVATE
private Set<Player> fPlayers;
private String fTeamName;
private String fHeadCoachName;
}
See Also :
Defensive copying
Immutable objects
Many programmers have a strong preference for using a for-each loop or an Iterator instead of a for loop.
The traditional for -loop always uses an explicit loop index - an integer that identifies each iteration. The
problem is that loop indexes have always been a fruitful source of error. For example, off-by-one errors
are very common in programming, and they are often related to these loop indexes. In addition, the
(somewhat unnatural) computer science tradition of starting loop indexes from 0 instead of 1 often
contributes to these sorts of bugs.
Since they are more error prone, for -loops with indexes should generally be avoided, in favor of the
more robust, compact, and elegant for-each loop.
Example
import java.util.*;
/** Different iteration styles. */
public class IterateNoIndex {
/**
* Iterating without an index is more compact and less
* error prone.
*/
public void withoutIndex(){
//for-each loop is usually preferred
List<String> trees = Arrays.asList("Maple", "Birch", "Poplar");
for(String tree: trees){
log(tree);
}
//Iterators are not as compact as a for-each loop
//but sometimes you need them: removing/replacing items,
//and 'parallel' iterations across two data structures
Iterator<String> iter = trees.iterator();
while (iter.hasNext()) {
log(iter.next());
}
}
/** Iterating with an index is more error prone. */
public void withIndex(){
//traditional for-loop
for(int idx=0; idx < 10; ++idx){
log("Iteration..." + idx);
}
}
// PRIVATE
private void log(String aMessage){
System.out.println(aMessage);
}
}
See Also :
Two ways of using Iterator
Use for-each liberally
Most programmers prefer the Java Collections Framework to arrays, Vector, and Hashtable , for the
following reasons:
the intent of the Collections Framework is to replace the older, less comprehensive tools
synchronization can be easily controlled (while Hashtable and Vector are always synchronized)
immutability can be easily controlled
search and sort algorithms are ready-made
the type of a reference to a Collection can be generic, while underlying implementation can be
easily changed
arrays are of fixed size, and are more error prone - off-by-one errors with array indexes have
always been a fruitful source of error
Arrays.asList may be used to pass an array to a method expecting a Collection or a List
Collection.toArray may be used to convert a Collection to an Object[] or a T[]
Collections.enumeration and Collections.list may be used to convert between enumerations
and collections
Since JDK 1.5, a strong case can be made that Collections should almost always be preferred over arrays.
Prior to JDK 1.5, the main disadvantage of using collections is that they always held an Object, and
retrieval of their contents required casting. Casting cannot be checked at compile-time. Arrays, on the
other hand, have the advantage of always being "parameterized" to hold objects of a specific type (in all
versions of the JDK), and this is verified by the compiler. Thus, in older versions of the JDK, changing
from a collection to an array forces one particular kind of error to appear at compile-time instead of runtime.
One minor advantage of arrays is that they are slightly easier to initialize. However, using
Arrays.asList makes the difference trivial:
import java.util.*;
public final class ArrayInit {
/** Initialize arrays and Lists. */
public static void main(String... args){
//Array initializers are compact
String[] paintings = {"oil", "watercolour"};
//Build a List using Arrays.asList(T...)
//This works for any type, not just for String
List<String> languages = Arrays.asList(
"urdu", "hindi", "pali", "sanskrit"
);
//Build a List in a more verbose way
List<String> nicePlaces = new ArrayList<>();
nicePlaces.add("luxembourg gardens");
nicePlaces.add("sea side");
nicePlaces.add("large magellanic cloud");
}
}
See Also :
Choosing the right Collection
Avoid basic style errors
188
See Also :
Iterate without an index
Use for-each liberally
See Also :
Iterate without an index
Two ways of using Iterator
Modernize old code
In general, references to objects should be as generic as possible. The user of such a reference will be
protected from possible changes to the underlying implementation class. The ripple effects of such a
change are limited to the single line of code which creates the object, and will not propagate any further.
In the case of collections, this means habitually referring to collection objects using List , Map , Set ,
Queue , and Deque interface references.
Note as well that all of these except Map can be referred to using the even more generic Collection .
Example
import java.util.*;
public class Nucleus {
public static void main (String... arguments) {
Nucleus lithium = new Nucleus (3,4);
//note the generic Map reference is used here, not LinkedHashMap
Map<String, Integer> quarks = lithium.getQuarkSummary();
log("Number of up quarks in lithium nucleus: " + quarks.get("Up"));
log("Number of down quarks in lithium nucleus: " + quarks.get("Down"));
}
public Nucleus(int aNumProtons, int aNumNeutrons) {
fNumProtons = aNumProtons;
fNumNeutrons = aNumNeutrons;
}
/** Note get method is final. */
public final int getNumProtons() {
return fNumProtons;
}
/** Note get method is final. */
public final int getNumNeutrons() {
return fNumNeutrons;
}
/**
* This method returns a Map which summarizes how many quarks of each
* flavour are in the nucleus.
*
* @return a generic Map reference, instead of a LinkedHashMap; the
* user will be protected from the detail of what implementation of Map
* has been selected here.
*/
public final Map<String, Integer> getQuarkSummary() {
LinkedHashMap<String, Integer> result = new LinkedHashMap<>();
int numUp =
fNumProtons * fUP_QUARKS_PER_PROTON +
fNumNeutrons * fUP_QUARKS_PER_NEUTRON
;
int numDown =
fNumProtons * fDOWN_QUARKS_PER_PROTON +
fNumNeutrons * fDOWN_QUARKS_PER_NEUTRON
;
//this makes use of auto-boxing of ints into Integers:
result.put("Up", numUp);
result.put("Down", numDown);
return result;
}
//PRIVATE
private final int fNumProtons;
private final int fNumNeutrons;
private static final int fUP_QUARKS_PER_PROTON = 2;
private static final int fDOWN_QUARKS_PER_PROTON = 1;
private static final int fUP_QUARKS_PER_NEUTRON = 1;
private static final int fDOWN_QUARKS_PER_NEUTRON = 2;
191
See Also :
Minimize ripple effects
interfaces
Before JDK 1.5, HashMap and HashSet were the preferred implementations of Map and Set . However, the
iterators returned by those classes have the somewhat bizarre property of having an undefined order. That
is, iterating over a HashMap or HashSet can return elements in a different order at different times. Their
iteration order is not guaranteed to be repeatable (and often isn't). Even though HashMap and HashSet
have slightly better overall performance, for most business applications it is likely best to avoid such
undefined behavior.
To retain the sorting of items being manipulated in a graphical user interface, TreeSet and TreeMap are
useful. They force a collection to maintain a sort order when the user adds and deletes items.
See Also :
Choosing the right Collection
Avoid clone
Avoid implementing clone .
is very tricky to implement correctly in all circumstances, nearly to the point of being
pathological
the importance of copying objects will always remain, since object fields often need to be
defensively copied
copy constructors and static factory methods provide an alternative to clone , and are much easier
to implement
clone
If you need to extend a superclass that implements clone , then your subclass must implement clone as
well. The quickest solution is for your subclass to simply throw an exception.
192
Example
This example shows a superclass with a typical implementation of clone , and a subclass which has
disabled its clone method.
import java.util.Date;
public abstract class Fruit implements Cloneable {
public Fruit(String aColour, Date aBestBeforeDate) {
super();
fColour = aColour;
//defensive copy needed for this mutable object
fBestBeforeDate = new Date(aBestBeforeDate.getTime());
}
public abstract void ripen();
public String getColour() {
return fColour;
}
public Date getBestBeforeDate() {
//return defensive copy of this mutable object
return new Date(fBestBeforeDate.getTime());
}
/**
* Implement clone as follows
* <ul>
* <li>the class declaration "implements Cloneable" (not needed if already
* declared in superclass)
* <li>declare clone method as public
* <li>if the class is final, clone does not need to throw CloneNotSupportedException
* <li>call super.clone and cast to this class
* <li>as in defensive copying, ensure each mutable field has an independent copy
* constructed, to avoid sharing internal state between objects
* </ul>
*/
@Override public Object clone() throws CloneNotSupportedException {
//get initial bit-by-bit copy, which handles all immutable fields
Fruit result = (Fruit)super.clone();
//mutable fields need to be made independent of this object, for reasons
//similar to those for defensive copies - to prevent unwanted access to
//this object's internal state
result.fBestBeforeDate = new Date(this.fBestBeforeDate.getTime());
return result;
}
// PRIVATE
/**
* Strings are always immutable.
*/
private String fColour;
/**
* Date is a mutable object. In this class, this object field is to be treated
* as belonging entirely to this class, and no user of this class is
* to be able to directly access and change this field's state.
*/
private Date fBestBeforeDate;
}
See Also :
Copy constructors
Defensive copying
Factory methods
Implementing compareTo
The compareTo method is the sole member of the Comparable interface, and is not a member of Object.
However, it is quite similar in nature to equals and hashCode . It provides a means of fully ordering
objects.
Implementing Comparable allows:
calling Collections.sort and Collections.binarySearch
calling Arrays.sort and Arrays.binarySearch
using objects as keys in a TreeMap
using objects as elements in a TreeSet
The compareTo method needs to satisfy the following conditions. These conditions have the goal of
allowing objects to be fully sorted, much like the sorting of a database result set on all fields.
anticommutation : x.compareTo(y) is the opposite sign of y.compareTo(x)
exception symmetry : x.compareTo(y) throws exactly the same exceptions as y.compareTo(x)
transitivity : if x.compareTo(y)>0 and y.compareTo(z)>0, then x.compareTo(z)>0 (and same
for less than)
if x.compareTo(y)==0, then x.compareTo(z) has the same sign as y.compareTo(z)
consistency with equals is highly recommended, but not required : x.compareTo(y)==0, if and
only if x.equals(y) ; consistency with equals is required for ensuring sorted collections (such as
TreeSet ) are well-behaved.
One can greatly increase the performance of compareTo by comparing first on items which are most
likely to differ.
When a class extends a concrete Comparable class and adds a significant field, a correct implementation
of compareTo cannot be constructed. The only alternative is to use composition instead of inheritance. (A
similar situation holds true for equals. See Effective Java for more information.)
Compare the various types of fields as follows:
194
numeric primitive : use < and >. There is an exception to this rule: float and double primitives
should be compared using Float.compare(float, float) and Double.compare(double, double). This
avoids problems associated with special border values. (Thanks to Roger Orr in the UK for
pointing this out.)
boolean primitive : use tests of the form (x && !y)
Object : use compareTo . (Note that possibly-null fields present a problem : while x.equals(null)
returns false , x.compareTo(null) will always throw a NullPointerException )
type-safe enumeration : use compareTo , like any Object
collection or array : Comparable does not seem to be intended for these kinds of fields. For
example, List , Map and Set do not implement Comparable . As well, some collections have no
definite order of iteration, so doing an element-by-element comparison cannot be meaningful in
those cases.
If the task is to perform a sort of items which are stored in a relational database, then it is usually much
preferred to let the database perform the sort using the ORDER BY clause, rather than in code.
An alternative to implementing Comparable is passing Comparator objects as parameters. Be aware that
if a Comparator compares only one of several significant fields, then the Comparator is very likely not
synchronized with equals.
All primitive wrapper classes implement Comparable . Note that Boolean did not implement Comparable
until version 1.5, however.
Example
import java.util.*;
import java.io.*;
public final class Account implements Comparable<Account> {
enum AccountType {CASH, MARGIN, RRSP};
public Account (
String aFirstName,
String aLastName,
int aAccountNumber,
int aBalance,
boolean aIsNewAccount,
AccountType aAccountType
) {
//..parameter validations elided
fFirstName = aFirstName;
fLastName = aLastName;
fAccountNumber = aAccountNumber;
fBalance = aBalance;
fIsNewAccount = aIsNewAccount;
fAccountType = aAccountType;
}
/**
* @param aThat is a non-null Account.
*
* @throws NullPointerException if aThat is null.
*/
@Override public int compareTo(Account aThat) {
final int BEFORE = -1;
final int EQUAL = 0;
final int AFTER = 1;
//this optimization is usually worthwhile, and can
//always be added
if (this == aThat) return EQUAL;
//primitive numbers follow this form
if (this.fAccountNumber < aThat.fAccountNumber) return BEFORE;
if (this.fAccountNumber > aThat.fAccountNumber) return AFTER;
//booleans follow this form
195
/**
* Type of the account, expressed as a type-safe enumeration (non-null).
*/
private AccountType fAccountType;
/**
* Exercise compareTo.
*/
public static void main (String[] aArguments) {
//Note the difference in behaviour in equals and compareTo, for nulls:
String text = "blah";
Integer number = new Integer(10);
//x.equals(null) always returns false:
System.out.println("false: " + text.equals(null));
196
197
public AccountOld (
String aFirstName,
String aLastName,
int aAccountNumber,
int aBalance,
boolean aIsNewAccount,
AccountType aAccountType
) {
//..parameter validations elided
fFirstName = aFirstName;
fLastName = aLastName;
fAccountNumber = aAccountNumber;
fBalance = aBalance;
fIsNewAccount = aIsNewAccount;
fAccountType = aAccountType;
}
/**
* @param aThat is a non-null AccountOld.
*
* @throws NullPointerException if aThat is null.
* @throws ClassCastException if aThat is not an AccountOld object.
*/
public int compareTo(Object aThat) {
final int BEFORE = -1;
final int EQUAL = 0;
final int AFTER = 1;
//this optimization is usually worthwhile, and can
//always be added
if ( this == aThat ) return EQUAL;
final AccountOld that = (AccountOld)aThat;
//primitive numbers follow this form
if (this.fAccountNumber < that.fAccountNumber) return BEFORE;
if (this.fAccountNumber > that.fAccountNumber) return AFTER;
//booleans follow this form
if (!this.fIsNewAccount && that.fIsNewAccount) return BEFORE;
if (this.fIsNewAccount && !that.fIsNewAccount) return AFTER;
//Objects, including type-safe enums, follow this form.
//Exception : Boolean implements Comparable in JDK 1.5, but not in 1.4
//Note that null objects will throw an exception here.
int comparison = this.fAccountType.compareTo(that.fAccountType);
if ( comparison != EQUAL ) return comparison;
comparison = this.fLastName.compareTo(that.fLastName);
if ( comparison != EQUAL ) return comparison;
comparison = this.fFirstName.compareTo(that.fFirstName);
if ( comparison != EQUAL ) return comparison;
if (this.fBalance < that.fBalance) return BEFORE;
if (this.fBalance > that.fBalance) return AFTER;
//all comparisons have yielded equality
//verify that compareTo is consistent with equals (optional)
assert this.equals(that) : "compareTo inconsistent with equals.";
return EQUAL;
}
/**
* Define equality of state.
*/
public boolean equals(Object aThat) {
if ( this == aThat ) return true;
if ( !(aThat instanceof Account) ) return false;
AccountOld that = (AccountOld)aThat;
return
( this.fAccountNumber == that.fAccountNumber ) &&
( this.fAccountType == that.fAccountType ) &&
( this.fBalance == that.fBalance ) &&
( this.fIsNewAccount == that.fIsNewAccount ) &&
( this.fFirstName.equals(that.fFirstName) ) &&
198
( this.fLastName.equals(that.fLastName) );
}
/**
* A class that overrides equals must also override hashCode.
*/
public int hashCode() {
int result = HashCodeUtil.SEED;
result = HashCodeUtil.hash( result, fAccountNumber );
result = HashCodeUtil.hash( result, fAccountType );
result = HashCodeUtil.hash( result, fBalance );
result = HashCodeUtil.hash( result, fIsNewAccount );
result = HashCodeUtil.hash( result, fFirstName );
result = HashCodeUtil.hash( result, fLastName );
return result;
}
//PRIVATE
private
private
private
private
private
/**
* Type of the account, expressed as a type-safe enumeration (non-null).
*/
private AccountType fAccountType;
/**
* Exercise compareTo.
*/
public static void main (String[] aArguments) {
//Note the difference in behaviour in equals and compareTo, for nulls:
String text = "blah";
Integer number = new Integer(10);
//x.equals(null) always returns false:
System.out.println("false: " + text.equals(null));
System.out.println("false: " + number.equals(null) );
//x.compareTo(null) always throws NullPointerException:
//System.out.println( text.compareTo(null) );
//System.out.println( number.compareTo(null) );
AccountOld flaubert = new AccountOld(
"Gustave", "Flaubert", 1003, 0, true, AccountType.MARGIN
);
//all of these other versions of "flaubert" differ from the
//original in only one field
AccountOld flaubert2 = new AccountOld(
"Guy", "Flaubert", 1003, 0, true, AccountType.MARGIN
);
AccountOld flaubert3 = new AccountOld(
"Gustave", "de Maupassant", 1003, 0, true, AccountType.MARGIN
);
AccountOld flaubert4 = new AccountOld(
"Gustave", "Flaubert", 2004, 0, true, AccountType.MARGIN
);
AccountOld flaubert5 = new AccountOld(
"Gustave", "Flaubert", 1003, 1, true, AccountType.MARGIN
);
AccountOld flaubert6 = new AccountOld(
"Gustave", "Flaubert", 1003, 0, false, AccountType.MARGIN
);
AccountOld flaubert7 = new AccountOld(
"Gustave", "Flaubert", 1003, 0, true, AccountType.CASH
);
System.out.println( "0: " + flaubert.compareTo(flaubert) );
System.out.println( "first name +: " + flaubert2.compareTo(flaubert) );
//Note capital letters precede small letters
System.out.println( "last name +: " + flaubert3.compareTo(flaubert) );
System.out.println( "acct number +: " + flaubert4.compareTo(flaubert) );
System.out.println( "balance +: " + flaubert5.compareTo(flaubert) );
System.out.println( "is new -: " + flaubert6.compareTo(flaubert) );
System.out.println( "account type -: " + flaubert7.compareTo(flaubert) );
}
199
See Also :
Type-Safe Enumerations
Implementing equals
Implementing hashCode
Don't perform basic SQL tasks in code
Modernize old code
Implementing equals
All objects have both identity (the object's location in memory) and state (the object's data). The ==
operator always compares identity. The default implementation of equals compares identity as well.
Sometimes the default implementation of equals has the desired behaviour (as in a type-safe
enumeration, for example), but equals should usually compare state, not identity. This is particularly true
for "data-centric" classes which map to database records.
hashCode
200
In an equals method, it's usually worthwhile to order field comparisons such that the most significant
comparisons are performed first. That is, fields most likely to differ should be evaluated first. This allows
the && "short-circuit" logical operator to minimize execution time.
Example 1
The above policies can be collected in a utility class:
/**
* Collected methods which allow easy implementation of <code>equals</code>.
*
* Example use case in a class called Car:
* <pre>
public boolean equals(Object aThat){
if ( this == aThat ) return true;
if ( !(aThat instanceof Car) ) return false;
Car that = (Car)aThat;
return
EqualsUtil.areEqual(this.fName, that.fName) &&
EqualsUtil.areEqual(this.fNumDoors, that.fNumDoors) &&
EqualsUtil.areEqual(this.fGasMileage, that.fGasMileage) &&
EqualsUtil.areEqual(this.fColor, that.fColor) &&
Arrays.equals(this.fMaintenanceChecks, that.fMaintenanceChecks); //array!
}
* </pre>
*
* <em>Arrays are not handled by this class</em>.
* This is because the <code>Arrays.equals</code> methods should be used for
* array fields.
*/
public final class EqualsUtil {
static public boolean areEqual(boolean aThis, boolean aThat){
//System.out.println("boolean");
return aThis == aThat;
}
static public boolean areEqual(char aThis, char aThat){
//System.out.println("char");
return aThis == aThat;
}
static public boolean areEqual(long aThis, long aThat){
/*
* Implementation Note
* Note that byte, short, and int are handled by this method, through
* implicit conversion.
*/
//System.out.println("long");
return aThis == aThat;
}
static public boolean areEqual(float aThis, float aThat){
//System.out.println("float");
return Float.floatToIntBits(aThis) == Float.floatToIntBits(aThat);
}
static public boolean areEqual(double aThis, double aThat){
//System.out.println("double");
return Double.doubleToLongBits(aThis) == Double.doubleToLongBits(aThat);
}
/**
* Possibly-null object field.
*
* Includes type-safe enumerations and collections, but does not include
* arrays. See class comment.
*/
static public boolean areEqual(Object aThis, Object aThat){
//System.out.println("Object");
return aThis == null ? aThat == null : aThis.equals(aThat);
}
}
201
Car
import java.util.*;
public final class Car {
public Car (
String aName, int aNumDoors, List<String> aOptions,
double aGasMileage, String aColor, Date[] aMaintenanceChecks
){
fName = aName;
fNumDoors = aNumDoors;
fOptions = new ArrayList<>(aOptions);
fGasMileage = aGasMileage;
fColor = aColor;
fMaintenanceChecks = new Date[aMaintenanceChecks.length];
for (int idx=0; idx < aMaintenanceChecks.length; ++idx) {
fMaintenanceChecks[idx] = new Date( aMaintenanceChecks[idx].getTime() );
}
}
@Override public boolean equals(Object aThat) {
//check for self-comparison
if ( this == aThat ) return true;
//use instanceof instead of getClass here for two reasons
//1. if need be, it can match any supertype, and not just one class;
//2. it renders an explict check for "that == null" redundant, since
//it does the check for null already - "null instanceof [type]" always
//returns false. (See Effective Java by Joshua Bloch.)
if ( !(aThat instanceof Car) ) return false;
//Alternative to the above line :
//if ( aThat == null || aThat.getClass() != this.getClass() ) return false;
//cast to native object is now safe
Car that = (Car)aThat;
//now a proper field-by-field evaluation can be made
return
EqualsUtil.areEqual(this.fName, that.fName) &&
EqualsUtil.areEqual(this.fNumDoors, that.fNumDoors) &&
EqualsUtil.areEqual(this.fOptions, that.fOptions) &&
EqualsUtil.areEqual(this.fGasMileage, that.fGasMileage) &&
EqualsUtil.areEqual(this.fColor, that.fColor) &&
Arrays.equals(this.fMaintenanceChecks, that.fMaintenanceChecks);
}
//..other methods elided
// PRIVATE
/**
* The following fields are chosen to exercise most of the different
* cases.
*/
private String fName;
private int fNumDoors;
private List<String> fOptions;
private double fGasMileage;
private String fColor; //treat as possibly-null
private Date[] fMaintenanceChecks;
/**
* Exercise the equals method.
*/
public static void main (String... aArguments) {
List<String> options = new ArrayList<String>();
options.add("sunroof");
Date[] dates = new Date[1];
dates[0] = new Date();
//Create a bunch of Cars; only one and two should be equal
Car one = new Car("Nissan", 2, options, 46.3, "Green", dates);
//two is equal to one
Car two = new Car("Nissan", 2, options, 46.3, "Green", dates);
202
"one
"one
"two
"one
"one
"one
"one
"one
"one
"one
=
=
=
=
=
=
=
=
=
=
}
}
An example run of this class demonstrates that only objects one and two are equal:
one
one
two
one
one
one
one
one
one
one
=
=
=
=
=
=
=
=
=
=
one: true
two: true
one: true
three: false
four: false
five: false
six: false
seven: false
eight: false
null: false
Example 2
The WEB4J tool has a utility class for implementing equals. The following is an example of a Model
Object implemented with WEB4J.
Items to note regarding this equals method:
it reads at a slightly higher level
it's easier to remember, and easier to write
it does not use multiple return statements
the fields used in equals and hashCode are clearly 'in sync' (as they must be), since they both call
the same method named getSignificantFields()
package hirondelle.fish.main.discussion;
203
import
import
import
import
import
import
java.util.*;
hirondelle.web4j.model.ModelCtorException;
hirondelle.web4j.model.ModelUtil;
hirondelle.web4j.model.Check;
hirondelle.web4j.security.SafeText;
static hirondelle.web4j.util.Consts.FAILS;
/**
Comment posted by a possibly-anonymous user.
*/
public final class Comment {
/**
Constructor.
@param aUserName identifies the logged in user posting the comment.
@param aBody the comment, must have content.
@param aDate date and time when the message was posted.
*/
public Comment (
SafeText aUserName, SafeText aBody, Date aDate
) throws ModelCtorException {
fUserName = aUserName;
fBody = aBody;
fDate = aDate.getTime();
validateState();
}
/** Return the logged in user name passed to the constructor. */
public SafeText getUserName() {
return fUserName;
}
/** Return the body of the message passed to the constructor.
public SafeText getBody() {
return fBody;
}
*/
/**
Return a <a href="http://www.javapractices.com/Topic15.cjp">defensive copy</a>
of the date passed to the constructor.
<P>The caller may change the state of the returned value, without affecting
the internals of this <tt>Comment</tt>. Such copying is needed since
a {@link Date} is a mutable object.
*/
public Date getDate() {
// the returned object is independent of fDate
return new Date(fDate);
}
/** Intended for debugging only. */
@Override public String toString() {
return ModelUtil.toStringFor(this);
}
@Override public boolean equals( Object aThat ) {
Boolean result = ModelUtil.quickEquals(this, aThat);
if ( result == null ){
Comment that = (Comment) aThat;
result = ModelUtil.equalsFor(
this.getSignificantFields(), that.getSignificantFields()
);
}
return result;
}
@Override public int hashCode() {
if ( fHashCode == 0 ) {
fHashCode = ModelUtil.hashCodeFor(getSignificantFields());
}
return fHashCode;
}
// PRIVATE //
private final SafeText fUserName;
private final SafeText fBody;
/** Long is used here instead of Date in order to ensure immutability.*/
204
See Also :
Implementing compareTo
Implementing hashCode
Beware of instanceof operator
Multiple return statements
Use boxing with care
Implementing hashCode
Implementing hashCode :
if a class overrides equals, it must override hashCode
when they are both overridden, equals and hashCode must use the same set of fields
if two objects are equal, then their hashCode values must be equal as well
if the object is immutable, then hashCode is a candidate for caching and lazy initialization
It's a popular misconception that hashCode provides a unique identifier for an object. It does not.
Example 1
The following utility class allows simple construction of an effective hashCode method. It is based on the
recommendations of Effective Java, by Joshua Bloch.
import java.lang.reflect.Array;
/**
* Collected methods which allow easy implementation of <tt>hashCode</tt>.
*
* Example use case:
* <pre>
* public int hashCode(){
*
int result = HashCodeUtil.SEED;
*
//collect the contributions of various fields
*
result = HashCodeUtil.hash(result, fPrimitive);
*
result = HashCodeUtil.hash(result, fObject);
*
result = HashCodeUtil.hash(result, fArray);
*
return result;
* }
* </pre>
205
*/
public final class HashCodeUtil {
/**
* An initial value for a <tt>hashCode</tt>, to which is added contributions
* from fields. Using a non-zero value decreases collisons of <tt>hashCode</tt>
* values.
*/
public static final int SEED = 23;
/** booleans. */
public static int hash(int aSeed, boolean aBoolean) {
log("boolean...");
return firstTerm( aSeed ) + (aBoolean ? 1 : 0);
}
/*** chars. */
public static int hash(int aSeed, char aChar) {
log("char...");
return firstTerm(aSeed) + (int)aChar;
}
/** ints. */
public static int hash(int aSeed , int aInt) {
/*
* Implementation Note
* Note that byte and short are handled by this method, through
* implicit conversion.
*/
log("int...");
return firstTerm(aSeed) + aInt;
}
/** longs. */
public static int hash(int aSeed , long aLong) {
log("long...");
return firstTerm(aSeed) + (int)(aLong ^ (aLong >>> 32));
}
/** floats. */
public static int hash(int aSeed , float aFloat) {
return hash(aSeed, Float.floatToIntBits(aFloat));
}
/** doubles. */
public static int hash(int aSeed , double aDouble) {
return hash( aSeed, Double.doubleToLongBits(aDouble) );
}
/**
* <tt>aObject</tt> is a possibly-null object field, and possibly an array.
*
* If <tt>aObject</tt> is an array, then each element may be a primitive
* or a possibly-null object.
*/
public static int hash(int aSeed , Object aObject) {
int result = aSeed;
if (aObject == null){
result = hash(result, 0);
}
else if (!isArray(aObject)){
result = hash(result, aObject.hashCode());
}
else {
int length = Array.getLength(aObject);
for (int idx = 0; idx < length; ++idx) {
Object item = Array.get(aObject, idx);
//if an item in the array references the array itself, prevent infinite
looping
if(! (item == aObject))
//recursive call!
result = hash(result, item);
}
}
return result;
}
// PRIVATE
private static final int fODD PRIME NUMBER = 37;
206
Here's an example of its use. When the logging statements are uncommented in HashCodeUtil, the reuse
of the boolean, char , int and long versions of hash is demonstrated:
boolean...
char...
int...
long...
long...
int...
int...
int...
int...
int...
hashCode value: -608077094
import java.util.*;
public final class ApartmentBuilding {
public ApartmentBuilding(
boolean aIsDecrepit,
char aRating,
int aNumApartments,
long aNumTenants,
double aPowerUsage,
float aWaterUsage,
byte aNumFloors,
String aName,
List<String> aOptions,
Date[] aMaintenanceChecks
){
fIsDecrepit = aIsDecrepit;
fRating = aRating;
fNumApartments = aNumApartments;
fNumTenants = aNumTenants;
fPowerUsage = aPowerUsage;
fWaterUsage = aWaterUsage;
fNumFloors = aNumFloors;
fName = aName;
fOptions = aOptions;
fMaintenanceChecks = aMaintenanceChecks;
}
@Override public boolean equals(Object that) {
if (this == that) return true;
if (!(that instanceof ApartmentBuilding)) return false;
ApartmentBuilding thatBuilding = (ApartmentBuilding)that;
return hasEqualState(thatBuilding);
}
@Override public int hashCode() {
//this style of lazy initialization is
//suitable only if the object is immutable
207
if (fHashCode == 0) {
int result = HashCodeUtil.SEED;
result = HashCodeUtil.hash(result,
result = HashCodeUtil.hash(result,
result = HashCodeUtil.hash(result,
result = HashCodeUtil.hash(result,
result = HashCodeUtil.hash(result,
result = HashCodeUtil.hash(result,
result = HashCodeUtil.hash(result,
result = HashCodeUtil.hash(result,
result = HashCodeUtil.hash(result,
result = HashCodeUtil.hash(result,
fHashCode = result;
}
return fHashCode;
fIsDecrepit);
fRating);
fNumApartments);
fNumTenants);
fPowerUsage);
fWaterUsage);
fNumFloors);
fName);
fOptions);
fMaintenanceChecks);
}
//..elided..
// PRIVATE
/**
* The following fields are chosen to exercise most of the different
* cases.
*/
private boolean fIsDecrepit;
private char fRating;
private int fNumApartments;
private long fNumTenants;
private double fPowerUsage;
private float fWaterUsage;
private byte fNumFloors;
private String fName; //possibly null, say
private List<String> fOptions; //never null
private Date[] fMaintenanceChecks; //never null
private int fHashCode;
/**
* Here, for two ApartmentBuildings to be equal, all fields must be equal.
*/
private boolean hasEqualState(ApartmentBuilding that) {
//note the different treatment for possibly-null fields
return
( this.fName==null ? that.fName==null : this.fName.equals(that.fName) ) &&
( this.fIsDecrepit == that.fIsDecrepit )&&
( this.fRating == that.fRating )&&
( this.fNumApartments == that.fNumApartments ) &&
( this.fNumTenants == that.fNumTenants ) &&
( this.fPowerUsage == that.fPowerUsage ) &&
( this.fWaterUsage == that.fWaterUsage ) &&
( this.fNumFloors == that.fNumFloors ) &&
( this.fOptions.equals(that.fOptions) )&&
( Arrays.equals(this.fMaintenanceChecks, that.fMaintenanceChecks) )
;
}
/** Exercise hashcode. */
public static void main (String [] aArguments) {
List<String> options = new ArrayList<>();
options.add("pool");
Date[] maintenanceDates = new Date[1];
maintenanceDates[0] = new Date();
byte numFloors = 8;
ApartmentBuilding building = new ApartmentBuilding (
false, 'B', 12, 396L,
5.2, 6.3f, numFloors, "Palisades",
options, maintenanceDates
);
System.out.println("hashCode value: " + building.hashCode());
}
}
208
Example 2
The WEB4J tool defines a utility class for implementing hashCode . Here is an example of a Model
Object implemented with that utility.
Items to note:
the hashCode value is calculated only once, and only if it is needed. This is only possible since this
is an immutable object.
calling the getSignificantFields() method ensures hashCode and equals remain 'in sync'
package hirondelle.fish.main.discussion;
import
import
import
import
import
import
java.util.*;
hirondelle.web4j.model.ModelCtorException;
hirondelle.web4j.model.ModelUtil;
hirondelle.web4j.model.Check;
hirondelle.web4j.security.SafeText;
static hirondelle.web4j.util.Consts.FAILS;
/**
Comment posted by a possibly-anonymous user.
*/
public final class Comment {
/**
Constructor.
@param aUserName identifies the logged in user posting the comment.
@param aBody the comment, must have content.
@param aDate date and time when the message was posted.
*/
public Comment (
SafeText aUserName, SafeText aBody, Date aDate
) throws ModelCtorException {
fUserName = aUserName;
fBody = aBody;
fDate = aDate.getTime();
validateState();
}
/** Return the logged in user name passed to the constructor. */
public SafeText getUserName() {
return fUserName;
}
/** Return the body of the message passed to the constructor.
public SafeText getBody() {
return fBody;
}
*/
/**
Return a <a href="http://www.javapractices.com/Topic15.cjp">defensive copy</a>
of the date passed to the constructor.
<P>The caller may change the state of the returned value, without affecting
the internals of this <tt>Comment</tt>. Such copying is needed since
a {@link Date} is a mutable object.
*/
public Date getDate() {
// the returned object is independent of fDate
return new Date(fDate);
}
/** Intended for debugging only. */
@Override public String toString() {
return ModelUtil.toStringFor(this);
}
@Override public boolean equals( Object aThat ) {
Boolean result = ModelUtil.quickEquals(this, aThat);
( result ==
){
209
if
null
Comment that = (Comment) aThat;
result = ModelUtil.equalsFor(
this.getSignificantFields(), that.getSignificantFields()
);
}
return result;
}
@Override public int hashCode() {
if ( fHashCode == 0 ) {
fHashCode = ModelUtil.hashCodeFor(getSignificantFields());
}
return fHashCode;
}
// PRIVATE //
private final SafeText fUserName;
private final SafeText fBody;
/** Long is used here instead of Date in order to ensure immutability.*/
private final long fDate;
private int fHashCode;
private Object[] getSignificantFields(){
return new Object[] {fUserName, fBody, new Date(fDate)};
}
private void validateState() throws ModelCtorException {
ModelCtorException ex = new ModelCtorException();
if( FAILS == Check.required(fUserName) ) {
ex.add("User name must have content.");
}
if ( FAILS == Check.required(fBody) ) {
ex.add("Comment body must have content.");
}
if ( ! ex.isEmpty() ) throw ex;
}
}
See Also :
Implementing equals
Immutable objects
Lazy initialization
Implementing toString
The toString method is widely implemented. It provides a simple, convenient mechanism for debugging
classes during development. It's also widely used for logging, and for passing informative error messages
to Exception constructors and assertions. When used in these informal ways, the exact format of
toString is not part of the contract of the method, and callers should not rely on the exact format of the
returned String.
The toString method may occasionally be used more formally, however. An example is a simple
mechanism for translating an object into a well-defined textual form ( toString ) and back again
( valueOf). In this case, it's particularly important to specify the exact form of such text in javadoc.
When implementing toString , StringBuilder can be used instead of the + concatenation operator,
since the StringBuilder.append operation is slightly faster.
210
Example 1
import java.util.*;
public final class Truck {
/** Simple test harness. */
public static void main(String... aArgs){
Truck planetKiller = new Truck();
System.out.println(planetKiller);
}
/**
* Intended only for debugging.
*
* <P>Here, the contents of every field are placed into the result, with
* one field per line.
*/
@Override public String toString() {
StringBuilder result = new StringBuilder();
String NEW_LINE = System.getProperty("line.separator");
result.append(this.getClass().getName() + " Object {" + NEW_LINE);
result.append(" Name: " + fName + NEW_LINE);
result.append(" Number of doors: " + fNumDoors + NEW_LINE);
result.append(" Year manufactured: " + fYearManufactured + NEW_LINE );
result.append(" Color: " + fColor + NEW_LINE);
//Note that Collections and Maps also override toString
result.append(" Options: " + fOptions + NEW_LINE);
result.append("}");
return result.toString();
}
//..other methods elided
// PRIVATE //
private
private
private
private
private
Example output:
Truck Object {
Name: Dodge
Number of doors: 2
Year manufactured: Wed Aug 29 15:49:10 ADT 2007
Color: Fuchsia
Options: [Air Conditioning]
}
Example 2
This implementation uses reflection to inspect both field names and field values. Note that superclass
fields do not contribute to this implementation.
import java.util.*;
import java.lang.reflect.Field;
public final class Van {
/**
* Build a Van object and display its textual representation.
*
* Note that the Collection classes have
* their own implementation of <code>toString</code>, as exemplified here
* by the List field holding the Options.
*/
211
Example output:
212
Van Object {
fName: Dodge
fNumDoors: 4
fYearManufactured: Thu Sep 30 19:16:14 EDT 2004
fColor: Blue
fOptions: [Air Conditioning, Leather Interior]
}
Example 3
The WEB4J tool provides a utility method for implementing toString in a single line of code. Its
implementation uses reflection.
import
import
import
import
import
import
import
java.math.BigDecimal;
hirondelle.web4j.model.ModelCtorException;
hirondelle.web4j.model.ModelUtil;
hirondelle.web4j.model.Id;
hirondelle.web4j.model.Check;
hirondelle.web4j.util.Util;
hirondelle.web4j.model.Validator;
Id fId;
String fName;
String fLocation;
BigDecimal fPrice;
String fComment;
//..elided
}
Example output:
213
hirondelle.fish.main.resto.Resto {
Name: Cedars Eatery
Location: Water and Prince
Id: 6
Comment: Big portions
Price: 7.89
}
Example 4
is a type safe enumeration. The return value of toString is used in the normal operation of
the program - not just for logging. Note that it forms a pair with the valueFrom(String) method, since
one formats the object into a String, and the other parses a String into an object.
QuoteField
package hirondelle.stocks.table;
/**
* Enumeration for the fields of the
* {@link hirondelle.stocks.quotes.Quote} class.
*
* Advantages to using this class as part of a table model :
* <ul>
* <li> can parse text which maps table columns to fields
* <li> can be used for column names
* <li> length of <tt>QuoteField.values()</tt> gives the column count
* </ul>
*/
public enum QuoteField {
Stock("Stock"),
Price("Price"),
Change("Change"),
PercentChange("%Change"),
Profit("Profit"),
PercentProfit("%Profit");
/**
* Return a text representation of the <tt>QuoteField</tt>.
*
* Return values : <tt>Stock, Price, Change, %Change, Profit, %Profit</tt>.
* @return value contains only letters, and possibly a percent sign.
*/
@Override public String toString() {
return fName;
}
/**
* Parse text into a <tt>QuoteField</tt>.
*
* <P>The text is matched according to the value of {@link #toString()},
* not from the symbolic name of the enumerated item.
*/
public static QuoteField valueFrom(String aText){
for (QuoteField quoteField: values()){
if(quoteField.toString().equals(aText)) {
return quoteField;
}
}
throw new IllegalArgumentException("Cannot parse into a QuoteField: " + aText);
}
private final String fName;
/**
* @param aName only letters and percent sign are valid characters.
*/
private QuoteField(String aName) {
fName = aName;
}
}
214
See Also :
String concatenation does not scale
Factory methods
Arrays as String
Arrays as String
Logging the contents of aCollection is simple, sinceAbstractCollection.toString is always
available. For an array, however, the default toString method is not very informative, and doesn't
include the array contents.
To provide more useful representations of arrays, various toString methods (and the deepToString
method) were added to the Arrays class in JDK 1.5. Those methods can be used when available, as in:
Arrays.toString(myArray);
Arrays.deepToString(myObjectArray); //recursive
If you need an alternative, you can simply convert the array to a collection, as in
Arrays.asList(myArray).toString();
215
package myapp.business;
import java.lang.reflect.Array;
/**
* Convenience method for producing a simple textual
* representation of an array.
*
* <P>The format of the returned <code>String</code> is the same as
* <code>AbstractCollection.toString</code>:
* <ul>
* <li>non-empty array: [blah, blah]
* <li>empty array: []
* <li>null array: null
* </ul>
*
* @author Jerome Lacoste
* @author www.javapractices.com
*/
public final class ArrayToString {
/**
* <code>aArray</code> is a possibly-null array whose elements are
* primitives or objects; arrays of arrays are also valid, in which case
* <code>aArray</code> is rendered in a nested, recursive fashion.
*/
public static String get(Object aArray){
if (aArray == null) return fNULL;
checkObjectIsArray(aArray);
StringBuilder result = new StringBuilder(fSTART_CHAR);
int length = Array.getLength(aArray);
for (int idx = 0 ; idx < length ; ++idx) {
Object item = Array.get(aArray, idx);
if (isNonNullArray(item)){
//recursive call!
result.append(get(item));
}
else{
result.append(item);
}
if (! isLastItem(idx, length)) {
result.append(fSEPARATOR);
}
}
result.append(fEND_CHAR);
return result.toString();
}
// PRIVATE
private static
private static
private static
private static
final
final
final
final
String
String
String
String
fSTART_CHAR = "[";
fEND_CHAR = "]";
fSEPARATOR = ", ";
fNULL = "null";
See Also :
Implementing toString
system Clipboard . It corresponds to the usual idea of sharing data between otherwise independent
applications running on the same computer.
Local Clipboard s are visible only within a single Java application. They are created by simply passing a
name to the Clipboard constructor.
import
import
import
import
import
import
import
import
java.awt.datatransfer.Clipboard;
java.awt.datatransfer.ClipboardOwner;
java.awt.datatransfer.Transferable;
java.awt.datatransfer.StringSelection;
java.awt.datatransfer.DataFlavor;
java.awt.datatransfer.UnsupportedFlavorException;
java.awt.Toolkit;
java.io.*;
218
This compiles in place, and creates .class files beside .java files. If you want to place generated class files
elsewhere, use the -d option to put them into an existing directory:
PROJECT_HOME>javac -cp lib\* -d build src\hirondelle\ante\*.java src\hirondelle\ante\deluvian\*.java
Also, notice that all jars in a directory can be succinctly referenced using 'lib\*'. However, referencing the
source files that you need to compile is a different story: all of your packages need to be listed one by
one. That is, you can't simply specify a single root directory for your source files. This is a rather serious
defect of javac.
If your jars are in various directories, then the classpath is a list delimited by semi-colons:
-cp lib\*;C:\abc\one.jar;C:\xyz\two.jar
219
An application is run by referencing a fully-qualified class name, but without the .class suffix. Assuming
that the classes have been compiled in place, our example application can be run using:
PROJECT_HOME>java -cp lib\*;src hirondelle.ante.Launcher
Set the initial and maximum size of the object allocation pool to 5 Meg and 100 Meg, respectively:
PROJECT_HOME>java -cp lib\*;src -Xms5m -Xmx100m hirondelle.ante.Launcher
The javaw command is the same as the java command, with the only difference being that javaw has no
associated console window.
Using javadoc
For files under the src directory, the following command will javadoc all packages starting with
'hirondelle.ante', and output the result to C:\@build\javadoc:
javadoc -sourcepath src -subpackages hirondelle.ante -classpath lib\* -d C:\@build\javadoc
The javadoc tool has many options. You may find these especially useful:
-linksource: links your javadoc to a static snapshot of the underlying source code (very nice).
-windowtitle, -header and -footer: often useful for showing the name and version of your
application.
-link: link to the JDK's javadoc as well
-noqualifier: remove tedious package prefixes from JDK classes
define the minimum class scope to be processed, using one of: -private, -package, -protected, public.
Here's a second example, with more options:
javadoc
-sourcepath src
-subpackages hirondelle.ante
-package
-classpath lib\*
-d C:\@build\javadoc
-linksource
-link http://docs.oracle.com/javase/7/docs/api/
-noqualifier java.*:javax.*
-windowtitle "My App 1.0"
-header "<b>My App 1.0</b>"
-footer "<a href='http://www.blah.com'>My App</a>"
Javadoc can also be controlled using the following items, placed beside your source code:
package-info.java - Each package can include a package-info.java file. It contains package-level
documentation. Despite its extension, a package-info.java file is not a Java class file.
doc-files subdirectories - each package can include a 'doc-files' subdirectory, containing items that
can be linked from javadoc (images, html files, and so on) using ordinary hypertext links.
overview.html - a single file describing general aspects of your project. (This file doesn't
necessarily reside in your source tree, but it makes sense to place it there, in analogy with the
package-info.java files.)
220
can be dangerous to use, depending on the context. The reason is that programmers tend to apply them to
tasks they really aren't meant for, simply out of habit.
The fundamental difference is that localized comparison depends on Locale, while String is largely
ignorant of Locale. Here is a quote from The Java Programming Language by Arnold, Gosling, and
Holmes:
"You should be aware that internationalization and localization issues of full Unicode strings are not
addressed with [String] methods. For example, when you're comparing two strings to determine which is
'greater', characters in strings are compared numerically by their Unicode values, not by their localized
notion of order."
The only robust way of doing localized comparison or sorting of Strings, in the manner expected by an
end user, is to use a Collator , not the methods of the String class.
Example 1 - Unicode Ordering
Here is an example of simple Unicode ordering of Strings. Note the use of
String.CASE_INSENSITIVE_ORDER, an implementation of Comparator.
Reminder - the following items are important with any form of comparison or sorting:
the Comparator and Comparable interfaces
the various sort methods of Collections and Arrays
import java.util.*;
/** Sorting Strings in Unicode order. */
public final class SortStringsNoLocale {
public static void main(String... aArgs){
List<String> insects = Arrays.asList("Wasp", "ant", "", "Bee");
log("Original:");
log(insects);
log("Sorted:");
221
sortList(insects);
log(insects);
log("");
Map<String,String> capitals = new LinkedHashMap<>();
capitals.put("finland", "Helsinki");
capitals.put("United States", "Washington");
capitals.put("Mongolia", "Ulan Bator");
capitals.put("Canada", "Ottawa");
log("Original:");
log(capitals);
log("Sorted:");
log(sortMapByKey(capitals));
}
private static void sortList(List<String> aItems){
Collections.sort(aItems, String.CASE_INSENSITIVE_ORDER);
}
private static void log(Object aObject){
System.out.println(String.valueOf(aObject));
}
private static Map<String, String> sortMapByKey(Map<String, String> aItems){
TreeMap<String, String> result =
new TreeMap<>(String.CASE_INSENSITIVE_ORDER)
;
result.putAll(aItems);
return result;
}
}
222
log(EMPTY_LINE);
log("Case kicks in only with Tertiary Collation Strength : ");
List<String> wordsForCase = Arrays.asList("cache", "CACHE", "Cache");
log(wordsForCase + " - Original Data");
sort(wordsForCase, Strength.Primary);
sort(wordsForCase, Strength.Secondary);
sort(wordsForCase, Strength.Tertiary);
log(EMPTY_LINE);
log("Accents kick in with Secondary Collation Strength.");
log("Compare with no accents present: ");
compare("abc", "ABC", Strength.Primary);
compare("abc", "ABC", Strength.Secondary);
compare("abc", "ABC", Strength.Tertiary);
log(EMPTY_LINE);
log("Compare with accents present: ");
compare("abc", "BC", Strength.Primary);
compare("abc", "BC", Strength.Secondary);
compare("abc", "BC", Strength.Tertiary);
}
// PRIVATE //
private static final String EMPTY_LINE = "";
private static final Locale TEST_LOCALE = Locale.FRANCE;
/** Transform some Collator 'int' consts into an equivalent enum. */
private enum Strength {
Primary(Collator.PRIMARY), //base char
Secondary(Collator.SECONDARY), //base char + accent
Tertiary(Collator.TERTIARY), // base char + accent + case
Identical(Collator.IDENTICAL); //base char + accent + case + bits
int getStrength() { return fStrength; }
private int fStrength;
private Strength(int aStrength){
fStrength = aStrength;
}
}
private static void sort(List<String> aWords, Strength aStrength){
Collator collator = Collator.getInstance(TEST_LOCALE);
collator.setStrength(aStrength.getStrength());
Collections.sort(aWords, collator);
log(aWords.toString() + " " + aStrength);
}
private static void compare(String aThis, String aThat, Strength aStrength){
Collator collator = Collator.getInstance(TEST_LOCALE);
collator.setStrength(aStrength.getStrength());
int comparison = collator.compare(aThis, aThat);
if ( comparison == 0 ) {
log("Collator sees them as the same : " + aThis + ", " + aThat + " - " +
aStrength);
}
else {
log("Collator sees them as DIFFERENT : " + aThis + ", " + aThat + " - " +
aStrength);
}
}
private static void log(String aMessage){
System.out.println(aMessage);
}
}
Strength'
Abc, abc,
Abc, abc,
bc, bc,
bc, bc,
See Also :
Implementing compareTo
Determine if Strings are equal
Choosing the right Collection
Copy an array
There are several ways to copy an array:
use the various copyOf and copyOfRange methods of the Arrays class - probably the simplest
method
use System.arraycopy - useful when copying parts of an array
call its clone method, and do a cast - the simplest style, but only a shallow clone is performed
use a for loop - more than one line, and needs a loop index
Example
This example class demonstrates:
relative performance of the various methods (in many cases the differences in speed will not be of
practical benefit).
how clone is a shallow copy, and leads to independent storage only for primitive, one dimensional
arrays.
import java.util.*;
public final class ArrayCopier {
public static void main (String... aArguments) {
String action = aArguments[0];
int numIterations = 0;
if (aArguments.length == 2) {
numIterations = Integer.parseInt(aArguments[1]);
}
if ("performance".equals(action)) {
demoPerformance(numIterations);
}
else if ("storage".equals(action)) {
demoIndependanceOfStorage();
}
}
/**
224
seems to have slightly better performance. Differences between the various styles are
small, however, and would often be regarded as a micro-optimization. (As usual, such judgements
depend on the context).
System.arraycopy
clone: 108.168 ms
System.arraycopy: 125.334 ms
Arrays.copyOf: 190.490 ms
for loop: 392.026 ms
The above example use the -Xint option to turn off the Just In Time compiler. Here, bytecodes are
interpreted at runtime, but never compiled by the HotSpot compiler into native code. This provides a
uniform environment for executing tests of relative execution time, since there is no "warm-up" period.
Example run demonstrating independence of storage, or lack thereof:
>java -cp . ArrayCopier storage
Altered clone has NOT affected original:
numbersClone[0]: 0
numbers[0]: 1
Altered clone has affected original:
matrixClone element 0-0:0
matrix element 0-0: 0
Original date: Mon Sep 30 15:47:58 EDT 2002
Altered clone has affected original:
datesClone[0]:Wed Dec 31 19:00:00 EST 1969
dates[0]: Wed Dec 31 19:00:00 EST 1969
See Also :
Time execution speed
To determine if two String objects match exactly, you should almost always use the equals method, and
not the == operator.
The equals method compares the actual content of the Strings, using the underlying Unicode
representation, while == compares only the identity of the objects, using their address in memory (which
is usually not desired).
Example
/**
* Illustrates the incorrectness of using == for typical
* String comparisons.
*/
public final class EqualStrings {
/**
* Pass in the String "blah" on the command line in
* order to exercise the code.
*/
public static void main(String... arguments) {
String text = (arguments.length == 1 ? arguments[0] : null);
testUsingEquals(text);
testUsingOperator(text);
}
// PRIVATE
private static final String BLAH = "blah";
private static void testUsingEquals(String aString) {
if (BLAH.equals(aString)) {
log("Equals: YES, the user entered \"blah\".");
}
else {
log("Equals: NO, the user did not enter \"blah\".");
}
}
private static void testUsingOperator(String aString) {
if (aString == BLAH) {
log("== operator: YES, the user entered \"blah\".");
}
else {
log("== operator: NO, the user did not enter \"blah\".");
}
}
private static void log(String aMessage){
System.out.println(aMessage);
}
}
Note that x.equals(y) will generate a NullPointerException if x is null , but will not generate such an
exception if only y is null . In the above example, for instance, BLAH.equals(aString) is used instead
of aString.equals(BLAH) , since aString may be null .
It's true that the intern method may be used to allow meaningful comparisons using ==. (See The Java
Programming Language by Arnold, Gosling, and Holmes for further information.) The intern technique
is meant as a performance optimization, and should usually be avoided since it's more complex.
227
Examine bytecode
Examination of the bytecode of a class is not a common task. However, if you're interested, it's possible
to view bytecode using the javap tool included in the JDK. Here's an example of its use, where it shows
the effect on bytecode of setting fields explicitly to their default initial values:
>javap -c -classpath . Quark
Compiled from Quark.java
public final class Quark extends java.lang.Object {
public Quark(java.lang.String,double);
}
Method Quark(java.lang.String,double)
0 aload_0
1 invokespecial #1 <Method java.lang.Object()>
4 aload_0
5 aconst_null
6 putfield #2 <Field java.lang.String fName>
9 aload_0
10 dconst_0
11 putfield #3 <Field double fMass>
14 aload_0
15 aload_1
16 putfield #2 <Field java.lang.String fName>
19 aload_0
20 dload_2
21 putfield #3 <Field double fMass>
24 return
228
try {
connection = fURL.openConnection();
}
catch (IOException ex) {
log("Cannot open connection to URL: " + fURL);
}
//not all headers come in key-value pairs - sometimes the key is
//null or an empty String
int headerIdx = 0;
String headerKey = null;
String headerValue = null;
while ( (headerValue = connection.getHeaderField(headerIdx)) != null ) {
headerKey = connection.getHeaderFieldKey(headerIdx);
if (headerKey != null && headerKey.length()>0) {
result.append(headerKey);
result.append(" : ");
}
result.append(headerValue);
result.append(NEWLINE);
headerIdx++;
}
return result.toString();
}
// PRIVATE
private URL fURL;
private
private
private
private
private
static
static
static
static
static
final
final
final
final
final
String
String
String
String
String
HTTP = "http";
HEADER = "header";
CONTENT = "content";
END_OF_INPUT = "\\Z";
NEWLINE = System.getProperty("line.separator");
230
Example 2
This example generates random integers in a specific range.
import java.util.Random;
/** Generate random integers in a certain range. */
public final class RandomRange {
public static final void main(String... aArgs){
log("Generating random integers in the range 1..10.");
int START = 1;
int END = 10;
Random random = new Random();
for (int idx = 1; idx <= 10; ++idx){
showRandomInteger(START, END, random);
}
log("Done.");
}
private static void showRandomInteger(int aStart, int aEnd, Random aRandom){
231
Example 3
This example generates random floating point numbers in a Gaussian (normal) distribution.
import java.util.Random;
/**
Generate pseudo-random floating point values, with an
approximately Gaussian (normal) distribution.
Many physical measurements have an approximately Gaussian
distribution; this provides a way of simulating such values.
*/
public final class RandomGaussian {
public static void main(String... aArgs){
RandomGaussian gaussian = new RandomGaussian();
double MEAN = 100.0f;
double VARIANCE = 5.0f;
for (int idx = 1; idx <= 10; ++idx){
log("Generated : " + gaussian.getGaussian(MEAN, VARIANCE));
}
}
private Random fRandom = new Random();
private double getGaussian(double aMean, double aVariance){
return aMean + fRandom.nextGaussian() * aVariance;
}
private static void log(Object aMsg){
System.out.println(String.valueOf(aMsg));
}
}
Generated
Generated
Generated
Generated
Generated
Generated
Generated
Generated
:
:
:
:
:
:
:
:
106.78740794978813
105.57315286730545
97.35077643206589
92.56233774920052
98.29311772993057
102.04954815575822
104.88458607780176
97.11126014402141
See Also :
Recovering resources
Connection pools
try {
System.gc();
Thread.currentThread().sleep(fSLEEP_INTERVAL);
System.runFinalization();
Thread.currentThread().sleep(fSLEEP_INTERVAL);
}
catch (InterruptedException ex){
ex.printStackTrace();
}
}
}
ObjectSizer
aResult.add(fNEW_LINE);
}
else {
try {
Class theClass = Class.forName(aLine);
ObjectSizer sizer = new ObjectSizer();
long size = sizer.getObjectSize(theClass);
if (size > 0){
Object[] insertedData = {theClass, new Long(size)};
MessageFormat sizeMessage = new MessageFormat(fPATTERN);
String message = sizeMessage.format(insertedData);
aResult.add(message);
aResult.add(fNEW_LINE);
}
aResult.add(fDEFAULT_PROMPT);
}
catch (ClassNotFoundException ex){
//recover by asking the user for corrected input
aResult.clear();
aResult.add(fERROR_PROMPT);
}
}
if (aResult.isEmpty()) {
throw new IllegalStateException("Result must be non-empty.");
}
return hasRequestedQuit;
}
/**
* Return the text to be displayed upon start-up of the Interpreter.
*/
public String getHelloPrompt() {
return fHELLO_PROMPT;
}
// PRIVATE
private static final String fHELLO_PROMPT = "Please enter a class name>";
private static final String fDEFAULT_PROMPT = "Please enter a class name>";
private static final String fERROR_PROMPT = "Invalid.
Example:\"java.lang.String\">";
private static final String fPATTERN = "Approximate size of {0} objects in bytes:
{1}";
private static final String fQUIT = "quit";
private static final String fEXIT = "exit";
private static final String fNEW_LINE = System.getProperty("line.separator");
}
See Also :
Measure application performance
Console input
the number of items shown to the user in a search result - 10, 50, 100
Code tables are often presented in drop down lists, as in:
Number of Results:
10
Chocolate
(Radio buttons or other presentation styles may also be appropriate, according to the needs of each case.)
Code Table Structure
Most applications use a relational database. Code tables usually represent the simplest kinds of tables that
you can model in a database. Here are some example code tables, as defined by SQL CREATE TABLE
statements:
Number of items shown in a search result:
CREATE TABLE NumResults (
Id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,
NumItems TINYINT UNSIGNED UNIQUE NOT NULL,
PRIMARY KEY (Id)
) TYPE=InnoDB;
The same list of countries, but with a field added to control sort order:
CREATE TABLE Country (
Id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
OrderWith MEDIUMINT UNIQUE UNSIGNED NOT NULL,
Text VARCHAR(50)UNIQUE NOT NULL,
ShortText VARCHAR(2)UNIQUE NOT NULL,
PRIMARY KEY (Id)
) TYPE=InnoDB;
It's often useful to picture each row in a code table as representing a set of aliases for a single idea. One
alias can be more appropriate than another, according to the context in which it's used. For example, in a
report which tries to squeeze as much information as possible onto pages of fixed width, short
abbreviations are often useful. When presenting a drop-down list to an end user, it may be more desirable
to show a longer description, instead of an abbreviation.
So, you may decide to represent an item in a code table using what amounts to several aliases for the
same item:
a numeric id
the 'regular' text, usually seen by the user
a short abbreviation used when appropriate
some other alias appropriate to a given case
For this idea to make sense, each of the above fields would need a UNIQUE constraint, and each would
need to be non-null.
Code Table Evolution
As shown above, code tables don't have a specific, definitive structure. You aren't locked into a specific
238
style. A single application often has many code tables, but those code tables don't necessarily share the
exact same form. Also, it's not uncommon for a code table to start its life in a simple form, and then later
grow into something more elaborate. In this sense, code tables are roughly similar to Java's enumeration
types, which can start out being very simple. Since enumerations are also classes, you can add more
structure to them later, if needed.
In-Memory Caching
Code tables often represent relatively static data. Since the data doesn't change very often, it usually
makes sense to consider reading in all code tables once upon startup. Then, each time a code table is
needed, the in-memory representations of the code tables are referenced directly, instead of repeatedly
going back to the database. This usually improves application performance.
In-Memory Joins
If code tables are cached in memory after startup, then you will often be able to move logic formerly
implemented by a database JOIN operation into in-memory operations in Java instead. (This is an
exception to the rule of not performing database tasks in code.) There are two advantages to this:
it will likely improve performance.
your SQL statements will be simpler, since the JOIN s can be left out
For illustration, consider an application that models a team sport, in which each player on a team is
assigned a specific position of some sort (bowler, pitcher, fullback, or whatever). In this case, the Team
table has a foreign key into the Position code table. To retrieve the list of players on a team, you might
have an explicit join to the Position code table, as in :
SELECT
Name, Number, PositionName
FROM
Team JOIN Position ON PositionFK = Position.Id
WHERE
Team.Id = ?
Alternatively, the JOIN might be dropped in favor of returning the 'raw' PositionFK identifier, instead of
the PositionName text, as in :
SELECT
Name, Number, PositionFK
FROM
Team
WHERE
Team.Id = ?
Of course, the PositionFK identifier would need to be translated into text (in Java-land) before presenting
the result to the user.
Id For Code, Text For User
The fact that a code table item is essentially a collection of aliases for a single idea can be put to use in
the following way. In Java-land, it's best to identify items using internal, static codes, instead of the text
visible to the user. Forms in web applications are a good example of this:
<select name="Flavor">
<option value='1'>Chocolate</option>
<option value='2'>Strawberry</option>
<option value='3'>Vanilla</option>
</select>
Here, the text ('Chocolate') is shown to the user in a drop-down, but the value submitted to the server is
actually a numeric id ('1'), as controlled by the value attribute of the option tag. This separates two
things nicely. The text may change for many reasons (change the spelling, add translations into another
language), but such changes will not ripple into Java, since Java-land uses numeric codes, not the text.
(Some object to exposing database primary keys to the end user like this. But in this case, it doesn't seem
239
to do any harm.)
Sorting
Sorting of code tables can be tricky. If the sort is alphabetical, then that's simple to implement. However,
sorts aren't always alphabetical. Sometimes they depend on arbitrary rules. For example, if an application
is used only in Australia, a list of countries having Australia and New Zealand at the beginning may be
required. In such cases, one option is to define a column in the underlying code table which explicitly
defines the sort order.
The sort order can also be affected by whether an application is multilingual. If a code table is sorted
alphabetically in each language, then the order of presentation to the user will usually not be the same in
all languages.
Monster Code Tables
Some applications put all of their code tables into a single "monster" code table. The monster code table
has two primary keys -- one to identify the code table, and one to identify the value within the code
table. This seems to be an inferior design:
it lumps together items which aren't logically related to each other. It's almost always a mistake to
put implementation details before meaning. That's a bad sign.
foreign keys to code tables are no longer possible. Since foreign keys are the heart and soul of a
relational database, this is a major drawback.
since some foreign keys are absent, the clarity of a database's structure (and related SQL
statements) is significantly reduced.
when a single code table has special needs, then it usually can't fit into the structure of the monster
code table.
code tables can't evolve independently and still remain in the monster code table.
JOINs to monster code tables are usually doubled. What used to be a single join becomes 2 joins -one join to define the code table, another to define the value within the code table.
in large SQL statements, the large number of joins can rapidly become annoying.
See Also :
Don't perform basic SQL tasks in code
Internationalization
Some applications need to use more than one language in the user interface. Changing a program to
allow for this is called "internationalization", while the actual translation is called "localization".
Translation is usually applied only to user interface elements (menus, labels, and so on), and not to
business data stored in the database.
Most implementations of multilingual applications use ResourceBundle and Locale. However, there are
some problems with this style, so one might consider alternatives to ResourceBundle.
Here's a simple illustration of the basic ResourceBundle mechanism:
BankMachine is a user of a ResourceBundle , whose Locale is set by the end user.
ListResourceBundle , but PropertyResourceBundle is likely used more often.
240
import java.util.ResourceBundle;
import java.util.Locale;
public final class BankMachine {
/**
* Run first a French version, then an English version.
*/
public static void main(String[] aArguments) {
BankMachine bankMachine = new BankMachine();
bankMachine.selectLanguage(fFRENCH);
bankMachine.showDisplay();
bankMachine.selectLanguage(fENGLISH);
bankMachine.showDisplay();
}
/**
* The user's first action is to select a language.
*/
public void selectLanguage(String aLanguage){
if (aLanguage.equals(fENGLISH)) {
fText = ResourceBundle.getBundle(fBUNDLE_NAME, Locale.ENGLISH);
}
else if (aLanguage.equals(fFRENCH)) {
fText = ResourceBundle.getBundle(fBUNDLE_NAME, Locale.FRENCH);
}
else {
throw new IllegalStateException("Unknown language");
}
}
public void showDisplay() {
//use the bundle to get the proper version of a string
//note that the variable names - Text.Hello, etc - reflect the content,
//so these method calls clearly indicate what is being displayed
//to the user
System.out.println(fText.getString(Text.Hello));
System.out.println(fText.getString(Text.PleaseSelectAction));
}
// PRIVATE
private static String fENGLISH = Text.English;
private static String fFRENCH = Text.French;
private static String fBUNDLE_NAME = "Text";
/**
* Default is English.
* Note that this default is perfectly suitable for any initial user
* selection of language, since the Strings representing the languages
* themselves have only one representation, which are defined in the
* default bundle.
*/
private ResourceBundle fText = ResourceBundle.getBundle(fBUNDLE_NAME,
Locale.ENGLISH);
}
The default ResourceBundle contains both constants and the default representations of all text:
import java.util.*;
public final class Text extends ListResourceBundle {
public final Object[][] getContents() {
return fContents;
241
}
/**
* These constants are used by the caller to identify what text is
* needed; which version of the text is actually returned is determined
* by the ResourceBundle mechanism.
*
* Using variable names which reflect the content clarifies the
* intent of the code in the user of this class.
*/
public static final String Hello = "Hello";
public static final String PleaseSelectAction = "PleaseSelectAction";
/**
* Language names presented as a list of choices to the user
* are special, since they are always presented in the native language.
* Thus, this default bundle defines the
* unique representations of all language names, and no other
* bundle provides a translation for them.
*/
public static final String English = "English";
public static final String French = "Francais";
// PRIVATE
private static final Object[][] fContents = {
{Text.Hello, "Hello"},
{Text.PleaseSelectAction, "Please select an action"} ,
{Text.English, "English"}, //never defined elsewhere
{Text.French, "Francais"}, //never defined elsewhere
};
}
The other ResourceBundle classes do not define constants, just the translations :
import java.util.*;
public final class Text_fr extends ListResourceBundle {
public final Object[][] getContents() {
return fContents;
}
//No constants are defined here
// PRIVATE
private static final Object[][] fContents = {
{Text.Hello, "Bonjour"},
{Text.PleaseSelectAction, "Veuillez choisir une action"}
};
}
See Also :
Try alternatives to ResourceBundle
Logging messages
Java's logging facility (see Oracle's overview and API) has two parts: a configuration file, and an API for
using logging services. It is a good tool, and is perfectly fine for simple and moderate logging needs. (For
complex logging needs, you might consider the log4j tool as an alternative.)
242
Log entries can be sent to these destinations, as either simple text or as XML:
the console
a file
a stream
memory
a TCP socket on a remote host
The Level class defines seven levels of logging enlightenment:
FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE
ALL and OFF are defined values as well
Here is one style of using these Level s in code, which may be modified as desired:
upon startup, use CONFIG to log configuration parameters
during normal operation, use INFO to log high-level "heartbeat" information
when bugs or critical conditions occur, use SEVERE
debugging information might default to FINE , with FINER and FINEST used occasionally, according
to taste.
There is flexibility in how logging levels can be changed at runtime, without the need for a restart:
simply change the configuration file and call LogManager.readConfiguration .
or, change the level in the body of your code, using the logging API ; for example, one might
automatically increase the logging level in response to unexpected events
Levels are attached to these items :
an originating logging request (from a single line of code)
a Logger (usually attached to the package containing the above line of code)
a Handler (attached to an application)
The flow of execution for a particular logging request usually proceeds as follows:
logging request of some level is made to logger attached to current package
if the request level is too low for that package's logger {
discard it
}
otherwise {
cycle through all handlers {
if the request level is too low for that handler {
discard it
}
otherwise {
log the request
}
}
}
#
#
#
#
#
#
#
#
An application should likely centralize the naming policy for Loggers, since switching naming styles
becomes a simple edit in one method, instead of a large number of edits spread throughout the
244
// Logger.getLogger(SimpleLogger.class.getPackage().getName())
//;
//This style uses a hard-coded literal and should likely be avoided:
//private static final Logger fLogger = Logger.getLogger("myapp.business");
}
Example Run 2, showing the ConsoleHandler output, using the logging.properties file which ships
with the JDK, and which logs only INFO level and above:
>java -cp . myapp.business.SimpleLogger
Jan 8, 2003 10:52:31 AM myapp.business.SimpleLogger doSomething
INFO: this is info
Jan 8, 2003 10:52:31 AM myapp.business.SimpleLogger doSomething
WARNING: this is a warning
Jan 8, 2003 10:52:31 AM myapp.business.SimpleLogger doSomething
SEVERE: this is severe
Jan 8, 2003 10:52:31 AM class myapp.business.SimpleLogger doSomething
INFO: blah
Jan 8, 2003 10:52:31 AM myapp.business.SimpleLogger doSomething
SEVERE: Some message
246
Other tools include vmstat for Unix hosts, and perfmon for Windows hosts.
See Also :
Get size of object in memory
Time execution speed
Measure web app performance
improved) the character of typical Java code. When updating old code to more recent versions of Java,
it's helpful to use a quick checklist of things to look out for. Here's a list of such items related to the Java
5 release:
Use @Override liberally
The @Override standard annotation identifies methods that override a superclass method. It should be
used liberally to indicate your intent to override. It's also used when implementing methods defined by an
interface.
Avoid raw types
Raw types should almost always be avoided in favor of parameterized types.
Use for-each loops
The enhanced for loop (also called the for-each loop) should be used whenever available. It's more
compact, concise, and clear.
Replace constants with enumerations
Using public static final constants to represent sets of related items should be avoided in favor of
the enumerations now supported by Java. In addition, you should consider replacing any "roll-your-own"
implementations of type-safe enumerations with the new language construct.
Replace StringBuffer with StringBuilder
The older StringBuffer class is thread-safe, while the new StringBuilder is not. However, it's almost
always used in a context in which thread safety is superfluous. Hence, the extra cost of synchronization is
paid without benefit. Although the performance improvement in moving from StringBuffer to
StringBuilder may only be very slight (or perhaps not even measurable), it's generally considered better
form to prefer StringBuilder .
Use sequence parameters when appropriate
Sequence parameters (varargs) let you replace Object[] parameters (containing 0..N items) appearing at
the end of a parameter list with an alternate form more convenient for the caller. For example,
public static void main(String[] aArgs){}
In addition, JDK 7 also has some new tools which improve the character of typical Java code:
Use 'diamond' syntax for generic declarations
Declarations of the form:
List<String> blah = new ArrayList<String>();
Map<String, String> blah = new LinkedHashMap<String, String>();
248
Use try-with-resources
The try-with-resources feature lets you eliminate most finally blocks in your code.
Use Path and Files for basic input/output
The new java.nio package is a large improvement over the older File API.
Example
Here's an example of code written using Java 5 features:
import java.util.*;
public final class Office {
/**
* Use sequence parameter (varargs) for main method.
*
* Use a sequence parameter whenever array parameter appears at
* the END of the parameter list, and represents 0..N items.
*/
public static void main(String... aArgs){
//Use parameterized type 'List<String>', not the raw type 'List'
List<String> employees = Arrays.asList("Tom", "Fiorella", "Pedro");
Office office = new Office(AirConditioning.OFF, employees);
System.out.println(office);
//prefer the for-each style of loop
for(String workingStiff: employees){
System.out.println(workingStiff);
}
}
/**
* Preferred : use enumerations, not int or String constants.
*/
enum AirConditioning {OFF, LOW, MEDIUM, HIGH}
/*
* Definitely NOT the preferred style :
*/
public static final int OFF = 1;
public static final int LOW = 2;
public static final int MEDIUM = 3;
public static final int HIGH = 4;
Office(AirConditioning aAirConditioning, List<String> aEmployees){
fAirConditioning = aAirConditioning;
fEmployees = aEmployees; //(no defensive copy here)
}
AirConditioning getAirConditioning(){
return fAirConditioning;
}
List<String> getEmployees(){
return fEmployees;
}
/*
* Get used to typing @Override for toString, equals, and hashCode :
*/
@Override public String toString(){
//..elided
}
@Override public boolean equals(Object aThat){
//..elided
}
249
See Also :
Type-Safe Enumerations
Implementing compareTo
Know the core libraries
Overloading can be tricky
Use for-each liberally
Use @Override liberally
Avoid raw types
Prefer modern libraries for concurrency
These methods use the current classloader in order to find files, so they have the most flexibility. The
following example reads in a text file placed in the same directory as the running class. Note that the
code references the file in the simplest possible way, using its simple file name, without any
qualification. As long as the text file retains the same name, and is in the same directory as the class, this
code will work.
import
import
import
import
import
import
import
import
java.io.BufferedReader;
java.io.IOException;
java.io.InputStream;
java.io.InputStreamReader;
java.net.URI;
java.net.URISyntaxException;
java.net.URL;
java.nio.charset.Charset;
250
import
import
import
import
import
import
java.nio.charset.StandardCharsets;
java.nio.file.Files;
java.nio.file.Path;
java.nio.file.Paths;
java.util.List;
java.util.Scanner;
/** Read a text file that's in the same directory as this (.class) file. */
public final class ReadNeighbouringTextFile {
public static void main(String... aArgs) throws IOException {
log("Classpath: " + System.getProperty("java.class.path"));
log("user.dir: " + System.getProperty("user.dir"));
ReadNeighbouringTextFile read = new ReadNeighbouringTextFile();
read.readFileViaStream();
read.readFileasViaUrl();
//read.readFileViaPath();
}
void readFileViaStream() throws IOException {
log("Via stream...");
try (
//uses the class loader search mechanism:
InputStream input = this.getClass().getResourceAsStream("test.txt");
InputStreamReader isr = new InputStreamReader(input, ENCODING);
BufferedReader reader = new BufferedReader(isr);
){
String line = null;
while ((line = reader.readLine()) != null) {
//process the line in some way
log(line);
}
}
}
void readFileasViaUrl() throws IOException{
log("Via URL...");
//uses the class loader search mechanism:
URL url = this.getClass().getResource("test.txt");
URI uri = null;
try {
uri = url.toURI();
}
catch(URISyntaxException ex){
//in practice this will be very rare
ex.printStackTrace();
}
Path path = Paths.get(uri);
//now that you have the path, it's just regular text file processing
//this gets the whole content at once:
List<String> lines = Files.readAllLines(path, ENCODING);
log("Number of lines in the file: " + lines.size());
//OR, use this style, to process each line one at a time
try (Scanner scanner = new Scanner(path, ENCODING.name())){
while (scanner.hasNextLine()){
//process each line in some way
log(scanner.nextLine());
}
}
}
/**
Here, relative Path objects don't know about the file system in the same way that a
classloader does. It only knows about the 'user.dir' directory, the base
directory of the runtime; this style is much less flexible, and is not
recommended.
*/
void readFileViaPath() throws IOException{
log("Via path (not recommended)...");
//Succeeds: absolute reference
//Path path = Paths.get("C:\\myproj\\test-api\\bin\\test.txt");
/*
* Relative reference.
*
* Fails when the file is beside the .class file.
251
See Also :
Reading and writing text files
Package by feature, not layer
Parse text
There are various ways of parsing text. The usual tools are:
String.split methods
StringTokenizer and StreamTokenizer classes
Scanner class
Pattern and Matcher classes, which implement regular expressions
for the most complex parsing tasks, you can use tools such as JavaCC
Example 1
This example uses Scanner. Here, the contents of a file containing name-value pairs is read, and each
line is parsed into its constituent data.
import
import
import
import
import
import
java.io.IOException;
java.nio.charset.Charset;
java.nio.charset.StandardCharsets;
java.nio.file.Path;
java.nio.file.Paths;
java.util.Scanner;
:
:
:
:
Example 2
This example uses StringTokenizer . This class is used to parse the text entered into a search box on a
web page. It returns a Set of tokens to be used for pattern matching. Here, any text appearing in quotes is
treated as a single search token. All other text is split into tokens based simply on whitespace.
253
An example run:
>java -cp . SearchBoxParser
[mars, sun, milky way, venus]
import java.util.*;
/**
* The user enters text into a search box. This class is used
* to parse that text into specific search terms (or tokens).
* It eliminates common words, and allows for the quoting of text, using
* double quotes.
* JDK 7+.
*/
public final class SearchBoxParser {
public static void main(String... aArguments) {
SearchBoxParser parser = new SearchBoxParser("mars venus \"milky way\" sun");
Set<String> tokens = parser.parseSearchText();
//display the tokens
System.out.println(tokens);
}
/**
* @param aSearchText is non-null, but may have no content,
* and represents what the user has input in a search box.
*/
public SearchBoxParser(String aSearchText) {
if (aSearchText == null) {
throw new IllegalArgumentException("Search Text cannot be null.");
}
fSearchText = aSearchText;
}
/**
* Parse the user's search box input into a Set of String tokens.
*
* @return Set of Strings, one for each word in fSearchText; here "word"
* is defined as either a lone word surrounded by whitespace, or as a series
* of words surrounded by double quotes, "like this"; also, very common
* words (and, the, etc.) do not qualify as possible search targets.
*/
public Set<String> parseSearchText() {
Set<String> result = new LinkedHashSet<>();
boolean returnTokens = true;
String currentDelims = fWHITESPACE_AND_QUOTES;
StringTokenizer parser = new StringTokenizer(
fSearchText, currentDelims, returnTokens
);
String token = null;
while (parser.hasMoreTokens()) {
token = parser.nextToken(currentDelims);
if (!isDoubleQuote(token)){
addNonTrivialWordToResult(token, result);
}
else {
currentDelims = flipDelimiters(currentDelims);
}
}
return result;
}
// PRIVATE
private String fSearchText;
private static final Set<String> fCOMMON_WORDS = new LinkedHashSet<>();
private static final String fDOUBLE_QUOTE = "\"";
//the parser flips between these two sets of delimiters
private static final String fWHITESPACE_AND_QUOTES = " \t\r\n\"";
private static final String fQUOTES_ONLY ="\"";
/**Very common words to be excluded from searches.*/
static {
fCOMMON_WORDS.add("a");
254
fCOMMON_WORDS.add("and");
fCOMMON_WORDS.add("be");
fCOMMON_WORDS.add("for");
fCOMMON_WORDS.add("from");
fCOMMON_WORDS.add("has");
fCOMMON_WORDS.add("i");
fCOMMON_WORDS.add("in");
fCOMMON_WORDS.add("is");
fCOMMON_WORDS.add("it");
fCOMMON_WORDS.add("of");
fCOMMON_WORDS.add("on");
fCOMMON_WORDS.add("to");
fCOMMON_WORDS.add("the");
}
/**
* Use to determine if a particular word entered in the
* search box should be discarded from the search.
*/
private boolean isCommonWord(String aSearchTokenCandidate){
return fCOMMON_WORDS.contains(aSearchTokenCandidate);
}
private boolean textHasContent(String aText){
return (aText != null) && (!aText.trim().equals(""));
}
private void addNonTrivialWordToResult(String aToken, Set<String> aResult){
if (textHasContent(aToken) && !isCommonWord(aToken.trim())) {
aResult.add(aToken.trim());
}
}
private boolean isDoubleQuote(String aToken){
return aToken.equals(fDOUBLE_QUOTE);
}
private String flipDelimiters(String aCurrentDelims){
String result = null;
if (aCurrentDelims.equals(fWHITESPACE_AND_QUOTES)){
result = fQUOTES_ONLY;
}
else {
result = fWHITESPACE_AND_QUOTES;
}
return result;
}
}
Example 3
This example demonstrates use of regular expressions, by parsing a fully-qualified type name into two
parts - the package and the "simple" type name.
import java.util.regex.*;
public final class RegularExpressions {
/**
* The pattern is matched to the first argument.
*/
public static void main (String... aArguments) {
matchParts(aArguments[0]);
matchAll(aArguments[0]);
}
/**
* The Matcher.find method attempts to match *parts* of the input
* to the given pattern.
*/
private static void matchParts(String aText){
log(fNEW_LINE + "Match PARTS:");
//note the necessity of the comments flag, since our regular
//expression contains comments:
255
256
See Also :
Reading and writing text files
Pattern-match lines of a file
Compile regular expressions once
java.io.BufferedReader;
java.io.IOException;
java.io.LineNumberReader;
java.nio.charset.Charset;
java.nio.charset.StandardCharsets;
java.nio.file.Files;
java.nio.file.Path;
java.nio.file.Paths;
java.util.regex.Matcher;
java.util.regex.Pattern;
See Also :
Reading and writing text files
The following example extracts the name and version of the package containing the class
InternetAddress . (If the javax.mail.internet package is not on your class path, you can easily modify
this code to examine some other package.)
import javax.mail.internet.InternetAddress;
/**
Display package name and version information for
javax.mail.internet.
As long as you can build on object of some known class,
you may get its related package info in this way.
*/
public final class ReadVersion {
public static void main(String... aArgs){
ReadVersion readVersion = new ReadVersion();
readVersion.readVersionInfoInManifest();
}
public void readVersionInfoInManifest(){
//build an object whose class is in the target jar
InternetAddress object = new InternetAddress();
//navigate from its class object to a package object
Package objPackage = object.getClass().getPackage();
//examine the package object
String name = objPackage.getSpecificationTitle();
String version = objPackage.getSpecificationVersion();
//some jars may use 'Implementation Version' entries in the manifest instead
System.out.println("Package name: " + name);
System.out.println("Package version: " + version);
}
}
Recovering resources
Expensive resources should be reclaimed as soon as possible, by an explict call to a clean-up method
defined for this purpose. If this is not done, then system performance can degrade. In the worst cases, the
system can even fail entirely.
Resources include:
input-output streams
database result sets, statements, and connections
threads
graphic resources
sockets
Resources which are created locally within a method must be cleaned up within the same method, by
calling a method appropriate to the resource itself, such as close or dispose. (The exact name of the
method is arbitrary, but it usually has those conventional names.) This is usually done automatically,
using the try-with-resources feature, added in JDK 7.
259
If try-with-resources isn't available, then you need to clean up resources explicitly, by calling a clean-up
method in a finally clause.
For the case of a resource which is a field, however, there's more work to do:
implement a clean-up method which the user must call when finished with the object, with a name
such as close or dispose
the caller should be able to query an object to see if its clean-up method has been executed
non- private methods (other than the clean-up method itself) should throw an
IllegalStateException if the clean-up method has already been invoked
as a safety net, implement finalize to call the clean-up method as well; if the user of the class
neglects to call the clean-up method, then this may allow recovery of the resource by the system
never rely solely on finalize
This example shows a class which retains a database connection during its lifetime. (This example is
artificial. Actually writing such a class would not seem necessary in practice, since connection pools
already perform such clean-up in the background. It's used merely to demonstrate the ideas mentioned
above.)
import java.sql.*;
import java.text.*;
import java.util.*;
/**
* This class has an enforced life cycle: after destroy is
* called, no useful method can be called on this object
* without throwing an IllegalStateException.
*/
public final class DbConnection {
public DbConnection () {
//build a connection and assign it to a field
//elided.. fConnection = ConnectionPool.getInstance().getConnection();
}
/**
* Ensure the resources of this object are cleaned up in an orderly manner.
*
* The user of this class must call destroy when finished with
* the object. Calling destroy a second time is permitted, but is
* a no-operation.
*/
public void destroy() throws SQLException {
if (fIsDestroyed) {
return;
}
else{
if (fConnection != null) fConnection.close();
fConnection = null;
//flag that destory has been called, and that
//no further calls on this object are valid
fIsDestroyed = true;
}
}
/**
* Fetches something from the db.
*
* This is an example of a non-private method which must ensure that
* <code>destroy</code> has not yet been called
* before proceeding with execution.
*/
synchronized public Object fetchBlah(String aId) throws SQLException {
validatePlaceInLifeCycle();
//..elided
return null;
}
/**
* If the user fails to call <code>destroy</code>, then implementing
260
See Also :
Always close streams
Never rely on finalize
Finally and catch
Get database connection
Always shut down an ExecutorService
import
import
import
import
import
import
java.nio.file.FileVisitor;
java.nio.file.Files;
java.nio.file.Path;
java.nio.file.Paths;
java.nio.file.SimpleFileVisitor;
java.nio.file.attribute.BasicFileAttributes;
Older style, JDK 6The File class has methods for listing the contents of a single directory, such as list and listFiles ,
but there is no method for listing all files in a directory tree.
Here's a simple utility which returns a sorted List of File objects which reside under a given root
directory. It uses recursion.
import java.util.*;
import java.io.*;
/**
* Recursive file listing under a specified directory.
*
* @author javapractices.com
* @author Alex Wong
* @author anonymous user
*/
public final class FileListing {
/**
* Demonstrate use.
*
* @param aArgs - <tt>aArgs[0]</tt> is the full name of an existing
* directory that can be read.
*/
public static void main(String... aArgs) throws FileNotFoundException {
File startingDirectory= new File(aArgs[0]);
FileListing listing = new FileListing();
List<File> files = listing.getFileListing(startingDirectory);
//print out all file names, in the the order of File.compareTo()
for(File file : files){
System.out.println(file);
}
}
/**
* Recursively walk a directory tree and return a List of all
* Files found; the List is sorted using File.compareTo().
*
262
Example run:
>java -cp . FileListing "C:\TEMP\"
C:\TEMP\Hokkaido.txt
C:\TEMP\Honshu.txt
C:\TEMP\mb
C:\TEMP\mb\Kyushu.txt
C:\TEMP\mb\Shikoku.txt
Replace a substring
Static Replacement
263
Replacing one static String with another can be done in various ways:
public final class ReplaceSubstring {
/**
* Simplest in Java 1.5, using the replace method, which
* takes CharSequence objects.
*/
public static String replace15(
String aInput, String aOldPattern, String aNewPattern
){
return aInput.replace(aOldPattern, aNewPattern);
}
/**
* Not quite as simple in Java 1.4. The replaceAll method works,
* but requires more care, since it uses regular expressions, which
* may contain special characters.
*/
public static String replace14(
String aInput, String aOldPattern, String aNewPattern
){
/*
* The replaceAll method is a bit dangerous to use.
* The aOldPattern is converted into a regular expression.
* Thus, if aOldPattern may contain characters which have
* special meaning to regular expressions, then they must
* be 'escaped' before being passed to replaceAll. It is
* easy to forget to do this.
*
* In addition, aNewPattern treats '$' as special characters
* as well: they refer to 'back references'.
*/
return aInput.replaceAll(aOldPattern, aNewPattern);
/*
Here is an alternative implementation using Pattern and Matcher,
which is preferred when the same pattern is used repeatedly
final Pattern pattern = Pattern.compile( aOldPattern );
final Matcher matcher = pattern.matcher( aInput );
return matcher.replaceAll( aNewPattern );
*/
}
/**
* If Java 1.4 is unavailable, the following technique may be used.
*
* @param aInput is the original String which may contain substring aOldPattern
* @param aOldPattern is the non-empty substring which is to be replaced
* @param aNewPattern is the replacement for aOldPattern
*/
public static String replaceOld(
final String aInput,
final String aOldPattern,
final String aNewPattern
){
if ( aOldPattern.equals("") ) {
throw new IllegalArgumentException("Old pattern must have content.");
}
final StringBuffer result = new StringBuffer();
//startIdx and idxOld delimit various chunks of aInput; these
//chunks always end where aOldPattern begins
int startIdx = 0;
int idxOld = 0;
while ((idxOld = aInput.indexOf(aOldPattern, startIdx)) >= 0) {
//grab a part of aInput which does not include aOldPattern
result.append( aInput.substring(startIdx, idxOld) );
//add aNewPattern to take place of aOldPattern
result.append( aNewPattern );
//reset the startIdx to just after the current match, to see
//if there are any further matches
startIdx = idxOld + aOldPattern.length();
}
//the final chunk will go to the end of aInput
result.append( aInput.substring(startIdx) );
return result.toString();
264
}
/** Example: update an ip address appearing in a link. */
public static void main (String[] aArguments) {
String OLD_IP = "45.23.102.12";
//escape the '.', a special character in regular expressions
String OLD_IP_REGEX = "45\\.23\\.102\\.12";
String NEW_IP = "99.104.106.95";
String LINK = "http://45.23.102.12:8080/index.html";
log("Old link : " + LINK);
String newLink = replace15(LINK, OLD_IP, NEW_IP);
log("New link with Java 1.5 replace: " + newLink);
newLink = replace14(LINK, OLD_IP_REGEX, NEW_IP);
log("New link with Java 1.4 replaceAll: " + newLink);
newLink = replaceOld(LINK, OLD_IP, NEW_IP);
log("New link with oldest style: " + newLink);
}
private static void log(String aMessage){
System.out.println(aMessage);
}
}
link
link
link
link
: http://45.23.102.12:8080/index.html
with Java 1.5 replace: http://99.104.106.95:8080/index.html
with Java 1.4 replaceAll: http://99.104.106.95:8080/index.html
with oldest style: http://99.104.106.95:8080/index.html
Dynamic Replacement
If the replacement string is not fixed, and needs to be created dynamically, then another approach is
required. In the following example, strings of the form "href=Topic182.cjp" are replaced with a
corresponding string "href=#182 ".
The number 182 is taken only as an example. It is in fact extracted dynamically, and referenced in the
replacement string using the back reference "$1", where 1 is the index of the matching group for these
digits.
import java.util.regex.*;
public final class ReplaceSubstringDynamically {
public static void main (String... aArguments) {
String htmlText = "<a href=\"Topic27.cjp\">xyz</a> blah <a
href=Topic8.cjp>abc</a>";
ReplaceSubstringDynamically replace = new ReplaceSubstringDynamically();
System.out.println("Old HTML text : " + htmlText);
System.out.println("New HTML text : " + replace.replaceLinks(htmlText));
}
/**
* Replace the document links in a snippet of HTML with corresponding
* fragment links, which start with the # sign, and refer to labelled
* locations within a single document.
*/
String replaceLinks(String aHtmlTextWithLinks){
Pattern pattern = Pattern.compile(fLINK);
Matcher matcher = pattern.matcher(aHtmlTextWithLinks);
return matcher.replaceAll(fFRAGMENT);
}
/**
* The single matching group of this regex are the digits ((?:\\d){1,3}),
* which correspond to group 1.
*/
265
Here's a second example, where the replacement string is computed without using back references.
import java.util.regex.*;
public final class ReplaceSubstringAppendReplacement {
public static void main (String... aArguments) {
String text = "Apples and oranges are better for all.";
ReplaceSubstringAppendReplacement repl = new ReplaceSubstringAppendReplacement();
System.out.println("Old text : " + text);
System.out.println("New text : " + repl.getEditedText(text));
}
/**
* Replace all words starting with the letter 'a' or 'A' with
* their uppercase forms.
*/
String getEditedText(String aText){
StringBuffer result = new StringBuffer();
Matcher matcher = fINITIAL_A.matcher(aText);
while (matcher.find()) {
matcher.appendReplacement(result, getReplacement(matcher));
}
matcher.appendTail(result);
return result.toString();
}
private static final Pattern fINITIAL_A = Pattern.compile(
"(?:\\s|^)a(?:\\w)*",
Pattern.CASE_INSENSITIVE
);
private String getReplacement(Matcher aMatcher){
return aMatcher.group(0).toUpperCase();
}
}
Warning
The methods:
String.replaceAll(String, String)
String.replaceFirst(String, String)
Matcher.appendReplacement(StringBuffer, String)
treat '$' and '\' in the replacement text as special characters. If the replacement text can contain
arbitrary text, then these characters will usually be escaped using Matcher.quoteReplacement(String) .
266
See Also :
Escape special characters
Representing money
Representing money:
use BigDecimal , int , or long ( BigDecimal is the recommended default)
the int and long forms represent pennies (or the equivalent, of course)
BigDecimal is a little more inconvenient to use, but has built-in rounding modes
double or float are not recommended, since they always carry small rounding differences
the Currency class encapsulates standard identifiers for the world's currencies
Number of digits:
<=9 : use BigDecimal , int , or long
<=18 : use BigDecimal , or long
>18 : use BigDecimal
static
static
static
static
int EXTRA_DECIMALS = 4;
final BigDecimal TWO = new BigDecimal("2");
BigDecimal HUNDRED = new BigDecimal("100");
BigDecimal PERCENTAGE = new BigDecimal("5.25");
}
private BigDecimal rounded(BigDecimal aNumber){
return aNumber.setScale(DECIMALS, ROUNDING_MODE);
}
}
Example 2
The lack of an actual Money class in the standard JDK libraries is frustrating. Such a class would have
some nice advantages:
the name Money reads at a higher level of abstraction than BigDecimal
operations on BigDecimal can be wrapped to yield a more suitable form for practical use
Here's an example of such a Money class.
import
import
import
import
import
import
import
import
java.util.*;
java.io.Serializable;
java.io.IOException;
java.io.ObjectInputStream;
java.io.ObjectOutputStream;
java.math.BigDecimal;
static java.math.BigDecimal.ZERO;
java.math.RoundingMode;
/**
* Represent an amount of money in any currency.
*
* <P>This class assumes <em>decimal currency</em>, without funky divisions
* like 1/5 and so on. <tt>Money</tt> objects are immutable. Like {@link BigDecimal},
* many operations return new <tt>Money</tt> objects. In addition, most operations
* involving more than one <tt>Money</tt> object will throw a
* <tt>MismatchedCurrencyException</tt> if the currencies don't match.
*
* <h2>Decimal Places and Scale</h2>
* Monetary amounts can be stored in the database in various ways. Let's take the
* example of dollars. It may appear in the database in the following ways :
* <ul>
* <li>as <tt>123456.78</tt>, with the usual number of decimal places
*
associated with that currency.
* <li>as <tt>123456</tt>, without any decimal places at all.
* <li>as <tt>123</tt>, in units of thousands of dollars.
* <li>in some other unit, such as millions or billions of dollars.
* </ul>
*
* <P>The number of decimal places or style of units is referred to as the
* <em>scale</em> by {@link java.math.BigDecimal}. This class's constructors
* take a <tt>BigDecimal</tt>, so you need to understand it use of the idea of scale.
*
* <P>The scale can be negative. Using the above examples :
* <table border='1' cellspacing='0' cellpadding='3'>
* <tr><th>Number</th><th>Scale</th></tr>
* <tr><td>123456.78</th><th>2</th></tr>
* <tr><td>123456</th><th>0</th></tr>
* <tr><td>123 (thousands)</th><th>-3</th></tr>
* </table>
269
*
* <P>Note that scale and rounding are two separate issues.
* In addition, rounding is only necessary for multiplication and division operations.
* It doesn't apply to addition and subtraction.
*
* <h2>Operations and Scale</h2>
* <P>Operations can be performed on items having <em>different scale</em>.
* For example, these operations are valid (using an <em>ad hoc</em>
* symbolic notation):
* <PRE>
* 10.plus(1.23) => 11.23
* 10.minus(1.23) => 8.77
* 10.gt(1.23) => true
* 10.eq(10.00) => true
* </PRE>
* This corresponds to typical user expectations.
* An important exception to this rule is that {@link #equals(Object)} is sensitive
* to scale (while {@link #eq(Money)} is not) . That is,
* <PRE>
*
10.equals(10.00) => false
* </PRE>
*
* <h2>Multiplication, Division and Extra Decimal Places</h2>
* <P>Operations involving multiplication and division are different, since the result
* can have a scale which exceeds that expected for the given currency. For example
* <PRE>($10.00).times(0.1256) => $1.256</PRE>
* which has more than two decimals. In such cases, <em>this class will always round
* to the expected number of decimal places for that currency.</em>
* This is the simplest policy, and likely conforms to the expectations of most
* end users.
*
* <P>This class takes either an <tt>int</tt> or a {@link BigDecimal} for its
* multiplication and division methods. It doesn't take <tt>float</tt> or
* <tt>double</tt> for those methods, since those types don't interact well with
* <tt>BigDecimal</tt>. Instead, the <tt>BigDecimal</tt> class must be used when the
* factor or divisor is a non-integer.
*
* <P><em>The {@link #init(Currency, RoundingMode)} method must be called at least
* once before using the other members of this class.</em> It establishes your
* desired defaults. Typically, it will be called once (and only once) upon startup.
*
* <P>Various methods in this class have unusually terse names, such as
* {@link #lt} and {@link #gt}. The intent is that such names will improve the
* legibility of mathematical expressions. Example :
* <PRE> if ( amount.lt(hundred) ) {
*
cost = amount.times(price);
* }</PRE>
*/
public final class Money implements Comparable<Money>, Serializable {
/**
* Thrown when a set of <tt>Money</tt> objects do not have matching currencies.
*
* <P>For example, adding together Euros and Dollars does not make any sense.
*/
public static final class MismatchedCurrencyException extends RuntimeException {
MismatchedCurrencyException(String aMessage){
super(aMessage);
}
}
/**
* Set default values for currency and rounding style.
*
* <em>Your application must call this method upon startup</em>.
* This method should usually be called only once (upon startup).
*
* <P>The recommended rounding style is {@link RoundingMode#HALF_EVEN}, also called
* <em>banker's rounding</em>; this rounding style introduces the least bias.
*
* <P>Setting these defaults allow you to use the more terse constructors of this
class,
* which are much more convenient.
*
* <P>(In a servlet environment, each app has its own classloader. Calling this
* method in one app will never affect the operation of a second app running in the
same
* servlet container. They are independent.)
*/
public static void init(Currency aDefaultCurrency, RoundingMode aDefaultRounding){
270
DEFAULT_CURRENCY = aDefaultCurrency;
DEFAULT_ROUNDING = aDefaultRounding;
}
/**
* Full constructor.
*
* @param aAmount is required, can be positive or negative. The number of
* decimals in the amount cannot <em>exceed</em> the maximum number of
* decimals for the given {@link Currency}. It's possible to create a
* <tt>Money</tt> object in terms of 'thousands of dollars', for instance.
* Such an amount would have a scale of -3.
* @param aCurrency is required.
* @param aRoundingStyle is required, must match a rounding style used by
* {@link BigDecimal}.
*/
public Money(BigDecimal aAmount, Currency aCurrency, RoundingMode aRoundingStyle){
fAmount = aAmount;
fCurrency = aCurrency;
fRounding = aRoundingStyle;
validateState();
}
/**
* Constructor taking only the money amount.
*
* <P>The currency and rounding style both take default values.
* @param aAmount is required, can be positive or negative.
*/
public Money(BigDecimal aAmount){
this(aAmount, DEFAULT_CURRENCY, DEFAULT_ROUNDING);
}
/**
* Constructor taking the money amount and currency.
*
* <P>The rounding style takes a default value.
* @param aAmount is required, can be positive or negative.
* @param aCurrency is required.
*/
public Money(BigDecimal aAmount, Currency aCurrency){
this(aAmount, aCurrency, DEFAULT_ROUNDING);
}
/** Return the amount passed to the constructor. */
public BigDecimal getAmount() { return fAmount; }
/** Return the currency passed to the constructor, or the default currency. */
public Currency getCurrency() { return fCurrency; }
/** Return the rounding style passed to the constructor, or the default rounding
style. */
public RoundingMode getRoundingStyle() { return fRounding; }
/**
* Return <tt>true</tt> only if <tt>aThat</tt> <tt>Money</tt> has the same currency
* as this <tt>Money</tt>.
*/
public boolean isSameCurrencyAs(Money aThat){
boolean result = false;
if ( aThat != null ) {
result = this.fCurrency.equals(aThat.fCurrency);
}
return result;
}
/** Return <tt>true</tt> only if the amount is positive. */
public boolean isPlus(){
return fAmount.compareTo(ZERO) > 0;
}
/** Return <tt>true</tt> only if the amount is negative. */
public boolean isMinus(){
return fAmount.compareTo(ZERO) < 0;
}
/** Return <tt>true</tt> only if the amount is zero. */
public boolean isZero(){
return fAmount.compareTo(ZERO) == 0;
271
}
/**
* Add <tt>aThat</tt> <tt>Money</tt> to this <tt>Money</tt>.
* Currencies must match.
*/
public Money plus(Money aThat){
checkCurrenciesMatch(aThat);
return new Money(fAmount.add(aThat.fAmount), fCurrency, fRounding);
}
/**
* Subtract <tt>aThat</tt> <tt>Money</tt> from this <tt>Money</tt>.
* Currencies must match.
*/
public Money minus(Money aThat){
checkCurrenciesMatch(aThat);
return new Money(fAmount.subtract(aThat.fAmount), fCurrency, fRounding);
}
/**
* Sum a collection of <tt>Money</tt> objects.
* Currencies must match. You are encouraged to use database summary functions
* whenever possible, instead of this method.
*
* @param aMoneys collection of <tt>Money</tt> objects, all of the same currency.
* If the collection is empty, then a zero value is returned.
* @param aCurrencyIfEmpty is used only when <tt>aMoneys</tt> is empty; that way,
this
* method can return a zero amount in the desired currency.
*/
public static Money sum(Collection<Money> aMoneys, Currency aCurrencyIfEmpty){
Money sum = new Money(ZERO, aCurrencyIfEmpty);
for(Money money : aMoneys){
sum = sum.plus(money);
}
return sum;
}
/**
* Equals (insensitive to scale).
*
* <P>Return <tt>true</tt> only if the amounts are equal.
* Currencies must match.
* This method is <em>not</em> synonymous with the <tt>equals</tt> method.
*/
public boolean eq(Money aThat) {
checkCurrenciesMatch(aThat);
return compareAmount(aThat) == 0;
}
/**
* Greater than.
*
* <P>Return <tt>true</tt> only if 'this' amount is greater than
* 'that' amount. Currencies must match.
*/
public boolean gt(Money aThat) {
checkCurrenciesMatch(aThat);
return compareAmount(aThat) > 0;
}
/**
* Greater than or equal to.
*
* <P>Return <tt>true</tt> only if 'this' amount is
* greater than or equal to 'that' amount. Currencies must match.
*/
public boolean gteq(Money aThat) {
checkCurrenciesMatch(aThat);
return compareAmount(aThat) >= 0;
}
/**
* Less than.
*
* <P>Return <tt>true</tt> only if 'this' amount is less than
* 'that' amount. Currencies must match.
*/
272
*/
public String toString(){
return fAmount.toPlainString() + " " + fCurrency.getSymbol();
}
/**
* Like {@link BigDecimal#equals(java.lang.Object)}, this <tt>equals</tt> method
* is also sensitive to scale.
*
* For example, <tt>10</tt> is <em>not</em> equal to <tt>10.00</tt>
* The {@link #eq(Money)} method, on the other hand, is <em>not</em>
* sensitive to scale.
*/
public boolean equals(Object aThat){
if (this == aThat) return true;
if (! (aThat instanceof Money) ) return false;
Money that = (Money)aThat;
//the object fields are never null :
boolean result = (this.fAmount.equals(that.fAmount) );
result = result && (this.fCurrency.equals(that.fCurrency) );
result = result && (this.fRounding == that.fRounding);
return result;
}
public int hashCode(){
if ( fHashCode == 0 ) {
fHashCode = HASH_SEED;
fHashCode = HASH_FACTOR * fHashCode + fAmount.hashCode();
fHashCode = HASH_FACTOR * fHashCode + fCurrency.hashCode();
fHashCode = HASH_FACTOR * fHashCode + fRounding.hashCode();
}
return fHashCode;
}
public int compareTo(Money aThat) {
final int EQUAL = 0;
if ( this == aThat ) return EQUAL;
//the object fields are never null
int comparison = this.fAmount.compareTo(aThat.fAmount);
if ( comparison != EQUAL ) return comparison;
comparison = this.fCurrency.getCurrencyCode().compareTo(
aThat.fCurrency.getCurrencyCode()
);
if ( comparison != EQUAL ) return comparison;
comparison = this.fRounding.compareTo(aThat.fRounding);
if ( comparison != EQUAL ) return comparison;
return EQUAL;
}
// PRIVATE //
/**
* The money amount.
* Never null.
* @serial
*/
private BigDecimal fAmount;
/**
* The currency of the money, such as US Dollars or Euros.
* Never null.
* @serial
*/
private final Currency fCurrency;
/**
* The rounding style to be used.
* See {@link BigDecimal}.
* @serial
*/
private final RoundingMode fRounding;
274
/**
* The default currency to be used if no currency is passed to the constructor.
*/
private static Currency DEFAULT_CURRENCY;
/**
* The default rounding style to be used if no currency is passed to the constructor.
* See {@link BigDecimal}.
*/
private static RoundingMode DEFAULT_ROUNDING;
/** @serial */
private int fHashCode;
private static final int HASH_SEED = 23;
private static final int HASH_FACTOR = 37;
/**
* Determines if a deserialized file is compatible with this class.
*
* Maintainers must change this value if and only if the new version
* of this class is not compatible with old versions. See Sun docs
* for <a href=http://java.sun.com/products/jdk/1.1/docs/guide
* /serialization/spec/version.doc.html> details. </a>
*
* Not necessary to include in first version of the class, but
* included here as a reminder of its importance.
*/
private static final long serialVersionUID = 7526471155622776147L;
/**
* Always treat de-serialization as a full-blown constructor, by
* validating the final state of the de-serialized object.
*/
private void readObject(
ObjectInputStream aInputStream
) throws ClassNotFoundException, IOException {
//always perform the default de-serialization first
aInputStream.defaultReadObject();
//defensive copy for mutable date field
//BigDecimal is not technically immutable, since its non-final
fAmount = new BigDecimal( fAmount.toPlainString() );
//ensure that object state has not been corrupted or tampered with maliciously
validateState();
}
private void writeObject(ObjectOutputStream aOutputStream) throws IOException {
//perform the default serialization for all non-transient, non-static fields
aOutputStream.defaultWriteObject();
}
private void validateState(){
if( fAmount == null ) {
throw new IllegalArgumentException("Amount cannot be null");
}
if( fCurrency == null ) {
throw new IllegalArgumentException("Currency cannot be null");
}
if ( fAmount.scale() > getNumDecimalsForCurrency() ) {
throw new IllegalArgumentException(
"Number of decimals is " + fAmount.scale() + ", but currency only takes " +
getNumDecimalsForCurrency() + " decimals."
);
}
}
private int getNumDecimalsForCurrency(){
return fCurrency.getDefaultFractionDigits();
}
private void checkCurrenciesMatch(Money aThat){
if (! this.fCurrency.equals(aThat.getCurrency())) {
throw new MismatchedCurrencyException(
aThat.getCurrency() + " doesn't match the expected currency : " + fCurrency
);
}
}
/** Ignores scale: 0 same as 0.00 */
private int compareAmount(Money aThat){
275
return this.fAmount.compareTo(aThat.fAmount);
}
private BigDecimal asBigDecimal(double aDouble){
String asString = Double.toString(aDouble);
return new BigDecimal(asString);
}
}
See Also :
Beware of floating point numbers
Send an email
The JavaMail API is not part of core Java SE, but an optional extension. (It's required in Java Enterprise
Edition.) The JavaMail packages can be accessed in two ways:
by placing both mail.jar and activation.jar in the classpath
or, by placing j2ee.jar in the classpath
The javax.mail API uses a properties file for reading server names and related configuration. These
settings will override any system defaults. Alternatively, the configuration can be set directly in code,
using the JavaMail API.
Example
An email configuration file (to run the example, substitute valid values for the uncommented items):
#
#
#
#
276
import
import
import
import
import
java.util.*;
java.io.*;
java.nio.file.*;
javax.mail.*;
javax.mail.internet.*;
/**
* Simple demonstration of using the javax.mail API.
*
* Run from the command line. Please edit the implementation
* to use correct email addresses and host name.
*/
public final class Emailer {
public static void main( String... aArguments ){
Emailer emailer = new Emailer();
//the domains of these email addresses should be valid,
//or the example will fail:
emailer.sendEmail(
"fromblah@blah.com", "toblah@blah.com",
"Testing 1-2-3", "blah blah blah"
);
}
/**
* Send a single email.
*/
public void sendEmail(
String aFromEmailAddr, String aToEmailAddr,
String aSubject, String aBody
){
//Here, no Authenticator argument is used (it is null).
//Authenticators are used to prompt the user for user
//name and password.
Session session = Session.getDefaultInstance(fMailServerConfig, null);
MimeMessage message = new MimeMessage(session);
try {
//the "from" address may be set in code, or set in the
//config file under "mail.from" ; here, the latter style is used
//message.setFrom(new InternetAddress(aFromEmailAddr));
message.addRecipient(
Message.RecipientType.TO, new InternetAddress(aToEmailAddr)
);
message.setSubject(aSubject);
message.setText(aBody);
Transport.send(message);
}
catch (MessagingException ex){
System.err.println("Cannot send email. " + ex);
}
}
/**
* Allows the config to be refreshed at runtime, instead of
* requiring a restart.
*/
public static void refreshConfig() {
fMailServerConfig.clear();
fetchConfig();
}
// PRIVATE
private static Properties fMailServerConfig = new Properties();
static {
fetchConfig();
}
/**
* Open a specific text file containing mail server
* parameters, and populate a corresponding Properties object.
*/
private static void fetchConfig() {
//This file contains the javax.mail config properties mentioned above.
Path path = Paths.get("C:\\Temp\\MyMailServer.txt");
try (InputStream input = Files.newInputStream(path)) {
fMailServerConfig.load(input);
}
277
See Also :
Launch other applications
import java.math.BigDecimal;
/**
Time the execution of any block of code.
<P>This implementation times the duration using <tt>System.nanoTime</tt>.
<P>On most systems <tt>System.currentTimeMillis</tt> has a time
resolution of about 10ms, which is quite poor for timing code, so it is
avoided here.
*/
public final class Stopwatch {
/**
An example of the use of this class to
time the execution of simple String manipulation code.
*/
278
See Also :
Copy an array
String concatenation does not scale
Measure application performance
Abstract Factory
Using references to interfaces instead of references to concrete classes is an important way of minimizing
ripple effects. The user of an interface reference is always protected from changes to the underlying
implementation.
The Abstract Factory pattern is one example of this technique. Users of an Abstract Factory can create
families of related objects without any knowledge of their concrete classes. (A typical business
application would usually not need to use this technique, at least as applied to Data Access Objects.)
Example
280
An Abstract Factory is a major part of the full Data Access Object (DAO) scheme. Here, the idea is to
allow the business layer to interact with the data layer almost entirely through interface references. The
business layer remains ignorant of the concrete classes which implement the datastore.
There are two distinct families of items here:
the various datastore implementations (in this case, text files, or a relational database)
the various business objects which need persistence (in this case, User and Device)
Let's take the example of storing Device objects. They may be stored in either a text file or a relational
database. Since the calling code needs to remain ignorant of which is being used, it's natural, of course,
to define an interface that reflects this, along with two corresponding concrete implementations:
package myapp;
public interface DeviceDAO {
Device fetch(String aId) throws DataAccessException;
void add(Device aDevice) throws DataAccessException;
void change(Device aDevice) throws DataAccessException;
void delete(Device aDevice) throws DataAccessException;
}
281
(The details of these classes are left out, so that you can see the structure better.)
Next, the calling application needs a way to interact with the database, without knowing about the
concrete implementations. This is done using a factory class:
package myapp;
/**
Returns all DAO instances.
Reads a configuration item (defined by your program) to decide
which family of DAO objects it should be returning, for the currently
running program, either file-based or relational-based.
The configuration mechanism may be a System property, a properties file, an
XML file, and so on. The config is often read when the system initializes,
perhaps using a static initializer.
*/
final class DAOFactory {
/* some would implement all of the methods here as static methods */
DeviceDAO getDeviceDAO(){
//elided:
return null;
}
UserDAO getUserDAO(){
//elided:
return null;
}
}
package myapp;
public final class Application {
/**
This calling code is completely unaware of the underlying datastore.
It could be a text file, or a relational database.
*/
void addNewDevice(Device aDevice){
DAOFactory factory = new DAOFactory();
try {
factory.getDeviceDAO().add(aDevice);
}
catch (DataAccessException ex) {
ex.printStackTrace();
}
}
}
282
To pile on even more interfaces, one variation on the above is to make an abstraction or the factory itself:
the factory is defined as an interface, with methods similar to the above. Then, two concrete
implementations of the factory interface are defined, one for each kind of datastore.
package myapp;
public interface Factory {
DeviceDAO getDeviceDAO();
UserDAO getUserDAO();
}
package myapp;
public final class FactoryBuilder {
/** returns a specific implementation of Factory */
Factory getFactory(){
//elided
return null;
}
}
See Also :
Factory methods
Data access objects
Data exception wrapping
Construct Object using class name
Minimize ripple effects
Parse parameters into domain objects
Command objects
It's a common practice to define an interface for processing a user's request at a high level of abstraction.
This corresponds roughly to the Command pattern described in Design Patterns.
Example
This example is in the context of a web application. Action is an interface which:
executes a business operation (validates input, interacts with the datastore, and places appropriate
model objects in request scope or session scope)
sets the identity of the resource which will render the model objects (typically a JSP)
controls when a redirect should be sent to the browser
package hirondelle.web4j.action;
283
import hirondelle.web4j.model.AppException;
/**
<span class="highlight">
Process an HTTP request, and return a {@link ResponsePage}.
</span>
<P><b>This interface is likely the most important
abstraction in WEB4J.</b> Almost every feature implemented by the programmer will
need an implementation of this interface.
<P>Typically, one of the <em>ActionXXX</em> abstract base classes are used to
build implementations of this interface.
*/
public interface Action {
/**
Execute desired operation.
<P>Typical operations include :
<ul>
<li>validate user input
<li>interact with the database
<li>place required objects in the appropriate scope
<li>set the appropriate {@link ResponsePage}.
</ul>
<P>Returns an identifier for the resource (for example a JSP) which
will display the end result of this <tt>Action</tt> (using either a
forward or a redirect).
*/
ResponsePage execute() throws AppException;
}
implementations perform similar tasks. These tasks should be placed in an Abstract Base Class
(ABC), which will simplify concrete implementations. The WEB4J tool defines several such ABC's:
Action
The various ActionTemplateXXX classes implement the template method design pattern. The application
programmer chooses a particular template based in the desired style of user interface.
Here's an example of a concrete Action, which subclasses the ActionTemplateShowAndApply class
mentioned above. It provides implementations for three methods: show , validateUserInput, and apply .
package hirondelle.fish.main.discussion;
import
import
import
import
import
import
import
import
import
import
import
hirondelle.web4j.BuildImpl;
hirondelle.web4j.action.ActionTemplateShowAndApply;
hirondelle.web4j.action.ResponsePage;
hirondelle.web4j.database.DAOException;
hirondelle.web4j.database.Db;
hirondelle.web4j.database.SqlId;
hirondelle.web4j.model.AppException;
hirondelle.web4j.model.ModelCtorException;
hirondelle.web4j.model.ModelFromRequest;
hirondelle.web4j.request.RequestParameter;
hirondelle.web4j.request.RequestParser;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
284
/**
List comments, and add new ones.
<P>Comments are listed, along with a paging mechanism.
@sql statements.sql
@view view.jsp
*/
public final class CommentAction extends ActionTemplateShowAndApply {
public static final SqlId FETCH_RECENT_COMMENTS = new
SqlId("FETCH_RECENT_COMMENTS");
public static final SqlId ADD_COMMENT = new SqlId("ADD_COMMENT");
/** Constructor. */
public CommentAction(RequestParser aRequestParser){
super(FORWARD, REDIRECT, aRequestParser);
}
public static final RequestParameter COMMENT_BODY = RequestParameter.withLengthCheck(
"Comment Body"
);
/** Used for the paging mechanism. */
public static final RequestParameter PAGE_SIZE = RequestParameter.withRegexCheck(
"PageSize", Pattern.compile("(\\d){1,4}")
);
/** Used for the paging mechanism. */
public static final RequestParameter PAGE_INDEX = RequestParameter.withRegexCheck(
"PageIndex", Pattern.compile("(\\d){1,4}")
);
/** Show the listing of comments, and a form for adding new messages. */
protected void show() throws AppException {
addToRequest(ITEMS_FOR_LISTING, fetchRecentComments());
}
/** Ensure user input can build a new {@link Comment}. */
protected void validateUserInput() throws AppException {
try {
ModelFromRequest builder = new ModelFromRequest(getRequestParser());
/*
This is an example of using a time which, for testing purposes,
can be made independent of the true system time. The value of
the 'now' variable depends on the implementation of TimeSource.
*/
long now = BuildImpl.forTimeSource().currentTimeMillis();
fComment = builder.build(
Comment.class, getLoggedInUserName(), COMMENT_BODY, new Date(now)
);
}
catch (ModelCtorException ex){
addError(ex);
}
}
/** Add a new {@link Comment} to the database. */
protected void apply() throws AppException {
//no possibility of a duplicate error.
addNew(fComment);
}
// PRIVATE //
private Comment fComment;
private static final ResponsePage FORWARD = new ResponsePage(
"Discussion", "view.jsp", CommentAction.class
);
private static final ResponsePage REDIRECT = new ResponsePage(
"CommentAction.show?PageIndex=1&PageSize=10"
);
/*
Here, the DAO methods are not in a separate class, but simply regular methods of
this Action. This reasonable since the methods are short, and they do not make
this Action class significantly more complex.
*/
285
/**
Return an immutable {@link List} of recent {@link Comment} objects.
<P>The definition of what constitutes "recent" is left deliberately vague, to
allow various versions of "recent" - last 5 messages, messages entered over
the last N days, etc.
*/
private List<Comment> fetchRecentComments() throws DAOException {
return Db.list(Comment.class, FETCH_RECENT_COMMENTS);
}
/**
Add <tt>aNewComment</tt> to the database. No duplicates are possible.
@param aNewComment to be added to the datastore.
*/
private void addNew(Comment aNewComment) throws DAOException {
Object[] params = {
aNewComment.getUserName(), aNewComment.getBody(), aNewComment.getDate()
};
Db.edit(ADD_COMMENT, params);
}
}
See Also :
Refactor large Controllers
Parse parameters into domain objects
Template method
Forward versus redirect
A Web App Framework - WEB4J
Factory methods
Factory methods are static methods that return an instance of the native class. Examples in the JDK:
LogManager.getLogManager
Pattern. compile
Collections.unmodifiableCollection, Collections.synchronizeCollection , and so on
Calendar.getInstance
Factory methods:
have names, unlike constructors, which can clarify code.
do not need to create a new object upon each invocation - objects can be cached and reused, if
necessary.
can return a subtype of their return type - in particular, can return an object whose implementation
class is unknown to the caller. This is a very valuable and widely used feature in many frameworks
which use interfaces as the return type of static factory methods.
Common names for factory methods include getInstance and valueOf. These names are not mandatory
- choose whatever makes sense for each case.
Example
public final class ComplexNumber {
286
/**
* Static factory method returns an object of this class.
*/
public static ComplexNumber valueOf(float aReal, float aImaginary) {
return new ComplexNumber(aReal, aImaginary);
}
/**
* Caller cannot see this private constructor.
*
* The only way to build a ComplexNumber is by calling the static
* factory method.
*/
private ComplexNumber(float aReal, float aImaginary) {
fReal = aReal;
fImaginary = aImaginary;
}
private float fReal;
private float fImaginary;
//..elided
}
See Also :
Immutable objects
Private constructor
Implementing toString
Data access objects
Avoid clone
Abstract Factory
Immutable objects
Immutable objects are simply objects whose state (the object's data) cannot change after construction.
Examples of immutable objects from the JDK include String and Integer.
Immutable objects greatly simplify your program, since they:
are simple to construct, test, and use
are automatically thread-safe and have no synchronization issues
don't need a copy constructor
don't need an implementation of clone
allow hashCode to use lazy initialization, and to cache its return value
don't need to be copied defensively when used as a field
make good Map keys and Set elements (these objects must not change state while in the collection)
have their class invariant established once upon construction, and it never needs to be checked
again
always have "failure atomicity" (a term used by Joshua Bloch): if an immutable object throws an
exception, it's never left in an undesirable or indeterminate state
Immutable objects have a very compelling list of positive qualities. Without question, they are among the
simplest and most robust kinds of classes you can possibly build. When you create immutable classes,
entire categories of problems simply disappear.
287
//
//
//
//
*/
public Date getDateOfDiscovery() {
return fDateOfDiscovery;
}
/**
* Returns a mutable object - good style.
*
* Returns a defensive copy of the field.
* The caller of this method can do anything they want with the
* returned Date object, without affecting the internals of this
* class in any way. Why? Because they do not have a reference to
* fDate. Rather, they are playing with a second Date that initially has the
* same data as fDate.
*/
public Date getDateOfDiscovery() {
return new Date(fDateOfDiscovery.getTime());
}
// PRIVATE
/**
* Final primitive data is always immutable.
*/
private final double fMass;
/**
* An immutable object field. (String objects never change state.)
*/
private final String fName;
/**
* A mutable object field. In this case, the state of this mutable field
* is to be changed only by this class. (In other cases, it makes perfect
* sense to allow the state of a field to be changed outside the native
* class; this is the case when a field acts as a "pointer" to an object
* created elsewhere.)
*/
private final Date fDateOfDiscovery;
Note that javadoc 1.4 includes the -tag option, whereby simple custom tags may be defined. One might
define an @is.Immutable tag, for example, to document a class as being immutable.
You might also consider defining your own tag interface for immutable objects.
See Also :
Validate state with class invariants
Copy constructors
Defensive copying
Factory methods
Use final liberally
Implementing hashCode
Lazy initialization
Document thread safety
Avoid JavaBeans style of construction
Model Objects
Tag or marker interfaces
Lazy initialization
289
Lazy initialization is a performance optimization. It's used when data is deemed to be 'expensive' for some
reason. For example:
if the hashCode value for an object might not actually be needed by its caller, always calculating
the hashCode for all instances of the object may be felt to be unnecessary.
since accessing a file system or network is relatively slow, such operations should be put off until
they are absolutely required.
Lazy initialization has two objectives:
delay an expensive operation until it's absolutely necessary
store the result of that expensive operation, such that you won't need to repeat it again
As usual, the size of any performance gain, if any, is highly dependent on the problem, and in many cases
may not be significant. As with any optimization, this technique should be used only if there is a clear
and significant benefit.
To avoid a NullPointerException , a class must self-encapsulate fields that have lazy initialization. That
is, a class cannot refer directly to such fields, but must access them through a method.
The hashCode method of an immutable Model Object is a common candidate for lazy initialization.
Example 1
In this example, there are two fields with lazy initialization - fHashCode and fAwards.
import java.util.*;
public final class Athlete {
public Athlete(int aId){
//a toy implementation:
fId = aId;
fName = "Roger Bannister";
//fAwards is not set here!
}
//..elided
/**
Lazy initialization is used here; this assumes that awards
may not always be of interest to the caller,
and that for some reason it is particularly expensive to
fetch the List of Awards.
*/
public List<String> getAwards(){
if (fAwards == null) {
//the fAwards field has not yet been populated
//Here is a toy implementation
List<String> awards = new ArrayList<>();
awards.add("Gold Medal 2006");
awards.add("Bronze Medal 1998");
fAwards = awards;
}
return fAwards;
}
/**
This style applies only if the object is immutable.
Another alternative is to calculate the hashCode once, when the
object is initially constructed (again, applies only when object is
immutable).
*/
@Override public int hashCode(){
if (fHashCode == 0) {
fHashCode = HashCodeUtil.SEED;
fHashCode = HashCodeUtil.hash(fHashCode, fId);
290
Example 2
Here, the look up of the printers available to a desktop PC is treated as an expensive operation.
import java.util.Arrays;
import java.util.List;
import
import
import
import
import
import
import
javax.print.DocFlavor;
javax.print.PrintService;
javax.print.PrintServiceLookup;
javax.print.attribute.HashPrintRequestAttributeSet;
javax.print.attribute.PrintRequestAttributeSet;
javax.print.attribute.standard.OrientationRequested;
javax.print.attribute.standard.Sides;
Example 3
291
Lazy initialization is particularly useful for GUIs which take a long time to construct.
There are several policies for GUI construction which a design may follow:
always build - construct the window many times, whenever it is demanded, and do not cache the
result.
first-request build - construct the window once, when first requested. Cache the result for any
further requests, should they occur.
background build - construct the window once, in a low priority worker thread, when the system is
initialized. Cache the result for any requests, should they occur.
Here is an example of the first-request style, in which the fEditor field has lazy initialization (see the
actionPerformed method).
package hirondelle.stocks.preferences;
import
import
import
import
java.awt.event.*;
javax.swing.*;
java.util.*;
java.util.logging.*;
import
import
import
import
import
hirondelle.stocks.util.Args;
hirondelle.stocks.util.ui.StandardEditor;
hirondelle.stocks.util.ui.UiUtil;
hirondelle.stocks.preferences.PreferencesEditor;
hirondelle.stocks.util.Util;
/**
* Present dialog to allow update of user preferences.
*
* <P>Related preferences are grouped together and placed in
* a single pane of a <tt>JTabbedPane</tt>, which corresponds to an
* implementation of {@link PreferencesEditor}. Values are pre-populated with
* current values for preferences.
*
*<P>Most preferences have default values. If so, a
* <tt>Restore Defaults</tt> button is provided for that set of related
* preferences.
*
*<P>Preferences are not changed until the <tt>OK</tt> button is pressed.
* Exception: the logging preferences take effect immediately, without the need
* for hitting <tt>OK</tt>.
*/
public final class EditUserPreferencesAction extends AbstractAction {
/**
* Constructor.
*
* @param aFrame parent window to which this dialog is attached.
* @param aPrefEditors contains implementations of {@link PreferencesEditor},
* each of which is placed in a pane of a <tt>JTabbedPane</tt>.
*/
public EditUserPreferencesAction (JFrame aFrame, List<PreferencesEditor>
aPrefEditors) {
super("Preferences...", UiUtil.getEmptyIcon());
Args.checkForNull(aFrame);
Args.checkForNull(aPrefEditors);
fFrame = aFrame;
putValue(SHORT_DESCRIPTION, "Update user preferences");
putValue(LONG_DESCRIPTION, "Allows user input of preferences.");
putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_P));
fPrefEditors = aPrefEditors;
}
/** Display the user preferences dialog. */
@Override public void actionPerformed(ActionEvent event) {
fLogger.info("Showing user preferences dialog.");
//lazy construction: fEditor is created only once, when this action
//is explicitly invoked
if (fEditor == null) {
fEditor = new Editor("Edit Preferences", fFrame);
}
292
fEditor.showDialog();
}
// PRIVATE
private JFrame fFrame;
private java.util.List<PreferencesEditor> fPrefEditors;
private static final Logger fLogger =
Util.getLogger(EditUserPreferencesAction.class);
/**
* Specifying this as a field allows for "lazy" creation and use of the GUI, which is
See Also :
293
Constructors in general
Implementing hashCode
Self-encapsulate fields
Standardized dialogs
Model Objects
The term Model Object is an informal term, with no widely accepted definition. Here, Model Objects
(MOs) refer to data-centric classes which encapsulate closely related items.
Model Objects:
are very common, and are used in almost all applications
are often central to an application, since they usually model problem domain objects
often map roughly to the records of a corresponding database table
are often used as return values for Data Access Object methods
are easily tested using JUnit (or a similar tool)
can be used to implement the Model in a Model-View-Controller pattern
Should Model Objects follow the JavaBeans conventions?
Not necessarily. In fact, some argue that the JavaBeans style is to be avoided as a general model for
Model Objects.
Should Model Objects be immutable?
Given the deep simplicity of immutable objects, some prefer to design their Model Objects as immutable.
However, when the underlying data changes, a new object must be created, instead of simply calling a
setXXX method on an existing object. Some argue that this penalty is too high, while others argue that it
is a micro-optimization - especially in cases where the data is "read-mostly", and the state of
corresponding Model Objects changes only rarely.
Implementing Model Objects as immutable seems particularly natural in web applications. There, Model
Objects are most commonly placed in request scope, not session scope. In this case, there is no longlived object for the user to directly alter, so the Model Object can be immutable.
Example
represents a comment posted to a message board. Its implementation follows the Immutable
Object pattern. Comment provides the usual getXXX methods. Note that, in this case, a defensive copy is
used for the Date field. It also implements the toString , equals, and hashCode methods.
Comment
The constructor is responsible for establishing the class invariant, and performs Model Object validation.
package hirondelle.fish.main.discussion;
import
import
import
import
import
import
java.util.*;
hirondelle.web4j.model.ModelCtorException;
hirondelle.web4j.model.ModelUtil;
hirondelle.web4j.model.Check;
hirondelle.web4j.security.SafeText;
static hirondelle.web4j.util.Consts.FAILS;
/**
Comment posted by a possibly-anonymous user.
*/
public final class Comment {
294
/**
Constructor.
@param aUserName identifies the logged in user posting the comment.
@param aBody the comment, must have content.
@param aDate date and time when the message was posted.
*/
public Comment (
SafeText aUserName, SafeText aBody, Date aDate
) throws ModelCtorException {
fUserName = aUserName;
fBody = aBody;
fDate = aDate.getTime();
validateState();
}
/** Return the logged in user name passed to the constructor. */
public SafeText getUserName() {
return fUserName;
}
/** Return the body of the message passed to the constructor.
public SafeText getBody() {
return fBody;
}
*/
/**
Return a <a href="http://www.javapractices.com/Topic15.cjp">defensive copy</a>
of the date passed to the constructor.
<P>The caller may change the state of the returned value, without affecting
the internals of this <tt>Comment</tt>. Such copying is needed since
a {@link Date} is a mutable object.
*/
public Date getDate() {
// the returned object is independent of fDate
return new Date(fDate);
}
/** Intended for debugging only. */
@Override public String toString() {
return ModelUtil.toStringFor(this);
}
@Override public boolean equals( Object aThat ) {
Boolean result = ModelUtil.quickEquals(this, aThat);
if ( result == null ){
Comment that = (Comment) aThat;
result = ModelUtil.equalsFor(
this.getSignificantFields(), that.getSignificantFields()
);
}
return result;
}
@Override public int hashCode() {
if ( fHashCode == 0 ) {
fHashCode = ModelUtil.hashCodeFor(getSignificantFields());
}
return fHashCode;
}
// PRIVATE //
private final SafeText fUserName;
private final SafeText fBody;
/** Long is used here instead of Date in order to ensure immutability.*/
private final long fDate;
private int fHashCode;
private Object[] getSignificantFields(){
return new Object[] {fUserName, fBody, new Date(fDate)};
}
private void validateState() throws ModelCtorException {
ModelCtorException ex = new ModelCtorException();
if( FAILS == Check.required(fUserName) ) {
ex.add("User name must have content.");
295
}
if ( FAILS == Check.required(fBody) ) {
ex.add("Comment body must have content.");
}
if ( ! ex.isEmpty() ) throw ex;
}
}
See Also :
Validate state with class invariants
Defensive copying
Immutable objects
Use a testing framework (JUnit)
Data access objects
Avoid JavaBeans style of construction
Use Model-View-Controller framework
A Web App Framework - WEB4J
Validation belongs in a Model Object
Plugin Factory
It's often useful to be able to quickly and easily switch one implementation of a given feature with
another. This is especially useful when writing unit tests for your code, but the technique isn't strictly
limited to unit tests.
A plugin factory is one way of quickly swapping implementations. The general idea is to:
define a Java interface for the methods whose implementation you want to be able to swap.
define 2 or more concrete implementations of that interface.
create a corresponding method in a plugin factory class to return one of those implementations, as
defined by some configuration setting.
Using configuration of some kind (often simply a text file), the plugin factory knows which concrete
implementation it's supposed to return to its caller.
It's important for your application to treat the Plugin Factory as the sole source for implementations of
the corresponding interfaces. That is, the rest of your app is not supposed to have direct knowledge of
the concrete implementations. The Plugin Factory is meant to keep that knowledge secret.
A plugin factory can have a number of methods defined, each returning an implementation of a specific
interface.
A recurring theme in object programming is allowing old code to call new code. A Plugin Factory is
simply another variation on that important theme.
Example
As an example, let's take the idea of a fake system clock. In this case, you want the current time to be
defined centrally, in one place. You also want to be able to swap in various ways of defining the current
296
Here's a caller that uses a concrete implementation, without knowing its underlying class:
package myapp;
/**
Use a concrete implementation of an interface, without being linked directly to the
the implementing class.
The concrete implementation is known only to the PluginFactory class.
*/
public final class UseFakeSystemClock {
public void doSomethingThatDependsOnTime(){
TimeSource timesource = PluginFactory.getTimeSource();
long currentTime = timesource.currentTimeMillis();
System.out.println("Current millisecond value: " + currentTime);
}
}
Finally, here's a sketch of the Plugin Factory itself. Note that you can add more methods to this class,
each corresponding to a different interface.
package myapp;
import java.util.LinkedHashMap;
import java.util.Map;
/** Return concrete implementations for specific, known interfaces. */
public final class PluginFactory {
/**
Read in configuration data that maps names of interfaces to names of
297
See Also :
Construct Object using class name
Use a fake system clock
Private constructor
Private constructors prevent a class from being explicitly instantiated by its callers.
There are some common cases where a private constructor can be useful:
298
Singleton
Singleton classes represent objects for which only one single instance should exist.
The important question here is this: will any actual harm come to the system if more than 1 object is
created? If the answer is "no" (and it usually is), then there's no need whatsoever to use a singleton. If
the answer is "yes", then you will need to consider it. The main point is that a singleton should only be
used if it's really necessary.
299
Here's an article from Oracle describing them. This article, along with many others, reports many subtle
problems with singletons. You should likely exercise care when using this pattern.
Example 1
This is the preferred style of implementing singletons. It uses a simple enumeration. It has no special
needs for serialization, and is immune to clever attacks.
/** Preferred style for singletons. */
public enum SantaClaus {
INSTANCE;
/**Add some behavior to the object. */
public void distributePresents(){
//elided
}
/** Demonstrate use of SantaClaus. */
public static void main(String... aArgs){
SantaClaus fatGuy = SantaClaus.INSTANCE;
fatGuy.distributePresents();
//doesn't compile :
//SantaClaus fatGuy = new SantaClaus();
}
}
Example 2
Here's an alternate style. If you decide that the class should no longer be a singleton, you may simply
change the implementation of getInstance.
public final class Universe {
public static Universe getInstance() {
return fINSTANCE;
}
// PRIVATE
/**
* Single instance created upon class loading.
*/
private static final Universe fINSTANCE = new Universe();
/**
* Private constructor prevents construction outside this class.
*/
private Universe() {
//..elided
}
}
Example 3
If the above style of singleton is to be Serializable as well, then you must add a readResolve method.
import java.io.*;
public final class EasterBunny implements Serializable {
public static EasterBunny getInstance() {
return fINSTANCE;
}
// PRIVATE
/**
* Single instance created upon class loading.
300
*/
private static final EasterBunny fINSTANCE =
new EasterBunny();
/**
* Private constructor prevents construction outside this class.
*/
private EasterBunny() {
//..elided
}
/**
* If the singleton implements Serializable, then this
* method must be supplied.
*/
private Object readResolve() throws ObjectStreamException {
return fINSTANCE;
}
}
You might also consider defining your own tag interface for singleton classes.
See Also :
Private constructor
Implementing Serializable
Some classes need readResolve
Tag or marker interfaces
Template method
Template methods:
are used in most abstract base classes
are perhaps the most commonly used of all design patterns
define the general steps of a method, while deferring the implementation of at least one of the steps
to a concrete subclass
Example
is an abstract base class which defines a template method for executing multiple database
operations within a transaction. It's useful to define these steps in one place. The alternative is to repeat
the same structure every time a transaction is required. As usual, such code repetition should always be
aggressively eliminated.
TxTemplate
The executeTx method is the template method. It's final , and defines the general outline of how to
execute a database transaction. The specific database actions to be taken are implemented by calling the
abstract method executeMultipleSqls .
import java.sql.*;
import java.util.logging.*;
import hirondelle.web4j.BuildImpl;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.util.Consts;
/**
* Template for executing a local, non-distributed transaction versus a
* single database, using a single connection.
*
301
* <P>This abstract base class implements the template method design pattern.
*/
public abstract class TxTemplate implements Tx {
//..elided
/**
* <b>Template</b> method calls the abstract method {@link #executeMultipleSqls}.
* <P>Returns the same value as <tt>executeMultipleSqls</tt>.
*
* <P>A <tt>rollback</tt> is performed if <tt>executeMultipleSqls</tt> fails.
*/
public final int executeTx() throws DAOException {
int result = 0;
fLogger.fine(
"Editing within a local transaction, with isolation level : " +
fTxIsolationLevel
);
ConnectionSource connSource = BuildImpl.forConnectionSource();
if(Util.textHasContent(fDatabaseName)){
fConnection = connSource.getConnection(fDatabaseName);
}
else {
fConnection = connSource.getConnection();
}
try {
TxIsolationLevel.set(fTxIsolationLevel, fConnection);
startTx();
result = executeMultipleSqls(fConnection);
endTx(result);
}
catch(SQLException rootCause){
fLogger.fine("Transaction throws SQLException.");
rollbackTx();
String message =
"Cannot execute edit. ErrorId code : " + rootCause.getErrorCode() +
Consts.SPACE + rootCause
;
if (rootCause.getErrorCode() ==
DbConfig.getErrorCodeForDuplicateKey().intValue()){
throw new DuplicateException(message, rootCause);
}
throw new DAOException(message, rootCause);
}
catch (DAOException ex){
fLogger.fine("Transaction throws DAOException.");
rollbackTx();
throw ex;
}
finally {
DbUtil.logWarnings(fConnection);
DbUtil.close(fConnection);
}
fLogger.fine("Total number of edited records: " + result);
return result;
}
/**
* Execute multiple SQL operations in a single local transaction.
*
* <P>This method returns the number of records edited.
*/
public abstract int executeMultipleSqls(
Connection aConnection
) throws SQLException, DAOException;
// PRIVATE
private Connection fConnection;
private String fDatabaseName;
private final TxIsolationLevel fTxIsolationLevel;
private static final boolean fOFF = false;
private static final boolean fON = true;
private static final Logger fLogger = Util.getLogger(TxTemplate.class);
private void startTx() throws SQLException {
302
fConnection.setAutoCommit(fOFF);
}
private void endTx(int aNumEdits) throws SQLException, DAOException {
if ( BUSINESS_RULE_FAILURE == aNumEdits ) {
fLogger.severe("Business rule failure occured. Cannot commit transaction.");
rollbackTx();
}
else {
fLogger.fine("Commiting transaction.");
fConnection.commit();
fConnection.setAutoCommit(fON);
}
}
private void rollbackTx() throws DAOException {
fLogger.severe("ROLLING BACK TRANSACTION.");
try {
fConnection.rollback();
}
catch(SQLException ex){
throw new DAOException("Cannot rollback transaction", ex);
}
}
}
Here's an example of using TxTemplate . It alters the set of roles attached to an end user, first by deleting
all existing roles, and then by adding the new roles one at a time.
final class RoleDAO {
//..elided
/**
* Update all roles attached to a user.
*
* <P>This implementation will treat all edits to user roles as
* '<tt>DELETE-ALL</tt>, then <tt>ADD-ALL</tt>' operations.
*/
boolean change(UserRole aUserRole) throws DAOException {
Tx update = new UpdateTransaction(aUserRole);
return Util.isSuccess(update.executeTx());
}
// PRIVATE //
/** Cannot be a {@link hirondelle.web4j.database.TxSimple}, since there is looping.
*/
private static final class UpdateTransaction extends TxTemplate {
UpdateTransaction(UserRole aUserRole){
super(ConnectionSrc.ACCESS_CONTROL);
fUserRole = aUserRole;
}
public int executeMultipleSqls(
Connection aConnection
) throws SQLException, DAOException {
int result = 0;
//perform edits using a shared connection
result = result + DbTx.edit(aConnection, ROLES_DELETE, fUserRole.getUserName());
for(Id roleId : fUserRole.getRoles()){
result = result +
DbTx.edit(aConnection,ROLES_ADD,fUserRole.getUserName(),roleId);
}
return result;
}
private UserRole fUserRole;
}
}
303
See Also :
Consider composition instead of subclassing
Command objects
Wrapper (Decorator)
Type-Safe Enumerations
Enumerations are sets of closely related items, for example:
cardinal directions - north, south, east, west
types of novels - mystery, classic, fantasy, romance, science-fiction
flavours of ice cream - chocolate, vanilla, raspberry, maple
Type-safe enumerations (also called "enum types", or simply "enums") were added to the Java language
in JDK 1.5, and represent a special kind of class. If JDK 1.5 is not available, type-safe enumerations can
still be implemented as a regular Java class.
Type-safe enumerations should be used liberally. In particular, they are a robust alternative to the simple
String or int constants used in many older APIs to represent sets of related items.
Reminders:
enums are implicitly final subclasses of java.lang.Enum
if an enum is a member of a class, it's implicitly static
new can never be used with an enum, even within the enum type itself
name and valueOf simply use the text of the enum constants, while toString may be overridden to
provide any content, if desired
for enum constants, equals and == amount to the same thing, and can be used interchangeably
enum constants are implicitly public static final
the order of appearance of enum constants is called their "natural order", and defines the order used
by other items as well : compareTo , iteration order of values , EnumSet, EnumSet.range .
enums have a built-in serialization mechanism, which can't be overridden. The mechanism uses the
name and valueOf methods.
Warning:
As with any class, it's easy to provide methods in an enum type which change the state of an enum
constant. Thus, the term "enum constant" is rather misleading. What is constant is the identity of the
enum element, not its state. Perhaps a better term would have been "enum element" instead of "enum
constant".
Constructors for an enum type should be declared as private. The compiler allows non private declares
for constructors, but this seems misleading to the reader, since new can never be used with enum types.
Here are some simple examples of defining and using enums:
import java.util.EnumSet;
public final class EnumExamples {
public static final void main(String... aArgs){
log("Exercising enumerations...");
304
exerEnumsMiscellaneous();
exerMutableEnum();
exerEnumRange();
exerBitFlags();
exerEnumToStringAndValueOf();
log("Done.");
}
// PRIVATE //
private static void log(Object aText){
System.out.println(String.valueOf(aText));
}
/** Example 1 - simple list of enum constants. */
enum Quark {
/*
* These are called "enum constants".
* An enum type has no instances other than those defined by its
* enum constants. They are implicitly "public static final".
* Each enum constant corresponds to a call to a constructor.
* When no args follow an enum constant, then the no-argument constructor
* is used to create the corresponding object.
*/
UP,
DOWN,
CHARM,
STRANGE,
BOTTOM,
TOP
}
//does not compile, since Quark is "implicitly final":
//private static class Quarky extends Quark {}
/**
* Example 2 - adding a constructor to an enum.
*
* If no constructor is added, then the usual default constructor
* is created by the system, and declarations of the
* enum constants will correspond to calling this default constructor.
*/
public enum Lepton {
//each constant implicity calls a constructor :
ELECTRON(-1, 1.0E-31),
NEUTRINO(0, 0.0);
/*
* This constructor is private.
* Legal to declare a non-private constructor, but not legal
* to use such a constructor outside the enum.
* Can never use "new" with any enum, even inside the enum
* class itself.
*/
private Lepton(int aCharge, double aMass){
//cannot call super ctor here
//calls to "this" ctors allowed
fCharge = aCharge;
fMass = aMass;
}
final int getCharge() {
return fCharge;
}
final double getMass() {
return fMass;
}
private final int fCharge;
private final double fMass;
}
/**
* Example 3 - adding methods to an enum.
*
* Here, "static" may be left out, since enum types which are class
* members are implicitly static.
*/
static enum Direction {
NORTH,
SOUTH,
EAST,
WEST; //note semicolon needed only when extending behavior
305
log("East is cold");
}
else {
log("East is not cold.");
}
log("Electron charge : " + Lepton.ELECTRON.getCharge());
//parsing text into an enum constant :
Lepton lepton = Enum.valueOf(Lepton.class, "ELECTRON");
log("Lepton mass : " + lepton.getMass());
//throws IllegalArgumentException if text is not known to enum type :
try {
Lepton anotherLepton = Enum.valueOf(Lepton.class, "Proton");
}
catch (IllegalArgumentException ex){
log("Proton is not a Lepton.");
}
//More compact style for parsing text:
Lepton thirdLepton = Lepton.valueOf("NEUTRINO");
log("Neutrino charge : " + thirdLepton.getCharge() );
}
private static void exerMutableEnum(){
Flavor.VANILLA.setCalories(75); //change the state of the enum "constant"
log("Calories in Vanilla: " + Flavor.VANILLA.getCalories());
}
private static void exerEnumRange(){
for (Direction direction : EnumSet.range(Direction.NORTH, Direction.SOUTH)){
log("NORTH-SOUTH: " + direction);
}
}
private static void exerBitFlags(){
EnumSet<Direction> directions = EnumSet.of(Direction.EAST, Direction.NORTH);
for(Direction direction : directions) {
log(direction);
}
}
/**
* The valueOf method uses name(), not toString(). There is no need
* to synchronize valueOf with toString.
*/
private static void exerEnumToStringAndValueOf(){
Direction dir = Direction.valueOf("EAST"); //successful
log("Direction toString : " + dir);
dir = Direction.valueOf("Direction: EAST"); //fails
}
}
return result;
}
private static void log(Object aMessage){
System.out.println(String.valueOf(aMessage));
}
}
VerySimpleSuit
VerySimpleSuit
VerySimpleSuit
VerySimpleSuit
/**
* Private constructor prevents construction outside of this class.
*/
private VerySimpleSuit() {
//empty
}
}
Example 2
is another simple style of implementing a type-safe enumeration, which includes an
implementation of toString :
SimpleSuit
308
/**
* Enumeration
*/
public static
public static
public static
public static
SimpleSuit
SimpleSuit
SimpleSuit
SimpleSuit
Example 3
Suit
import java.util.*;
public final class Suit implements Comparable {
/**
* Enumeration elements are constructed once upon class loading.
* Order of appearance here determines the order of compareTo.
*/
public static final Suit CLUBS = new Suit ("Clubs");
public static final Suit DIAMONDS = new Suit ("Diamonds");
public static final Suit HEARTS = new Suit ("Hearts");
public static final Suit SPADES = new Suit ("Spades");
public String toString() {
return fName;
}
/**
* Parse text into an element of this enumeration.
*
* @param aText takes one of the values 'Clubs',
* 'Diamonds', 'Hearts', 'Spades'.
*/
public static Suit valueOf(String aText){
Iterator iter = VALUES.iterator();
while (iter.hasNext()) {
Suit suit = (Suit)iter.next();
if ( aText.equals(suit.toString()) ){
return suit;
}
}
//this method is unusual in that IllegalArgumentException is
//possibly thrown not at its beginning, but at its end.
throw new IllegalArgumentException(
"Cannot parse into an element of Suit : '" + aText + "'"
);
}
309
Example 4
The AccountType enumeration:
exports avalueOf method for parsing text into an enumeration element
exports a List of VALUES
implements Comparable
implements Serializable
/**
* The only element which is serialized is an ordinal identifier. Thus,
* any enumeration values added in the future must be constructed AFTER the
* already existing objects, otherwise serialization will be broken.
*/
import java.io.*;
import java.util.*;
public class AccountType implements Serializable, Comparable {
public static final AccountType CASH = new AccountType("Cash");
public static final AccountType MARGIN = new AccountType("Margin");
public static final AccountType RSP = new AccountType("RSP");
//FUTURE VALUES MUST BE CONSTRUCTED HERE, AFTER ALL THE OTHERS
public String toString() {
return fName;
}
/**
* Parse text into an element of this enumeration.
*
* @param takes one of the values 'Cash', 'Margin', 'RSP'.
*/
public static AccountType valueOf(String aText){
Iterator iter = VALUES.iterator();
while (iter.hasNext()){
AccountType account = (AccountType)iter.next();
if ( aText.equals(account.toString()) ){
return account;
}
}
throw new IllegalArgumentException(
"Cannot be parsed into an enum element : '" + aText + "'"
);
310
}
public int compareTo(Object aObject) {
return fOrdinal - ((AccountType)aObject).fOrdinal;
}
// PRIVATE
private transient final String fName;
private static int fNextOrdinal = 1;
private final int fOrdinal = fNextOrdinal++;
private AccountType (String aName) {
fName = aName;
}
//export VALUES with these two items
private static final AccountType[] fValues = {CASH, MARGIN, RSP};
public static final List VALUES =
Collections.unmodifiableList(Arrays.asList(fValues));
//Implement Serializable with these two items
private Object readResolve() throws ObjectStreamException {
return fValues[fOrdinal];
}
private static final long serialVersionUID = 64616131365L;
}
See Also :
Use enums to restrict arguments
Modernize old code
Wrapper (Decorator)
Wrapper (or Decorator) is one of the most important design patterns.
Wrappers are commonly used in:
input-output stream APIs
servlet filters
GUI toolkits (when adding scrollbars or other decorations to components, for example)
The basic idea of a wrapper is to call-forward to an underlying object, while simultaneously allowing for
new code to be executed just before and/or just after the call. Wrappers can be chained together, one
after another. In this way, you can mix-and-match behaviors in various ways.
Note that this changes the behavior of an object, without needing to alter the implementation of a class,
and also without needing to extend a class.
Here's an example implementation of a wrapper design pattern (other variations are also possible):
public interface TransformText {
String render(String aInputText);
}
312
See Also :
Template method
See Also :
Modernize old code
Many hold that the instanceof operator should be used only as a last resort, and that an overridden
method is usually (but not always) a better alternative.
The instanceof operator can be used to call a method based explicitly on the class of some object,
instead of implicitly using an overridden method and polymorphism. Thus, inexperienced programmers
may mistakenly use instanceof where an overidden method is more appropriate.
A common exception to this guideline, however, is the use of instanceof within an equals method.
From Effective C++, by Scott Meyers :
"Anytime you find yourself writing code of the form "if the object is of type T1, then do something, but if
it's of type T2, then do something else," slap yourself.
Here is an example of the type of abuse Scott Meyers is speaking of:
/**
* Naive, incorrect use of instanceof.
*/
public final class BadInstanceOf {
public static void doSomething(Animal aAnimal){
if (aAnimal instanceof Fish){
Fish fish = (Fish)aAnimal;
fish.swim();
}
else if (aAnimal instanceof Spider){
Spider spider = (Spider)aAnimal;
spider.crawl();
}
}
// PRIVATE
private static class Animal {}
private static final class Fish extends Animal {
void swim(){}
}
private static final class Spider extends Animal {
void crawl(){}
}
}
See Also :
Implementing equals
Avoid basic style errors
- <tt>line.separator</tt>*/
String NEW_LINE = System.getProperty("line.separator");
- <tt>file.separator</tt>*/
String FILE_SEPARATOR = System.getProperty("file.separator");
- <tt>path.separator</tt>*/
String PATH_SEPARATOR = System.getProperty("path.separator");
public
public
public
public
public
public
String
String
String
String
String
String
static
static
static
static
static
static
final
final
final
final
final
final
EMPTY_STRING = "";
SPACE = " ";
TAB = "\t";
SINGLE_QUOTE = "'";
PERIOD = ".";
DOUBLE_QUOTE = "\"";
// PRIVATE //
/**
The caller references the constants using <tt>Consts.EMPTY_STRING</tt>,
and so on. Thus, the caller should be prevented from constructing objects of
this class, by declaring this private constructor.
*/
private Consts(){
//this prevents even the native class from
//calling this ctor as well :
throw new AssertionError();
}
}
The caller refers to these constants using static references, such as Consts.EMPTY_STRING ,
Consts.SUCCESS , and so on. Since the caller will never need to create a Consts object, a private
constructor is used to enforce that policy. The members of such a constants class are excellent candidates
for a static import.
It's also possible to use an interface to bring constants into scope. Many argue strongly that such a
practice is a strange use of interfaces, which are intended to state the services provided by a class.
See Also :
Interface for constants
Private constructor
Use static imports rarely
For web apps, CSS media queries can help you adapt to a wide range of screen sizes.
don't hard-code colors using numeric values - use the symbolic constants in java.awt.Color and
java.awt.SystemColor instead.
don't hard-code text sizes.
use the System.getProperty(String) method to refer to items which depend on the system, such
as line terminators and path separators.
Common System.getProperty(String) items can be placed in a general purpose constants class:
/**
* Collected constants of very general utility.
*
* All constants must be immutable.
* No instances of this class can be constructed.
*/
public final class Consts {
/**
* Prevent object construction outside of this class.
*/
private Consts(){
//empty
}
/**
* Only refer to primitives and immutable objects.
*
* Arrays present a problem since arrays are always mutable.
* DO NOT USE public static final array fields.
* One style is to instead use an unmodifiable List, built in a
* static initializer block.
*
* Another style is to use a private array and wrap it up like so:
* <pre>
* private static final Vehicle[] PRIVATE_VEHICLES = {...};
* public static final List VEHICLES =
*
Collections.unmodifiableList(Arrays.asList(PRIVATE_VEHICLES));
* </pre>
*/
//characters
public static final String NEW_LINE = System.getProperty("line.separator");
public static final String FILE_SEPARATOR = System.getProperty("file.separator");
public static final String PATH_SEPARATOR = System.getProperty("path.separator");
public
public
public
public
static
static
static
static
final
final
final
final
//algebraic signs
public static final
public static final
public static final
public static final
String
String
String
String
EMPTY_STRING = "";
SPACE = " ";
PERIOD = ".";
TAB = "\t";
int POSITIVE = 1;
int NEGATIVE = -1;
String PLUS_SIGN = "+";
String NEGATIVE_SIGN = "-";
See Also :
Class for constants
Thread priorities are not portable
Most local variables are declared and initialized on the same line, at the point in the method where both
its initial value is available and the variable itself is immediately useful.
Declaring local variables without using them immediately may unnecessarily increase their scope. This
decreases legibility, and increases the likelihood of error.
There are two common cases where a local variable is assigned some default initial value (typically
null, 0, false, or an empty String):
variables which need to be visible outside of a try block, and are thus declared and initialized just
before the try block (in modern code using try-with-resources, this is now relatively rare)
some loop variables, which are initialized to some default value just before the loop
Example
Here, input and output are examples of local variables being initialized to null , since they need to be
visible in both the try and finally blocks.
As well, line is an example of a loop variable declared and initialized outside the loop.
Note that this example uses JDK 6, simply to illustrate the point. In JDK 7, try-with-resources would be
used to automatically close streams, and the issue of stream references being initialized to null would not
occur.
import java.io.*;
/** JDK 6 or before. */
public class ReadWriteTextFile {
/**
* Fetch the entire contents of a text file, and return it in a String.
* This style of implementation does not throw Exceptions to the caller.
*
* @param aFile is a file which already exists and can be read.
*/
static public String getContents(File aFile) {
//...checks on aFile are elided
StringBuilder contents = new StringBuilder();
try {
//use buffering, reading one line at a time
//FileReader always assumes default encoding is OK!
BufferedReader input = new BufferedReader(new FileReader(aFile));
try {
String line = null; //not declared within while loop
/*
* readLine is a bit quirky :
* it returns the content of a line MINUS the newline.
* it returns null only for the END of the stream.
* it returns an empty String if two newlines appear in a row.
*/
while (( line = input.readLine()) != null){
contents.append(line);
contents.append(System.getProperty("line.separator"));
}
}
finally {
input.close();
}
}
catch (IOException ex){
ex.printStackTrace();
}
return contents.toString();
}
320
/**
* Change the contents of text file in its entirety, overwriting any
* existing text.
*
* This style of implementation throws all exceptions to the caller.
*
* @param aFile is an existing file which can be written to.
* @throws IllegalArgumentException if param does not comply.
* @throws FileNotFoundException if the file does not exist.
* @throws IOException if problem encountered during write.
*/
static public void setContents(File aFile, String aContents)
throws FileNotFoundException, IOException {
if (aFile == null) {
throw new IllegalArgumentException("File should not be null.");
}
if (!aFile.exists()) {
throw new FileNotFoundException ("File does not exist: " + aFile);
}
if (!aFile.isFile()) {
throw new IllegalArgumentException("Should not be a directory: " + aFile);
}
if (!aFile.canWrite()) {
throw new IllegalArgumentException("File cannot be written: " + aFile);
}
//use buffering
Writer output = new BufferedWriter(new FileWriter(aFile));
try {
//FileWriter always assumes default encoding is OK!
output.write( aContents );
}
finally {
output.close();
}
}
/** Simple test harness.
*/
public static void main (String... aArguments) throws IOException {
File testFile = new File("C:\\Temp\\blah.txt");
System.out.println("Original file contents: " + getContents(testFile));
setContents(testFile, "The content of this file has been overwritten...");
System.out.println("New file contents: " + getContents(testFile));
}
}
See Also :
Initializing fields to 0-false-null is redundant
object fields are 'hot spots' in your code, and need special care.
It's only the last case - the mutable object private field - where inappropriate direct access to a private
field is possible. Here's an illustration:
Example 1
import java.util.Date;
/**
* Planet is an immutable class, since there is no way to change
* its state after construction.
*/
public final class Planet {
public Planet (double aMass, String aName, Date aDateOfDiscovery) {
fMass = aMass;
fName = aName;
//make a private copy of aDateOfDiscovery
//this is the only way to keep the fDateOfDiscovery
//field private, and shields this class from any changes that
//the caller may make to the original aDateOfDiscovery object
fDateOfDiscovery = new Date(aDateOfDiscovery.getTime());
}
/**
* Returns a primitive value.
*
* The caller can do whatever they want with the return value, without
* affecting the internals of this class. Why? Because this is a primitive
* value. The caller sees its "own" double that simply has the
* same value as fMass.
*/
public double getMass() {
return fMass;
}
/**
* Returns an immutable object.
*
* The caller gets a direct reference to the internal field. But this is not
* dangerous, since String is immutable and cannot be changed.
*/
public String getName() {
return fName;
}
// /**
// * Returns a mutable object - likely bad style.
// *
// * The caller gets a direct reference to the internal field. This is usually
dangerous,
// * since the Date object state can be changed both by this class and its caller.
// * That is, this class is no longer in complete control of fDate.
// */
// public Date getDateOfDiscovery() {
//
return fDateOfDiscovery;
// }
/**
* Returns a mutable object - good style.
*
* Returns a defensive copy of the field.
* The caller of this method can do anything they want with the
* returned Date object, without affecting the internals of this
* class in any way. Why? Because they do not have a reference to
* fDate. Rather, they are playing with a second Date that initially has the
* same data as fDate.
*/
public Date getDateOfDiscovery() {
return new Date(fDateOfDiscovery.getTime());
}
// PRIVATE
/**
* Final primitive data is always immutable.
322
*/
private final double fMass;
/**
* An immutable object field. (String objects never change state.)
*/
private final String fName;
/**
* A mutable object field. In this case, the state of this mutable field
* is to be changed only by this class. (In other cases, it makes perfect
* sense to allow the state of a field to be changed outside the native
* class; this is the case when a field acts as a "pointer" to an object
* created elsewhere.)
*/
private final Date fDateOfDiscovery;
}
The above class uses a defensive copy as part of its design, but there are cases in which defensive copies
aren't desired. The point is that for mutable fields, you need to know the difference, and make the
appropriate choice.
Example 2
This is a counter-example.
A common exception to this rule is that primitive constants and immutable objects can be declared as
public static final fields. For example, see class for constants.
See Also :
Defensive copying
Immutable objects
Minimize ripple effects
See Also :
Type-Safe Enumerations
Class for constants
Use static imports rarely
324
type inference
try-with-resources
java.nio.file - new ways to interact with the file system
multi-catch - catching multiple exceptions at once
JavaFX
binary literals
The Java 6 release includes:
scripting
the JConsole monitoring tool
various Swing improvements
The Java 5 release includes:
generics
an enhanced for loop
enums
autoboxing
and annotations
The Java 1.4 release includes:
regular expressions
assertions
logging services
See Also :
Modernize old code
325
"This so greatly reduces implementation dependencies between subsystems that it leads to the following
principle of reusable object-oriented design :
Program to an interface, not an implementation.
Don't declare variables to be instances of particular concrete classes. Instead, commit only to an
interface defined by an abstract class. You will find this to be a common theme of the design patterns in
this book."
(They state a second general principle as well: "Favor object composition over class inheritance.")
See Also :
Fields should usually be private
Use interface references to Collections
Reading and writing text files
Data access objects
Consider composition instead of subclassing
Abstract Factory
Parse parameters into domain objects
Naming conventions
It's common to use a variable naming convention to distinguish between fields, arguments, and local
variables.
Within the body of a method, all of these types of variables can appear. Many find naming conventions to
be a helpful aid in getting a rapid understanding of a method's implementation. These conventions have
only one purpose: to pour understanding into the brain of the reader as quickly as possible.
To understand a method, you need to understand its data. An important part of understanding data is
understanding where it's defined - as a field, argument, or local variable. Naming conventions which
distinguish these cases usually allow the reader to understand an implementation more quickly, since they
no longer need to scan the class to determine where items are defined.
Some different styles are:
field: fBlah, _blah, blah_, this.blah, m_blah, myBlah
argument: aBlahBlah, pBlah
local variable: blahBlah
constant: BLAH_BLAH
As well, some use a naming convention for type names, to distinguish between interfaces and regular
classes:
interface: IBlahBlah
class: BlahBlah, CBlahBlah
Note that this 'I' and 'C' convention is fundamentally different from the other conventions mentioned
above. This convention is visible to the caller, while the other naming conventions, being confined to the
implementation, are not.
326
import
import java.util.TimerTask;
public final class MakeMovie {
/**
Static, since has no need of being attached to a parent instance.
*/
static final class Actor {
Actor(String aName){
fName = aName;
}
String getName(){
return fName;
}
private String fName;
}
/**
Non-static (inner) class, since casting will need to know about the
movie's data - its name, director, and budget.
*/
final class Casting {
void chooseFrom(List<Actor> aActors){
//can reference the parent's data directly:
if(fDirector.equals("Stanley Kubrick")){
//elided...
}
}
List<Actor> getSelectedCast(){
return fSelectedCast;
}
//static void doThis(){} //does not compile, since static
private List<Actor> fSelectedCast;
}
void wrapParty(){
Timer timer = new Timer();
//anonymous class - the implementation of TimerTask
//this is usually how anonymous classes are used - to define a single method
timer.schedule(
new TimerTask(){
@Override public void run() {
//elided...
};
},
new Date()
);
}
void shootScene(final int aSceneNumber){
//local class - this style seems to be rather rare
class Camera {
void shoot(){
//won't compile unless aSceneNumber is final:
System.out.println(aSceneNumber);
}
}
Camera camera = new Camera();
camera.shoot();
}
//elided...
private String fName;
private String fDirector;
private BigDecimal fBudget;
}
Output parameters
328
A method may occasionally need to use a parameter for its return value - what might be loosely called an
"output parameter" or a "result parameter". The caller creates an output parameter object, and then passes
it to a method which changes the state of the object (its data). When the method returns, the caller then
examines this new state.
Output parameters:
must be visible in both a method and its caller
cannot be null , since the object needs to be created by the caller
cannot be immutable objects - a String, for example, cannot be used as an output parameter, since
it cannot change state
should probably be used only occasionally, and after careful consideration
Example
Here, there's both an output parameter ( aCookieValue) and a boolean return value: if the output
parameter is indeed populated, then the return value is set to true .
import javax.servlet.http.*;
public final class CookieValue {
/**
* Determine if a user's request contains a particular cookie, and if it does
* then place its value in an "out" parameter.
*
* Example
* <pre>
* if ( CookieValue.getCookieValue("ID", aRequest, aIdValue) ) {
*
//use aIdValue
* }
* </pre>
*
* @param aCookieName the non-null name attached to the Cookie.
* @param aRequest is the non-null request forwarded from the user's browser which
* is to be examined for the presence of the given cookie.
* @param aCookieValue is a non-null "out" parameter in which the
* cookie's value is placed - if found - for further examination by the
* caller; it must have a zero length.
* @throws IllegalArgumentException if aCookieValue.length() > 0.
*/
static boolean getCookieValue(
String aCookieName, HttpServletRequest aRequest, final StringBuilder aCookieValue
){
if (aCookieValue.length()>0) throw new IllegalArgumentException();
boolean userHasCookie = false;
Cookie[] cookies = aRequest.getCookies();
if (cookies != null){
for(Cookie cookie : cookies){
if (aCookieName.equals(cookie.getName())){
userHasCookie = true;
//change the state of the output param (aCookieValue)
aCookieValue.append(cookie.getValue());
}
}
}
return userHasCookie;
}
}
An alternative design is to return a null value if the cookie is not actually present. This would replace
the current boolean return value with a String return value which should be checked for null .
However, that alternative design seems weaker, since it seems prudent to avoid using null return values.
329
See Also :
Immutable objects
Avoid null if possible
See Also :
Construct classes from the outside in
Avoid basic style errors
/**
* Illustrates the speed difference between + operator and
* StringBuilder.append, when performing many concatenations.
*/
public final class AvoidConcatenation {
/**
* Takes a single argument : the number of iterations to perform.
*/
public static void main (String... arguments) {
fNumIterations = Integer.parseInt(arguments[0]);
long start = System.nanoTime();
doWithConcatenationOperator();
long finish = System.nanoTime();
System.out.println("Num iterations: " + fNumIterations);
StringBuilder message = new StringBuilder();
message.append("Task using + operator: ");
message.append( finish - start );
message.append(" nanoseconds");
System.out.println(message);
start = System.nanoTime();
doWithStringBuilder();
finish = System.nanoTime();
message = new StringBuilder("Task using StringBuilder.append: ");
message.append( finish - start );
message.append(" nanoseconds");
System.out.println(message);
}
// PRIVATE
private static int fNumIterations;
private static String doWithConcatenationOperator() {
String result = "start";
331
See Also :
Implementing toString
Time execution speed
Why define an interface with no methods? Since there are no methods, a tag interface can never define
behavior, at least not in the typical sense. However, even though it has no methods, a tag interface always
carries type information. In some cases, type information itself can be used to solve a problem.
For example, Java's serialization mechanism requires an object to implement Serializable before it will
serialize it. As stated in its javadoc:
The serialization interface has no methods or fields and serves only to identify the semantics of being
serializable.
Tools will often use instanceof or reflection to inspect objects, to see if they implement a given tag
interface. But this isn't the only way a tag interface can be useful. There are some common cases for
which you might consider defining your own tag interface, even without any corresponding use of
instanceof or reflection:
immutable objects (or immutable classes, if you prefer)
singleton classes
The advantages of doing so are:
it emphasizes important, high-level aspects of a class, not otherwise expressed in code.
for both human readers and tools, it allows quick identification of classes having specific
properties.
the javadoc of the tag interface is a natural home for documenting all the characteristics of items
that implement the given interface.
332
See Also :
Immutable objects
Singleton
When importing classes that are not among the most widely used, one should probably use another style,
in which each class has an explicit import statement. If a class is unfamiliar to a reader, an explicit
import makes it easier to find related documentation.
Example
For example, if a class imports items from the Apache Commons FileUpload tool, then instead of :
import org.apache.commons.fileupload.*;
See Also :
Use static imports rarely
The main reason @Override was created was to deal with simple (but nasty) typographical errors. For
example, a method mistakenly declared as
public int hashcode(){...}
is in fact not an override - the method name has all lower case letters, so it doesn't exactly match the
name of the hashCode() method. It will however compile perfectly well. Such an error is easy to make,
and difficult to catch, which is a dangerous combination. Using the @Override annotation prevents you
from making such errors.
You should be in the habit of using @Override whenever you override a superclass method, or implement
an interface method.
See Also :
Overridable methods need special care
Modernize old code
334
Example
import java.util.*;
import java.lang.reflect.Field;
/** This class cannot be extended, since it's final. */
public final class Boat {
public Boat(final String aName, final int aLength, final Date aDateManufactured){
fName = aName;
fLength = aLength;
//make a defensive copy of the date
fDateManufactured = new Date(aDateManufactured.getTime());
//does not compile, since the items are final:
//aDateManufactured = null;
//aLength = 0;
}
/** Cannot be overridden, since the class itself is final. */
public void setDate(final Date aNewDate){
//even though the field is final, its state can change:
fDateManufactured.setTime(aNewDate.getTime());
//does not compile, since field is final:
//fDateManufactured = aNewDate;
}
/** Return the highest race score. */
public Integer bestRaceScore(){
//the result reference can't be final, since it can be
//re-pointed to different objects
Integer result = Integer.valueOf(0);
//final Integer result = Integer.valueOf(0); //doesn't compile
//this example is artificial, since fRaceScores could be
//referenced directly here...
final List<Integer> scores = fRaceScores;
for(Integer score : scores){
if (score > result){
result = score; //re-point to the max value
}
}
return result;
}
//..elided
// PRIVATE
private final String fName;
private final int fLength;
private List<Integer> fRaceScores = new ArrayList<>();
private final Date fDateManufactured;
}
See Also :
Immutable objects
Designing for subclassing
Overridable methods need special care
Remember styles of inheritance
A method header combined with its associated javadoc form the specification, or contract, of a method. If
the caller fulfills the stated requirements, then the method undertakes to fulfill its stated promises.
Using Javadoc acknowledges that there are two distinct questions a reader can ask about code:
what is this supposed to do? (answered only by the javadoc and method header)
how does it try to do it? (answered only by the implementation)
If javadoc is written correctly, then:
one can understand exactly what services are offered by a method ("what is this supposed this
do?"), without having to look at its implementation ("how does it try to do it?"). Reading an
implementation usually takes a lot more effort than reading javdoc.
the implementation can be checked for correctness versus the specification. That is, some bugs can
be found just by reading the code, as opposed to executing it.
Oracle has published this style guide for writing javadoc comments.
Note that:
if you wish to provide high level descriptions, then you may use the file names overview.html (a
conventional name) and package-info.java to do so (see below).
javadoc is "inherited" - javadoc written for an interface method or an abstract method is
automatically associated with corresponding concrete implementations
it's a common error to put the class level javadoc comment at the very start of a source file, before
the import statements. This is an error, since it must appear just above the class header.
Javadoc 1.4 has added many features, including:
the -tag option allows user-defined custom tags. This feature can be used to implement common
items such as @to.do, @is.Mutable, or @no.Nulls . (Oracle recommends placing a period
somewhere in the custom tag, to avoid potential future conflicts with tags defined by Oracle.)
the -linksource option generates an HTML version of your source code, and links the javadoc to
the source. (The source is presented without any syntax highlighting, but this remains a very nice
feature.)
there is finer-grained control over how javadoc is inherited. The {@inheritDoc} tag is useful here
(warning: this is broken in JDK 1.4.0)
Javadoc 1.5 has these notable additions :
support for new language features, such as generics and annotations
the @literal and @code tags for ensuring text is not treated as markup
Example:
/*
* This comment is NOT a class level javadoc comment.
* Such comments appear just above the class declaration, not at the
* start of the file.
*/
import java.math.BigDecimal;
/**
* Guitar Model Object.
*
* <P>Various attributes of guitars, and related behaviour.
*
* <P>Note that {@link BigDecimal} is used to model the price - not double or float.
336
*/
*/
/** Value - {@value}, key for storing the current guitar of interest in the
session.*/
public static final String KEY = "guitar";
/**
* Play the guitar.
*
* This method makes no guarantees as to how <em>well</em> the song is played.
* @param aSongTitle must have content, and must have trimmed length greater than 2.
*/
void play(String aSongTitle){
//..elided
}
/**
* Apply standard tuning to the guitar.
*
* @return <tt>true</tt> only if the guitar has been properly tuned.
*/
boolean tune(){
return true;
}
/**
* Destroy the guitar while on stage.
*
* @deprecated Not good for the environment.
*/
void lightOnFireAndSmashLikeAWildman(){
//..elided
}
//...other methods elided
}
package-info.java
The package-info.java file is unusual since it doesn't contain a class. Indeed, the name package-info
is not a valid class name. Here is a simple example of its contents:
/** Edit the roles attached to a user. */
package hirondelle.fish.access.role;
See Also :
Document thread safety
Construct classes from the outside in
Design by Contract
Avoid basic style errors
An example in which a static import is likely acceptable is a constants class. For example, a scientific or
engineering application might make wide use ofMath.PI. A static import ofjava.lang.Math.* would
Math.PI
PI
338
with
More generally, a business application might define a constants class, and import it statically. This would
allowConsts.NEW_LINE to be referenced asNEW_LINE , for example.
See Also :
Class for constants
Uncommon classes need explicit imports
Interface for constants
should be used with care. The normal method of terminating a program is to terminate all
user threads.
Cases in which System.exit is appropriate:
utility scripts
GUI applications, in which the event dispatch user thread is created in the background. Here, the
program may be terminated by calling System.exit, or, for example, by setting:
JFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
Subversion
Getting Started With Subversion
download
edit the file named %APPDATA%\Subversion\config (no file extension) You will likely want to edit
global-ignores , editor-cmd , and set enable-auto-props=yes . There are also settings for
pointing to your diff tool.
consider defining an environment variable SVNROOT to point to your subversion repository
Common Commands
svn checkout %SVNROOT%/blah/trunk
Checks out the trunk of a project named blah.
svn commit -m "Some message" blah.txt
Commits a change made to a file in the current directory.
svn help <command>
Get help on a command.
svn status -uv
Shows status. Recursive by default. Talks to server for any updates that you haven't seen yet.
svn log -v
View change sets.
Occasional Commands
svnadmin create C:\my-repos
Create a local repository.
svn import -m "Importing" . %SVNROOT%/blah/trunk
Import project named blah into a repository. Imports the local directory and all of its subdirectories.
svn info
Shows high level information; shows which branch you are on.
svn ls %SVNROOT%
Lists the projects in the repository. Allows you to navigate the repository's tree structure, by the usual
drill-down. '-R' is recursive, and shows each file, not just directories.
Branching
svn mkdir -m "Making first release branch" %SVNROOT%/blah/branch
Create a directory for the blah project to hold all future branches.
340
CVS
CVS Terminology
repository - where CVS stores the source code for projects.
CVSROOT - an environment variable which points to the repository.
workspace or working directory - where the developer does their work. A private copy of the code,
on the developer's host.
checkout - the initial fetch of a project's source tree from the repository.
update - a re-fetch of a file or set of files after the initial checkout.
commit - after verifying edits are correct, the developer will use a commit to post their edits from
their workspace back to the repository.
revision - version of a file, denoted by a string of numbers, as in 1.2 (on the trunk) or 1.1.2.3 (on a
branch).
trunk or mainline - the main branch of development. Releases are often branched off the trunk.
dead files - files that are removed from the project aren't really removed. Instead, they are moved
into the 'dead' state.
regular tag - label for a particular set of revisions in the project. Allows you to define specific
snapshots of the project, which can be used later to recreate that snapshot. Tags always start with a
letter, so you can always distinguish them from revisions (which are numeric, as in 1.2 or 1.1.2.3).
branch tag - label for a project branch. Always starts with a letter. Branches allow parallel
development.
HEAD - a reserved tag name, refers to the most recent revision in the repository.
BASE - a reserved tag name, refers to the revision checked out into the workspace. If a developer
has modified a file, then the BASE is no longer the same as the workspace copy.
sticky - many CVS commands are sticky. This means that the command is implicitly applied using
the most recent tag that was specified (either regular tag or branch tag). Thus, you can specify a tag
once, and then subsequent commands will assume that tag also applies as the default. The idea is to
let you work without repeatedly specifying the tag over and over again.
341
Common Commands
cvs status Blah.java
Lists general information about the file, whether it has been edited, it's revision, and what branch it's on.
cvs status -q Blah.java
More concise status listing.
cvs -q update -P -d
Updates your workspace with the latest repository files. Performs a merge. Most merges will succeed, but
some will fail, and will result in a conflict. Conflicts must be handled manually.
This is a 'sticky' operation (see above). If your workspace is on the trunk, then you are updated with new
trunk files. If your workspace is on a branch, then you are updated with files from that branch.
cvs -nq update -P -d
Compares your workspace with the repository without actually doing the update. Does not write to your
workspace. The '-n' option means "don't really do this, just show me the current differences between my
workspace and the HEAD of the repository."
cvs -q update -P -d -A
Updates your workspace, and ensures you're updated with the trunk, not a branch.
342
Occasional Commands
work>cvs checkout myproject
Fetches an entire project from the repository, and places it in the local directory named work/myproject.
cvs add Blah.java
cvs commit -m "My new file." Blah.java
Adds a new file to the repository. Note that you need to commit the file before the addition is complete.
cvs add -kb jstl.jar
cvs commit -m "Required library." jstl.jar
Adds a new binary file to the repository. Again, you need to commit the file before the addition is
complete.
work>del Blah.java
work>cvs remove Blah.java
work>cvs commit "No longer needed." Blah.java
Deletes a file. This has 3 steps.
cvs history -c -D "2010/01/01" -z AST Blah.java
Displays a history of commits on a given file, after a given date, with date-time output in a specific time
zone (AST).
cvs log -d today Blah.java
Shows messages for all commits done today for a given file.
cvs -q log -wmyuserid -d"2010/04/13 13:00:00" -S
Shows messages for all commits done by a certain user, after a given date-time
cvs log -rREL_1_0 -wmyuserid Blah.java
Shows the commit messages for a given user id, performed on a specific branch.
work>rename SolarSys.java SolarSystem.java
work>cvs remove SolarSys.java
work>cvs add SolarSystem.java
work>cvs commit -m "Better name." SolarSystem.java
Renames a file. This has 4 steps.
cvs rtag -b RB_1_0 myproject
Creates a release branch named 'RB_1_0' for a project named 'myproject'. This command is applied to the
repository, not to the workspace. A release branch can be created before the release, if desired. Then,
each release candidate is built using the head of the release branch. When the release is final, then you
apply a regular tag to the release branch, to mark the exact point of release.
cvs tag REL_1_0
343
Tags the repository files in your BASE -- the unmodified revisions checked out to your local workspace.
cvs diff --side-by-side Blah.java
Shows the differences between your workspace and the BASE.
CVS Quirks
after you have initially checked out a project to a given local workspace, CVS will remember both
the location of the repository, and your login credentials (as long as you don't log out). Thus,
there's no need to repeatedly state the location of the repository when issuing CVS commands.
if you try to commit a file which actually has no differences with the repository file, CVS will not
create a new revision.
that all calls of a private method are valid. If desired, the assert keyword can be used to verify
private method arguments, to check the internal consistency of the class. (Using assert for checking
the arguments of a non- private method is not recommended, since disabling such assertions would mean
the contract of the non- private method is no longer being enforced.)
When validating an argument, an Exception is thrown if the test fails. It's often one of these unchecked
exceptions that are thrown:
IllegalArgumentException
NullPointerException
IllegalStateException
Example 2
Some validations are very common:
check that an object is not null
check that text has visible content
check that a number is in some range
Providing a class for such validations can often be useful. In this example, the Args class throws
IllegalArgumentException if a corresponding boolean check fails. Using Args to validate arguments
will increase legibility noticeably, especially in the case of multiple validations.
package hirondelle.web4j.util;
import java.util.regex.*;
/**
Utility methods for common argument validations.
<P>Replaces <tt>if</tt> statements at the start of a method with
more compact method calls.
<P>Example use case.
<P>Instead of :
<PRE>
public void doThis(String aText){
if (!Util.textHasContent(aText)){
throw new IllegalArgumentException();
}
//..main body elided
}
</PRE>
<P>One may instead write :
<PRE>
public void doThis(String aText){
Args.checkForContent(aText);
//..main body elided
}
</PRE>
*/
public final class Args {
/**
If <code>aText</code> does not satisfy {@link Util#textHasContent}, then
throw an <code>IllegalArgumentException</code>.
<P>Most text used in an application is meaningful only if it has visible content.
*/
public static void checkForContent(String aText){
if( ! Util.textHasContent(aText) ){
throw new IllegalArgumentException("Text has no visible content");
}
}
/**
If {@link Util#isInRange} returns <code>false</code>, then
throw an <code>IllegalArgumentException</code>.
@param aLow is less than or equal to <code>aHigh</code>.
*/
public static void checkForRange(int aNumber, int aLow, int aHigh) {
if ( ! Util.isInRange(aNumber, aLow, aHigh) ) {
throw new IllegalArgumentException(aNumber + " not in range " + aLow + ".." +
aHigh);
}
}
/**
If <tt>aNumber</tt> is less than <tt>1</tt>, then throw an
<tt>IllegalArgumentException</tt>.
346
*/
public static void checkForPositive(int aNumber) {
if (aNumber < 1) {
throw new IllegalArgumentException(aNumber + " is less than 1");
}
}
/**
If {@link Util#matches} returns <tt>false</tt>, then
throw an <code>IllegalArgumentException</code>.
*/
public static void checkForMatch(Pattern aPattern, String aText){
if (! Util.matches(aPattern, aText)){
throw new IllegalArgumentException(
"Text " + Util.quote(aText) + " does not match '" +aPattern.pattern()+ "'"
);
}
}
/**
If <code>aObject</code> is null, then throw a <code>NullPointerException</code>.
<P>Use cases :
<pre>
doSomething( Football aBall ){
//1. call some method on the argument :
//if aBall is null, then exception is automatically thrown, so
//there is no need for an explicit check for null.
aBall.inflate();
//2. assign to a corresponding field (common in constructors):
//if aBall is null, no exception is immediately thrown, so
//an explicit check for null may be useful here
Args.checkForNull( aBall );
fBall = aBall;
//3. pass on to some other method as parameter :
//it may or may not be appropriate to have an explicit check
//for null here, according the needs of the problem
Args.checkForNull( aBall ); //??
fReferee.verify( aBall );
}
</pre>
*/
public static void checkForNull(Object aObject) {
if (aObject == null) {
throw new NullPointerException();
}
}
// PRIVATE
private Args(){
//empty - prevent construction
}
}
See Also :
Validate state with class invariants
Javadoc all exceptions
Assert is for private arguments only
Avoid @throws in javadoc
Model Objects
It's important to realize that the coding techniques you learn should not be applied blindly, without
thinking. That is, while a practice can indeed be applicable most of the time, there are very few rules
which should absolutely never be broken. Rules are blind, in the sense that they often don't deal with the
full context of a problem, or the complete picture.
You shouldn't be afraid to consider breaking the rules, if you have thought about the problem, and when
you feel it's an appropriate thing to do.
When you begin your career as a programmer, following a standard set of rules is likely appropriate. But
as you progress, your skills become stronger, and you start to develop something which is lacking in the
beginner - a sense of taste. This sense of taste, this wisdom which you've acquired through real
experience, is what really guides a mature programmer. In essence, wisdom transcends rules.
Barry Schwartz has an interesting TED talk on this general idea, which is worth listening to.
See Also :
Coding conventions
A common exception is the template method design pattern. There, the safest style is to make all items in
the abstract base class final , except for the single abstract method which needs to be implemented by
the subclass.
348
See Also :
Use final liberally
Serialization and subclassing
Avoid clone
Overridable methods need special care
Remember styles of inheritance
Constructors shouldn't call overridables
Any method which is not private, static, or final can be overridden. Allowing a method to be
overridden should always be done intentionally, not by accident. Overridable methods, and any methods
which call them, represent unusual places in your code, to which special attention must be paid. This is
because subclassing violates encapsulation, in the sense that it's possible for a subclass to break its
superclass's contract. See Effective Java for more information.
If you don't intend a method to be overridden, then you should declare it as private, static, or final .
When you do override a method, you should use the @Override annotation. This allows the compiler to
verify that the method is indeed a valid override of an existing method. For example, your
implementations of toString , equals, and hashCode should always use @Override in the method
header.
See Also :
Use final liberally
Designing for subclassing
Standardized dialogs
Constructors shouldn't call overridables
Use @Override liberally
See Also :
Use final liberally
Designing for subclassing
Overridable methods need special care
350
import
import
import
import
import
import
import
import
import
import
import
java.io.BufferedInputStream;
java.io.BufferedOutputStream;
java.io.File;
java.io.FileInputStream;
java.io.FileOutputStream;
java.io.IOException;
java.io.InputStream;
java.io.OutputStream;
java.util.ArrayList;
java.util.Arrays;
java.util.List;
/**
Detect and remove Byte Order Marks (BOMs) from text files saved with a
Unicode encoding.
<P>Dev tool only. If you use this tool to remove BOMs, please ensure
you have made a backup.
<P>This class assumes the UTF-8 encoding for the BOM, but
is easily changed to handle any encoding.
See http://en.wikipedia.org/wiki/Byte_order_mark for more info.
JDK 5+.
*/
public final class BomDetector {
/** Run the tool against a root directory.*/
public static void main(String... aArgs) throws IOException{
BomDetector bom = new BomDetector(
"C:\\Temp3\\test\\",
".txt", ".jsp", ".jspf", ".tag", ".html",
".css", ".xml", ".js", ".sql", ".tld"
);
int count = 0;
for(String file : bom.findBOMs()){
log(file);
++count;
}
log("Number of files with BOM:" + count);
/*
for(String file : bom.removeBOMs()){
log("Removed BOM from: " + file);
}
*/
}
public BomDetector(String aRootDirectory, String... aFileExtensions){
fRootDir = new File(aRootDirectory);
fExtensions = Arrays.asList(aFileExtensions);
if(!fRootDir.exists() || fRootDir.isFile() ){
throw new RuntimeException("Root directory not valid.");
}
}
/** Find files with BOMs under the given root directory. Return their names. */
public List<String> findBOMs() throws IOException {
List<String> result = new ArrayList<String>();
for(File textFile : findTextFilesBeneath(fRootDir)){
if(startsWithBOM(textFile)){
result.add(textFile.getCanonicalPath());
}
}
return result;
}
/**
Find and remove BOMs from files under the given root directory.
Overwrites files.
Return the names of the affected files.
*/
public List<String> removeBOMs() throws IOException{
List<String> result = new ArrayList<String>();
for(String bomFile : findBOMs()){
stripBomFrom(bomFile);
result.add(bomFile);
352
}
return result;
}
// PRIVATE
private File fRootDir;
private List<String> fExtensions;
/** Different encodings will have different BOMs. This is for UTF-8. */
private final int[] BYTE_ORDER_MARK = {239, 187, 191};
private static void log(Object aThing){
System.out.println(String.valueOf(aThing));
}
private List<File> findTextFilesBeneath(File aStartingDir) throws IOException {
List<File> result = new ArrayList<File>();
File[] filesAndDirs = aStartingDir.listFiles();
List<File> filesDirs = Arrays.asList(filesAndDirs);
for(File file : filesDirs){
if (isTextFile(file)){
result.add(file);
}
if( file.isDirectory() ) {
//recursive call!!
List<File> deeperList = findTextFilesBeneath(file);
result.addAll(deeperList);
}
}
return result;
}
private boolean isTextFile(File aFile) throws IOException{
boolean result = false;
String fileName = aFile.getCanonicalPath();
int finalDot = fileName.lastIndexOf(".");
if (finalDot > -1){
String extension = fileName.substring(finalDot);
result = fExtensions.contains(extension);
}
return result;
}
private boolean startsWithBOM(File aTextFile) throws IOException {
boolean result = false;
if(aTextFile.length() < BYTE_ORDER_MARK.length) return false;
//open as bytes here, not characters
int[] firstFewBytes = new int[BYTE_ORDER_MARK.length];
InputStream input = null;
try {
input = new FileInputStream(aTextFile);
for(int index = 0; index < BYTE_ORDER_MARK.length; ++index){
firstFewBytes[index] = input.read(); //read a single byte
}
result = Arrays.equals(firstFewBytes, BYTE_ORDER_MARK);
}
finally {
input.close();
}
return result;
}
private void stripBomFrom(String aTextFile) throws IOException{
File bomFile = new File(aTextFile);
long initialSize = bomFile.length();
long truncatedSize = initialSize - BYTE_ORDER_MARK.length;
byte[] memory = new byte[(int)(truncatedSize)];
InputStream input = null;
try {
input = new BufferedInputStream(new FileInputStream(bomFile));
input.skip(BYTE_ORDER_MARK.length);
int totalBytesReadIntoMemory = 0;
while(totalBytesReadIntoMemory < truncatedSize){
int bytesRemaining = (int)truncatedSize - totalBytesReadIntoMemory;
int bytesRead = input.read(memory, totalBytesReadIntoMemory, bytesRemaining);
if(bytesRead > 0){
totalBytesReadIntoMemory = totalBytesReadIntoMemory + bytesRead;
353
}
}
overwriteWithoutBOM(memory, bomFile);
}
finally {
input.close();
}
File after = new File(aTextFile);
long finalSize = after.length();
long changeInSize = initialSize - finalSize;
if(changeInSize != BYTE_ORDER_MARK.length){
throw new RuntimeException(
"Change in file size: " + changeInSize +
" Expected change: " + BYTE_ORDER_MARK.length
);
}
}
private void overwriteWithoutBOM(
byte[] aBytesWithoutBOM, File aTextFile
) throws IOException{
OutputStream output = null;
try {
output = new BufferedOutputStream(new FileOutputStream(aTextFile));
output.write(aBytesWithoutBOM);
}
finally {
output.close();
}
}
}
See Also :
Prefer UTF-8 in all layers
Beware of DecimalFormat
Parsing user input into numbers (especially numbers representing money) is a very common task. Such
parsing is often implemented with DecimalFormat . However, parsing with DecimalFormat should be
done with care. You may even want to consider an alternative. Here's why:
surprisingly, a Format does not need to parse all of its input. That is, if it finds a match at the start
of an input String, it will cease parsing, and return a value. When parsing user input, this kind of
behavior is not usually desired. To accomodate this behavior, an extra step is needed - a
ParsePosition object needs to be passed as an output parameter to DecimalFormat .
the class seems to be rather buggy.
the class is harder than most to understand.
Here's an example of parsing with DecimalFormat . An example run appears below. As you can see,
parsing user input will always require the caller to use ParsePosition to detect if the whole input
has been parsed.
there are cases when the parsing seems just plain wrong.
import java.text.*;
354
Example run:
>java -cp . BewareDecimalFormat
Format : #,##0.00
1000 parsed OK. ParsePos: 4, Parse Result: 1000
1,000.00 parsed OK. ParsePos: 8, Parse Result: 1000
1000.33 parsed OK. ParsePos: 7, Parse Result: 1000.33
.20 parsed OK. ParsePos: 3, Parse Result: 0.2
.2 parsed OK. ParsePos: 2, Parse Result: 0.2
.222 parsed OK. ParsePos: 4, Parse Result: 0.222
.222333444 parsed OK. ParsePos: 10, Parse Result: 0.222333444
100.222333444 parsed OK. ParsePos: 13, Parse Result: 100.222333444
355
Clarifying method
The structure of a method can often be made clearer by splitting it up into smaller methods. Martin
Fowler, author of Refactoring, is an enthusiastic advocate of this style.
Refactoring an original method into smaller pieces has several advantages:
it allows the original method to stay at a higher level of abstraction, which always increases
legibility
it makes the original method simpler
356
return result;
}
// PRIVATE
private String fSearchText;
private static final Set<String> fCOMMON_WORDS = new LinkedHashSet<>();
private static final String fDOUBLE_QUOTE = "\"";
//the parser flips between these two sets of delimiters
private static final String fWHITESPACE_AND_QUOTES = " \t\r\n\"";
private static final String fQUOTES_ONLY ="\"";
/**Very common words to be excluded from searches.*/
static {
fCOMMON_WORDS.add("a");
fCOMMON_WORDS.add("and");
fCOMMON_WORDS.add("be");
fCOMMON_WORDS.add("for");
fCOMMON_WORDS.add("from");
fCOMMON_WORDS.add("has");
fCOMMON_WORDS.add("i");
fCOMMON_WORDS.add("in");
fCOMMON_WORDS.add("is");
fCOMMON_WORDS.add("it");
fCOMMON_WORDS.add("of");
fCOMMON_WORDS.add("on");
fCOMMON_WORDS.add("to");
fCOMMON_WORDS.add("the");
}
/**
* Use to determine if a particular word entered in the
* search box should be discarded from the search.
*/
private boolean isCommonWord(String aSearchTokenCandidate){
return fCOMMON_WORDS.contains(aSearchTokenCandidate);
}
private boolean textHasContent(String aText){
return (aText != null) && (!aText.trim().equals(""));
}
private void addNonTrivialWordToResult(String aToken, Set<String> aResult){
if (textHasContent(aToken) && !isCommonWord(aToken.trim())) {
aResult.add(aToken.trim());
}
}
private boolean isDoubleQuote(String aToken){
return aToken.equals(fDOUBLE_QUOTE);
}
private String flipDelimiters(String aCurrentDelims){
String result = null;
if (aCurrentDelims.equals(fWHITESPACE_AND_QUOTES)){
result = fQUOTES_ONLY;
}
else {
result = fWHITESPACE_AND_QUOTES;
}
return result;
}
}
See Also :
Avoid basic style errors
Coding conventions
358
Many groups use a specific set of coding conventions as a programming style guide.
Oracle's coding conventions are popular, but be warned that Joshua Bloch (author of Effective Java) once
stated that this document should not be taken too seriously, as it's not actually used at Oracle (formerly
Sun Microsystems)!
Doug Lea, author of Concurrent Programming in Java, has also published a set of coding conventions,
and many others are available on the web.
Coding conventions should be used as a guide, not as a sacred, immutable law of nature. Those with less
experience will initially benefit from following conventions, but as you gain experience, you should rely
more on your own sense of taste, as opposed to blindly following the rules. The problem with rules is
that there are many cases in which they should be broken.
In general, the question in your mind should always be one of compassion for the reader: "Will this
help or hinder a maintainer who is unfamiliar with my code?". That is the guiding principle.
See Also :
Naming conventions
Wisdom, not rules
Be wary of the these convenience methods, which compile a regular expression each time they are called:
Pattern.matches
String.matches
String.replaceAll
String.replaceFirst
String.split
Example
Here's a comparison of the performance of String.matches versus the use of Pattern and Matcher.
An example run (with the HotSpot compiler turned off) gives:
Time for String: 67.693 ms
Time for Pattern: 12.178 ms
import
import
import
import
import
import
java.io.IOException;
java.nio.charset.Charset;
java.nio.charset.StandardCharsets;
java.nio.file.Path;
java.nio.file.Paths;
java.util.Scanner;
359
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** JDK 7+. */
public final class FindMatch {
/**
Count the number of times the word "assert" occurs in a file.
Compare the execution times of two techniques.
*/
public static void main (String... aArguments) throws IOException{
FindMatch findMatch = new FindMatch("assert");
Path path = Paths.get("C:\\Temp\\InheritanceInterpreter.java");
Stopwatch stopwatch = new Stopwatch();
stopwatch.start();
findMatch.countMatchesUsingString(path);
stopwatch.stop();
log("Time for String: " + stopwatch);
stopwatch.start();
findMatch.countMatchesUsingPattern(path);
stopwatch.stop();
log("Time for Pattern: " + stopwatch);
}
public FindMatch(String aTarget){
fTargetWord = aTarget;
fPattern = Pattern.compile(fTargetWord);
}
int countMatchesUsingString(Path aPath) throws IOException {
int result = 0;
try (Scanner scanner = new Scanner(aPath, ENCODING.name())){
while (scanner.hasNextLine()){
String line = scanner.nextLine();
if( line.matches(fTargetWord) ){
++ result;
}
}
}
return result;
}
int countMatchesUsingPattern(Path aPath) throws IOException {
int result = 0;
Matcher matcher = fPattern.matcher("");
try (Scanner scanner = new Scanner(aPath, ENCODING.name())){
while (scanner.hasNextLine()){
String line = scanner.nextLine();
matcher.reset(line); //reset the input each time
if (! matcher.find()) {
++result;
}
}
}
return result;
}
// PRIVATE
private final String fTargetWord;
private final Pattern fPattern;
private final static Charset ENCODING = StandardCharsets.UTF_8;
private static void log(Object aObject){
System.out.println(aObject);
}
}
See Also :
360
Conditional compile
The conditional compilation practice is used to optionally remove chunks of code from the compiled
version of a class. It uses the fact that compilers will ignore any unreachable branches of code.
To implement conditional compilation,
define a static final boolean value as a non-private member of some class
place code which is to be conditionally compiled in an if block which evaluates the boolean
set the value of the boolean to false to cause the compiler to ignore the if block; otherwise, keep
its value as true
This practice is used mostly for implementing debugging statements, logging statements, and for
assertions. With the advent of assert and the Logging API in JDK 1.4, the utility of conditional
compilation is probably reduced.
Example
public final class Debug {
//set to false to allow compiler to identify and eliminate
//unreachable code
public static final boolean ON = true;
}
System.out.println("Processing doPost");
}
doHttpRequest(aRequest, aResponse);
}
// PRIVATE //
private void doHttpRequest(
HttpServletRequest aRequest,
HttpServletResponse aResponse
) throws ServletException, IOException {
//empty
}
}
See Also :
Assertions in general
Logging messages
GregorianCalendar
you may use long as
See Also :
Defensive copying
Immutable objects
363
See Also :
Naming conventions
Stack trace as String
Multiple return statements
Defensive copying
A mutable object is simply an object which can change its state after construction. For example,
StringBuilder and Date are mutable objects, while String and Integer are immutable objects.
A class may have a mutable object as a field. There are two possible cases for how the state of a mutable
object field can change:
its state can be changed only by the native class - the native class creates the mutable object field,
and is the only class which is directly aware of its existence
its state can be changed both by the native class and by its callers - the native class simply points
to a mutable object which was created elsewhere
Both cases are valid design choices, but you must be aware of which one is appropriate for each case.
If the mutable object field's state should be changed only by the native class, then a defensive copy of the
mutable object must be made any time it's passed into (constructors and set methods) or out of ( get
methods) the class. If this is not done, then it's simple for the caller to break encapsulation, by changing
the state of an object which is simultaneously visible to both the class and its caller.
364
Example
Planet has a mutable object field fDateOfDiscovery, which is defensively copied in all constructors,
and in getDateOfDiscovery . Planet represents an immutable class, and has no set methods for its fields.
Note that if the defensive copy of DateOfDiscovery is not made, then Planet is no longer immutable!
import java.util.Date;
/**
* Planet is an immutable class, since there is no way to change
* its state after construction.
*/
public final class Planet {
public Planet (double aMass, String aName, Date aDateOfDiscovery) {
fMass = aMass;
fName = aName;
//make a private copy of aDateOfDiscovery
//this is the only way to keep the fDateOfDiscovery
//field private, and shields this class from any changes that
//the caller may make to the original aDateOfDiscovery object
fDateOfDiscovery = new Date(aDateOfDiscovery.getTime());
}
/**
* Returns a primitive value.
*
* The caller can do whatever they want with the return value, without
* affecting the internals of this class. Why? Because this is a primitive
* value. The caller sees its "own" double that simply has the
* same value as fMass.
*/
public double getMass() {
return fMass;
}
/**
* Returns an immutable object.
*
* The caller gets a direct reference to the internal field. But this is not
* dangerous, since String is immutable and cannot be changed.
*/
public String getName() {
return fName;
}
// /**
// * Returns a mutable object - likely bad style.
// *
// * The caller gets a direct reference to the internal field. This is usually
dangerous,
// * since the Date object state can be changed both by this class and its caller.
// * That is, this class is no longer in complete control of fDate.
// */
// public Date getDateOfDiscovery() {
//
return fDateOfDiscovery;
// }
/**
* Returns a mutable object - good style.
*
* Returns a defensive copy of the field.
* The caller of this method can do anything they want with the
* returned Date object, without affecting the internals of this
* class in any way. Why? Because they do not have a reference to
* fDate. Rather, they are playing with a second Date that initially has the
* same data as fDate.
*/
public Date getDateOfDiscovery() {
return new Date(fDateOfDiscovery.getTime());
}
// PRIVATE
/**
* Final primitive data is always immutable.
*/
365
See Also :
Copy constructors
Fields should usually be private
Immutable objects
Avoid clone
Encapsulate collections
Design by Contract
The specification of a class or interface is the collection of non-private items provided as services to the
caller, along with instructions for their use, as stated in javadoc. It's a challenge to construct a
specification which:
is as simple as possible
is as clear as possible
has no ambiguity
is completely accurate
allows the reader to completely ignore implementation details (unless there is a bug)
pours understanding into the mind of the reader as quickly as possible, with little chance for
misunderstanding
Design By Contract is an effective guideline of lasting value for creating a specification.
The fundamental idea of Design By Contract is to treat the services offered by a class or interface as a
contract between the class (or interface) and its caller. Here, the word "contract" is meant to convey a
kind of formal, unambiguous agreement between two parties. C++ FAQs describes a contract as being
made of two parts:
requirements upon the caller made by the class
promises made by the class to the caller
If the caller fulfills the requirements, then the class promises to deliver some well-defined service.
Some changes to a specification/contract will break the caller, and some won't. For determining if a
change will break a caller, C++ FAQs uses the memorable phrase "require no more, promise no less": if
366
the new specification does not require more from the caller than before, and if it does not promise to
deliver less than before, then the new specification is compatible with the old, and will not break the
caller.
Although Design By Contract is a formal part of some programming languages, such as Eiffel, it's not a
formal part of Java. Nevertheless, Design By Contract is very useful for designing classes and interfaces.
It can both guide the discovery of a more robust design, and allow more effective expression of that
design in javadoc.
Requirements are simply any conditions on use, for example:
conditions on argument values
conditions on order of execution of methods
condtions on execution in a multi-threaded environment
Requirements must be stated in javadoc, and may be enforced by throwing checked or unchecked
exceptions when the stated conditions are violated. Using assertions for enforcing requirements is not
recommended.
Promises are stated in javadoc. They can be enforced by assertions at the end of a method.
See Also :
Use javadoc liberally
Construct classes from the outside in
Assert use cases
367
If you are using TAB characters, then this could easily appear in another tool as:
import java.util.*;
public final class TabsTwo {
void decideWhatToDo(){
if(!bored){
continueWhatYourAreDoing();
}
else {
if (! hasCarpalTunnelIssues()) {
code();
}
else {
eatSomething();
}
}
}
}
Even worse, some tools can even render text with TABs with negative indentation:
import java.util.*;
public final class TabsThree {
void decideWhatToDo(){
if(!bored){
continueWhatYourAreDoing();
}
else {
if (! hasCarpalTunnelIssues()) {
code();
}
else {
eatSomething();
}
}
}
}
Most people would consider such code as much more difficult to read, and annoying. Thus, using TAB
characters in source code is often considered highly undesirable.
You should use 2-4 spaces to indent your code, not TAB characters. Modern IDE's let you map TAB
keystrokes to a given number of spaces instead.
Here's a related quote from Code Complete, by Steve McConnell:
"The study concluded that two-to-four-space indentation was optimal. Interestingly, many subjects in the
experiment felt that the six-space indentation was easier to use than the smaller indentations, even
though their scores were lower. That's probably because six spaces looks pleasing. But regardless of how
pretty it looks, six-space indentation turns out to be less readable. This is an example of a collision
between aesthetic appeal and readability."
368
Example
import java.util.*;
/**
* When using parentheses, extra spaces may be used to slightly
* increase legibility. Here, both styles are illustrated for comparison.
*/
public final class ExtraSpace {
public void explode( String aThing ) {
expand( aThing ); //with extra spaces
burst(aThing);
//without extra spaces
}
// PRIVATE
private static final String fBALLOON = "balloon";
private static final String fTIRE = "tire";
private void expand( String aThing ){
if ( aThing.equals( fBALLOON ) ) { //with extra spaces
//..elided
}
else if (aThing.equals(fTIRE)){
//without extra spaces
//..elided
}
else {
//..elided
}
}
private void burst( String aThing ){
//..elided
}
Style 1 - UUID
Starting with Java 5, the UUID class provides a simple means for generating unique ids. The identifiers
generated by UUID are actually universally unique identifiers.
Example
import java.util.UUID;
public class GenerateUUID {
public static final void main(String... aArgs){
//generate random UUIDs
UUID idOne = UUID.randomUUID();
UUID idTwo = UUID.randomUUID();
369
Example run:
>java -cp . GenerateUUID
UUID One: 067e6162-3b6f-4ae2-a171-2470b63dff00
UUID Two: 54947df8-0e9e-4471-a2f9-9af509fb5889
If Java 5 is not available, then there are other more laborious ways to generate unique ids (see below).
is often used as a checksum, for verifying that data has not been altered since its creation.
Example
import java.security.SecureRandom;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public final class GenerateId {
public static void main (String... arguments) {
try {
//Initialize SecureRandom
//This is a lengthy operation, to be done only upon
//initialization of the application
SecureRandom prng = SecureRandom.getInstance("SHA1PRNG");
//generate a random number
String randomNum = new Integer(prng.nextInt()).toString();
//get its digest
MessageDigest sha = MessageDigest.getInstance("SHA-1");
byte[] result = sha.digest(randomNum.getBytes());
System.out.println("Random number: " + randomNum);
System.out.println("Message digest: " + hexEncode(result));
}
catch (NoSuchAlgorithmException ex) {
370
System.err.println(ex);
}
}
/**
* The byte[] returned by MessageDigest does not have a nice
* textual representation, so some form of encoding is usually performed.
*
* This implementation follows the example of David Flanagan's book
* "Java In A Nutshell", and converts a byte array into a String
* of hex characters.
*
* Another popular alternative is to use a "Base64" encoding.
*/
static private String hexEncode(byte[] aInput){
StringBuilder result = new StringBuilder();
char[] digits = {'0', '1', '2', '3',
'4','5','6','7','8','9','a','b','c','d','e','f'};
for (int idx = 0; idx < aInput.length; ++idx) {
byte b = aInput[idx];
result.append(digits[ (b&0xf0) >> 4 ]);
result.append(digits[ b&0x0f]);
}
return result.toString();
}
}
Example run:
>java -cp . GenerateId
Random number: -1103747470
Message digest: c8fff94ba996411079d7114e698b53bac8f7b037
Style 3 - UID
Finally, here is another method, using a java.rmi.server.UID . The Serializable identifiers generated
by this class are unique on the host on which they are generated, provided that
the host takes more than one millisecond to reboot
the host's clock is never set to run backwards
In order to construct a UID that is globally unique, simply pair a UID with an InetAddress.
Example
import java.rmi.server.UID;
public class UniqueId {
/**
* Build and display some UID objects.
*/
public static void main (String... arguments) {
for (int idx=0; idx<10; ++idx){
UID userId = new UID();
System.out.println("User Id: " + userId);
}
}
}
Example run:
User Id: 3179c3:ec6e28a7ef:-8000
User Id: 3179c3:ec6e28a7ef:-7fff
371
User
User
User
User
User
User
User
User
Id:
Id:
Id:
Id:
Id:
Id:
Id:
Id:
3179c3:ec6e28a7ef:-7ffe
3179c3:ec6e28a7ef:-7ffd
3179c3:ec6e28a7ef:-7ffc
3179c3:ec6e28a7ef:-7ffb
3179c3:ec6e28a7ef:-7ffa
3179c3:ec6e28a7ef:-7ff9
3179c3:ec6e28a7ef:-7ff8
3179c3:ec6e28a7ef:-7ff7
Clearly, these are not secure identifiers - knowing one, it's easy to guess another.
Date Ranges
A related issue is specific to date values. It's common to have a date range specified using dates only
(with no time portion), while the underlying data actually includes both a date and a time. This can easily
lead to off-by-one errors.
For example, taking the following expression:
2010-01-01 <= x <= 2010-01-31
372
String fName;
int fNumDoors;
List<String> fOptions;
double fGasMileage;
String fColor;
Date[] fMaintenanceChecks;
See Also :
Implementing equals
Conventional name for return value
Avoid basic style errors
method will be called based on declared compile-time type, not run-time type. For the case in which
overloaded methods have the same number of arguments, the rules regarding this decision can sometimes
be a bit tricky.
If there may be confusion, you may simplify the design:
use different method names, and avoid overloading altogether
retain overloading, but ensure each method has a distinct number of arguments
In addition, it's recommended that varargs not be used when a method is overloaded, since this makes it
more difficult to determine which overload is being called.
Reminder:
Overloading requires methods with distinct signatures. The signature of a method includes its name and
the ordered list of its argument types. All other items appearing in a method header, such as exceptions,
return type, final , and synchronized, do not contribute to a method's signature.
Package By Feature
Package-by-feature uses packages to reflect the feature set. It tries to place all items related to a single
feature (and only that feature) into a single directory/package. This results in packages with high cohesion
and high modularity, and with minimal coupling between packages. Items that work closely together are
placed next to each other. They aren't spread out all over the application. It's also interesting to note that,
in some cases, deleting a feature can reduce to a single operation - deleting a directory. (Deletion
operations might be thought of as a good test for maximum modularity: an item has maximum
modularity only if it can be deleted in a single operation.)
In package-by-feature, the package names correspond to important, high-level aspects of the problem
domain. For example, a drug prescription application might have these packages:
com.app.doctor
com.app.drug
com.app.patient
com.app.presription
com.app.report
com.app.security
com.app.webmaster
com.app.util
and so on...
Each package usually contains only the items related to that particular feature, and no other feature. For
example, the com.app.doctor package might contain these items:
DoctorAction.java - an action
Doctor.java - a Model Object
or controller object
374
It's important to note that a package can contain not just Java code, but other files as well. Indeed, in
order for package-by-feature to really work as desired, all items related to a given feature - from user
interface, to Java code, to database items - must be placed in a single directory dedicated to that feature
(and only that feature).
In some cases, a feature/package will not be used by any other feature in the application. If that's the
case, it may be removed simply by deleting the directory. If it is indeed used by some other feature, then
its removal will not be as simple as a single delete operation.
That is, the package-by-feature idea does not imply that one package can never use items belonging to
other packages. Rather, package-by-feature aggressively prefers package-private as the default scope,
and only increases the scope of an item to public only when needed.
Package By Layer
The competing package-by-layer style is different. In package-by-layer, the highest level packages
reflect the various application "layers", instead of features, as in:
com.app.action
com.app.model
com.app.dao
com.app.util
Here, each feature has its implementation spread out over multiple directories, over what might be
loosely called "implementation categories". Each directory contains items that usually aren't closely
related to each other. This results in packages with low cohesion and low modularity, with high
coupling between packages. As a result, editing a feature involves editing files across different
directories. In addition, deleting a feature can almost never be performed in a single operation.
backwards.
Separates Both Features and Layers
The package-by-feature style still honors the idea of separating layers, but that separation is implemented
using separate classes. The package-by-layer style, on the other hand, implements that separation using
both separate classes and separate packages, which doesn't seem necessary or desirable.
Minimizes Scope
Minimizing scope is another guiding principle of lasting value. Here, package-by-feature allows some
classes to decrease their scope from public to package-private. This is a significant change, and will help
to minimize ripple effects. The package-by-layer style, on the other hand, effectively abandons packageprivate scope, and forces you to implement nearly all items as public. This is a fundamental flaw, since
it doesn't allow you to minimize ripple effects by keeping secrets.
Better Growth Style
In the package-by-feature style, the number of classes within each package remains limited to the items
related to a specific feature. If a package becomes too large, it may be refactored in a natural way into
two or more packages. The package-by-layer style, on the other hand, is monolithic. As an application
grows in size, the number of packages remains roughly the same, while the number of classes in each
package will increase without bound.
If you still need further convincing, consider the following.
Directory Structure Is Fundamental To Your Code
"As any designer will tell you, it is the first steps in a design process which count for most. The first few
strokes, which create the form, carry within them the destiny of the rest." - Christopher Alexander
(Christopher Alexander is an architect. Without having worked as programmer, he has influenced many
people who think a lot about programming. His early book A Pattern Language was the original
inspiration for the Design Patterns movement. He has thought long and hard about how to build beautiful
things, and these reflections seem to largely apply to software construction as well.)
In a CBC radio interview, Alexander recounted the following story (paraphrased here): "I was working
with one of my students. He was having a very difficult time building something. He just didn't know
how to proceed at all. So I sat with him, and I said this: Listen, start out by figuring out what the most
important thing is. Get that straight first. Get that straight in your mind. Take your time. Don't be too
hasty. Think about it for a while. When you feel that you have found it, when there is no doubt in your
mind that it is indeed the most important thing, then go ahead and make that most important thing. When
you have made that most important thing, ask yourself if you can make it more beautiful. Cut the bullshit,
just get it straight in your head, if you can make it better or not. When that's done, and you feel you
cannot make it any better, then find the next most important thing."
What are the first strokes in an application, which create its overall form? It is the directory structure.
The directory structure is the very first thing encountered by a programmer when browsing source code.
Everything flows from it. Everything depends on it. It is clearly one of the most important aspects of
your source code.
Consider the different reactions of a programmer when encountering different directory structures. For
the package-by-feature style, the thoughts of the application programmer might be like this:
"I see. This lists all the top-level features of the app in one go. Nice."
"Let's see. I wonder where this item is located....Oh, here it is. And everything else I am going to
need is right here too, all in the same spot. Excellent."
376
For the package-by-layer style, however, the thoughts of the application programmer might be more like
this:
"These directories tell me nothing. How many features in this app? Beats me. It looks exactly the
same as all the others. No difference at all. Great. Here we go again..."
"Hmm. I wonder where this item is located....I guess its parts are all over the app, spread around in
all these directories. Do I really have all the items I need? I guess we'll find out later."
"I wonder if that naming convention is still being followed. If not, I will have to look it up in that
other directory."
"Wow, would you look at the size of this single directory...sheesh."
Package-By-Layer in Other Domains is Ineffective
By analogy, one can see that the package-by-layer style leads to poor results. For example, imagine a car.
At the highest level, a car's 'implementation' is divided this way (package-by-feature) :
safety
engine
steering
fuel system
and so on...
Now imagine a car whose 'implementation' under the hood is first divided up according to these lower
level categories (package-by-layer) :
electrical
mechanical
hydraulic
In the case of a transmission problem, for example, you might need to tinker around in these three
compartments. This would mean moving from one part of the car to another completely different one.
While in these various compartments, you could 'see' items having absolutely nothing to do with problem
you are trying to solve. They would simply be in the way, always and everywhere distracting you from
the real task at hand. Wouldn't it make more sense if there was a single place having exactly what you
need, and nothing else?
As a second example, consider a large bureacracy divided up into various departments (package-byfeature):
front office
back office
accounting
personnel
mail room
If a package-by-layer style was used, the primary division would be something like :
executives
managers
employees
Now imagine the bureacracy being divided physically according to these three categories. Each manager
is located, for example, with all the other managers, and not with the employees working for them.
Would that be effective? No, it wouldn't.
377
So why should software be any different? It seems that package-by-layer is just a bad habit waiting to be
broken.
The example applications that come with WEB4J uses the package-by-feature style.
See Also :
JSPs should contain only presentation logic
A Web App Framework - WEB4J
Open file in native directory
The intent here is to store text which cannot be easily reverse-engineered back into the original
password. The salt-value is a random string added to the password. It's added to prevent simple
dictionary-style reverse engineering of the hashed value of the plain text password.
(Regarding Tomcat5: its implementation of form-based login doesn't allow for the salt value.)
Hash Functions
SHA-1, SHA-256, SHA-384, and SHA-512 are all examples of hash functions. A hash function is also
called a MessageDigest . (The MD5 hash has been shown to be defective, and should usually be
avoided.)
A hash function is not an encryption. Encrypted items are always meant for eventual decryption. A hash
function, on the other hand, is meant only as a one-way operation. The whole idea of a hash function is
that it should be very difficult to calculate the original input value, given the hash value.
If y = hash(x) is a hash function, and y and x both represent text strings, then
given the output y, it's very hard to deduce the original input x
similar inputs will give markedly different outputs
input x can have arbitrary length
output y will have fixed length
there is only a trivial chance of "collisions", where different inputs give the same output
The above properties allow for passwords (or pass phrases) of arbitrary length, while still letting the
underlying database column which stores the hash value to be of fixed width.
Example
This example uses MessageDigest and the SHA-1 hash function:
378
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** Example hash function values.*/
public final class HashExamples {
public static void main(String... aArgs) {
try {
MessageDigest sha = MessageDigest.getInstance("SHA-1");
byte[] hashOne = sha.digest("color".getBytes());
log("Hash of 'color': " + hexEncode(hashOne));
sha.reset();
byte[] hashTwo = sha.digest("colour".getBytes());
log("Hash of 'colour': " + hexEncode(hashTwo));
}
catch (NoSuchAlgorithmException ex){
log("No such algorithm found in JRE.");
}
}
private static void log(Object aObject) {
System.out.println(String.valueOf(aObject));
}
/**
* The byte[] returned by MessageDigest does not have a nice
* textual representation, so some form of encoding is usually performed.
*
* This implementation follows the example of David Flanagan's book
* "Java In A Nutshell", and converts a byte array into a String
* of hex characters.
*/
static private String hexEncode( byte[] aInput){
StringBuffer result = new StringBuffer();
char[] digits = {'0', '1', '2', '3',
'4','5','6','7','8','9','a','b','c','d','e','f'};
for (int idx = 0; idx < aInput.length; ++idx) {
byte b = aInput[idx];
result.append( digits[ (b&0xf0) >> 4 ] );
result.append( digits[ b&0x0f] );
}
return result.toString();
}
}
Example run:
Hash of 'color': 6dd0fe8001145bec4a12d0e22da711c4970d000b
Hash of 'colour': 79d41a47e8fec55856a6a6c5ba53c2462be4852e
The Collections class contains several type safe methods which return empty items (which are also
immutable and Serializable) :
Collections.emptyList()
Collections.emptySet()
Collections.emptyMap()
See Also :
Prefer Collections over older classes
Avoid null if possible
Extraneous leading or trailing whitespace around text is easy to add and difficult to detect - a bad
combination. If text has some logical meaning to the program (for example, the name of a configuration
parameter), then extraneous whitespace around it is almost always undesired.
(This problem occurs for text data, but not for numeric data such as BigDecimal or Integer.)
To help detect such unwanted whitespace, simply quote dynamic text values when logging. It's likely a
good idea to define a simple utility method for this purpose.
Example
This example uses String.valueOf(Object):
public final class Util {
/**
* Surround the result of <tt>String.valueOf(aObject)</tt> with single quotes.
*/
public static String quote(Object aObject){
return "'" + String.valueOf(aObject) + "'";
}
}
See Also :
Logging messages
Self-encapsulate fields
Within the implementation of a class, it's common to refer directly to private fields. There is another
style, in which all internal references to fields are indirect, and proceed through the appropriate get and
set methods. This is called self-encapsulation.
Self-encapsulation:
is needed for lazy initialization
is slightly more difficult to read than direct field access
can establish a synchronization policy for the field
allows subclasses to override the get and set methods, and provide their own definition of how
fields behave
Example
public final class Infant {
public Infant (final String aName, final int aWeight, final String aMood){
setName(aName);
setWeight(aWeight);
setMood(aMood);
}
public void becomeHungry(){
//instead of fMood = "Cranky", use:
setMood("Cranky");
}
381
See Also :
Lazy initialization
Many dislike using such structures since they're open to abuse. Any public, mutable members are
382
dangerous, since there's no encapsulation. This is indeed a valid objection to their wide-spread use.
However, structs can still be occasionally useful, and one shouldn't be afraid to use them when
appropriate. In particular, if the data is neither public nor mutable, then the usual objections to structs
lose much of their force.
For example, if the above Book class was in fact a nested private class, and was just a dumb little data
carrier used only inside a single class, then there would be little objection to using it:
like all private data, the struct would be invisible outside the class
since the struct's members are all immutable, then there's no way to change the state of its member
objects
See Also :
Immutable objects
method may be used to test the operation of a class. It should probably be used informally, since it lacks
the features of a more complete testing tool such as JUnit.
When finished testing, main can simply be deleted, or it may be kept for future needs. If kept, it's
probably a good idea to change its scope to private, in order to avoiding polluting the class API with a
method intended only for development. Of course, if main needs to be run again later, you will need to
temporarily change its scope back to public.
Example
Here, SplashScreen.main exercises the display of a splash screen. It's placed at the very bottom of the
class, since it's intended only as a development tool. The informal character of this main is reflected in
the call to Thread.sleep, which simply wastes time for a few seconds. Doing this in the real application
would be silly, since a splash screen's purpose is to give the illusion of rapid feedback. In the real
application, SplashScreen is disposed of not after a fixed number of seconds, but rather after the
appearance of the primary window.
package hirondelle.stocks.main;
import java.awt.*;
import java.net.URL;
import java.awt.image.ImageObserver;
/**
* Present a simple graphic to the user upon launch of the application, to
* provide a faster initial response than is possible with the main window.
*
* <P>Adapted from an
* <a href=http://developer.java.sun.com/developer/qow/archive/24/index.html>item</a>
* on Sun's Java Developer Connection.
*
* <P>This splash screen appears within about 2.5 seconds on a development
* machine. The main screen takes about 6.0 seconds to load, so use of a splash
* screen cuts down the initial display delay by about 55 percent.
383
*
* <P>When JDK 6+ is available, its java.awt.SplashScreen class should be used instead
* of this class.
*/
final class SplashScreen extends Frame {
/**
* Construct using an image for the splash screen.
*
* @param aImageId must have content, and is used by
* {@link Class#getResource(java.lang.String)} to retrieve the splash screen image.
*/
SplashScreen(String aImageId) {
/*
* Implementation Note
* Args.checkForContent is not called here, in an attempt to minimize
* class loading.
*/
if (aImageId == null || aImageId.trim().length() == 0){
throw new IllegalArgumentException("Image Id does not have content.");
}
fImageId = aImageId;
}
/**
* Show the splash screen to the end user.
*
* <P>Once this method returns, the splash screen is realized, which means
* that almost all work on the splash screen should proceed through the event
* dispatch thread. In particular, any call to <tt>dispose</tt> for the
* splash screen must be performed in the event dispatch thread.
*/
void splash(){
initImageAndTracker();
setSize(fImage.getWidth(NO_OBSERVER), fImage.getHeight(NO_OBSERVER));
center();
fMediaTracker.addImage(fImage, IMAGE_ID);
try {
fMediaTracker.waitForID(IMAGE_ID);
}
catch(InterruptedException ex){
System.out.println("Cannot track image load.");
}
SplashWindow splashWindow = new SplashWindow(this,fImage);
}
// PRIVATE
private final String fImageId;
private MediaTracker fMediaTracker;
private Image fImage;
private static final ImageObserver NO_OBSERVER = null;
private static final int IMAGE_ID = 0;
private void initImageAndTracker(){
fMediaTracker = new MediaTracker(this);
URL imageURL = SplashScreen.class.getResource(fImageId);
fImage = Toolkit.getDefaultToolkit().getImage(imageURL);
}
/**
* Centers the frame on the screen.
*
*<P>This centering service is more or less in {@link
hirondelle.stocks.util.ui.UiUtil};
* this duplication is justified only because the use of
* {@link hirondelle.stocks.util.ui.UiUtil} would entail more class loading, which is
See Also :
Use a testing framework (JUnit)
Splash screen
Using various TimeSource implementations, you can mimic any desired behavior for a system clock.
Possible behaviors include:
skip ahead to the future
go back to the past
use a fixed date, and a fixed time
use a fixed date, but still let the time vary
increment by one second each time you 'look' at the clock
change the rate at which time passes, by speeding up or slowing down by a certain factor
use the normal system clock without alteration
For this to work, an application must avoid calling these items directly:
System.currentTimeMillis()
the default constructor for the Date
See Also :
387
Test Doubles
Unit testing is about testing classes in isolation, one at a time. However, classes usually don't operate in
isolation - they usually have collaborators, other classes needed for correct operation. In some cases,
those collaborators aren't immediately available during unit testing. For example, in a servlet application,
the underlying request and response objects are created by the servlet container, and passed to the
application for further processing. During unit testing, the 'real' request and response are not typically
available, since the servlet container isn't running.
Thus, test doubles are often required during unit testing. Test doubles are used to mimic the collaborators
of a class being tested. See xUnitPatterns.com by Gerard Meszaros for more information.
Design by Contract
Choose form validation style carefully
Use a fake system clock
389
390
compile:
[javac] Compiling 5 source files
[echo] Classpath: C:\johanley\Projects\ant-test\lib\junit.jar
test:
[junit] Testsuite: hirondelle.ante.TESTLauncher
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0 sec
jar:
[jar] Building jar: C:\@build\dist\example-app-1.1.jar
javadoc:
[javadoc]
[javadoc]
[javadoc]
[javadoc]
[javadoc]
[javadoc]
[javadoc]
[javadoc]
[javadoc]
Generating Javadoc
Javadoc execution
Loading source files for package hirondelle.ante...
Loading source files for package hirondelle.ante.deluvian...
Constructing Javadoc information...
Standard Doclet version 1.5.0_07
Building tree for all the packages and classes...
Generating C:\@build\javadoc\hirondelle/ante/\package-summary.html...
Copying file C:\johanley\Projects\ant-test\src\hirondelle\ante\doc-files\VersionHistory.html
to directory C:\@build\javadoc\hirondelle\ante\doc-files...
[javadoc] Building index for all the packages and classes...
[javadoc] Building index for all classes...
text-templates:
[copy] Copying 1 file to C:\@build\templates
391
For those already familiar with Ant, here are some reminders regarding its use.
General
tasks placed outside of a target will always be run. You can think of them as being 'global' in
scope.
many tasks are incremental (<javac>, for example), and inspect file timestamp information.
more than a single target can be specified when calling Ant.
file separators can go either way in a build file, \ or /. Both are accepted.
for booleans, on/true/yes means true, and all other values are false.
sometimes a feature of Ant will not be available in an IDE.
Datatypes
usually Paths or Filesets
can be declared within a task, or outside of a task
the id's assigned to a datatype must be unique across all datatypes
Paths
similar to PATH and CLASSPATH
ordered list of directories and files
can include Filesets
many Path items include 'path' in their name, but some don't
Filesets
set of files rooted in a single directory.
Ant doesn't guarantee the order in which files are processed.
membership in a Fileset is done with a Selector, based on file name, size, last-modified date, file
content, and so on.
Filesets are resolved when the declaration is first evaluated. After that, they stay the same, while
the content of the file system can change.
Patternset
just a list of file name patterns
the Patternset doesn't contain files, just file name patterns
usually embedded inside Filesets, but can be used as a datatype itself
'excludes' override 'includes'
a number of file types are excluded by default (CVS files, etc.)
the <defaultexcludes> task controls the list of default excludes
Patternsets use 4 special char's:
* - 0..N characters
392
? - single character
** - used to signify all directories from that point down
/ and \ - path separators
Other datatypes
Filelist - ordered list of files/directories, that may or may not exist.
Dirset - directories only. Used only in javadoc task.
Filterset - alter file content (version number, timestamp), during a copy or move. Uses '@'
character to delimit text in the file that needs to be updated.
FilterReader - again, alters file content. Can be chained together. Flavors: tabs-to-spaces, replace
tokens, escape non-ASCII chars, etc.
Properties
Properties are immutable once set.
Sources of Properties
<property> tasks
properties files referenced by a <property> task
ANT_OPTS environment variable
command line arguments, 'ant -Dx=y', or 'ant -propertfile blah.properties'. This style can never be
overidden by other settings.
Built-in Properties
all JRE system properties (user.name, user.home, etc.)
ant.project.name
ant.file
ant.home
ant.java.version
ant.version
basedir
Properties Files
are typically used to avoid editing the build file itself.
resolve the ${xyz} syntax when used with <property>, but NOT when used with '-propertyfile' on
the Ant command line.
equate to using <property name='x' value='y'> since the 'location' attribute isn't used, this isn't
recommended for files and directories, since this will not resolve relative references. If you do
specify a location in a properties file, then it should be absolute, not relative. In addition, you'll
need to escape backslashes.
Example
# example properties file
app.version = 1.0
#
#
#
#
#
See Also :
Use a testing framework (JUnit)
objects have both state and identity, while primitives have only state (the value)
Occasionally, these differences can cause problems when using boxing.
Some points to remember:
be careful with null s. Auto-unboxing a null object will cause a NullPointerException .
comparing items with == and equals must be done with care.
The rules for comparison of primitives and wrapper objects are as follows.
If x and y are either both primitives, or both objects, then no boxing occurs:
Two primitives
Two objects
Operation
x == y
compare value
compare identity
x.equals(y)
compare value
If one item is a primitive, and the other item is a corresponding wrapper object, then boxing can occur:
Operation
Behavior
x == y
treat as two primitives, and compare value
x.equals(y)
does not compile if x is a primitive; otherwise
treat as two objects, and compare value
Example
import java.util.*;
public final class BoxingExamples {
public static final void main(String... aArgs){
//pass Integer where int expected
explode(new Integer(3));
//pass literal where Boolean expected
tellTruth(true);
//calculate "int-erchangably" with int and Integer
Integer integerYear = new Integer(1989);
Integer otherIntegerYear = integerYear + 10;
int intYear = integerYear + new Integer(15);
log(integerYear.toString());
log(otherIntegerYear.toString());
System.out.println(intYear);
/*
* Comparison of primitives and wrapper objects using == and equals.
*
* When both items are of the same type :
*
2 primitives
2 objects
* ---------------------------------------------------------* ==
: value
identity
* equals() : not applicable
value
*
*
* When one item is a primitive, and the other is an object :
* ==
: treat as two primitives
* x.equals(y) : treat as two objects; do not compile if x is primitive
*/
intYear = 1880;
integerYear = new Integer(1880);
if (intYear == integerYear){
log("intYear == integerYear: TRUE"); // yes
}
395
else {
log("intYear == integerYear : FALSE");
}
if (integerYear.equals(intYear)){
log("integerYear.equals(intYear): TRUE"); //yes
}
else {
log("integerYear.equals(intYear): FALSE");
}
//does not compile "int cannot be dereferenced" :
//intYear.equals(integerYear);
intYear = 1881; //new value
if (intYear == integerYear){
log("intYear == integerYear: TRUE");
}
else {
log("intYear == integerYear : FALSE"); // yes
}
if (integerYear.equals(intYear)){
log("integerYear.equals(intYear): TRUE");
}
else {
log("integerYear.equals(intYear): FALSE"); //yes
}
}
// PRIVATE
private static void explode(int aNumTimes){
log("Exploding " + aNumTimes + " times.");
}
private static void tellTruth(Boolean aChoice){
//instead of if ( aChoice.booleanValue() ) {
if (aChoice) {
log("Telling truth.");
}
else {
log("Lying like a rug.");
}
}
private static void log(String aText){
System.out.println(aText);
}
}
See Also :
Implementing equals
396
Any reader who is not familiar with the specification of this method will be forced to look up the
meaning of the boolean literal before they can understand the method call. An alternative style is to
define a well-named variable which explains the intent:
boolean CREATE_IF_ABSENT = true;
aRequest.getSession(CREATE_IF_ABSENT);
However, the caller is not compelled to use such a style. If a type-safe enumeration is used for the
argument, then the caller is indeed forced into a style which has greater clarity.
aRequest.getSession(HttpServletRequest.CreateIfAbsent.YES);
Here, CreateIfAbsent would be a static nested type-safe enumeration with two elements, YES and NO.
This style has these advantages:
the method is forced to be more intelligible at the point of call
the need to look up the specification is greatly reduced
it serves as a reminder to those who are already familiar with the specification
it has compile-time safety (not an issue with a boolean argument, but important if the argument is
a String or int )
This style can be a bit verbose. It might be one of those rare occasions where a static import of the enum
class is appropriate, to avoid using fully qualified names.
Example 1
Here's a stub class illustrating an alternate form of createSession :
import javax.servlet.http.HttpSession;
/** Clarify method call with enum argument.
public final class HttpRequestWithEnum {
*/
/**
* Example of calling the method with an enum arg.
*/
public static void main(String... aArgs){
HttpRequestWithEnum request = new HttpRequestWithEnum();
HttpSession session = request.createSession(CreateIfAbsent.YES);
}
public HttpSession createSession(CreateIfAbsent aCreateIfNeeded){
return null; //stubbed out
}
/** The enumeration itself. */
public enum CreateIfAbsent {YES, NO};
}
Example 2
397
See Also :
Type-Safe Enumerations
398
Such immutable classes represent the simplest case, since validation is performed only once, during
construction (and deserialization, if necessary). By definition, an immutable object cannot change state
after construction, so performing validations at other times in the object's life is never necessary.
package hirondelle.fish.main.resto;
import
import
import
import
import
import
import
import
import
hirondelle.web4j.model.ModelCtorException;
hirondelle.web4j.model.ModelUtil;
hirondelle.web4j.model.Id;
hirondelle.web4j.security.SafeText;
hirondelle.web4j.model.Decimal;
static hirondelle.web4j.model.Decimal.ZERO;
hirondelle.web4j.model.Check;
hirondelle.web4j.model.Validator;
static hirondelle.web4j.util.Consts.FAILS;
Example 2
Here's an example of a mutable, Serializable class which defines class invariants.
Items to note:
the assertion at the end of the close method
the call to validateState at the end of the readObject method
the implementation is significantly more complex, since the class is mutable
import java.text.StringCharacterIterator;
import java.util.*;
import java.io.*;
/**
* In this style of implementation, both the entire state of the object
* and its individual fields are validated without duplicating any code.
*
* Argument validation usually has if's and thrown exceptions at the
* start of a method. Here, these are replaced with a simple
* call to validateXXX. Validation is separated cleanly from the
* regular path of execution, improving legibility.
* JDK 7+.
*/
public final class BankAccount implements Serializable {
/**
* @param aFirstName contains only letters, spaces, and apostrophes.
* @param aLastName contains only letters, spaces, and apostrophes.
* @param aAccountNumber is non-negative.
*
* @throws IllegalArgumentException if any param does not comply.
*/
public BankAccount(String aFirstName, String aLastName, int aAccountNumber) {
//don't call an overridable method in a constructor
setFirstName(aFirstName);
setLastName(aLastName);
setAccountNumber(aAccountNumber);
}
/**
400
}
}
private static final long serialVersionUID = 7526472295622776147L;
/**
* Always treat de-serialization as a full-blown constructor, by
* validating the final state of the de-serialized object.
*/
private void readObject(ObjectInputStream aInputStream)
throws ClassNotFoundException, IOException {
//always perform the default de-serialization first
aInputStream.defaultReadObject();
//ensure that object state has not been corrupted or
//tampered with maliciously
validateState();
}
/** Test harness. */
public static void main (String... aArguments) {
BankAccount account = new BankAccount("Joe", "Strummer", 532);
//exercise specific validations.
account.setFirstName("John");
account.setAccountNumber(987);
//exercise the post-condition assertion
//requires enabled assertions: "java -ea"
account.close();
//exercise the serialization
try (
OutputStream file = new FileOutputStream("account.ser");
OutputStream buffer = new BufferedOutputStream(file);
ObjectOutput output = new ObjectOutputStream(buffer);
){
output.writeObject(account);
}
catch(IOException exception){
System.err.println(exception);
}
//exercise the deserialization
try (
InputStream file = new FileInputStream("account.ser");
InputStream buffer = new BufferedInputStream(file);
ObjectInput input = new ObjectInputStream(buffer);
){
BankAccount recoveredAccount = (BankAccount)input.readObject();
System.out.println("Recovered account: " + recoveredAccount);
}
catch(IOException | ClassNotFoundException exception ){
System.err.println(exception);
}
}
}
See Also :
Validate method arguments
Immutable objects
Implementing Serializable
Conditional compile
Assert is for private arguments only
Assert use cases
Model Objects
403
hirondelle.web4j.model.ModelCtorException;
hirondelle.web4j.model.ModelUtil;
hirondelle.web4j.model.Id;
hirondelle.web4j.security.SafeText;
hirondelle.web4j.model.Decimal;
static hirondelle.web4j.model.Decimal.ZERO;
hirondelle.web4j.model.Check;
hirondelle.web4j.model.Validator;
static hirondelle.web4j.util.Consts.FAILS;
405
See Also :
JSPs should contain only presentation logic
Model Objects
See Also :
Validate method arguments
406
Design by Contract
fLength += getLengthIncrease(fLength);
//post-condition: length has increased
assert fLength > oldLength;
//check the class invariant
assert hasValidState(): this;
}
/**
* Decrease the length by one unit, but only if the resulting length
* will still be greater than 0.
*/
public void wither(){
//this local class exists only to take a snapshot of the current state.
//although bulky, this style allows post-conditions of arbitrary complexity.
class OriginalState {
OriginalState() {
fOriginalLength = fLength;
}
int getLength() {
return fOriginalLength;
}
private final int fOriginalLength;
}
OriginalState originalState = null;
//construct object inside an assertion, in order to ensure that
//no construction takes place when assertions are disabled.
//this assert is rather unusual in that it will always succeed, and in that
//it has side-effects - it creates an object and sets a reference
assert (originalState = new OriginalState()) != null;
if (fLength > 1) {
--fLength;
}
//post-condition: length has decreased by one or has remained the same
assert fLength <= originalState.getLength();
//check the class invariant
assert hasValidState(): this;
}
/**
* Randomly select one of three actions
* <ul>
* <li>do nothing
* <li>grow
* <li>wither
* </ul>
*/
public void randomGrowOrWither() {
//(magic numbers are used here instead of symbolic constants
//to slightly clarify the example)
Random generator = new Random();
int action = generator.nextInt(3);
//according to the documentation for the Random class, action
//should take one of the values 0,1,2.
if (action == 0) {
//do nothing
}
else if (action == 1) {
grow();
}
else if (action == 2) {
wither();
}
else {
//this is still executed if assertions are disabled
throw new AssertionError("Unexpected value for action: " + action);
}
//check the class invariant
assert hasValidState(): this;
}
/** Use for debugging only. */
public String toString(){
final StringBuilder result = new StringBuilder();
result.append(this.getClass().getName());
408
result.append(": Species=");
result.append(fSpecies);
result.append(" Length=");
result.append(fLength);
return result.toString();
}
// PRIVATE
private final String fSpecies;
private int fLength;
/**
* Implements the class invariant.
*
* Perform all checks on the state of the object.
* One may assert that this method returns true at the end
* of every public method.
*/
private boolean hasValidState(){
return isValidSpecies(fSpecies) && isValidLength(fLength);
}
/** Species must have content. */
private boolean isValidSpecies(final String aSpecies) {
return aSpecies != null && aSpecies.trim().length()>0;
}
/** Length must be greater than 0. */
private boolean isValidLength(final int aLength) {
return aLength > 0;
}
/** Length increase depends on current length. */
private int getLengthIncrease(int aOriginalLength) {
//since this is a private method, an assertion
//may be used to validate the argument
assert aOriginalLength > 0: this;
int result = 0;
if (aOriginalLength > 10) {
result = 2;
}
else {
result = 1;
}
assert result > 0 : result;
return result;
}
}
See Also :
Validate state with class invariants
Design by Contract
Assertions in general
Assertions in general:
when an assertion is fired, pass the offending piece of data to AssertionError
the checks performed inside an assertion are usually simple boolean conditions - side effects are
usually to be avoided
since assertions can be disabled, the behaviour of a program must not depend upon assertions
being executed
409
all classes
all classes in the com.data package, and all of its sub packages
See Also :
Conditional compile
Pass all pertinent data to exceptions
See Also :
Naming conventions
If arguments need to be passed to the constructor, then these alternatives may be used instead:
Class.getConstructor
Constructor.newInstance
The most common use of reflection is to instantiate a class whose generic type is known at design-time,
but whose specific implementation class is not. See the plugin topic for an example. Other uses of
reflection are rather rare, and appear mostly in special-purpose programs.
Example
Interpreter
import java.util.*;
/**
* Parse a line of text and return a result.
*/
public interface Interpreter {
/**
* @param aLine is non-null.
* @param aResult is a non-null, empty List which acts as an "out"
* parameter; when returned, aResult must contain a non-null, non-empty
* List of items which all have a <code>toString</code> method, to be used
* for displaying a result to the user.
*
* @return true if the user has requested to quit the Interpreter.
* @exception IllegalArgumentException if a param does not comply.
*/
boolean parseInput(String aLine, List<Object> aResult);
/**
* Return the text to be displayed upon start-up of the Interpreter.
*/
String getHelloPrompt();
}
The task is to create a concrete implementation of this interface at runtime, using only the name of a
class as input. In this example, the user inputs a package-qualified class name directly on the command
line. Then, a corresponding Object is created and cast to the expected type (here, Interpreter). The
object can then be used in the same way as any other object.
import
import
import
import
import
import
java.io.BufferedReader;
java.io.IOException;
java.io.InputStreamReader;
java.io.Reader;
java.util.ArrayList;
java.util.List;
/**
* Sends text back and forth between the command line and an
* Interpreter. JDK less than 6.
*/
public final class Console {
/**
* Build and launch a specific <code>Interpreter</code>, whose
* package-qualified name is passed in on the command line.
*/
public static void main(String... aArguments) {
try {
Class theClass = Class.forName(aArguments[0]);
Interpreter interpreter = (Interpreter)theClass.newInstance();
Console console = new Console(interpreter);
console.run();
}
catch (ClassNotFoundException ex){
System.err.println(ex + " Interpreter class must be in class path.");
}
catch(InstantiationException ex){
System.err.println(ex + " Interpreter class must be concrete.");
}
catch(IllegalAccessException ex){
System.err.println(ex + " Interpreter class must have a no-arg constructor.");
}
}
public Console(Interpreter aInterpreter) {
if (aInterpreter == null) {
throw new IllegalArgumentException("Cannot be null.");
}
fInterpreter = aInterpreter;
}
/**
* Display a prompt, wait for a full line of input, and then parse
414
See Also :
Console input
Abstract Factory
Plugin Factory
415
Constructors in general
Constructors:
if super or this are called, they can only be called on the first line of the constructor
an exception can be thrown if a parameter is invalid
you should ensure that the constructed object is in a valid state
constructors should never call an overridable method (an overridable method is one which is
neither private, static, nor final)
constructors are never synchronized
constructors can create thread objects, but the thread should not be started within a constructor
constructors shouldn't pass a this reference to other objects
constructors can be private, in order to restrict construction
Note that a conventional distinction is made in Java between the "default constructor" and a "noargument constructor":
the default constructor is the constructor provided by the system in the absence of any constructor
provided by the programmer. Once a programmer supplies any constructor whatsoever, the default
constructor is no longer supplied.
a no-argument constructor, on the other hand, is a constructor provided by the programmer which
takes no arguments.
This distinction is necessary because the behavior of the two kinds of constructor are unrelated: a default
constructor has a fixed behavior defined by Java, while the behavior of a no-argument constructor is
defined by the application programmer.
Example
Resto
is an immutable class, since its state cannot change after construction. Note that:
all validation is performed in its constructor
almost all of its javadoc is concerned with stating precisely what is to be passed to the constructor
package hirondelle.fish.main.resto;
import
import
import
import
import
import
import
import
import
hirondelle.web4j.model.ModelCtorException;
hirondelle.web4j.model.ModelUtil;
hirondelle.web4j.model.Id;
hirondelle.web4j.security.SafeText;
hirondelle.web4j.model.Decimal;
static hirondelle.web4j.model.Decimal.ZERO;
hirondelle.web4j.model.Check;
hirondelle.web4j.model.Validator;
static hirondelle.web4j.util.Consts.FAILS;
*/
public Resto(
Id aId, SafeText aName, SafeText aLocation, Decimal aPrice, SafeText aComment
) throws ModelCtorException {
fId = aId;
fName = aName;
fLocation = aLocation;
fPrice = aPrice;
fComment = aComment;
validateState();
}
public
public
public
public
public
417
See Also :
Validate method arguments
Validate state with class invariants
Initializing fields to 0-false-null is redundant
Immutable objects
Private constructor
Avoid JavaBeans style of construction
Constructors shouldn't call overridables
Don't pass 'this' out of a constructor
Constructors shouldn't start threads
418
See Also :
Constructors in general
Designing for subclassing
Overridable methods need special care
See Also :
Constructors in general
Don't pass 'this' out of a constructor
Copy constructors
Copy constructors:
provide an attractive alternative to the rather pathological clone method
are easily implemented
simply extract the argument's data, and forward to a regular constructor
are unnecessary for immutable objects
Example
public final class Galaxy {
419
/**
* Regular constructor.
*/
public Galaxy(double aMass, String aName) {
fMass = aMass;
fName = aName;
}
/**
* Copy constructor.
*/
public Galaxy(Galaxy aGalaxy) {
this(aGalaxy.getMass(), aGalaxy.getName());
//no defensive copies are created here, since
//there are no mutable object fields (String is immutable)
}
/**
* Alternative style for a copy constructor, using a static newInstance
* method.
*/
public static Galaxy newInstance(Galaxy aGalaxy) {
return new Galaxy(aGalaxy.getMass(), aGalaxy.getName());
}
public double getMass() {
return fMass;
}
/**
* This is the only method which changes the state of a Galaxy
* object. If this method were removed, then a copy constructor
* would not be provided either, since immutable objects do not
* need a copy constructor.
*/
public void setMass(double aMass){
fMass = aMass;
}
public String getName() {
return fName;
}
// PRIVATE
private double fMass;
private final String fName;
/** Test harness. */
public static void main (String... aArguments){
Galaxy m101 = new Galaxy(15.0, "M101");
Galaxy m101CopyOne = new Galaxy(m101);
m101CopyOne.setMass(25.0);
System.out.println("M101 mass: " + m101.getMass());
System.out.println("M101Copy mass: " + m101CopyOne.getMass());
Galaxy m101CopyTwo = Galaxy.newInstance(m101);
m101CopyTwo.setMass(35.0);
System.out.println("M101 mass: " + m101.getMass());
System.out.println("M101CopyTwo mass: " + m101CopyTwo.getMass());
}
}
420
See Also :
Defensive copying
Immutable objects
Avoid clone
See Also :
Constructors in general
Constructors shouldn't start threads
If the bytecode of the Quark class is examined, the duplicated operations become clear (here, Oracle's
javac compiler was used):
Method Quark(java.lang.String,double)
0 aload_0
1 invokespecial #1 <Method
java.lang.Object()>
4 aload_0
5 aload_1
6 putfield #2 <Field java.lang.String
fName>
9 aload_0
10 dload_2
11 putfield #3 <Field double fMass>
14 return
Method Quark(java.lang.String,double)
0 aload_0
1 invokespecial #1 <Method
java.lang.Object()>
4 aload_0
5 aconst_null
6 putfield #2 <Field java.lang.String
fName>
9 aload_0
10 dconst_0
11 putfield #3 <Field double fMass>
14 aload_0
15 aload_1
16 putfield #2 <Field java.lang.String
fName>
19 aload_0
20 dload_2
21 putfield #3 <Field double fMass>
24 return
See Also :
Constructors in general
Don't declare local variables before use
423
Examine bytecode
Implementing Serializable
Do not implement Serializable lightly, since it restricts future flexibility, and publicly exposes class
implementation details which are usually private. As well, implementing Serializable correctly is not
trivial.
The serialVersionUID is a universal version identifier for a Serializable class. Deserialization uses
this number to ensure that a loaded class corresponds exactly to a serialized object. If no match is found,
then an InvalidClassException is thrown.
Guidelines for serialVersionUID:
always include it as a field, for example:
private static final long serialVersionUID = 7526472295622776147L;
include this field even in the first version of the class, as a reminder of its importance
don't change the value of this field in future versions, unless you are knowingly making changes to
the class which will render it incompatible with old serialized objects
new versions of Serializable classes may or may not be able to read old serialized objects; it
depends upon the nature of the change; provide a pointer to Oracle's guidelines for what constitutes
a compatible change, as a convenience to future maintainers
Modern IDE's can generate a value of serialVersionUID for you. In addition, the JDK includes the
tool for generating these values. Here are the docs for both Win and Unix. (The class name
you pass to this tool doesn't include the .class extension.)
serialver
readObject
and writeObject:
Other points:
use javadoc's @serial tag to denote Serializable fields
the .ser extension is conventionally used for files representing serialized objects
no static or transient fields undergo default serialization
extendable classes should not be Serializable, unless necessary
inner classes should rarely, if ever, implement Serializable
container classes should usually follow the style of Hashtable , which implements Serializable
by storing keys and values, as opposed to a large hash table data structure
Example
import java.io.Serializable;
import java.text.StringCharacterIterator;
import java.util.*;
424
import java.io.*;
public final class SavingsAccount implements Serializable {
/**
* This constructor requires all fields to be passed as parameters.
*
* @param aFirstName contains only letters, spaces, and apostrophes.
* @param aLastName contains only letters, spaces, and apostrophes.
* @param aAccountNumber is non-negative.
* @param aDateOpened has a non-negative number of milliseconds.
*/
public SavingsAccount(
String aFirstName, String aLastName, int aAccountNumber, Date aDateOpened
){
setFirstName(aFirstName);
setLastName(aLastName);
setAccountNumber(aAccountNumber);
//make a defensive copy of the mutable Date passed to the constructor
setDateOpened(new Date(aDateOpened.getTime()));
//there is no need here to call validateState.
}
public SavingsAccount(){
this("FirstName", "LastName", 0, new Date(System.currentTimeMillis()));
}
public String getFirstName(){
return fFirstName;
}
public String getLastName(){
return fLastName;
}
public int getAccountNumber(){
return fAccountNumber;
}
/**
* Returns a defensive copy of the field.
* The caller may change the state of the returned object in any way,
* without affecting the internals of this class.
*/
public Date getDateOpened() {
return new Date(fDateOpened.getTime());
}
/**
* Names must contain only letters, spaces, and apostrophes.
* Validate before setting field to new value.
*
* @throws IllegalArgumentException if the new value is not acceptable.
*/
public void setFirstName(String aNewFirstName) {
validateName(aNewFirstName);
fFirstName = aNewFirstName;
}
/**
* Names must contain only letters, spaces, and apostrophes.
* Validate before setting field to new value.
*
* @throws IllegalArgumentException if the new value is not acceptable.
*/
public void setLastName (String aNewLastName) {
validateName(aNewLastName);
fLastName = aNewLastName;
}
/**
* Validate before setting field to new value.
*
* @throws IllegalArgumentException if the new value is not acceptable.
*/
public void setAccountNumber(int aNewAccountNumber){
validateAccountNumber(aNewAccountNumber);
fAccountNumber = aNewAccountNumber;
}
425
See Also :
427
Type-Safe Enumerations
Singleton
Serialization and subclassing
import java.io.*;
/** A Serializable subclass. */
public final class Tyranny extends Government implements Serializable {
public Tyranny(String aWinningParty) {
super(aWinningParty);
}
// PROTECTED
protected String draftLegislation() {
return "Forbid all use of C++.";
}
// PRIVATE
/**
* Custom deserialization is needed.
*/
private void readObject(
ObjectInputStream aStream
) throws IOException, ClassNotFoundException {
aStream.defaultReadObject();
//manually deserialize and init superclass
String winningParty = (String)aStream.readObject();
init(winningParty);
}
/**
* Custom serialization is needed.
*/
private void writeObject(ObjectOutputStream aStream) throws IOException {
aStream.defaultWriteObject();
//manually serialize superclass
aStream.writeObject(getWinningParty());
}
}
429
See Also :
Implementing Serializable
Designing for subclassing
Avoid ThreadGroup
was originally intended for applet security, and should almost always be ignored. The
services it provides are limited, and sometimes even flawed.
ThreadGroup
430
lock-always - the caller always needs to obtain an external lock before performing any operation.
Instances are mutable, and the implementation of the class performs no internal synchronization.
lock-sometimes - the caller sometimes needs to obtain a lock externally before performing some
operations. Instances are mutable, and the implementation of the class performs most synchronization
internally. The caller usually doesn't need to obtain an external lock, but there are some operations in
which the caller does indeed need to obtain an external lock. The classic example of this is iteration over
a collection.
lock-hostile - operations cannot be performed safely in a multi-threaded environment at all, even when
an external lock is always obtained. This is a rare pathological case. It usually occurs when static data is
modified without proper synchronization.
In practice, lock-always and lock-never are likely the most common cases.
To help you with documenting thread-safety, you might consider using:
annotations, for example those defined by Brian Goetz
javadoc's -tag option, which lets you define simple custom javadoc tags
Another reasonable option is to state in the javadoc overview that, unless stated otherwise, a class is to be
considered lock-always.
See Also :
Immutable objects
Use javadoc liberally
Synchronized is implementation detail
See Also :
Stack trace as String
Handle InterruptedException
In thread-related code, you will often need to handle an InterruptedException . There are two common
ways of handling it:
just throw the exception up to the caller (perhaps after doing some clean up)
call the interrupt method on the current thread
In the second case, the standard style is:
try {
...thread-related code...
}
catch(InterruptedException ex){
...perhaps some error handling here...
//re-interrupt the current thread
Thread.currentThread().interrupt();
}
Background
In multi-threaded code, threads can often block; the thread pauses execution until some external
condition is met, such as :
a lock is released
another thread completes an operation
some I/O operation completes
and so on...
Threads can be interrupted. An interrupt asks a thread to stop what it's doing in an orderly fashion.
However, the exact response to the interrupt depends on the thread's state, and how the thread is
implemented:
if the thread is currently blocking
stop blocking early
throw InterruptedException
else thread is doing real work
thread's interrupted status is set to true
if thread polls isInterrupted() periodically
(which is preferred)
orderly cleanup and stop execution
throw InterruptedException
else
regular execution continues
The main idea here is that an interrupt should not shutdown a thread immediately, in the middle of a
computation. Rather, a thread and it's caller communicate with each other to allow for an orderly
shutdown.
See also this article by Brian Goetz.
Java programs always begin with a main method, called in a user thread.
There are two kinds of threads in Java. They are distinguished soley by how they affect program
termination:
user threads, which prevent a program from terminating.
daemon threads, which don't prevent a program from terminating.
A program terminates when there are no more user threads left to execute.
In addition, all user threads are created equal. In particular, the first user thread created upon launch of a
program has no special status, and its termination does not necessarily imply that the entire program will
terminate.
Example
The FlightSimulator class exists only to launch two other (user) threads. The thread which launches
FlightSimulator itself quickly ends, but the program still continues, since there are two other user
threads still running.
Output from a sample run of FlightSimulator :
Running Flight Simulator.
Terminating the original user thread.
Running Charles de Gaulle Airport.
Charles de Gaulle Has Available Runway:
Flight 8875: waiting for runway...
Charles de Gaulle Has Available Runway:
Flight 8875: taking off now...
Flight 8875: flying now...
Charles de Gaulle Has Available Runway:
Charles de Gaulle Has Available Runway:
Charles de Gaulle Has Available Runway:
Charles de Gaulle Has Available Runway:
Charles de Gaulle Has Available Runway:
Charles de Gaulle Has Available Runway:
Charles de Gaulle Has Available Runway:
Charles de Gaulle Has Available Runway:
Charles de Gaulle Has Available Runway:
Flight 8875: waiting for runway...
Charles de Gaulle Has Available Runway:
Flight 8875: landing now...
Charles de Gaulle Has Available Runway:
Charles de Gaulle Has Available Runway:
false
true
false
true
false
true
false
true
false
true
false
true
false
true
planeOne.start();
//notice that this user thread now ends, but the program itself does
//NOT end since the threads created above are also user
//threads. All user threads have equal status, and there
//is nothing special about the thread which launches a program.
System.out.println("Terminating the original user thread.");
}
}
See Also :
Objects communicating across threads
Use System.exit with care
import java.util.List;
import java.util.concurrent.CountDownLatch;
435
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.print.PrintService;
final class PrinterListDAO {
/** This must be called early upon startup. */
static void init(){
fLatch = new CountDownLatch(1);
fWorker = new PrinterListWorker(fLatch);
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(fWorker);
executor.shutdown();//reclaims resources
}
/** Return the list of printers that can print PDFs (double-sided, portrait).*/
List<PrintService> getPrinters(){
try {
//block until the worker has set the latch to 0:
fLatch.await();
}
catch (InterruptedException ex){
log(ex.toString());
Thread.currentThread().interrupt();
}
return fWorker.getPrinterList();
}
// PRIVATE
/** Used to communicate between threads. */
static private CountDownLatch fLatch;
static private PrinterListWorker fWorker;
private static void log(String aMsg){
System.out.println(aMsg);
}
}
import
import
import
import
import
import
import
import
import
java.util.*;
java.util.concurrent.CountDownLatch;
javax.print.PrintService;
javax.print.PrintServiceLookup;
javax.print.attribute.HashPrintRequestAttributeSet;
javax.print.attribute.PrintRequestAttributeSet;
javax.print.attribute.standard.OrientationRequested;
javax.print.attribute.standard.Sides;
javax.print.DocFlavor;
fLatch.countDown();
}
/** Return an unmodifiable list of printers. */
List<PrintService> getPrinterList(){
return Collections.unmodifiableList(fPrintServices);
}
// PRIVATE
/** Used to communicate between threads. */
private CountDownLatch fLatch;
private List<PrintService> fPrintServices;
private static void log(String aMsg){
System.out.println(aMsg);
}
}
Older JDKs
If a modern JDK is not available to you, then you can't use the services of java.util.concurrent .
Instead, you will need to rely on other means - often the Thread class, and the wait and notify methods
of the Object class.
The following technique uses the wait-loop idiom and notifyAll . To be safe, always use notifyAll
instead of notify. (As an optimization, notify can be used instead of notifyAll , but only if you know
exactly what you are doing.)
Important points:
wait
wait
Example
In this example, the Airplane always needs to check with the Airport to see if it has an available
runway before it's able to take off or land.
/** Uses wait loop idiom for inter-thread communication. */
public final class Airplane implements Runnable {
public Airplane (Airport aAirport, String aFlightId){
fAirport = aAirport;
fFlightId = aFlightId;
}
@Override public void run() {
takeOff();
fly();
land();
}
// PRIVATE
private Airport fAirport;
private String fFlightId;
private void takeOff() {
synchronized( fAirport ) {
437
Thread.sleep(1000);
}
catch (InterruptedException ex){
System.err.println(ex);
Thread.currentThread().interrupt();
}
}
}
//PRIVATE
private boolean fHasAvailableRunway = true;
private String fName;
}
false
true
false
true
false
true
false
true
false
true
false
true
false
true
439
See Also :
Launch thread is just another user thread
java.io.IOException;
java.net.MalformedURLException;
java.net.URL;
java.net.URLConnection;
java.util.ArrayList;
java.util.Arrays;
java.util.Collection;
java.util.List;
java.util.concurrent.Callable;
java.util.concurrent.CompletionService;
java.util.concurrent.ExecutionException;
java.util.concurrent.ExecutorCompletionService;
java.util.concurrent.ExecutorService;
java.util.concurrent.Executors;
java.util.concurrent.Future;
/**
Ping N web sites in parallel.
The ping simply does a GET, and looks at the first header line.
This example could be applied to many sorts of similar tasks.
<P>No time-out is used here. As usual, be wary of warm-up
of the just-in-time compiler. You might want to use -Xint.
*/
public final class CheckSites {
/** Run this tool. */
public static final void main(String... aArgs) {
CheckSites checker = new CheckSites();
try {
log("Parallel, report each as it completes:");
440
checker.pingAndReportEachWhenKnown();
log("Parallel, report all at end:");
checker.pingAndReportAllAtEnd();
log("Sequential, report each as it completes:");
checker.pingAndReportSequentially();
}
catch(InterruptedException ex){
Thread.currentThread().interrupt();
}
catch(ExecutionException ex){
log("Problem executing worker: " + ex.getCause());
}
catch(MalformedURLException ex){
log("Bad URL: " + ex.getCause());
}
log("Done.");
}
/**
Check N sites, in parallel, using up to 4 threads.
Report the result of each 'ping' as it comes in.
(This is likely the style most would prefer.)
*/
void pingAndReportEachWhenKnown() throws InterruptedException, ExecutionException
int numThreads = URLs.size() > 4 ? 4 : URLs.size(); //max 4 threads
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
CompletionService<PingResult> compService = new
ExecutorCompletionService<>(executor);
for(String url : URLs){
Task task = new Task(url);
compService.submit(task);
}
for(String url : URLs){
Future<PingResult> future = compService.take();
log(future.get());
}
executor.shutdown(); //always reclaim resources
}
/**
Check N sites, in parallel, using up to 4 threads.
Report the results only when all have completed.
*/
void pingAndReportAllAtEnd() throws InterruptedException, ExecutionException {
Collection<Callable<PingResult>> tasks = new ArrayList<>();
for(String url : URLs){
tasks.add(new Task(url));
}
int numThreads = URLs.size() > 4 ? 4 : URLs.size(); //max 4 threads
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
List<Future<PingResult>> results = executor.invokeAll(tasks);
for(Future<PingResult> result : results){
PingResult pingResult = result.get();
log(pingResult);
}
executor.shutdown(); //always reclaim resources
}
/**
Check N sites, but sequentially, not in parallel.
Does not use multiple threads at all.
*/
void pingAndReportSequentially() throws MalformedURLException {
for(String url : URLs){
PingResult pingResult = pingAndReportStatus(url);
log(pingResult);
}
}
// PRIVATE
private static final List<String> URLs = Arrays.asList(
"http://www.youtube.com/", "http://www.google.ca/",
"http://www.date4j.net", "http://www.web4j.com"
);
private static void log(Object aMsg){
441
System.out.println(String.valueOf(aMsg));
}
/** Try to ping a URL. Return true only if successful. */
private final class Task implements Callable<PingResult> {
Task(String aURL){
fURL = aURL;
}
/** Access a URL, and see if you get a healthy response. */
@Override public PingResult call() throws Exception {
return pingAndReportStatus(fURL);
}
private final String fURL;
}
private PingResult pingAndReportStatus(String aURL) throws MalformedURLException {
PingResult result = new PingResult();
result.URL = aURL;
long start = System.currentTimeMillis();
URL url = new URL(aURL);
try {
URLConnection connection = url.openConnection();
int FIRST_LINE = 0;
String firstLine = connection.getHeaderField(FIRST_LINE);
result.SUCCESS = true;
long end = System.currentTimeMillis();
result.TIMING = end - start;
}
catch(IOException ex){
//ignore - fails
}
return result;
}
/** Simple struct to hold all the date related to a ping. */
private static final class PingResult {
String URL;
Boolean SUCCESS;
Long TIMING;
@Override public String toString(){
return "Result:" + SUCCESS + " " +TIMING + " msecs " + URL;
}
}
}
For executing tasks, there are 3 important interfaces, which are linked in an inheritance chain. Starting at
the top level, they are:
442
submitted - the task has been submitted to an Executor , but hasn't been started yet. In this state,
the task can always be cancelled.
started - the task has begun. The task may be cancelled, if the task is responsive to interruption.
completed - the task has finished. Cancelling a completed task has no effect.
See Also :
Modernize old code
Read-write locks
In the great majority of database applications, the frequency of read operations greatly exceeds the
frequency of write operations. This is why databases implement read-write locks for their records, which
allow for concurrent reading, but still demand exclusive writing. This can markedly increase
performance.
Occasionally, a class may benefit as well from a read-write lock, for exactly the same reasons - reads are
much more frequent than writes. As usual, you should measure performance to determine if a read-write
lock is really improving performance.
Here's a sketch of how to use such locks. It uses the ReentrantReadWriteLock of the
java.util.concurrent package.
import
import
import
import
java.util.LinkedHashMap;
java.util.Map;
java.util.concurrent.locks.Lock;
java.util.concurrent.locks.ReentrantReadWriteLock;
/**
User preferences, using a read-write lock.
<P>The context: preference information is read in upon startup.
The config data is 'read-mostly': usually, a caller simply reads the
information. It gets updated only occasionally.
<P>Using a read-write lock means that multiple readers can access the
same data simultaneously. If a single writer gets the lock, however, then
all other callers (either reader or writer) will block until the lock is
released by the writer.
444
*/
See Also :
Use finally to unlock
Acquiring the instance lock only blocks other threads from invoking a synchronized instance method; it
does not block other threads from invoking an un-synchronized method, nor does it block them from
invoking a static synchronized method.
Similarly, acquiring the static lock only blocks other threads from invoking a static synchronized
method; it does not block other threads from invoking an un-synchronized method, nor does it block them
from invoking a synchronized instance method.
Outside of a method header, synchronized(this) acquires the instance lock.
The static lock can be acquired outside of a method header in two ways:
synchronized(Blah.class) , using
synchronized(this.getClass()) ,
See Also :
Synchronize access to mutable fields
and ScheduledFuture
The first pair is the more modern API. As usual, the more modern API is usually the preferred one. The
main difference between these two APIs is that the first always uses relative times, while the second does
not. If needed, you always can transform a Date into a relative time:
Date futureDate = ...
long startTime = futureDate.getTime() - System.currentTimeMillis();
java.util.concurrent.Executors;
java.util.concurrent.ScheduledExecutorService;
java.util.concurrent.ScheduledFuture;
java.util.concurrent.TimeUnit;
/**
Run a simple task once every second, starting 3 seconds from now.
Cancel the task after 20 seconds.
*/
public final class AlarmClock {
/** Run the example. */
public static void main(String... aArgs) throws InterruptedException {
log("Main started.");
446
ScheduledExecutorService fScheduler;
long fInitialDelay;
long fDelayBetweenRuns;
long fShutdownAfter;
Example 2
This example uses the older classes, Timer and TimerTask .
Here, a task is performed once a day at 4 a.m., starting tomorrow morning.
447
import
import
import
import
import
java.util.Timer;
java.util.TimerTask;
java.util.Calendar;
java.util.GregorianCalendar;
java.util.Date;
See Also :
Timers
*/
// PRIVATE
private boolean fIsStopRequested;
private synchronized boolean isStopRequested() {
return fIsStopRequested;
}
}
See Also :
Prefer modern libraries for concurrency
/**
* A mutable object field.
* The state of a Date can change after construction.
*/
private Date fDateOfDiscovery;
}
See Also :
Immutable objects
Remember the types of intrinsic lock
Document thread safety
451
See Also :
Document thread safety
Here's an example which follows the above pattern, with a read-write lock.
import
import
import
import
java.util.LinkedHashMap;
java.util.Map;
java.util.concurrent.locks.Lock;
java.util.concurrent.locks.ReentrantReadWriteLock;
/**
User preferences, using a read-write lock.
<P>The context: preference information is read in upon startup.
The config data is 'read-mostly': usually, a caller simply reads the
information. It gets updated only occasionally.
<P>Using a read-write lock means that multiple readers can access the
same data simultaneously. If a single writer gets the lock, however, then
all other callers (either reader or writer) will block until the lock is
released by the writer.
<P>(In practice, Brian Goetz reports that the implementation of ConcurrentHashMap
is so good that it would likely suffice in many cases, instead of a read-write
lock.)
*/
public final class Preferences {
452
*/
See Also :
Finally and catch
Read-write locks
Copyright Hirondelle Systems.
453