I want to write a utility for general memoization in Java, I want the code be able to look like this:
Util.memoize(() -> longCalculation(1));
where
private Integer longCalculation(Integer x) {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {}
return x * 2;
}
To do this, I was thinking I could do something like this:
public class Util{
private static final Map<Object, Object> cache = new ConcurrentHashMap<>();
public interface Operator<T> {
T op();
}
public static<T> T memoize(Operator<T> o) {
ConcurrentHashMap<Object, T> memo = cache.containsKey(o.getClass()) ? (ConcurrentHashMap<Object, T>) cache.get(o.getClass()) : new ConcurrentHashMap<>();
if (memo.containsKey(o)) {
return memo.get(o);
} else {
T val = o.op();
memo.put(o, val);
return val;
}
}
}
I was expecting this to work, but I see no memoization being done. I have tracked it down to the o.getClass() being different for each invocation.
I was thinking that I could try to get the run-time type of T but I cannot figure out a way of doing that.
The answer by Lino points out a couple of flaws in the code, but doesn't work if not reusing the same lambda.
This is because o.getClass() does not return the class of what is returned by the lambda, but the class of the lambda itself. As such, the below code returns two different classes:
Util.memoize(() -> longCalculation(1));
Util.memoize(() -> longCalculation(1));
I don't think there is a good way to find out the class of the returned type without actually executing the potentially long running code, which of course is what you want to avoid.
With this in mind I would suggest passing the class as a second parameter to memoize(). This would give you:
#SuppressWarnings("unchecked")
public static <T> T memoize(Operator<T> o, Class<T> clazz) {
return (T) cache.computeIfAbsent(clazz, k -> o.op());
}
This is based on that you change the type of cache to:
private static final Map<Class<?>, Object> cache = new ConcurrentHashMap<>();
Unfortunately, you have to downcast the Object to a T, but you can guarantee that it is safe with the #SuppressWarnings("unchecked") annotation. After all, you are in control of the code and know that the class of the value will be the same as the key in the map.
An alternative would be to use Guavas ClassToInstanceMap:
private static final ClassToInstanceMap<Object> cache = MutableClassToInstanceMap.create(new ConcurrentHashMap<>());
This, however, doesn't allow you to use computeIfAbsent() without casting, since it returns an Object, so the code would become a bit more verbose:
public static <T> T memoize(Operator<T> o, Class<T> clazz) {
T cachedCalculation = cache.getInstance(clazz);
if (cachedCalculation != null) {
return cachedCalculation;
}
T calculation = o.op();
cache.put(clazz, calculation);
return calculation;
}
As a final side note, you don't need to specify your own functional interface, but you can use the Supplier interface:
#SuppressWarnings("unchecked")
public static <T> T memoize(Supplier<T> o, Class<T> clazz) {
return (T) cache.computeIfAbsent(clazz, k -> o.get());
}
The problem you have is in the line:
ConcurrentHashMap<Object, T> memo = cache.containsKey(o.getClass()) ? (ConcurrentHashMap<Object, T>) cache.get(o.getClass()) : new ConcurrentHashMap<>();
You check whether an entry with the key o.getClass() exists. If yes, you get() it else you use a newly initialized ConcurrentHashMap. The problem now with that is, you don't save this newly created map, back in the cache.
So either:
Place cache.put(o.getClass(), memo); after the line above
Or even better use the computeIfAbsent() method:
ConcurrentHashMap<Object, T> memo = cache.computeIfAbsent(o.getClass(),
k -> new ConcurrentHashMap<>());
Also because you know the structure of your cache you can make it more typesafe, so that you don't have to cast everywhere:
private static final Map<Object, Map<Operator<?>, Object>> cache = new ConcurrentHashMap<>();
Also you can shorten your method even more by using the earlier mentioned computeIfAbsent():
public static <T> T memoize(Operator<T> o) {
return (T) cache
.computeIfAbsent(o.getClass(), k -> new ConcurrentHashMap<>())
.computeIfAbsent(o, k -> o.op());
}
(T): simply casts the unknown return type of Object to the required output type T
.computeIfAbsent(o.getClass(), k -> new ConcurrentHashMap<>()): invokes the provided lambda k -> new ConcurrentHashMap<>() when there is no mapping for the key o.getClass() in cache
.computeIfAbsent(o, k -> o.op());: this is invoked on the returned value from the computeIfAbsent call of 2.. If o doesn't exist in the nested map then execute the lambda k -> o.op() the return value is then stored in the map and returned.
Related
I need a method to order a Map of generic object by a generic attribute of the object. I tried the code below, similar to other example I found on StackOverFlow, but I didn't find any example with a generic attribute. I'm not expert of lamda, so for me it is hard to understand clearly some logics.
I get error on compareTo; Ntebeans says me:
"cannot find symbol symbol: method compareTo(CAP#1)
location: class Object where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?"
Example:
I have a object 'car' with attribute 'name'
I have an Hashmap<Integer,car>, containing items: key 1, object with name=Ford --- key 2, object with name=Audi --- key 3, object with name=Fiat
The first element of the map has key 1, the second has key 2, the third has key 3
I would like to have in output an Arraylist where: - The first element is object 'Audi', the second is object 'Fiat', the third is object 'Ford', so to have the 3 names sorted.
In order to invoke this method I would use for example:
ArrayList<Car> SORTED_Cars = get_ListOfObject_SortedByAttribute(myHashMap, car -> car.getName() );
I should get an ArrayList of object 'car' ordered by attribute 'name'.
The final task is to have a method that I'll use with Map of different Objects, then with different attributes.
Note that I use this checking condition
if (MyMap_Arg!=null && MyMap_Arg.size()>0 && MyMap_Arg.values()!=null)
because I prefer get null when the ordering is not possible or the map is empty.
How should be the code below to work?
private static <T> List<T> get_ListOfObject_SortedByAttribute(final Map<?, T> MyMap_Arg, final Function<T, ?> MY_AttributeValueExtractor__Arg ) {
List<T> result = null;
try {
if (MyMap_Arg!=null && MyMap_Arg.size()>0 && MyMap_Arg.values()!=null){
if (MY_AttributeValueExtractor__Arg!=null ) {
//_____________________________________________________
//Crea una lista di oggetti che hanno MY_AttributeValueExtractor_1_Arg!=null; altrimenti applicando '.compare' darebbe exception
List<T> MY_LIST__SenzaNull = MyMap_Arg.values().stream().filter( o -> MY_AttributeValueExtractor__Arg.apply(o)!=null ).collect(Collectors.toList());
//_____________________________________________________
//TEST ********* Ordina la lista di oggetti alfabeticamente in base a MY_AttributeValueExtractor_1_Arg
result = MY_LIST__SenzaNull.stream().sorted(
(o1, o2)-> MY_AttributeValueExtractor__Arg.apply(o1).
compareTo( MY_AttributeValueExtractor__Arg.apply(o2) )
).
collect(Collectors.toList());
//_____________________________________________________
}
}
} catch (Exception ex) {
result=null;
}
return result;
}
Given your code (with refactorings for clarity)
static <T> List<T> getListSortedByAttribute(Map<?, T> aMap,
Function<T, ?> attributeValueExtractor) {
List<T> result = null;
try {
if (aMap != null && aMap.size() > 0 && aMap.values() != null) {
if (attributeValueExtractor != null) {
List<T> listWithoutElementsReturingNullAsAttributeValue = aMap
.values()
.stream()
.filter(o -> attributeValueExtractor.apply(o) != null)
.collect(Collectors.toList());
result = listWithoutElementsReturingNullAsAttributeValue
.stream()
.sorted((o1, o2) -> attributeValueExtractor.apply(o1).
compareTo(attributeValueExtractor.apply(o2)))
.collect(Collectors.toList());
}
}
} catch (Exception ex) {
result = null;
}
return result;
}
you want to use a compareTo method to compare (sort) list elements by one of their attributes given as function
(o1, o2) -> attributeValueExtractor.apply(o1)
.compareTo(attributeValueExtractor.apply(o2))
With which you get the compile-time error
Error:(149, 78) java: cannot find symbol
symbol: method compareTo(capture#1 of ?)
location: class java.lang.Object
Meaning, there's nothing in your code which ensures that your attributes have such a method (and can be compared for sorting). In particular, Function<T, ?> attributeValueExtractor) says that something type T is mapped (by your function) to type ? which can only be Object; and Object doesn't have a compareTo method. (Note that Object doesn't have such a method because there's simply no meaningful way of comparing arbitrary objects with each other).
To fix it (at the very least), you need to ensure that your objects implement such a method. So your method signature
static <T> List<T> getListSortedByAttribute(
Map<?, T> aMap, Function<T, ?> attributeValueExtractor)
needs to change to
static <T, U extends Comparable<U>> List<T> getListSortedByAttribute(
Map<?, T> aMap, Function<T, U> attributeValueExtractor)
where U types are required to implement the interface Comparable which has the method compareTo.
With that you get (with a few more refactorings and addition of an example)
public static void main(String[] args) {
Map<Integer, Car> map = new HashMap<>();
map.put(1, new Car("Ford"));
map.put(2, new Car("Audi"));
map.put(3, new Car("Fiat"));
map.put(4, new Car(null));
List<Car> list = getListSortedByAttribute(map, Car::getName);
System.out.println(list);
}
static <T, U extends Comparable<U>> List<T> getSortedValues(
Map<?, T> aMap, Function<T, U> attributeExtractor) {
List<T> result = null;
try {
if (aMap != null && aMap.size() > 0) {
if (attributeExtractor != null) {
result = aMap
.values()
.stream()
.filter(o -> attributeExtractor.apply(o) != null)
.sorted((o1, o2) -> attributeExtractor.apply(o1).
compareTo(attributeExtractor.apply(o2)))
.collect(Collectors.toList());
}
}
} catch (Exception ex) {
result = null;
}
return result;
}
static class Car {
private final String name;
Car(String name) { this.name = name; }
String getName() { return name; }
#Override
public String toString() { return name; }
// code for equals() and hashCode() omitted for brevity
}
which prints
[Audi, Fiat, Ford]
As for List<Car> list = getListSortedByAttribute(map, Car::getName); note that you can use a lambda expression (Car car) -> car.getName(), or a method reference Car::getName as method argument. I chose the later because I find it shorter.
The invocation then works because the return type of Car::getName is a String which implements Comparable and therefore has a compareTo method implementation.
Notes about your code (my opinion)
When using a method which returns a Collection type like yours it's very surprising if such a method returns null instead of an empty collection – and it'll be the cause of many (and also surprising) NullPointerExceptions down the road.
As pointed out in the comments aMap.values() != null is always true because (surprise) the Java API designers/implementers decided that this method should always return a non-null collection, i.e. an empty collection if there are no values. Anyhow that condition has no effect, it's always true.
aMap != null and attributeValueExtractor != null, simply throw instead because calling your method with null arguments simply shouldn't be a valid invocation which is allowed to continue (fail fast principle).
aMap.size() > 0 isn't really needed as the subsequent stream-code handles that without any problem, i.e. the result is simply an empty list. And intentionally returing null for that case isn't something I'd every do (as explained above).
Don't swallow exceptions, either throw (fail fast principle) or recover meaningfully. But returning null as outlined above isn't a meaningful recovery.
With respect to the above and further changes you then get
static <T, U extends Comparable<U>> List<T> getSortedValues(
Map<?, T> aMap, Function<T, U> attributeExtractor) {
Objects.requireNonNull(aMap, "Map to be sorted cannot be null");
Objects.requireNonNull(attributeExtractor, "Function to extract a value cannot be null");
return aMap
.values()
.stream()
.filter(o -> attributeExtractor.apply(o) != null)
.sorted(Comparator.comparing(attributeExtractor))
.collect(Collectors.toList());
}
I have a Map<String, List<SomeClass>> someMap and I'm retrieving the value based on someKey and for each element of the list of SomeClass I'm performing other operations.
someMap.getOrDefault(someKey, new ArrayList<>()).forEach(...)
I also want to be able to log messages when I don't find someKey. How would I be able to achieve it optimally? Is there any other function/way to achieve this behavior?
Map<String, List<String>> map = new HashMap<>();
List<String> l = new ArrayList<>();
l.add("b");
map.put("a", l);
Yes, you can do it in a single statement. Use .compute().
map.compute("a", (k, v) -> {
if (v == null) {
System.out.println("Key Not Found");
return new ArrayList<>();
}
return v;
}).forEach(System.out::println);
There's also computeIfAbsent() which will only compute the lambda if the key is not present.
Note, from the documentation:
Attempts to compute a mapping for the specified key and its current
mapped value (or null if there is no current mapping).
This will add the key which was not found in your map.
If you want to remove those keys later, then simply add those keys to a list inside the if and remove them in one statement like this:
map.keySet().removeAll(listToRemove);
You can create a function to do that. For example, I created a function which will get the value from the map, return it if it is not null, or an empty list otherwise. Before returning the empty list, you can run a Runnable action. The main benefit of that is that you can do more than just logging there.
#Slf4j
public class Main {
public static Collection<String> retrieveOrRun(Map<String, Collection<String>> map, String key, Runnable runnable) {
final Collection<String> strings = map.get(key);
if (strings == null) {
runnable.run();
return Collections.emptyList();
} else {
return strings;
}
}
public static void main(String[] args) {
Map<String, Collection<String>> map = new HashMap<>();
Collection<String> strings = retrieveOrRun(map, "hello", () -> log.warn("Could not find a value for the key : {}", "hello"));
}
}
I think you have two choices:
Either you use a wrapper method, doing the actual call (getOrDefault, etc) and handling missing keys.
public static <K,V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
V value = map.get(key);
if (value == null) {
logMissingValue(key);
return defaultValue;
}
return value;
}
Or you create new implementation of Map doing just that, with a delegation to method that should be delegated (I won't do here in this example, but Eclipse work pretty well: Alt + Shift + S > Create delegate methods):
class LoggerMap<K,V> implements Map<K,V> {
private final Map<K,V> internal;
public LoggerMap(Map<K,V> internal) {
this.internal = Objects.requireNonNull(internal, "internal");
}
#Override
public V getOrDefault(K key, V defaultValue) {
... if not found logMissingValue(key); ...
}
}
Now about which is optimal, that depends on your needs: if you know you will always use the wrapper method, then your missing keys will always be logged. Creating a new map implementation would be overkill.
If your need is to log absolutely all missing keys - even if foreign code (for example, some API taking a map as a parameter), then your best choice is a map implementation:
In terms of performance, I don't think you should worry about delegation: I did not test it using a benchmark, but the JVM should be able to optimize that.
There are other parts where a key might return a missing value (eg: remove, get, ...), using such an implementation will allow you to easily trace those as well.
I have stream of files, and a method which takes two files as an argument, and return if they have same content or not.
I want to reduce this stream of files to a set (or map) of sets grouping all the files with identical content.
I know this is possible by refactoring the compare method to take one file, returning a hash and then grouping the stream by the hash returned by the function given to the collector. But what is the cleanest way to achieve this with a compare method, which takes two files and returns a boolean?
For clarity, here is an example of the obvious way with the one argument function solution
file.stream().collect(groupingBy(f -> Utility.getHash(f))
But in my case I have the following method which I want to utilize in the partitioning process
public boolean isFileSame(File f, File f2) {
return Files.equal(f, f2)
}
If all you have is a BiPredicate without an associated hash function that would allow an efficient lookup, you can only use a linear probing. There is no builtin collector doing that, but a custom collector working close to the original groupingBy collector can be implemented like
public static <T> Collector<T,?,Map<T,Set<T>>> groupingBy(BiPredicate<T,T> p) {
return Collector.of(HashMap::new,
(map,t) -> {
for(Map.Entry<T,Set<T>> e: map.entrySet())
if(p.test(t, e.getKey())) {
e.getValue().add(t);
return;
}
map.computeIfAbsent(t, x->new HashSet<>()).add(t);
}, (m1,m2) -> {
if(m1.isEmpty()) return m2;
m2.forEach((t,set) -> {
for(Map.Entry<T,Set<T>> e: m1.entrySet())
if(p.test(t, e.getKey())) {
e.getValue().addAll(set);
return;
}
m1.put(t, set);
});
return m1;
}
);
but, of course, the more resulting groups you have, the worse the performance will be.
For your specific task, it will be much more efficient to use
public static ByteBuffer readUnchecked(Path p) {
try {
return ByteBuffer.wrap(Files.readAllBytes(p));
} catch(IOException ex) {
throw new UncheckedIOException(ex);
}
}
and
Set<Set<Path>> groupsByContents = your stream of Path instances
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(YourClass::readUnchecked, Collectors.toSet()),
map -> new HashSet<>(map.values())));
which will group the files by contents and does hashing implicitly. Keep in mind that equal hash does not imply equal contents but this solution does already take care of this. The finishing function map -> new HashSet<>(map.values()) ensures that the resulting collection does not keep the file’s contents in memory after the operation.
A possible solution by the helper class Wrapper:
files.stream()
.collect(groupingBy(f -> Wrapper.of(f, Utility::getHash, Files::equals)))
.keySet().stream().map(Wrapper::value).collect(toList());
If you won't to use the Utility.getHash for some reason, try to use File.length() for the hash function. The Wrapper provides a general solution to customize the hash/equals function for any type (e.g. array). it's useful to keep it into your tool kit. Here is the sample implementation for the Wrapper:
public class Wrapper<T> {
private final T value;
private final ToIntFunction<? super T> hashFunction;
private final BiFunction<? super T, ? super T, Boolean> equalsFunction;
private int hashCode;
private Wrapper(T value, ToIntFunction<? super T> hashFunction, BiFunction<? super T, ? super T, Boolean> equalsFunction) {
this.value = value;
this.hashFunction = hashFunction;
this.equalsFunction = equalsFunction;
}
public static <T> Wrapper<T> of(T value, ToIntFunction<? super T> hashFunction, BiFunction<? super T, ? super T, Boolean> equalsFunction) {
return new Wrapper<>(value, hashFunction, equalsFunction);
}
public T value() {
return value;
}
#Override
public int hashCode() {
if (hashCode == 0) {
hashCode = value == null ? 0 : hashFunction.applyAsInt(value);
}
return hashCode;
}
#Override
public boolean equals(Object obj) {
return (obj == this) || (obj instanceof Wrapper && equalsFunction.apply(((Wrapper<T>) obj).value, value));
}
// TODO ...
}
I have a method that should only accept a Map whose key is of type String and value of type Integer or String, but not, say, Boolean.
For example,
map.put("prop1", 1); // allowed
map.put("prop2", "value"); // allowed
map.put("prop3", true); // compile time error
It is not possible to declare a Map as below (to enforce compile time check).
void setProperties(Map<String, ? extends Integer || String> properties)
What is the best alternative other than declaring the value type as an unbounded wildcard and validating for Integer or String at runtime?
void setProperties(Map<String, ?> properties)
This method accepts a set of properties to configure an underlying service entity. The entity supports property values of type String and Integer alone. For example, a property maxLength=2 is valid, defaultTimezone=UTC is also valid, but allowDuplicate=false is invalid.
Another solution would be a custom Map implementation and overrides of the put and putAll methods to validate the data:
public class ValidatedMap extends HashMap<String, Object> {
#Override
public Object put(final String key, final Object value) {
validate(value);
return super.put(key, value);
}
#Override
public void putAll(final Map<? extends String, ?> m) {
m.values().forEach(v -> validate(v));
super.putAll(m);
}
private void validate(final Object value) {
if (value instanceof String || value instanceof Integer) {
// OK
} else {
// TODO: use some custom exception
throw new RuntimeException("Illegal value type");
}
}
}
NB: use the Map implementation that fits your needs as base class
Since Integer and String closest common ancestor in the class hierarchy is Object you cannot achieve what you are trying to do - you can help compiler to narrow the type to Object only.
You can either
wrap your value into a class which can contain either Integer or String, or
extend Map as in the #RC's answer, or
wrap 2 Maps in a class
You can’t declare a type variable to be either of two types. But you can create a helper class to encapsulate values not having a public constructor but factory methods for dedicated types:
public static final class Value {
private final Object value;
private Value(Object o) { value=o; }
}
public static Value value(int i) {
// you could verify the range here
return new Value(i);
}
public static Value value(String s) {
// could reject null or invalid string contents here
return new Value(s);
}
// these helper methods may be superseded by Java 9’s Map.of(...) methods
public static <K,V> Map<K,V> map(K k, V v) { return Collections.singletonMap(k, v); }
public static <K,V> Map<K,V> map(K k1, V v1, K k2, V v2) {
final HashMap<K, V> m = new HashMap<>();
m.put(k1, v1);
m.put(k2, v2);
return m;
}
public static <K,V> Map<K,V> map(K k1, V v1, K k2, V v2, K k3, V v3) {
final Map<K, V> m = map(k1, v1, k2, v2);
m.put(k3, v3);
return m;
}
public void setProperties(Map<String, Value> properties) {
Map<String,Object> actual;
if(properties.isEmpty()) actual = Collections.emptyMap();
else {
actual = new HashMap<>(properties.size());
for(Map.Entry<String, Value> e: properties.entrySet())
actual.put(e.getKey(), e.getValue().value);
}
// proceed with actual map
}
If you are using 3rd party libraries with map builders, you don’t need the map methods, they’re convenient for short maps only. With this pattern, you may call the method like
setProperties(map("mapLength", value(2), "timezone", value("UTC")));
Since there are only the two Value factory methods for int and String, no other type can be passed to the map. Note that this also allows using int as parameter type, so widening of byte, short etc. to int is possible here.
Define two overloads:
void setIntegerProperties(Map<String, Integer> properties)
void setStringProperties(Map<String, String> properties)
They have to be called different things, because you can't have two methods with the same erasure.
I'm fairly certain if any language was going to disallow multiple accepted types for a value, it would be Java. If you really need this kind of capability, I'd suggest looking into other languages. Python can definitely do it.
What's the use case for having both Integers and Strings as the values to your map? If we are really dealing with just Integers and Strings, you're going to have to either:
Define a wrapper object that can hold either a String or an Integer. I would advise against this though, because it will look a lot like the other solution below.
Pick either String or Integer to be the value (String seems like the easier choice), and then just do extra work outside of the map to work with both data types.
Map<String, String> map;
Integer myValue = 5;
if (myValue instanceof Integer) {
String temp = myValue.toString();
map.put(key, temp);
}
// taking things out of the map requires more delicate care.
try { // parseInt() can throw a NumberFormatException
Integer result = Integer.parseInt(map.get(key));
}
catch (NumberFormatException e) {} // do something here
This is a very ugly solution, but it's probably one of the only reasonable solutions that can be provided using Java to maintain some sense of strong typing to your values.
This question already has answers here:
How to convert List to Map?
(20 answers)
Closed 7 years ago.
I would like to find a way to take the object specific routine below and abstract it into a method that you can pass a class, list, and fieldname to get back a Map.
If I could get a general pointer on the pattern used or , etc that could get me started in the right direction.
Map<String,Role> mapped_roles = new HashMap<String,Role>();
List<Role> p_roles = (List<Role>) c.list();
for (Role el : p_roles) {
mapped_roles.put(el.getName(), el);
}
to this? (Pseudo code)
Map<String,?> MapMe(Class clz, Collection list, String methodName)
Map<String,?> map = new HashMap<String,?>();
for (clz el : list) {
map.put(el.methodName(), el);
}
is it possible?
Using Guava (formerly Google Collections):
Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, Functions.toStringFunction());
Or, if you want to supply your own method that makes a String out of the object:
Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, new Function<Role,String>() {
public String apply(Role from) {
return from.getName(); // or something else
}});
Here's what I would do. I am not entirely sure if I am handling generics right, but oh well:
public <T> Map<String, T> mapMe(Collection<T> list) {
Map<String, T> map = new HashMap<String, T>();
for (T el : list) {
map.put(el.toString(), el);
}
return map;
}
Just pass a Collection to it, and have your classes implement toString() to return the name. Polymorphism will take care of it.
Java 8 streams and method references make this so easy you don't need a helper method for it.
Map<String, Foo> map = listOfFoos.stream()
.collect(Collectors.toMap(Foo::getName, Function.identity()));
If there may be duplicate keys, you can aggregate the values with the toMap overload that takes a value merge function, or you can use groupingBy to collect into a list:
//taken right from the Collectors javadoc
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
As shown above, none of this is specific to String -- you can create an index on any type.
If you have a lot of objects to process and/or your indexing function is expensive, you can go parallel by using Collection.parallelStream() or stream().parallel() (they do the same thing). In that case you might use toConcurrentMap or groupingByConcurrent, as they allow the stream implementation to just blast elements into a ConcurrentMap instead of making separate maps for each thread and then merging them.
If you don't want to commit to Foo::getName (or any specific method) at the call site, you can use a Function passed in by a caller, stored in a field, etc.. Whoever actually creates the Function can still take advantage of method reference or lambda syntax.
Avoid reflection like the plague.
Unfortunately, Java's syntax for this is verbose. (A recent JDK7 proposal would make it much more consise.)
interface ToString<T> {
String toString(T obj);
}
public static <T> Map<String,T> stringIndexOf(
Iterable<T> things,
ToString<T> toString
) {
Map<String,T> map = new HashMap<String,T>();
for (T thing : things) {
map.put(toString.toString(thing), thing);
}
return map;
}
Currently call as:
Map<String,Thing> map = stringIndexOf(
things,
new ToString<Thing>() { public String toString(Thing thing) {
return thing.getSomething();
}
);
In JDK7, it may be something like:
Map<String,Thing> map = stringIndexOf(
things,
{ thing -> thing.getSomething(); }
);
(Might need a yield in there.)
Using reflection and generics:
public static <T> Map<String, T> MapMe(Class<T> clz, Collection<T> list, String methodName)
throws Exception{
Map<String, T> map = new HashMap<String, T>();
Method method = clz.getMethod(methodName);
for (T el : list){
map.put((String)method.invoke(el), el);
}
return map;
}
In your documentation, make sure you mention that the return type of the method must be a String. Otherwise, it will throw a ClassCastException when it tries to cast the return value.
If you're sure that each object in the List will have a unique index, use Guava with Jorn's suggestion of Maps.uniqueIndex.
If, on the other hand, more than one object may have the same value for the index field (which, while not true for your specific example perhaps, is true in many use cases for this sort of thing), the more general way do this indexing is to use Multimaps.index(Iterable<V> values, Function<? super V,K> keyFunction) to create an ImmutableListMultimap<K,V> that maps each key to one or more matching values.
Here's an example that uses a custom Function that creates an index on a specific property of an object:
List<Foo> foos = ...
ImmutableListMultimap<String, Foo> index = Multimaps.index(foos,
new Function<Foo, String>() {
public String apply(Foo input) {
return input.getBar();
}
});
// iterate over all Foos that have "baz" as their Bar property
for (Foo foo : index.get("baz")) { ... }