Minor nitpick:
References s1 and s2 in fact point to two distinct objects, which happen to represent the same value "kt". For String objects the result of the equals() method is based on that value, instead of the object id (referential equality).
HashSet's add() method uses the result of the equals() method to prevent multiple references that point to equal objects from being added. Therefore a copy of reference s2 will not be added, because the HashSet already contains a reference to an equal object; the copy of reference s1 which was added previously.
All this does not apply to the objects reffered to by ws1 and ws2 respectively, because the class WS does not override the equals() method inherited from java.lang.Object (which IS based on object id). So when the equals() method is used to compare the two distinct WS instances for equality, the comparison does not hold eventhough both instances are meant to represent the same value "kt". As a result the HashSet will contain references to both WS instances.
Two references to two distinct WS instances, and one reference to a String instance, makes a total of 3, which is the value returned HashSet's size() method.
[ March 22, 2008: Message edited by: Jelle Klap ]