Friday, December 28, 2007

You can't Skimp on HashCode

I sometimes use hashCode() like it's a necessary evil when overriding equals() (override hashCode() when you override equals() has been drummed into my head over and over again).

The hashCode() method has a few nuances worth noticing. If you ever add your Objects into a hash such a HashSet or a HashMap, it will take precedence over the equals() method when comparing Objects. This is quite subtle and it makes sense when you think about it for a while.

Say you create the ubiquitous Person class with a name, surname and age.


package com.blogspot.babyloncandle;

public final class Person {

private String name;
private String surname;
private int age;

public Person(final String name, final String surname,
final int age) {
this.name = name;
this.surname = surname;
this.age = age;
}

public String getName() {
return name;
}

public String getSurname() {
return surname;
}

public int getAge() {
return age;
}

@Override
public int hashCode() {
return surname.hashCode();
}

@Override
public boolean equals(final Object obj) {
if (obj == null || !(obj instanceof Person)) {
return false;
}

final Person incoming = (Person) obj;
return incoming.getSurname().equals(surname);
}
}



Say we initially state that any Person with the same Sunrname is equal. The hashCode() implementation also returns a value based on the Surname for a Person.

Using Instinct, we could then create a Context for Person to specify that 2 People with the same surname are equal:


package com.blogspot.babyloncandle;

import static com.googlecode.instinct.expect.Expect.expect;
import com.googlecode.instinct.integrate.junit4.InstinctRunner;
import com.googlecode.instinct.marker.annotate.Context;
import com.googlecode.instinct.marker.annotate.Specification;
import org.junit.runner.RunWith;

import java.util.HashSet;
import java.util.Set;

@Context
@RunWith(InstinctRunner.class)
public class APersonContext {

@Specification
public void shouldEquatePeopleWithTheSameSurname() {
final Person person1 = new Person("Humpty", "Dumpty", 5);
final Person person2 = new Person("Lumpy", "Dumpty", 6);

expect.that(person1).isEqualTo(person2);

final Set<Person> uniquePeople = new HashSet<Person>();
uniquePeople.add(person1);
uniquePeople.add(person2);

expect.that(uniquePeople).isOfSize(1);
}
}



The specification passes with no dramas. Now if we change the hashCode() method of the Person object such that the hashCode is calculated on age:


@Override
public int hashCode() {
return age;
}


If we rerun the specification, it fails with:


java.lang.AssertionError:
Expected: <1>
got: <2>


This implies that the uniquePeople Set now has 2 elements instead of 1. This almost seems counter-intuitive as we have not changed the implementation of the equals() method of Person.

Why is the spec failing? It has to do with how equals() and hashCode() works. If 2 Objects are equal they MUST have the same hashCode(). (But 2 Objects with the same hashCode() need not be equal). We have now broken this contract. (as equals() works on Surname and hashCode() works on Age) The implications are more subtle though. The only reason the specification actually failed is because we used a hash implementation (HashSet) to store the 2 Person Objects.

If we add a System.out.Println() to the equals() method of Person, we see that it is never called when it's in the Set. If we add a System.out.Println() to the hashCode() method of Person, we see that it is called for each Person Object within the Set.

This has to do with the way hashing works in Java. The hashCode is used to select a "bucket" into which each Object is added. If Object are in different buckets (meaning they have different hashCodes) they are never compared for equality. This is very important, because, if you go against the equals/hashCode specification, your Objects could never be found in hash implementations. If you revert the hashCode() of Person to use Surname, while retaining the System.out.Println() statements, you'll see that hashCode() is called on each Object before equals().

So it's not just that you should override hashCode() when you override equals() as many people do automatically. (without paying much attention to the hashCode value). You should override it such that you always maintain their contracts or you could end up with hard-to find errors.

Lots of people use a default value (eg. 42) for hashCode(). This satisfies the equals/hashCode contract. It is not so great for hash performance though, as all Objects land in the same bucket increasing processing time.

You also have to be careful between choosing a highly unique hashCode (A bucket per equal Objects of a certain type - this leads to an massive increase of buckets) and choosing a hashCode that is not unique at all (1 bucket for all Objects - this could lead to large processing times). Depending on the performance requirements of your application, you may need to make some compromises on the value of hashCode you choose to return.

Sunday, December 23, 2007

Ubuntu: Highlight me Vim

Vim (Vi Improved) gives you many features over that of the standard Vi feature set such, as syntax highlighting, incremental searching, bracket matching and many others. Here's how to install Vim for Ubuntu:

1. Run sudo apt-get install vim from a command prompt.
2. Copy the the default vimrc file from /etc/vim to your home directory with with the name .vimrc

cp /etc/vim/vimrc ~/.vimrc

3. " denote commented features. Uncomment any features you want to turn on. To turn on syntax highlighting change "syntax on to syntax on.

4. Open a java file to see the highlighting in action:

vim yourfile.java



5. If you get the following error:

E319: Sorry, the command is not available in this version: syntax on

it means that you haven't installed vim properly and that you are using tinyVim which, comes standard with Ubuntu. Install Vim as per Step1 again and look out for any errors.

The following is a list of features I use:


" This line should not be removed as it ensures that various options are
" properly set to work with the Vim-related packages available in Debian.
runtime! debian.vim

" Vim5 and later versions support syntax highlighting. Uncommenting the next
" line enables syntax highlighting by default.
syntax on

" If using a dark background within the editing area and syntax highlighting
" turn on this option as well
set background=dark

" The following are commented out as they cause vim to behave a lot
" differently from regular Vi. They are highly recommended though.
set showcmd " Show (partial) command in status line.
set showmatch " Show matching brackets.
set ignorecase " Do case insensitive matching
set smartcase " Do smart case matching
set incsearch " Incremental search
set autowrite " Automatically save before commands like :next and :make
set hidden " Hide buffers when they are abandoned
set mouse=a " Enable mouse usage (all modes) in terminals

" Source a global configuration file if available
" XXX Deprecated, please move your changes here in /etc/vim/vimrc
if filereadable("/etc/vim/vimrc.local")
source /etc/vim/vimrc.local
endif

"this line is a comment .... one which begins with double-quotes
" The best is the bold font, try all of these and pick one....
set guifont=8x13bold
"set guifont=9x15bold
"set guifont=7x14bold
"set guifont=7x13bold
"
" Highly recommended to set tab keys to 4 spaces
set tabstop=4
set shiftwidth=4
"
" The opposite is 'set wrapscan' while searching for strings....
set nowrapscan
"
" The opposite is set noignorecase
set ignorecase
set autoindent
"
" You may want to turn off the beep sounds (if you want quite) with visual bell
set vb

Friday, December 21, 2007

Configuring Eclipse Plugins May Cause Trauma, defection to Intellij

I've been trying to use Eclipse in an environment where Intellij was and is the IDE of choice. I hate to say it but, basic Eclipse plugins (such as for subversion and checksyle) are annoying to configure and use. Eg. The first thing that I was greeted with after installing the Subclipse plugin was a huge error message. Not exactly the confidence-building outcome you would expect.

The plugins also take multiple steps to do somewhat simple configuration. While recently using a Checkstyle plugin (Eclipse-CS) I was forced to configure it via 3 separate routes for all the settings to take effect! (One through Windows->Preferences, one through Project properties, and one through the context menu) This is obviously way to hard for the average user, who would readily turn to Intellij where basic plugins are very easy to setup, requiring little configuration.

I'm currently working on Instinct which was primarily developed using Intellij. We are now catering for Eclipse developers and want an even playing field for users and developers alike.
To this end I've been creating how-tos on using basic plugins for Eclipse.

I am quite pleased with the progress Eclipse has made in the last few years and don't think Intellij is miles ahead of it as it was maybe 3 years ago. What we need are better plugins and more userfriendly features.

The Harry Potter culture of Wizardry has got to stop! Wizards may seem to make your life easier initially but in the long run they make everything more prolong and confusing. Why click through 5 screens when you can set all your options in one place?

I also found that shortcut keys could not be associated with these plugins in Eclipse, while Intellij lets you define shortcuts even for plugins. All pretty basic really. Why right click, click and click again when you can simply press a shortcut in 1/2 the time?

How-tos on installing and configuring Subclipse and Eclipse-CS can be found below:

Installing and configuring Subclipse
Installing Eclipse-CS

Tuesday, December 4, 2007

Wireless Apple Keyboard on Vista

I recently purchased an Apple wireless keyboard. I blame it on being surrounded by Mac evangelists at the time. I had the quaint notion of using the keyboard on my Windows Vista box. To cut a long story short, I spent around a week trying to get the keyboard paired with my Asus bluetooth dongle (WL-BTD201M) and although I did succeed, the results were less than satisfactory. This was due to many reasons such as the keyboard being a Mac keyboard and Vista's flaky support for bluetooth. I say flaky because pairing the keyboard with a MacBook took around 20 seconds! I experienced unreliable bluetooth functionality on Vista with the connection reset every so often - too often to make it usable. Unlike other components on your system, if your keyboard or mouse is non-functional it gets frustrating pretty fast.

Here's what I liked about the apple keyboard:

1. Very small. See pic against my Microsoft keyboard.
2. The keys are very nice to the touch and spaced exactly right.
3. The bluetooth connection light emanates from an almost invisible led on the keyboard.
4. The packaging was fantastic as per usual Apple standard.
5. The keyboard is one of the thinnest and lightest keyboards I have ever seen. See pic.


Here's what I disliked about it:

1. It was maybe too small. I found it hard to type with it at times. But then again if you have smaller hands than I do, I doubt this will be a problem.
2. It was missing some important keys I frequently use like home, end, insert, backspace, page up/down and printscreen to name a few! I was told that home and end keys were (command + <-) and (command + ->) on the Mac, which is great if I could only find some software to do the mapping for me.
3. Some of the keys like the function key (fn) didn't work out of the box and needed special software to work. The remaining keys can be mapped with a tool like SharpKeys.

Here are the steps I followed to get the keyboard paired on Windows Vista:

This guide is for dongles using the Widcomm bluetooth stack only. You need another working keyboard (preferably wired) for use to enter the pin code.

1. Remove the following updates if installed:

Update 941600 (Cumulative update rollup for USB core components in Windows Vista)
Update 941649 (An update that improves the compatibility, reliability, and stability of Windows Vista)

2. Install the Widcomm drivers that came with your bluetooth dongle.
3. Launch regedit.
4. Locate the HKEY_CURRENT_USER\Software\Widcomm\BtConfig\General key.
5. Set the key PinCodeWord to a decimal value of 1111
6. Set the key UseFixedPin to a decimal value of 1
7. Set the key NoSleepingWhileConnected to a decimal value of 1 (Disallows the keyboard going to sleep while connected to via bluetooth)
8. Set the key DeviceInactivityDuration to a decimal value that represents the time in seconds before the keyboard disconnects from the bluetooth dongle. Eg. A value of 10800 is 3 hours. (60x60x3)
9. Set the key MinPINLength to a decimal value of 3. This is the minimum pin code length).
10. Restart the computer and log in with your other keyboard (not the Apple keyboard).
11. Attempt pairing the keyboard with the dongle. Once the keyboard is detected, select it and click next to proceed. On the next screen you will need to enter a pairing key, which you won't be able to enter! :) The key you entered into the registry at step 5 is what you need to enter here. Using your other keyboard type in the pin code. You will not see the key strokes but they will be entered into the dialog.
12. Press the Return key on the Apple keyboard.
13. The bluetooth authentication dialog should accept this value. If it doesn't your probably entered the wrong passkey. You may have to repeat the pairing process a few times before it succeeds.

If and when the keyboard disconnects, I use the bluetooth driver in Vista to disable and enable bluetooth (using the mouse since my keyboard is not working). This then redetects the keyboard and reconnects to it in about 10 seconds. Failing this I eject and reinsert the dongle after about a 10 second wait.







Sunday, December 2, 2007

JPA - Not Another API (NAA)?

I've recently started looking at JPA (Java Persistence API). I must admit I was a little annoyed. I already knew how to use hibernate and it suites most purposes. So why learn yet another annoying Sun API?

You can use JPA in EJB3 which really standardizes EJB persistence - this is a good thing. Fantastic. What if you avoid EJB technologies like the plague? Why do you need yet another API to handle standalone data persistence?

While looking for resources to get my first JPA project off the ground, I was pleasantly surprised by 2 excellent how-tos. One with Hibernate and one with TopLink. It seems very easy and it has much of the goodness of Hibernate - so it's not all bad! :)

Unfortunately it doesn't seem to have anything similar to the Criterion API in Hibernate. (I could be wrong - if so please let me know!) The Hibernate implementation also offers a few niceties which in my opinion subvert the JPA by making things "automatic". Obviously if you switch JPA providers this magic won't work - so it's not really standard. Hmmm. A non-standard use of standardized API. Wot tha?

A main driving factor for me learning JPA was the that Spring 2.5 only supports JPA and not the "old-school style" Hiberate templates. Hiss! Boo! Since many of the big players (Sun, Spring etc) are moving to JPA, it seems like a good time to learn to reinvent the persistent wheel!

As with any new API, the hardest part is to get your first "hello world" application running. I found 2 simple how-tos on JPA to help you get started:

HelloWorld JPA example in TopLink
HelloWorld JPA example in Hibernate 3