Chủ Nhật, 30 tháng 10, 2011

Dennis Ritchie Day

In the post Dennis Ritchie Day, Tim O’Reilly "declares this Sunday, October 30 to be Dennis Ritchie Day!" O'Reilly acknowledges that "I don't have the convening power of a Governor Brown" (who declared Sunday, October 16, as Steve Jobs Day), but calls for Dennis Ritchie Day to be observed by "those of us around the world who care." O'Reilly uses this post to enumerate many of the advances that Ritchie has had a hand in throughout the development of modern day processor-based systems. Related posts on this subject include Denver Post's Quillen: Computer superstar's death overlooked, The most important man in tech died last week…It wasn’t Steve Jobs., and Dennis Ritchie: The Shoulders Steve Jobs Stood On.

In Honor of Dennis Ritchie Day is a page for today only (offer expires on Halloween, 31 October 2011), O'Reilly is offering: "Save 50% on C/C++, Linux, and Unix Ebooks & Videos." The page also reminds us that "Ebooks from oreilly.com are DRM-free. You get free lifetime access, multiple file formats, free updates." I'm thinking about getting Unix Power Tools, Third Edition.

Not everyone is happy about books being sold by O'Reilly in honor of Dennis Ritchie Day. However, one poster points out, "If this prompts more people to discover Ritchie's work then I don't necessarily think it's a bad thing."

October 2011 has been a bad month for losing pioneers in our industry. Besides the loss of Steve Jobs (October 5) and Dennis Ritchie (October 12), we also lost John McCarthy in October (October 23). calibraxis suggests we can honor McCarthy by purchasing books on LISP-inspired technologies such as Clojure and Erlang. John Fitzpatrick also points out that we also lost Daniel McCracken this year (July 30) as well.

Thứ Bảy, 29 tháng 10, 2011

Guava Stopwatch

Guava's Stopwatch is another Guava class new to Guava Release 10 (as is Optional, the subject of another recent post). As its name implies, this simple class provides a method to conveniently measure time elapsed between two code points. It has several advantages over use of System.currentTimeMillis() or System.nanoTime(). I don't focus on these advantages here, but the Javadoc documentation for Stopwatch does cover some of these advantages.

As is true of many of Guava's classes, one of the endearing features of Stopwatch is its simplicity and appropriately named methods. The class features two constructors, one that takes no arguments (likely to be most commonly used) and one that accepts an customized extension of the Ticker class. Once an instance of Stopwatch is obtained, it's a simple matter of using methods with "obvious" names like start(), stop(), and reset() to control the stopwatch.

Any given Stopwatch instance records elapsed time in a cumulative fashion. In other words, you can start and stop the stopwatch multiple times (just don't start an already started stopwatch and don't stop an already stopped stopwatch) and the elapsed time accumulates with each start and stop. If that's not what is wanted and a single instance of the stopwatch is to be used to measure independent events (but not concurrent events), then the reset() method is used between the last run's stop() and the next run's start().

I have already alluded to several caveats to keep in mind when using Guava's Stopwatch. First, two successive start() methods should not be invoked on a given instance of Stopwatch without first stopping it with stop() before making the second call to stop(). Stopwatch has an instance method isRunning() that is useful for detecting a running stopwatch before trying to start it again or even before trying to stop one that has already been stopped or was never started. Most of these issues such as starting the stopwatch twice without stopping it or stopping a stopwatch that is not running or was never started lead to IllegalStateExceptions being thrown. Guava developers make use of their own Preconditions class to ascertain these aberrant conditions and to the throwing of these exceptions. The further implication of this, which is spelled out in the Javadoc documentation, is that Stopwatch is not thread-safe and should be used in a single-thread environment.

The methods covered so far handle constructing instances of Stopwatch and managing the stopwatch. However, a stopwatch is almost always useful only when the timed results are available for viewing. The Stopwatch class provides two main methods for accessing elapsed time recorded by the stopwatch instance. One method, elapsedMillis(), is similar to standard Java methods that return milliseconds since epoch time. The big difference here is that Stopwatch is returning milliseconds elapsed between given points in time (start() and stop() calls) versus since an absolute epoch time.

I prefer elapsedTime(TimeUnit) for acquiring the elapsed time recorded in my stopwatch instance. This method makes use of the TimeUnit enum (see my post on TimeUnit) to specify the units that the elapsed time should be expressed in. Both of these methods for reporting elapsed time can be run while the stopwatch is running or after it has stopped.

The following code listing contains a class that demonstrates the methods on Stopwatch that have been highlighted in this post.

StopWatchDemo.java

package dustin.examples;

import static java.lang.System.out;

import com.google.common.base.Stopwatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Demonstrates Guava's (Release 10) Stopwatch class.
*
* @author Dustin
*/
public class StopWatchDemo
{
private final static Logger LOGGER = Logger.getLogger(StopWatchDemo.class.getCanonicalName());

public static void doSomethingJustToBeDoingIt(final int numberOfTimesToDoNothing)
{
for (int count=0; count < numberOfTimesToDoNothing; count++)
{
try
{
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
}
catch (InterruptedException interruptEx)
{
LOGGER.log(Level.INFO, "Don't interrupt me when I'm trying to sleep!", interruptEx);
}
}
}

/**
* Print statistics on Stopwatch-reported times for provided number of loops.
*
* @param numberLoops Number of loops executed.
* @param stopwatch Stopwatch instance with time used statistics.
*/
public static void printElapsedTime(final int numberLoops, final Stopwatch stopwatch)
{
if (stopwatch.isRunning())
{
out.println("WARNING! Your stopwatch is still running!");
}
else // stopwatch not running
{
out.println(numberLoops + " loops required: ");
out.println("\t" + stopwatch.toString(6));
out.println("\t" + stopwatch.elapsedMillis() + " elapsed milliseconds.");
out.println("\t" + stopwatch.elapsedTime(TimeUnit.MINUTES) + " minutes");
out.println("\t" + stopwatch.elapsedTime(TimeUnit.SECONDS) + " seconds");
out.println("\t" + stopwatch.elapsedTime(TimeUnit.MILLISECONDS) + " milliseconds");
out.println("\t" + stopwatch.elapsedTime(TimeUnit.NANOSECONDS) + " nanoseconds");
}
}

public static void main(final String[] arguments)
{
final Stopwatch stopwatch = new Stopwatch();

int numberTimes = 5;
stopwatch.start();
doSomethingJustToBeDoingIt(numberTimes);
stopwatch.stop();
printElapsedTime(numberTimes, stopwatch);

numberTimes = 45;
stopwatch.reset();
stopwatch.start();
doSomethingJustToBeDoingIt(numberTimes);
stopwatch.stop();
printElapsedTime(numberTimes, stopwatch);

numberTimes = 125;
stopwatch.reset();
stopwatch.start();
doSomethingJustToBeDoingIt(numberTimes);
stopwatch.stop();
printElapsedTime(numberTimes, stopwatch);
}
}

When the above code is executed, its output is similar to that shown in the following screen snapshot.

If I comment out the lines that reset the stop watch instance, the stopwatch instance accumulates elapsed time rather than tracking it separately. This difference is shown in the next screen snapshot.

The Guava Stopwatch class makes it easy to perform simple timing measurements to analyze how long certain operations take. It is easy to use and provides the flexibility to readily provide output in the desired time scale.

Thứ Sáu, 28 tháng 10, 2011

Guava's New Optional Class

Guava Release 10 introduces the Optional class, which can be used where one might use a null object. I have built my own classes like this before, but the advantage of Guava's providing it is that it can be easily reused across projects and developers new to an organization might know about it already if they use Guava. In addition, use of the Guava class is advantageous when compared to use of one's own similar or null object class because of the normal advantages associated with open source communities such as more testing and more eyes on the code.

As I wrote about in the post Effective Java NullPointerException Handling, I generally don't like to have methods return null. With methods that return collections, returning an empty collection is an easy solution. When methods return arbitrary objects, however, it is not always easy to indicate an "empty" condition. Guava's Optional class can fill this need nicely.

The Optional class has no constructors, but provides three public static methods for acquiring an instance of the class. Optional.fromNullable(T) allows a null or non-null reference to be provided and wrapped in the new Optional instance. If the passed-in parameter is null, then the instance does not have any reference stored and is an "absent" instance. If the passed-in parameter is not null, then that non-null reference is stored within the new Optional instance.

Another publicly available static method for getting an instance of Optional is Optional.of(T). This method acts like Optional.fromNullable(T) except that it expects a non-null parameter to be passed to it. If null is passed to it, a NullPointerException is thrown.

The third static method for acquiring an instance of Optional is Optional.absent(). This is useful when one has code that knows the parameter that would have been provided to Optional.fromNullable(T) is null and it is more clear to express than an "absent" version of Optional should be returned.

Once an instance of Optional has been acquired (such as returned by a method), there are several instance methods that can be called on that instance. The Optional.isPresent() method is useful for determining if a given Optional instance has a non-null parameter within it.

Once it is known (such as by calling Optional.isPresent()) that an Optional instance contains a non-null reference, the Optional.get() method returns that stored non-null reference. Note that if there is no non-null reference, an exception is thrown upon this method's invocation, making it a good idea to call isPresent() first.

There are several overloaded or methods that allow defaults to be specified that are returned when the Optional instance does not contain its own non-null reference. These methods provide a nice way to specify a default value to be returned when it would be null returned otherwise (or an exception thrown). There may be times, especially when interacting with pre-existing APIs, that it is desirable to return a null rather than an "absent" Optional instance. This is easily accomplished through use of Optional.orNull() which returns either the reference type the instance contains or else returns null if it doesn't contain a non-null reference.

Many of the concepts discussed above are exemplified in the following code listing.

GuavaOptional.java

package dustin.examples;

import static java.lang.System.out;

import com.google.common.base.Optional;
import com.google.common.collect.Maps;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Demonstrate use of Guava's Optional class.
*
* @author Dustin
*/
public class GuavaOptional
{
/** java.util.logging Logger handle. */
private final static Logger LOGGER = Logger.getLogger(GuavaOptional.class.getCanonicalName());

/** Map of state names to the names of that state's capital. */
private final static Map<String, String> stateCapitals;

static
{
final Map<String, String> tempStatesToCapitals = Maps.newHashMap();
tempStatesToCapitals.put("Alaska", "Juneau");
tempStatesToCapitals.put("Arkansas", "Little Rock");
tempStatesToCapitals.put("Colorado", "Denver");
tempStatesToCapitals.put("Idaho", "Boise");
tempStatesToCapitals.put("Utah", "Salt Lake City");
tempStatesToCapitals.put("Wyoming", "Cheyenne");
stateCapitals = Collections.unmodifiableMap(tempStatesToCapitals);
}

/**
* Provide the name of the capital of the provided state. This method uses
* Guava's Optional.fromNullable(T) to ensure that a non-null Optional instance
* is always returned with a non-null contained reference or without a
* contained reference.
*
* @param stateName State whose capital is desired.
* @return Instance of Optional possibly containing the capital corresponding
* to provided the state name, if available.
*/
public Optional<String> getStateCapital(final String stateName)
{
return Optional.fromNullable(stateCapitals.get(stateName));
}

/**
* Provide quotient resulting from dividing dividend by divisor.
*
* @param dividend Dividend used in division.
* @param divisor Divisor used in division.
* @return Optional wrapper potentially containing Quotient from dividing
* dividend by divisor.
*/
public Optional<BigDecimal> getQuotient(final BigDecimal dividend, final BigDecimal divisor)
{
BigDecimal quotient;
try
{
quotient = dividend.divide(divisor);
}
catch (Exception ex)
{
LOGGER.log(Level.SEVERE, "Unable to divide " + dividend + " by " + divisor + "-", ex);
quotient = null;
}
return Optional.fromNullable(quotient);
}

/**
* Main function for demonstrating Guava's optional class.
*
* @param arguments Command-line arguments; none expected.
*/
public static void main(final String[] arguments)
{
final GuavaOptional me = new GuavaOptional();

final String wyoming = "Wyoming";
final Optional<String> wyomingCapitalWrapper = me.getStateCapital(wyoming);
if (wyomingCapitalWrapper.isPresent())
{
out.println("Capital of " + wyoming + " is " + wyomingCapitalWrapper.get());
}
out.println("Capital of " + wyoming + " is " + wyomingCapitalWrapper.orNull());

final String northDakota = "North Dakota";
final Optional<String> northDakotaCapitalWrapper = me.getStateCapital(northDakota);
out.println("Capital of " + northDakota + " is " + northDakotaCapitalWrapper);
out.println("Capital of " + northDakota + " is " + northDakotaCapitalWrapper.or("Unspecified"));
out.println("Capital of " + northDakota + " is " + northDakotaCapitalWrapper.orNull());

final Optional<String> nullOptional = me.getStateCapital(null);
out.println("Capital of null is " + nullOptional);


final BigDecimal dividend = new BigDecimal("5.0");
final BigDecimal divisor = new BigDecimal("0.0");
final Optional<BigDecimal> quotientWrapper = me.getQuotient(dividend, divisor);
out.println( "Quotient of " + dividend + " / " + divisor + " is "
+ quotientWrapper);
}
}

Proper use of Guava's Optional class can reduce NullPointerExceptions (NPEs) and increase the expressiveness of return values. However, even using Optional does not mean the end of exceptions. For example, passing a null as the parameter to the static method Optional.of(T) when attempting to obtain an instance of Optional leads to an NPE. This is not too surprising given that the method's documentation states, "Returns an Optional instance containing the given non-null reference." However, the Javadoc for the method does not explicitly state that this is thrown. It is interesting that this condition is detected using Guava's own Preconditions class. Another exception that can occur is IllegalStateException when the Optional.get() method is called on an instance of Optional that does not have a non-null reference contained within it. This method's Javadoc documentation does state conditions under which this one can be thrown.

Guava seems to specialize in providing common functionality and utilities many of us develop for our own code bases. The advantages of using Guava's classes include being able to use classes that Java developers outside a given organization have a chance to know about as well as the advantages of using products supported by open source communities. Guava's Optional provides a convenient and easy-to-use mechanism for adding safety and expressiveness to a code base. There are still situations where I prefer a more specific type of null object (the object represents either null or a particular non-null type), but Optional is nice for those situations where I need a general solution because I cannot justify the cost of creating a specific solution.

Guava Presents Java Throwable Stack Trace as Single String

Tip #5 of my blog post Ten Tips for Using Java Stack Traces demonstrates two example of using Java code to extract a Throwable's (implying Error or Exception) stack trace into a single String representation using either PrintWriter or PrintStream. These are not difficult to implement, but must be implemented for every code base in which it is desirable to get a String representing the entire stack trace. For a program that is already using Guava for other niceties it provides, a nice alternative is to use Guava's Throwables class and to specifically use its aptly named getStackTraceAsString(Throwable) method to extract a String from a provided Throwable.

The Javadoc documentation describes how simple it is to use this method:

Returns a string containing the result of toString(), followed by the full, recursive stack trace of throwable. Note that you probably should not be parsing the resulting string; if you need programmatic access to the stack frames, you can call Throwable.getStackTrace().

The Javadoc points out that this String should not be used for programmatic access of the stack trace. Instead, StackTraceElements should be accessed and manipulated from Throwable.getStackTrace() as I discussed in The Surprisingly Simple StackTraceElement.

Guava's Throwables class has other useful methods for dealing with Java's Exceptions and Errors as well. There are multiple methods for propagating Throwables based on certain conditions and there are methods for more precise control of chained exceptions. For example, Throwables.getRootCause(Throwable) returns the exception that is often most important (see Tips #2 and #3 in Ten Tips for Using Java Stack Traces) in a chain (the "innermost" or "first throwable" in the chain) from a provided Throwable. Another example is the method Throwables.getCausalChain() that presents the chained Throwables in a Java List.

I probably would not include Guava in a project solely to extract Throwable stack traces into Strings because I could write my own static method with a few lines of code to do the same thing and put that in a utilities or common package in my code base. However, if I'm using Guava anyway for other reasons and including it in my dependencies, then it is preferable to use the Guava-provided functionality rather than rolling out my own that others need to be made aware of and learn about.

Thứ Ba, 25 tháng 10, 2011

Guava Preconditions Class

Anyone who's written much Java has probably written methods that begin with conditionals that verify either the provided parameters or the state of the object being acted upon before proceeding with the remainder of the method's implementation. These can add verbosity to the method and sometimes, especially if there are multiple checks, can almost drown out the interesting business logic of the method. One way to reduce this clutter is to use Java assertions, but these are disabled at runtime by default. In fact, it is advised to "not use assertions for argument checking in public methods" for this reason and to use appropriate runtime exceptions instead. Guava provides a handy Preconditions class with aesthetic advantages of assertions, but which uses the normal Java exception mechanism and is not disabled at runtime. This post provides some illustrative examples of Guava's Preconditions class in action.

The next code listing shows contrived examples using Guava's Preconditions class. The example demonstrate use of static methods on the Preconditions class to check a parameter for null, check a parameter's placement in a provided array, check to ensure a valid value has been provided for a parameter, and to check that the state of the object on which the method is invoked is appropriate for that method's execution. Note also that I made use of the static import for the Preconditions class so that I am able to call its static methods without need to scope each call with the class name.

GuavaPreconditionsDemo.java

package dustin.examples;

import static java.lang.System.err;
import static com.google.common.base.Preconditions.*;

/**
* Simple demonstration of Guava's Preconditions support.
*
* @author Dustin
*/
public class GuavaPreconditionsDemo
{
private final boolean initialized = false;

/**
* Demonstrate Guava's Preconditions.checkNotNull methods.
*
* @param parameter Parameter that is checked for null-ness.
*/
public void testForNonNullArgument(final String parameter)
{
final String localParameter = checkNotNull(parameter, "Provided parameter is unacceptably null.");
}

public void testDivisorNotZero(final int divisor)
{
checkArgument(divisor != 0, "Zero divisor not allowed.");
}

public void testArrayElement(final String[] strArray, final int indexNumber)
{
final int index = checkElementIndex(indexNumber, strArray.length, "String array index number");
}

public void testArrayPosition(final String[] strArray, final int indexNumber)
{
final int index = checkPositionIndex(indexNumber, strArray.length, "String array index number");
}

public void testState()
{
checkState(this.initialized, "Cannot perform action because not initialized.");
}

public static void printHeader(final String newHeaderText)
{
err.println("\n==========================================================");
err.println("== " + newHeaderText);
err.println("==========================================================");
}

/**
* Main function for executing demonstrations of Guava's Preconditions.
*/
public static void main(final String[] arguments)
{
final GuavaPreconditionsDemo me = new GuavaPreconditionsDemo();

printHeader("Preconditions.checkNotNull");
try
{
me.testForNonNullArgument(null);
}
catch (NullPointerException npe)
{
npe.printStackTrace();
}

printHeader("Preconditions.checkArgument");
try
{
me.testDivisorNotZero(0);
}
catch (IllegalArgumentException illArgEx)
{
illArgEx.printStackTrace();
}

printHeader("Preconditions.checkElementIndex");
try
{
me.testArrayElement(new String[]{"Dustin", "Java"}, 3);
}
catch (IndexOutOfBoundsException ioobEx)
{
ioobEx.printStackTrace();
}

printHeader("Preconditions.checkPositionIndex");
try
{
me.testArrayPosition(new String[]{"Dustin", "Java"}, 3);
}
catch (IndexOutOfBoundsException ioobEx)
{
ioobEx.printStackTrace();
}

printHeader("Preconditions.checkState");
try
{
me.testState();
}
catch (IllegalStateException illStateEx)
{
illStateEx.printStackTrace();
}
}
}

Each of the cases demonstrated in the code listing above checked for preconditions on the method parameters or on the state of the object against which the method was being invoked without use of "noisy" conditional statements. Use of the static import allowed very concise expression of the conditions to be checked, Much of the class is the "main" function, used as a "test driver" in this case. I placed the calls within try-catch block to make sure all demonstrations are executed. The output of running the above is shown next.

Output From Executing Above Class

==========================================================
== Preconditions.checkNotNull
==========================================================
java.lang.NullPointerException: Provided parameter is unacceptably null.
at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:204)
at dustin.examples.GuavaPreconditionsDemo.testForNonNullArgument(GuavaPreconditionsDemo.java:22)
at dustin.examples.GuavaPreconditionsDemo.main(GuavaPreconditionsDemo.java:62)

==========================================================
== Preconditions.checkArgument
==========================================================
java.lang.IllegalArgumentException: Zero divisor not allowed.
at com.google.common.base.Preconditions.checkArgument(Preconditions.java:88)
at dustin.examples.GuavaPreconditionsDemo.testDivisorNotZero(GuavaPreconditionsDemo.java:27)
at dustin.examples.GuavaPreconditionsDemo.main(GuavaPreconditionsDemo.java:72)

==========================================================
== Preconditions.checkElementIndex
==========================================================
java.lang.IndexOutOfBoundsException: String array index number (3) must be less than size (2)
at com.google.common.base.Preconditions.checkElementIndex(Preconditions.java:301)
at dustin.examples.GuavaPreconditionsDemo.testArrayElement(GuavaPreconditionsDemo.java:32)
at dustin.examples.GuavaPreconditionsDemo.main(GuavaPreconditionsDemo.java:82)

==========================================================
== Preconditions.checkPositionIndex
==========================================================
java.lang.IndexOutOfBoundsException: String array index number (3) must not be greater than size (2)
at com.google.common.base.Preconditions.checkPositionIndex(Preconditions.java:351)
at dustin.examples.GuavaPreconditionsDemo.testArrayPosition(GuavaPreconditionsDemo.java:37)
at dustin.examples.GuavaPreconditionsDemo.main(GuavaPreconditionsDemo.java:92)

==========================================================
== Preconditions.checkState
==========================================================
java.lang.IllegalStateException: Cannot perform action because not initialized.
at com.google.common.base.Preconditions.checkState(Preconditions.java:145)
at dustin.examples.GuavaPreconditionsDemo.testState(GuavaPreconditionsDemo.java:42)
at dustin.examples.GuavaPreconditionsDemo.main(GuavaPreconditionsDemo.java:102)

The different static Preconditions methods throw different types of runtime exceptions when a specified condition is violated. They throw exceptions that tend to be appropriate for the particular case that is violated. This means that in most cases the result of a Preconditions static method call is the same as one would likely throw explicitly for that condition, but with much less code to make the check and throw the exception. I did not show it in this post, but overloaded versions of these static methods also allow for string arguments to be provided to fill placeholders in a patterned String. This is helpful for placing values associated with the errant condition in the exception message.

Conclusion

Guava eases the coding and improves the fluency and readability of contract checking in methods. It offers the advantages of assertions' concise syntax coupled with the advantages of traditional throwing of runtime exceptions.

Between Two Points with Groovy

I needed to check a ninth grader's math homework related to calculating three things about a line between two points. The assignment was to calculate the distance between two points, the slope of a line connecting the two points, and the midpoint on a line between the two points. Because I was tired after a long day and because I inherently possess the long-advertised software developer "laziness," I decided to write up a quick Groovy script to calculate these numbers for me. There were enough problems to check that I believe I either broke even or even came out a little ahead in terms of time required to write the script versus plugging the numbers into a calculator. I include that script in this post for easy future access when needed. It also provides a chance to look at some Groovy advantages.

findInfoBetweenTwoPoints.groovy

#!/usr/bin/env groovy
// findInfoBetweenTwoPoints.groovy x1 y1 x2 y2

if (args.length < 4)
{
println "You must enter four numerals: x1 y1 x2 y2"
System.exit(-1)
}

def inputX1 = args[0]
if (!inputX1.isNumber())
{
println "${inputX1} is NOT a numeral."
System.exit(-2)
}
def inputY1 = args[1]
if (!inputY1.isNumber())
{
println "${inputY1} is NOT a numeral."
System.exit(-3)
}
def inputX2 = args[2]
if (!inputX2.isNumber())
{
println "${inputX2} is NOT a numeral."
System.exit(-4)
}
def inputY2 = args[3]
if (!inputY2.isNumber())
{
println "${inputY2} is NOT a numeral."
System.exit(-5)
}

def x1 = inputX1 as BigDecimal
def y1 = inputY1 as BigDecimal
def x2 = inputX2 as BigDecimal
def y2 = inputY2 as BigDecimal

def distanceSquared = Math.pow(y2-y1, 2.0) + Math.pow(x2-x1, 2.0)
def distance = Math.sqrt(distanceSquared)
def slope = x2 != x1 ? ((y2 - y1)/(x2 - x1)) : Double.POSITIVE_INFINITY
if (slope == 0)
{
println "Horizontal line!"
}
else if (slope == Double.POSITIVE_INFINITY)
{
println "Vertical line!"
}
def midPointX = (x1 + x2) / 2
def midPointY = (y1 + y2) / 2

println "For points (${x1}, ${y1}) and (${x2}, ${y2}):";
println "\tDistance Between points: sqrt(${distanceSquared}) = ${distance}"
println "\tSlope (m): ${slope}"
println "\tMidpoint: (${midPointX}, ${midPointY})"

This is a fairly basic Groovy script, but it does remind one of how nice it is to strip away the normal ceremony of having a class and methods and all the other overhead to leave a fairly straightforward logic-heavy script. Some nice Groovy-isms are demonstrated including use of the implicit args handle, use of the as keyword to fluently cast the String arguments into BigDecimal instances, use of Groovy's (GDK) String.isNumber(), and use of placeholders in Groovy Strings for greater readability. It is really easy to quickly identify the core of this script, which is the mathematical functions used to calculate midpoint, slope, and distance.

The following screen snapshot shows a couple of examples of running this script on different values.

Conclusion

It says something for ease of use of a scripting language when it's easier to code up a simple script for performing calculations than it is to use a calculator to perform the same calculations manually. Of course, I could have programmed a sophisticated calculator to do the same thing, but I think it was probably easier to code this script than to go find my calculator or learn how to use and program the ninth grader's calculator!

Thứ Hai, 24 tháng 10, 2011

Guava's Objects Class: Equals, HashCode, and ToString

If you are fortunate enough to be using JDK 7, the newly available Objects class is the obvious (at least to me) choice for implementing the "common" Java object methods such as equals(Object) [with Objects.equals(Object,Object)], hashCode() [with Objects.hashCode(Object) or Objects.hash(Object...)], and toString() [with Objects.toString(Object)] to appropriately override the default Object implementations. I have written posts about using Objects class: JDK 7: The New Objects Class and Java 7 Objects-Powered Compact Equals.

If you're not yet using Java 7, your best choices might be the Apache Commons builders ToStringBuilder and EqualsBuilder and HashCodeBuilder (if you're using a version of Java prior to J2SE 5) or Guava (if you're using J2SE 5 or later). In this post, I look at using Guava's Objects class to implement the three common methods equals, hashCode, and toString().

Without Guava or other library to help, the three common methods discussed in this post are often highlighted as shown in the next code listing. These methods were generated with NetBeans 7.1 beta.

TraditionalEmployee

package dustin.examples;

import java.util.Calendar;

/**
* Simple employee class using NetBeans-generated 'common' methods
* implementations that are typical of many such implementations created
* without Guava or other library.
*
* @author Dustin
*/
public class TraditionalEmployee
{
public enum Gender{ FEMALE, MALE };

private final String lastName;
private final String firstName;
private final String employerName;
private final Gender gender;

/**
* Create an instance of me.
*
* @param newLastName The new last name my instance will have.
* @param newFirstName The new first name my instance will have.
* @param newEmployerName The employer name my instance will have.
* @param newGender The gender of my instance.
*/
public TraditionalEmployee(
final String newLastName, final String newFirstName,
final String newEmployerName, final Gender newGender)
{
this.lastName = newLastName;
this.firstName = newFirstName;
this.employerName = newEmployerName;
this.gender = newGender;
}

public String getEmployerName()
{
return this.employerName;
}

public String getFirstName()
{
return this.firstName;
}

public Gender getGender()
{
return this.gender;
}

public String getLastName()
{
return this.lastName;
}

/**
* NetBeans-generated method that compares provided object to me for equality.
*
* @param obj Object to be compared to me for equality.
* @return {@code true} if provided object is considered equal to me or
* {@code false} if provided object is not considered equal to me.
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final TraditionalEmployee other = (TraditionalEmployee) obj;
if ((this.lastName == null) ? (other.lastName != null) : !this.lastName.equals(other.lastName))
{
return false;
}
if ((this.firstName == null) ? (other.firstName != null) : !this.firstName.equals(other.firstName))
{
return false;
}
if ((this.employerName == null) ? (other.employerName != null) : !this.employerName.equals(other.employerName))
{
return false;
}
if (this.gender != other.gender)
{
return false;
}
return true;
}

/**
* NetBeans-generated method that provides hash code of this employee instance.
*
* @return My hash code.
*/
@Override
public int hashCode()
{
int hash = 3;
hash = 19 * hash + (this.lastName != null ? this.lastName.hashCode() : 0);
hash = 19 * hash + (this.firstName != null ? this.firstName.hashCode() : 0);
hash = 19 * hash + (this.employerName != null ? this.employerName.hashCode() : 0);
hash = 19 * hash + (this.gender != null ? this.gender.hashCode() : 0);
return hash;
}

/**
* NetBeans-generated method that provides String representation of employee
* instance.
*
* @return My String representation.
*/
@Override
public String toString()
{
return "TraditionalEmployee{" + "lastName=" + lastName + ", firstName=" + firstName
+ ", employerName=" + employerName + ", gender=" + gender + '}';
}
}

Although NetBeans 7.1 beta did the heavy lifting here, this code still must be maintained and can be made more readable. The next class is the same class, but with Guava-powered common methods instead of the NetBeans-generated 'typical' implementations shown above.

GuavaEmployee

package dustin.examples;

/**
* Simple employee class using Guava-powered 'common' methods implementations.
*
* I explicitly scope the com.google.common.base.Objects class here to avoid the
* inherent name collision with the java.util.Objects class.
*
* @author Dustin
*/
public class GuavaEmployee
{
public enum Gender{ FEMALE, MALE };

private final String lastName;
private final String firstName;
private final String employerName;
private final TraditionalEmployee.Gender gender;

/**
* Create an instance of me.
*
* @param newLastName The new last name my instance will have.
* @param newFirstName The new first name my instance will have.
* @param newEmployerName The employer name my instance will have.
* @param newGender The gender of my instance.
*/
public GuavaEmployee(
final String newLastName, final String newFirstName,
final String newEmployerName, final TraditionalEmployee.Gender newGender)
{
this.lastName = newLastName;
this.firstName = newFirstName;
this.employerName = newEmployerName;
this.gender = newGender;
}

public String getEmployerName()
{
return this.employerName;
}

public String getFirstName()
{
return this.firstName;
}

public TraditionalEmployee.Gender getGender()
{
return this.gender;
}

public String getLastName()
{
return this.lastName;
}

/**
* Using Guava to compare provided object to me for equality.
*
* @param obj Object to be compared to me for equality.
* @return {@code true} if provided object is considered equal to me or
* {@code false} if provided object is not considered equal to me.
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final GuavaEmployee other = (GuavaEmployee) obj;

return com.google.common.base.Objects.equal(this.lastName, other.lastName)
&& com.google.common.base.Objects.equal(this.firstName, other.firstName)
&& com.google.common.base.Objects.equal(this.employerName, other.employerName)
&& com.google.common.base.Objects.equal(this.gender, other.gender);
}

/**
* Uses Guava to assist in providing hash code of this employee instance.
*
* @return My hash code.
*/
@Override
public int hashCode()
{
return com.google.common.base.Objects.hashCode(
this.lastName, this.firstName, this.employerName, this.gender);
}

/**
* Method using Guava to provide String representation of this employee
* instance.
*
* @return My String representation.
*/
@Override
public String toString()
{
return com.google.common.base.Objects.toStringHelper(this)
.addValue(this.lastName)
.addValue(this.firstName)
.addValue(this.employerName)
.addValue(this.gender)
.toString();
}
}

As the code above proves, the use of Guava improves the readability of the implementations of the three common methods. The only thing that's not so nice is the need to explicitly scope Guava's Objects class in the code to avoid a naming collision with Java SE 7's Objects class. Of course, if one is not using Java 7, then this is not an issue and if one is using Java 7, it's most likely that the standard version should be used instead anyway.

Conclusion

Guava provides a nice approach for building safer and more readable common methods via its Objects class. Although I'll use the new java.util.Objects class instead for JDK 7 projects, Guava's com.google.common.base.Objects class provides a nice alternative for working in versions of Java prior to JDK 7.