Why You Shouldn’t Use Complex Objects as HashMap Keys

I’m a big believer in learning from my mistakes, but I’m an even bigger believer in learning from other people’s mistakes.  Hopefully someone else will be able to learn from my mistakes.

mistakes

 

This post is inspired by an issue that took me a number of days to track down and pin point the root cause.  It started with NullPointerExceptions randomly be thrown in one of my applications.  I wasn’t able to consistently replicate the issue, so I added an indiscriminate amount of logging to the code to see if I could track down what was going on.

What I found was that when I was attempting to pull a value out of a particular hashmap, the value would sometimes be Null, which was a bit puzzling because after initializing the map with the keys/values, there were no more calls to put(), only calls to get(), so there should have been no opportunity to put a null value in the map.

Below is a code snippet similar (but far more concise) to the one I had been working on.

There is a ProductSummaryBean with a short summary of the product, and a ProductDetailBean with further product details.  The summary bean is below and contains four properties.

 

Any guesses what happens when the code above is run?

Exception in thread "main" java.lang.NullPointerException
 at com.lynden.mapdemo.TestClass.runTest(TestClass.java:34)
 at com.lynden.mapdemo.TestClass.main(TestClass.java:50)

 

So what happened?  The HashMap stores its keys by using the hashcode of the key objects.  If we print out the hashcode when the ProductSummaryBean is first created and also after its read out of the DB we get the following.

SummaryBean hashcode before: -298224643
SummaryBean hashcode after: -298224679

 

We  can see that the hashcode before and after are different, so there must be something different about the two objects.

SummaryBean before: ProductBean{priceFormatter=java.text.DecimalFormat@67500, price=19.95, name=MyWidget, upcCode=Z332332}
SummaryBean after: ProductBean{priceFormatter=java.text.DecimalFormat@674dc, price=19.95, name=MyWidget, upcCode=Z332332}

 

Printing out the entire objects shows that while name, upc code, and price are all the same, the DecimalFormatter used for the price is different.  Since the DecimalFormatter is part of the hashcode() calculation for the ProductSummaryBean, the hashcodes between the before and after versions of the bean turned out different.  Since the hashcode was modified, the map was not able to find the corresponding ProductDetailBean which in turned caused the NullPointerException.

Now one may ask, should the DecimalFormat object in the bean been used as part of the equals() and hashcode() calculations?  In this case, probably not, but this may not be true in your case.  The safer way to go for the hashmap key would be to have used the product’s upc code as the HashMap key to avoid the danger of the keys changing unexpectedly.

 

 

 

 

 

How to Import the GMapsFX Component into SceneBuilder

I recently received a question regarding how to add the GMapsFX component to SceneBuilder, so it could be dragged and dropped onto the UI as its being constructed.

I thought I would address the question here since the good folks at Gluon have made it extremely easy to import custom components to SceneBuilder.

The first step is to click the small ‘gear’ icon just to the right of the Library search box and select the “JAR/FXML Manager” menu item from the popup menu.

screen-shot-2016-09-30-at-2-49-38-pm

 

 

Since the GMapsFX binaries are in the Maven Central repository SceneBuilder can directly download and install the component from there.  Click the “Search repository” link in the Library Manager dialog box.

screen-shot-2016-09-30-at-2-52-03-pm

 

Next, enter “GMapsFX” into the Group or artifact ID text box, and click the “Search” button.  You should get a search result with the com.lynden:GMapsFX artifact. Select the result and click “Add JAR”.

screen-shot-2016-09-30-at-2-52-24-pm

 

The GoogleMapView should be the only component available in the Jar file, select it and click the “Import Component” button.

screen-shot-2016-09-30-at-2-52-33-pm

 

Finally, you should get a confirmation that the library was imported into SceneBuilder.

screen-shot-2016-09-30-at-2-52-41-pm

 

At this point the GoogleMapView component should be visible in the “Custom” component section of the pallette, and ready to be dragged and dropped onto your UI.  Due to the way the component is constructed, a map will not display in SceneBuilder or the SceneBuilder preview tool, but the proper FXML will be generated and the map will display when the program is run.

screen-shot-2016-09-30-at-2-52-50-pm

 

Feel free to tweet me at:  @RobTerpilowski with any questions.

 

Mapping Directions with JavaFX using the GMapsFX Directions API

Mapping directions in a JavaFX application is easy with the Directions API that was recently introduced in GMapsFX.  In this blog post I’ll walk through an example of setting up an application with a map and a couple of text fields, one which will be used for the trip origin and the second which will be used for the trip destination.  When the user hits ‘Enter’ in the destination text field, the map will display the directions.

Starting off with the FXML file, we have an AnchorPane which contains the GoogleMapView and 2 TextFields.  The AnchorPane has a controller assigned to it named FXMLController, and both components have an FX ID associated with them so they will be accessible from the FXMLController class.  Also, the destination TextField has an action, “toTextFieldAction” associated with it, so this method will be called when the user hits the ‘Enter’ key in the TextField.

 

The result should look as follows:

Screen Shot 2016-08-19 at 2.56.27 PM

 

Next, I’ve cut up the relevant parts of the FXMLController class.  The MapComponentInitializedListener interface needs to be implemented by the controller since the underlying GoogleMap doesn’t get initialized immediately.  The DirectionsServiceCallback interface also needs to be implemented, although in this example I won’t be doing anything with it.

The GoogleMapView and the TextFields components from the FXML file are defined below and annotated with @FXML.

There is also a reference to the Directions Service as well as StringProperties to represent the ‘to’ and ‘from’ endpoints that the user will enter.

 

 

After the controller is created, its initialize method is called which will set the MapView’s initialization listener to the FXMLController as well as bind the ‘to’ and ‘from’ String properties to the TextProperties of their respective TextFields.

 

Once the map has been initialized, the DirectionService can be instantiated as well as a MapOptions object to set various attributes about the map.  The options are then configured and a GoogleMap object can be instantiated from the map view.  The directionsPane is a component which can be used to render the step by step direction text, in this example however, it won’t be displayed.

Finally, the action method defined in the FXML file when the user hits ‘Enter’ in the TextField is below.  The method will call the getRoute() method on the DirectionsService class, passing in a boolean value which will define whether the route can be modified by dragging it, the map object, and the DirectionsRequest object.

 

Below is an example when the user enters directions from Seattle to Redmond

 

Screen Shot 2016-08-19 at 3.37.38 PM

That’s it!  For completeness I’ll include the full source code of the example below.

 

 

Scene.fxml

 

MainApp.java

 

FXMLController.java

GMapsFX 2.0.9 Released

The latest version of GMapsFX has been released which contains a fix for a bug that was preventing the GoogleMapView component from being added as a custom component to SceneBuilder.

The fix will allow the MapView to be added as a custom component.  In a future blog post I will detail how to do this.

 

Screen Shot 2016-08-05 at 4.26.39 PM.png

Mapping an Address with JavaFX using the GMapsFX Geocoding API

Mapping an address in a JavaFX application is extremely easy with the Geocoding API that was recently introduced in GMapsFX.  In this blog post I’ll walk through an example of setting up an application with a map and a text field.  The map will recenter itself at whatever address or place the user types in the text field.

Starting off with the FXML file, we have an AnchorPane which contains the GoogleMapView and a TextField.  The AnchorPane has a controller assigned to it named FXMLController, and both components have an FX ID associated with them so they will be accessible from the FXMLController class.  Also, the TextField has an action, “addressTextFieldAction” associated with it, so this method will be called when the user hits the ‘Enter’ key in the TextField.

 

The result should look as follows:

Screen Shot 2016-07-29 at 3.12.18 PM.png

 

Next, I’ve cut up the relevant parts of the FXMLController class.  The MapComponentInitializedListener interface needs to be implemented by the controller since the underlying GoogleMap doesn’t get initialized immediately.  The GoogleMapView and TextField components from the FXML file are defined below and annotated with @FXML.

There is also a reference to the GeocodingService as well as a StringProperty to represent the address the user enters.

 

 

After the controller is created its initialize method is called which will set the MapView’s initialization listener to the FXMLController as well as bind the address property to the address TextField’s text property.

 

Once the map has been initialized, the GeocodingService can be instantiated as well as a MapOptions object to set various attributes about the map.  Once the options are configured, a GoogleMap object can be instantiated from the map view.

 

Finally, the action method defined in the FXML file when the user hits ‘Enter’ in the TextField is below.  The method will call the geocode() method on the GeocodeService class, passing in the value of the Address property as well as a callback method.

The callback will check the status of the results, and based on the outcome, will recenter the map at the latitude/longitude the user had entered.

 

Below is an example when the user enters New York City as the address.

Screen Shot 2016-07-29 at 4.27.56 PM

 

That’s it!  For completeness I’ll include the full source code of the example below.

 

 

Scene.fxml

 

MainApp.java

 

FXMLController.java

GMapsFX 2.0.7 Released

A new version of GMapsFX has been released to bintray and Maven Central.  The main feature in this version is to allow the use of custom marker/pin images, rather than relying on the default Google images.

A future blog post will demonstrate how to add custom markers to your GMapsFX application.

 

Screen Shot 2016-05-20 at 2.25.33 PM

 

GMapsFX 2.0.6 Released

A new version of GMapsFX has been released to bintray and Maven Central which contains

  • additional bug fixes related to hiding/showing the directions pane at runtime.
  • Ability to pass the map/directions language to the Google map at runtime, eliminating the need to hardcode it.

Screen Shot 2016-05-20 at 2.25.33 PM.png

<dependency> 
  <groupId>com.lynden</groupId> 
  <artifactId>GMapsFX</artifactId> 
  <version>2.0.6</version> 
</dependency>

 

GMapsFX Home: http://rterp.github.io/GMapsFX/
twitter: @RobTerpilowski