Here is an example method:
public void loadStuff(Map<String, Object> someMap) {
Map<String, Object> myMap = new HashMap<String, Object>();
//I now load defaults here first
myMap.put("One", someObject);
myMap.put("two", someObject);
myMap.put("three", someObject);
//Now I put the entire someMap so that only those keys that are present in someMap are overridden in myMap and others remain default.
myMap.putAll(someMap);
}
Now, is there a better way of doing these redundant puts as the number of defaults in my scenario are a lot.
Consider creating an initial map with your defaults in, and then use:
// Alternatively, you could use clone()
Map<String, Object> myMap = new HashMap<String, Object>(defaults);
myMap.putAll(someMap);
Aside from anything else that means you can load the "default map" from a properties file or whatever.
If you really don't like the fact that it will put each value twice, you could write a loop to check for each key - but I'd personally just use the above code. It's simple and it should work fine.
Are you wanting to preload a single answer for just a few items, or are you wanting a default for all unfound keys? If you want to change the default answer from null to something else, see this question. If you're wanting to preload some items, then you'll need to put all of them, though it's best not to embed the values in code like that; use a for loop instead that iterates over a single official list of the keys.
If you are going to be initializing blank copies of this Map frequently, it will make more sense to have a template Map that each myMap is constructed from; either a HashMap wrapped as unmodifiable or a Guava ImmutableMap are good choices there. Constructing from a preexisting Map instead of copying all of the elements into the new HashMap is much more efficient since the new one knows how big to make itself.
Related
I'm still getting used to Java Generics however I'm currently in the process of updating an application written prior to generics to use the latest version of java.
Problem is the code was not written with type safety in mind!
We have a whole bunch of Maps that basically hold various object types including strings. For example:
Map map = new HashMap();
map.put("key1", "String1");
map.put("key2", new Date());
map.put("key3", new CutsomClass());
Now I'm still struggling with the best way to handle these without getting into refactoring a whole lot of code. Refactoring is not an option at this time.
Currently I can't see anything past Map<String, Object> although Map<String, ? super Object> works but I think its essentially the same thing ?
I'm still struggling with the best way to handle these without getting
into refactoring a whole lot of code
So don't change them at all. The raw types - that is, the non-generic types - are still technically valid. It's not ideal and it will generate a compiler warning but the code will work (well, work as well as it ever did).
All classes extend Object so you can put any value you want into the following map:
Map<String, Object> map = new HashMap<>();
You get an additional guarantee that the key is a string, so its somewhat better than using the raw type.
Basically though, you should really try to avoid using a map if you can't define the type of the key or the value.
As of now, you can only replace the raw type Map with Map<String, Object>; but that type information is close to "useless". Unless you refactor your whole component to deal with different map objects, there isn't much you can do. Of course, you can get rid of the type warnings, but you still have to do instanceof checks each time you access a Map value.
On way out of this: assuming that number of "value" types is known and reasonably small, you could create a bunch of helper methods that go like:
public Map<String, Date> extractDates(Map<String, Object> allValues) {
...
This method could implement a "wrapper" around the allValues map that only provides those map entries that are actually Date objects.
Meaning: you keep your current map object, but you provide "more specialized" views on that map. That allows you to write new code exploiting the additional type information.
But of course, this doesn't come for free. It adds certain complexity, and defining the exact details of such "view maps" might turn out to be rather complicated.
There is little you can do to achieve the full static type checking available with Generics used properly. However, I don't believe you must abandon type checking completely and rely on run-time casting in this case. I think you can go one step towards that.
I assume this is a common map that the code uses for general storage, perhaps for persistence or properties etc. If this is the case then you can at least do something like this:
class AnyMap<K> {
final Map<K,Object> map;
public AnyMap(Map<K, Object> map) {
this.map = map;
}
public <V> Map<K,V> as(Class<V> theClass) {
return (Map<K,V>) map;
}
}
public void test() {
AnyMap<String> commonMap = new AnyMap<>(Collections.EMPTY_MAP);
// Use this one as a Date map.
Map<String,Date> dateMap = commonMap.as(Date.class);
// This one as a String map.
Map<String,String> stringMap = commonMap.as(String.class);
}
This is a kind of Map holder that can then deliver the map as a proper generic object with the correct bounds. Hopefully you will find that certain modules will use the common map exclusively for Dates and others will use Strings. In areas such as these you can use Map<X,Y> as(...) to give you a properly statically-checked map for that module/section and use that exclusively in that code section.
As Michael suggested Map<String, Object> map = new HashMap<>();
is a first step.
However, it assumes that you have only String as keys and you will probably need to cast objects you get from the map.
I think that a second step would be to split this map into multiple maps declaring a more specific type :
Map<String, Date> mapDates = new HashMap<>();
Map<String, String> mapStrings = new HashMap<>();
Map<String, CustomClass> mapCutsomClasses = new HashMap<>();
Dont use raw types... see this to know why..
Now, you can break it out as following, so you can get it:
your map<K,V> has as keys Strings only, so K = string will be correct, on the other hand "String1", new Date() and new Custom Class seems to have nothing in common, but wait, all the classes in java are actually inheriting the Object class... that means you can do V=Object
now your map can be declared as Map<String, Object> and all this
map.put("key1", "String1");
map.put("key2", new Date());
map.put("key3", new CutsomClass());
will be ok
Conversion from non-generics (e.g. old Java) to generics can be a real PITA. The easiest way to do it is to replace each Map, Set, List with related generic, e.g.
Map<String, Object> map = new HashMap<>();
but only if mapped objects are not generics too (or are generics just used for reading). E.g. if in your code had something like
Map hasmap = new HashMap();
hashmap.put("blah",123)
map.put("keyX",hashmap);
In such cases, when you find map getter, and you will have in original code a put, you will have lot of troubles:
Map hashmap = (HashMap)(map.get("keyX"));
Integer value = hashmap.get("blah");
hashmap.put("otherkey","mooo");
In this case, you can't have clear code: if you use question marks, you will face errors, like in
Map<?,?> hashmap = (HashMap<?,?>)(map.get("keyX"));
Integer value = (Integer)hashmap.get("blah"); // this works
hashmap.put("otherkey","mooo"); // this crashes
so you have two alternatives: rewrite code (to avoid warnings), or force everything at Objects, and receive Unchecked Casts warnings.
#SuppressWarnings("unchecked")
Map<Object,Object> hashmap = (HashMap<Object,Object>)(map.get("keyX"));
Integer value = (Integer)hashmap.get("blah"); // this works
hashmap.put("otherkey","mooo"); // this works too
Further details about question marks and generics can be found here: What is the difference between ? and Object in Java generics?
Looks like you want to quickly port the old code in and also you want to move the old code towards strict type safety without refactoring a large code base. Keep your old code by porting it in using this:
Map<String, Object> oldMapPorted = new HashMap<>();
New code written in this app can use a technique like this for strict type safety:
Map<String, Date> newMapDates = new HashMap<>();
Map<String, String> newMapStrings = new HashMap<>();
Map<String, CustomClass> newCutsomClasses = new HashMap<>();
A new class can be created for future edits and enhancements while the old code still has the same potential instability as usual.
If I have a hashmap like this:
private final Map<String, Collection<String>> descriptions = new HashMap<>();
How do I pass the values safely to an alien method?
If I do this:
myOtherObject.outputDesc(descriptions.values());
then myOtherObject could change the values.
Would this be a safe way of doing this?
myOtherObject.outputDesc(new ArrayList<>(descriptions .values()));
Creating a copy of the collection as you suggested is a way you could go. But the extra effort for copying the list is not needed. Java provides a more convenient way for preventing value changes:
myOtherObject.outputDesc(Collections.unmodifiableCollection(descriptions.values()));
I've read that when you define a {Map, Set, Etc} it is good practice use the interface name as so:
Map<Integer, String> map = new LinkedHashMap<Integer, String>();
instead of:
LinkedHashMap<Integer, String> map = new LinkedHashMap<Integer, String>();
I'm not sure why this is, but I've put it to practice in hopes I will understand at a later time. Maybe that time has come.
So I create a class that defines one and create a getter for the Map:
class Data{
private Map<Integer, String> map;
public Data(){
map = new LinkedHashMap<Integer, String>();
//dynamically put some things into the map
}
public Map<Integer, String> getMap(){
return map;
}
}
Now I come to my first impasse. I can't return a LinkedHashMap, I have to return a Map.
So in another class I get that Map
class Foo{
public Foo{
Data data = new Data();
Map<Integer, String> map = data.getMap();
}
}
Can someone explain what is happening to map when it gets passed?
Is it still a LinkedHashMap?
Is the data changed at all?
What would happen to the order of the Map if, after calling getData(), I put something in the Map?
Why wouldn't/shouldn't I just define the Map as in the second code snippet?
Is my method of getting the map done in ignorance?
Should I just make map public?
Now I come to my first impasse. I can't return a LinkedHashMap
Here's the misunderstanding: you can return a LinkedHashMap, because a LinkedHashMap is a Map, a particular sort of Map, but a Map anyway.
Can someone explain what is happening to map when it gets passed?
When it's passed, it is seen as any Map, like incognito, but it remains the same.
Is it still a LinkedHashMap?
Yes.
Is the data changed at all?
No.
What would happen to the order of the Map if, after calling getData(), I put something in the Map?
This is another topic.
Why wouldn't/shouldn't I just define the Map as in the second code snippet?
You needn't do that, since a LinkedHashMap is a Map (on the other hand, a Map is not necessarily a LinkedHashMap).
Is my method of getting the map done in ignorance?
Should I just make map public?
No.
You may want your variable to be able do what it needs to do and still be as flexible as possible in the way it does that. So your variable should be of the type that covers all your needed functionality but is as high as possible in hirachy.
I need a double table from which I get two values from a key or index. I have seen this question already and I want to know what would be a better approach considering also performance.
1) Create a HashMap on this way:
HashMap<Integer, HashMap<String, String>> = ...;
I don't know how to put values inside this the put method, I have this and Eclipse gives me an error prueba.put(0, new Hashtable<"Hi", "Bye">); As you can see I have never used something like this before I am sure is a simple question.
2) Create a HashMap on this way:
HashMap<Integer, YourFancyDatatype>
So I create a class which pack the two or more values I want to have in one Object inside a single key or index.
Which would perform better ? Also if you can help me about how to use number 1) approach. The HashMap will have about 20000 entries.
Thank you very much for your time and help :)
You would want something with a single key and a collection of values. I would suggest using Apache's MultiMap, as they already implement this functionality for you.
Your first approach uses the same datastructure as provided by the Guava's HashBasedTable so you can use it instead.
But if you want the best performance you could try to use something based on arrays (e.g. Guava's ArrayTable)
Anyway I suggest to make some simple performance tests to check which solution performs better.
It you want to do an "in-line" put, you can do this:
prueba.put(0, new HashMap<String, String>() {{put("Hi", "Bye");}});
This employs an anonymous subclass of HashMap that has an instance block that loads the values.
Note that this will create one extra class for the JVM (called MyClass$1 or similar).
I don't know how to put values inside this the put method, I have this
and Eclipse gives me an error prueba.put(0, new Hashtable<"Hi",
"Bye">); As you can see I have never used something like this before I
am sure is a simple question.
Firstly, Hashtable<String, String> is not a subtype of HashMap<String,String>. your HashMap expects a HashMap<String, String> as a value. either insert a hashmap into values or change your hashmap declaration to :
HashMap<Integer, ? extends Map<String, String>> = ...;
however your 2nd approach is more object oriented. so i'd recommend using 2nd approach
The second one would probably be easier in your case in this way
HashMap<Integer, HashMap<String, FancyDataType>> h= ...;
this is how you'll have to insert the data
h=HashMap<Integer, FancyDataType> new Hashtable<Integer,FancyDataType>();
numbers.put(0, new FancyDataType("o","x"));
numbers.put(1, new FancyDataType("t","y"));
numbers.put(1, new FancyDataType("q","z"));
/// ...so one for all 20000
Assuming FancyDataType is something like
class FancyDataType{
String k,v;
FancyDataType(String k,String v){
this.k=k;this.v=v;
}
}
I am trying to apply a filter to a Map. The intention is to keep only those keys which are part of a set. The following implementation does provide the required results but I want to know if this is the right way?
private void filterProperties(Map<String, Serializable> properties, Set<String> filterSet) {
Set<String> keys = properties.keySet();
keys.retainAll(filterSet);
}
Yes!
The set is backed by the map, so changes to the map are reflected in the set, and vice-versa
(see: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashMap.html#keySet())
Itay's answer is correct, however you should make sure that properties is not modified by other threads, or is itself a thread-safe Map implementation.
If Map is not thread-safe (e.g. HashMap) and is modified by other thread you may get ConcurrentModificationException.
your code looks good. You can write a single line as properties.keySet().retainAll(filterSet);
One problem I see is that the map could be un-modifiable. If that's a possibility then maybe building a new map with original entry set and then filtering and returning it will be a better option.