NetBeans 6 delivers great updates to the Matisse GUI builder. Spend a few minutes with Roman Strobl and get an expert briefing on what's new and what has changed. (sponsored)
In this, the third and final installation of Andres' Introduction to Groovy series, you learn about how Groovy handles variable numbers of arguments, named parameters, currying, and more about Groovy operators. Including, some new operators.
Swing Fuse (actually just Fuse), is a framework designed to make it easier to create your own custom desktop components. In this article, Daniel Spiewak shows you how to get started and provides sample source code you can download.
Willam Louth shows how he uses JXInsight Probes to investigate probable performance issues with code bases that he is not familiar with. He also highlights possible pitfalls in creating a benchmark, as well as in the analysis of results.
The Java Collections API in java.util.* is a very powerful and fundamental toolkit for managing sets of objects and is used in practically all facets of software development, from low-level caches, to O/R mapping, to GUIs.
However, occasionally we have needs that extend beyond the basic functionality provided in the API. One very common need not handled by the collections framework is to provide maps that can hold many values for a particular key, much like databases feature non-unique key indexes. Having such a collection class would make this job very simple in Java.
Collections are like LEGOs(tm)
It turns out, thanks to the well thought out architecture of the Collections classes, that it is indeed very easy to add this functionality using the existing classes as building blocks. And the new generics language extensions makes this job a lot easier and more pleasant too.
The basic idea is quite simple actually. To hold many values per key, just hold these values in a List
and store the lists in a Map
> with the corresponding key. This is actually very simple to manage and can be done quite comfortably without abstracting the functionality in a class. but doing so comes in handy when we need reusability.
For the implementation I chose containment instead of inheritance. The class does not implement any of the Collections intefaces because none of them quite fit the functional model. The one that comes closest however is the Map
interface and I modelled the API in my class to closely follow its usage guidelines.
Developer General's Warning
I will note however that by no means this class provides all the behaviour a developer might need from such a non-unique key map. This tip is mostly aimed at showing how to use collections and generics as basic building blocks for more sophisticated generic collections classes.
Furthermore, it probably would be a good idea for a complete implementation to abstract the functionality into a generic interface with different implementations. In this example I use a HashMap of ArrayLists, but versions using HashTables, ConcurrentHashTables, LinkedLists, Vectors, etc. might be desired too, and despite the different access strategies they would all share the same external interface.
Finally, the Code
publicclass Index<K,V> {
HashMap<K,ArrayList<V>> map;
public Index() {
map = new HashMap<K,ArrayList<V>>();
}
publicvoid put(K key, V value) {
ArrayList<V> list = map.get(key);
if ( list == null ) {
list = new ArrayList<V>();
map.put(key, list);
}
list.add(value);
}
public V get(K key, int index) {
ArrayList<V> list = map.get(key);
if ( list == null ) {
returnnull;
}
if ( index >= list.size() || index < 0 ) {
returnnull;
}
return list.get(index);
}
// other possible useful methods would be:
// boolean remove(K key, V targetValue);
public V remove(K key, int index) {
ArrayList<V> list = map.get(key);
if ( list == null ) {
returnnull;
}
if ( index >= list.size() || index < 0 ) {
returnnull;
}
V v = list.remove(index);
if ( list.size() == 0 ) {
map.remove(key);
}
return v;
}
// instead of using .size() I split the
// functionality into three different
// methods returning the necessary
// information. Remember, now the number
// of keys could be less than the number
// of values.
publicint getValueCount() {
int size = 0;
for ( ArrayList<V> list : map.values()) {
size += list.size();
}
return size;
}
publicint getValueCount(K key) {
ArrayList<V> list = map.get(key);
if ( list == null ) {
return 0;
}
return list.size();
}
publicint getKeyCount() {
return map.size();
}
publicboolean containsKey(K key) {
return map.containsKey(key);
}
publicboolean containsKey(K key, int index) {
ArrayList<V> list = map.get(key);
if ( list == null ) {
returnfalse;
}
if ( index >= list.size() || index < 0 ) {
returnfalse;
}
returntrue;
}
publicboolean isEmpty() {
return map.isEmpty();
}
// other possible useful methods would be:
// boolean containsValue(K key, V targetValue);
publicboolean containsValue(V targetValue) {
for ( ArrayList<V> list : map.values()) {
for ( V value : list ) {
if ( targetValue == value )
returntrue;
}
}
returnfalse;
}
publicvoid clear() {
map.clear();
}
}
Hmm did you test your code? put() never put the ArrayList in the map...
It may also be useful to add a method that returns an Iterator of the values for a key. Which brings the question, why is Iterator not Iterable?
> Hmm did you test your code? put() never put the ArrayList in the map...
You're right, what happened is that I posted a simplified version of my class and I kind of, err..., oversimplified. I fixed the example.
> It may also be useful to add a method that returns an Iterator of the values for a key.
Yes, that would be one of the things I said could be added to provide complete functionality, however it isn't hard to add. If I have time today I'll post that up too.
> What is wrong with org.apache.commons.collections.MultiHashMap,
I would say nothing, I simply prefer to develop my own classes when I don't wan't to include extra stuff I won't need.
> ... except it doesn't have a mindbogglingly difficult generics interface?
Perhaps then there is an advantage to my implementation. Generics is mistakingly viewed as difficult, when the code created is not more complex than the same code using casting. It is safer though.
> You are using ArrayList. Why not to add additional type to make exact type of collection parametrized?
Did you try it? I actually had a similar idea, where I would pass both a class extenfding Map and another extending List., like this:
class Index<K,V,M extends Map, L extends List> {
M<K,L<V>> map;
Unfortunately the generics syntax is not that flexible and the above is not possible, a type parameter is not viewed as a class and the following will generate a compiler error. Plus you can't instantiate from type parameters, you have to use reflection. The closest correct method you can come to this would be something like the following:
class Index<K,V,M extends Map<K,L>, L extends java.util.List<V>> {
M map;
public Index(M m, Class cl) {
listClass = (Class<L>)cl;
map = m;
}
publicvoid put(K key, V value) {
L list = map.get(key);
try {
if ( list == null ) {
list = listClass.newInstance();
map.put(key, list);
}
list.add(value);
}
catch (InstantiationException ie) {
// something wroung with instantiation
}
catch (IllegalAccessException ie) {
// something wroung with instantiation
}
}
// the rest of the code stays the same
}
Now, to use this, for example in an index of files acording to it's size (neat for finding duplicate files ), you'd use the following:
Index<Integer,File,HashMap<Integer,ArrayList<File>>,ArrayList<File>>
fileIndex
= new
Index<Integer,File,HashMap<Integer,ArrayList<File>>,ArrayList<File>>
(new HashMap<Integer,ArrayList<File>>(), ArrayList.class);
You can see now this can become quite complicated, it's probably just easier and maybe less coding to provide an abstract interface and sublass specializations.
Here's a nicer solution to generizing the implementation using an abstract base class.
publicabstractclass AbstractIndex<K,V, M extends Map<K,L>,L extends List<V> > {
M map;
public AbstractIndex(M m) {
map = m;
}
protectedabstract L createList();
publicvoid put(K key, V value) {
L list = map.get(key);
if ( list == null ) {
list = createList();
map.put(key, list);
}
list.add(value);
}
//rest is the same
}
Using this as a base class we can create a very simple subclass for every map/list combination we need, say HashMap with ArrayLists:
> > What is wrong with org.apache.commons.collections.MultiHashMap,
>
> I would say nothing, I simply prefer to develop my
> own classes when I don't wan't to include extra stuff
> I won't need.
Oh. Ok. I prefer to use built-in classes when they are available. I guess we have a different point of view on this.
> > ... except it doesn't have a mindbogglingly difficult generics interface?
>
> Perhaps then there is an advantage to my
> implementation. Generics is mistakingly viewed as
> difficult, when the code created is not more complex
> than the same code using casting. It is safer
> though.
I'm pretty sure you are wrong. Could you show us all an example on how to use the existing MultiHashMap and how your class is used? It will show that your code is much more complex.
Safer, sure, if this was really an issue. Very rarely do I find oranges in the apple basket. How often do you find such bugs?
Requred collection can be passed inside, and it will be used.
Why do I pass collection instead of it's class? Because of the cast required. I didn't find any way to avoid it. This cast is safe, because type returned is exactly what we need, but getClass has return value type Class<? extends Object>. So, if we will perform cast inside constructor, it will give additional check of the type of collection passed into. We have no ability to pass TreeSet<Object>, for instance.
I made this implementation that respect the Map interface.
I want to receive some feedback about the way it's implemented.
The Code:
public class MultiHashMap<K, V> extends HashMap<K, List<V>> {
public void put(K key, V value) {
List<V> elems = get(key);
if (elems == null) {
elems = new LinkedList<V>();
put(key, elems);
}
elems.add(value);
}
That's pretty much how I'd do it. I would probably use Collection<V> instead of List<V> unless the order of the elements within each bag is important.
Also, I'd suggest you use "add" instead of "put". The name "put" generally implies replacing the existing value, but what you're really doing is adding a new value. Then I'd provide a remove(K key, V value) method to remove a single value at a time and perhaps some other convenience methods like contains(K key, V value).
Other than those minor points, I'd say your approach is fine.
J2ME programmers count bytes the way a super-model counts calories.
Collections: Multivalue-per-key Maps
At 1:28 PM on Feb 1, 2005, Sebastian Ferreyra wrote:
Fresh Jobs for Developers Post a job opportunity
The Java Collections API in java.util.* is a very powerful and fundamental toolkit for managing sets of objects and is used in practically all facets of software development, from low-level caches, to O/R mapping, to GUIs.
However, occasionally we have needs that extend beyond the basic functionality provided in the API. One very common need not handled by the collections framework is to provide maps that can hold many values for a particular key, much like databases feature non-unique key indexes. Having such a collection class would make this job very simple in Java.
Collections are like LEGOs(tm)
It turns out, thanks to the well thought out architecture of the Collections classes, that it is indeed very easy to add this functionality using the existing classes as building blocks. And the new generics language extensions makes this job a lot easier and more pleasant too.
The basic idea is quite simple actually. To hold many values per key, just hold these values in a List and store the lists in a Map > with the corresponding key. This is actually very simple to manage and can be done quite comfortably without abstracting the functionality in a class. but doing so comes in handy when we need reusability.
For the implementation I chose containment instead of inheritance. The class does not implement any of the Collections intefaces because none of them quite fit the functional model. The one that comes closest however is the Map interface and I modelled the API in my class to closely follow its usage guidelines.
Developer General's Warning
I will note however that by no means this class provides all the behaviour a developer might need from such a non-unique key map. This tip is mostly aimed at showing how to use collections and generics as basic building blocks for more sophisticated generic collections classes.
Furthermore, it probably would be a good idea for a complete implementation to abstract the functionality into a generic interface with different implementations. In this example I use a HashMap of ArrayLists, but versions using HashTables, ConcurrentHashTables, LinkedLists, Vectors, etc. might be desired too, and despite the different access strategies they would all share the same external interface.
Finally, the Code
public class Index<K,V> { HashMap<K,ArrayList<V>> map; public Index() { map = new HashMap<K,ArrayList<V>>(); } public void put(K key, V value) { ArrayList<V> list = map.get(key); if ( list == null ) { list = new ArrayList<V>(); map.put(key, list); } list.add(value); } public V get(K key, int index) { ArrayList<V> list = map.get(key); if ( list == null ) { return null; } if ( index >= list.size() || index < 0 ) { return null; } return list.get(index); } // other possible useful methods would be: // boolean remove(K key, V targetValue); public V remove(K key, int index) { ArrayList<V> list = map.get(key); if ( list == null ) { return null; } if ( index >= list.size() || index < 0 ) { return null; } V v = list.remove(index); if ( list.size() == 0 ) { map.remove(key); } return v; } // instead of using .size() I split the // functionality into three different // methods returning the necessary // information. Remember, now the number // of keys could be less than the number // of values. public int getValueCount() { int size = 0; for ( ArrayList<V> list : map.values()) { size += list.size(); } return size; } public int getValueCount(K key) { ArrayList<V> list = map.get(key); if ( list == null ) { return 0; } return list.size(); } public int getKeyCount() { return map.size(); } public boolean containsKey(K key) { return map.containsKey(key); } public boolean containsKey(K key, int index) { ArrayList<V> list = map.get(key); if ( list == null ) { return false; } if ( index >= list.size() || index < 0 ) { return false; } return true; } public boolean isEmpty() { return map.isEmpty(); } // other possible useful methods would be: // boolean containsValue(K key, V targetValue); public boolean containsValue(V targetValue) { for ( ArrayList<V> list : map.values()) { for ( V value : list ) { if ( targetValue == value ) return true; } } return false; } public void clear() { map.clear(); } }Sebastian Ferreyra
14 replies so far (
Post your own)
Re: Collections: Multivalue-per-key Maps
Hmm did you test your code? put() never put the ArrayList in the map...It may also be useful to add a method that returns an Iterator of the values for a key. Which brings the question, why is Iterator not Iterable?
Re: Collections: Multivalue-per-key Maps
> Hmm did you test your code? put() never put the ArrayList in the map...You're right, what happened is that I posted a simplified version of my class and I kind of, err..., oversimplified. I fixed the example.
> It may also be useful to add a method that returns an Iterator of the values for a key.
Yes, that would be one of the things I said could be added to provide complete functionality, however it isn't hard to add. If I have time today I'll post that up too.
Seb
Re: Collections: Multivalue-per-key Maps
What is wrong with org.apache.commons.collections.MultiHashMap, except it doesn't have a mindbogglingly difficult generics interface?Commons Collections
If you want a ready-made, ready-tested multi-value map, try MultiHashMap at Commons Collections - http://jakarta.apache.org/collections.Re: Collections: Multivalue-per-key Maps
> What is wrong with org.apache.commons.collections.MultiHashMap,I would say nothing, I simply prefer to develop my own classes when I don't wan't to include extra stuff I won't need.
> ... except it doesn't have a mindbogglingly difficult generics interface?
Perhaps then there is an advantage to my implementation. Generics is mistakingly viewed as difficult, when the code created is not more complex than the same code using casting. It is safer though.
Seb
Re: Commons Collections
Echo... Echo...Enhancement to your class
You are using ArrayList. Why not to add additional type to make exact type of collection parametrized?public class Index<K,V,U extends Collection> {
HashMap<K,U<V>> map;
...
}
You will have an ability to use sets, sorted sets etc. in addition to lists.
Regards,
Eugene aka Skipy
Re: Enhancement to your class
> You are using ArrayList. Why not to add additional type to make exact type of collection parametrized?
Did you try it? I actually had a similar idea, where I would pass both a class extenfding Map and another extending List., like this:
class Index<K,V,M extends Map, L extends List> { M<K,L<V>> map;Unfortunately the generics syntax is not that flexible and the above is not possible, a type parameter is not viewed as a class and the following will generate a compiler error. Plus you can't instantiate from type parameters, you have to use reflection. The closest correct method you can come to this would be something like the following:
class Index<K,V,M extends Map<K,L>, L extends java.util.List<V>> { M map; public Index(M m, Class cl) { listClass = (Class<L>)cl; map = m; } public void put(K key, V value) { L list = map.get(key); try { if ( list == null ) { list = listClass.newInstance(); map.put(key, list); } list.add(value); } catch (InstantiationException ie) { // something wroung with instantiation } catch (IllegalAccessException ie) { // something wroung with instantiation } } // the rest of the code stays the same }Now, to use this, for example in an index of files acording to it's size (neat for finding duplicate files
Index<Integer,File,HashMap<Integer,ArrayList<File>>,ArrayList<File>> fileIndex = new Index<Integer,File,HashMap<Integer,ArrayList<File>>,ArrayList<File>> (new HashMap<Integer,ArrayList<File>>(), ArrayList.class);You can see now this can become quite complicated, it's probably just easier and maybe less coding to provide an abstract interface and sublass specializations.Seb
Re: Enhancement to your class
Here's a nicer solution to generizing the implementation using an abstract base class.
public abstract class AbstractIndex<K,V, M extends Map<K,L>,L extends List<V> > { M map; public AbstractIndex(M m) { map = m; } protected abstract L createList(); public void put(K key, V value) { L list = map.get(key); if ( list == null ) { list = createList(); map.put(key, list); } list.add(value); } //rest is the same }Using this as a base class we can create a very simple subclass for every map/list combination we need, say HashMap with ArrayLists:
public class MyIndex<K,V> extends AbstractIndex<K,V,HashMap<K,ArrayList<V>>,ArrayList<V>> { public MyIndex() { super(new HashMap<K,ArrayList<V>>()); } protected ArrayList<V> createList() { return new ArrayList<V>(); } }Now usage is back to a simple form:
We could also create subclasses where we specify the Map but leave the List implementation generic.
Seb
Re: Collections: Multivalue-per-key Maps
> > What is wrong with org.apache.commons.collections.MultiHashMap,>
> I would say nothing, I simply prefer to develop my
> own classes when I don't wan't to include extra stuff
> I won't need.
Oh. Ok. I prefer to use built-in classes when they are available. I guess we have a different point of view on this.
> > ... except it doesn't have a mindbogglingly difficult generics interface?
>
> Perhaps then there is an advantage to my
> implementation. Generics is mistakingly viewed as
> difficult, when the code created is not more complex
> than the same code using casting. It is safer
> though.
I'm pretty sure you are wrong. Could you show us all an example on how to use the existing MultiHashMap and how your class is used? It will show that your code is much more complex.
Safer, sure, if this was really an issue. Very rarely do I find oranges in the apple basket. How often do you find such bugs?
Re: Enhancement to your class
Oops! I didn't test it.Actually, I thought about something like this:
/* * MultiValueMap */ package tests; import java.util.*; public class MultiValueMap <K,V>{ private Map<K,Collection<V>> map; private Class<? extends Collection<V>> clazz; public MultiValueMap(Collection<V> coll){ map = new HashMap<K,Collection<V>>(); this.clazz = (Class<? extends Collection<V>>)coll.getClass(); } public void addValue(K key, V value){ Collection<V> collection = map.get(key); if (collection == null){ collection = createCollection(); if (collection == null) return; map.put(key,collection); } collection.add(value); } public Collection<V> getValues(K key){ Collection<V> collection = map.get(key); if (collection == null) return Collections.emptySet(); return collection; } private Collection<V> createCollection(){ Collection<V> collection = null; try{ collection = clazz.newInstance(); }catch(InstantiationException ex){ // handling here }catch(IllegalAccessException ex){ // handling here } return collection; } public static void main(String[] args) { final String KEY = "KEY"; MultiValueMap<String,String> mvm = new MultiValueMap<String,String>(new ArrayList<String>()); mvm.addValue(KEY,"bbb"); mvm.addValue(KEY,"ddd"); mvm.addValue(KEY,"ccc"); mvm.addValue(KEY,"aaa"); mvm.addValue(KEY,"eee"); System.out.println("For ArrayList: "); for(String value: mvm.getValues(KEY)){ System.out.println("Value: "+value); } mvm = new MultiValueMap<String,String>(new TreeSet<String>()); mvm.addValue(KEY,"bbb"); mvm.addValue(KEY,"ddd"); mvm.addValue(KEY,"ccc"); mvm.addValue(KEY,"aaa"); mvm.addValue(KEY,"eee"); System.out.println("For TreeSet: "); for(String value: mvm.getValues(KEY)){ System.out.println("Value: "+value); } } }Requred collection can be passed inside, and it will be used.
Why do I pass collection instead of it's class? Because of the cast required. I didn't find any way to avoid it. This cast is safe, because type returned is exactly what we need, but getClass has return value type Class<? extends Object>. So, if we will perform cast inside constructor, it will give additional check of the type of collection passed into. We have no ability to pass TreeSet<Object>, for instance.
Regards,
Eugene aka Skipy
Re: Collections: Multivalue-per-key Maps
I made this implementation that respect the Map interface.I want to receive some feedback about the way it's implemented.
The Code:
public class MultiHashMap<K, V> extends HashMap<K, List<V>> {
public void put(K key, V value) {
List<V> elems = get(key);
if (elems == null) {
elems = new LinkedList<V>();
put(key, elems);
}
elems.add(value);
}
}
Re: Collections: Multivalue-per-key Maps
Pablo,Just an FYI - I went through and replaced your < tags with < escaped characters.
Cheers,
Re: Collections: Multivalue-per-key Maps
That's pretty much how I'd do it. I would probably use Collection<V> instead of List<V> unless the order of the elements within each bag is important.Also, I'd suggest you use "add" instead of "put". The name "put" generally implies replacing the existing value, but what you're really doing is adding a new value. Then I'd provide a remove(K key, V value) method to remove a single value at a time and perhaps some other convenience methods like contains(K key, V value).
Other than those minor points, I'd say your approach is fine.