More succinct way of creating a constant String map in Java - java

Good day all,
Considering the following code example:
import java.util.HashMap;
import java.util.Map;
public class StaticPractice {
private final Map<String, String> mapperMap;
public StaticPractice(){
mapperMap = new HashMap<String, String>();
mapperMap.put("Foo1", "Bar1");
mapperMap.put("Foo2", "Bar1");
mapperMap.put("Foo3", "Bar2");
mapperMap.put("Foo3", "Bar3");
//...
mapperMap.put("MoreFoo", "BarAgain");
}
public void doSomething(){
//use mapperMap
}
}
I am looking for a more succinct way of creating a Map data structure that has a whole lot of constant Strings mapping to a whole lot of other constant Strings. In use, the example is far from "clean" or elegant, and is very verbose (there are alot of predefined constant mappings).
The goal of the class is to map objects referenced by these predefined constant Strings. It is commonplace in my particular code convention to use private static final String for all String constant, this example as well breaks that convention.
Would greatly appreciate any input, the wealth of knowledge from SO contributors always humbles me.
Much thanks.
Edit: Requirement specifies no external files.

One approach would be to create a builder class that generates the map. This has the advantage that you can optimize for concise syntax. You can also do things like making the generated map immutable -- useful if you want to use it as a publically accessible constant.
In your example, I notice that you have more than one key mapping to the same value. So, it would be more concise to have a method that takes a value followed by a list of keys. You can also make it more concise by having the builder return itself so that you can "chain" method calls:
class Builder<K,V> {
final Map<K,V> map = new HashMap<K,V>();
Builder add(V value, K... keys) {
for(K key : keys) {
map.put(key, value);
}
return this;
}
Map<K,V> build() {
return Collections.unmodifiableMap(new HashMap<K,V>(map));
}
}
// Usage:
mapperMap = new Builder<String,String>()
.add("Bar1", "Foo1", "Foo2")
.add("Bar2", "Foo3")
...
.build();
Alternately you might take a look at the Guava ImmutableMap class, which has a builder using the chaining syntax, though it doesn't have a way to map multiple keys to a single value in one call.

I Think you can try using Properties instead of map or initializing the map by reading strings from a configration file.

The most succint way I know is to define your map as an anonymous subclass of HashMap with an instance initializer:
private final Map<String, String> mapperMap =
Collections.unmodifiableMap(new HashMap() {{ // instance initializer
put("Foo1", "Bar1");
put("Foo2", "Bar1");
put("Foo3", "Bar2");
put("Foo3", "Bar3");
}});

If you want it to be a final String, you can always create a class with a couple of constant strings and the use a list of objects of that class.
You can do it like this:
class MapClass {
private static final String key;
private static final String value;
}
List<MapClass> l = new ArrayList<>();

Related

Java: Hashmap of Enum subset elements

I have a large enum in Java and I want to break it down into subsets (I think I can do this with EnumSet). I then want to be able to create a hashmap or enummap of only the elements in the EnumSet. How can I achieve this please?
public enum Test { ENUM1, ENUM2, ENUM3, ENUM4, ENUM5 }
EnumSet<Test> testSet = EnumSet.range(ENUM2, ENUM4);
HashMap<testSet, String> testHashMap; <--- Compilation Failure
Edit:
I want to be able to create the hashset to only have Keys for the available enums of the EnumSet and assign a unique string value to each enum in the EnumSet.
Thanks!
testSet is an instance of the EnumSet. You want to define the key type to be EnumSet<Test> instead:
HashMap<EnumSet<Test>, String> testHashMap = new HashMap<>();
...
testHashMap.put(testSet, "string");
It sounds like what you want is a HashMap<Test, String>, and you're going to have to enforce the restriction to the specified range yourself. You can't encode that restriction at the type system level in Java.
I don't think you can get compile time checking as in your example - unless you actually split the enum into multiple smaller enums. At runtime there are some options.
If possible, I would create an unmodifiable view of a map EnumMap<Test, String>. This essentially restricts the map keys to whatever Enum constants you choose.
The actual map is still mutable. If the strings need to change under your control, you can do that internally but only make the immutable map public. If everyone needs to be able to change the strings, you could use something like an unmodifiable EnumMap<Test, StringHolder> and pre-fill it with the the desired enum ranges and and empty holder (StringHolder is a simple pogo, with getter and setter for a single string value. Kind of like a mutable string).
Another option would be to your your own Map type (backed by a HashMap or EnumMap). Whenever a new key is created, check testSet and throw an exception if the key isn't in there.
You'll have to extend HashMap unfortunately. Each time an element is added, you'll have to check it against an EnumSet.
public class EnumSetMap<E, V> extends HashMap<E, V> implements Map<E, V> {
private EnumSet<E> allowed;
public EnumSetMap(EnumSet<E> allowed) {
this.allowed = allowed;
}
#Override
public V put(E key, V value) {
if (allowed.contains(element))
throw new IllegalArgumentException("Key must be in the Enum set.");
return super.put(E, V);
}
}
Your example code would become:
HashMap<Test, String> = new EnumSetMap<Test, String>(testSet);

BiMap single function to convert values to concatenated string value?

Is there something equivalent to get all keys (or inverses) from a bit map and concat each with a special character as a completely new string (without iterating through the map and building it manually?
private static final BiMap<String, String> stuff = HashBiMap.create();
static {
stuff.put("S1", "STUFF_TYPE_1");
stuff.put("S2", "STUFF_TYPE_2");
stuff.put("S3", "STUFF_TYPE_3");
}
// The non-terminal <> is what I'm asking if something like exists either with bimap or some other container?
private static final String concateKeys = <stuff.getAllKeys().assignDelimiter("|").toString();>
Then the Value for concateKeys = "S1|S2|S3"
Assuming this is a Guava BiMap, this is just
Joiner.on('|').join(stuff.keySet());
Maybe you want to take a look at the Joiner class of the Google Guava library.

Cannot instantiate the type Set

I am trying to create a Set of Strings which is filled with the keys from a Hashtable so a for-each loop can iterate through the Set and put defaults in a Hashtable. I am still learning Java but the way I am trying to do it isn't valid syntax. Could someone please demonstrate the proper way of doing this and explain why my way doesn't work and theirs does.
private Hashtable<String, String> defaultConfig() {
Hashtable<String, String> tbl = new Hashtable<String, String>();
tbl.put("nginx-servers","/etc/nginx/servers");
tbl.put("fpm-servers","/etc/fpm/");
tbl.put("fpm-portavail","9001");
tbl.put("webalizer-script","/usr/local/bin/webalizer.sh");
tbl.put("sys-useradd","/sbin/useradd");
tbl.put("sys-nginx","/usr/sbin/nginx");
tbl.put("sys-fpmrc","/etc/rc.d/php_fpm");
tbl.put("www-sites","/var/www/sites/");
tbl.put("www-group","www");
return tbl;
}
//This sets missing configuration options to their defaults.
private void fixMissing(Hashtable<String, String> tbl) {
Hashtable<String, String> defaults = new Hashtable<String, String>(defaultConfig());
//The part in error is below...
Set<String> keys = new Set<String>(defaults.keySet());
for (String k : keys) {
if (!tbl.containsKey(k)) {
tbl.put(k, defaults.get(k));
}
}
}
Set is not a class, it is an interface.
So basically you can instantiate only class implementing Set (HashSet, LinkedHashSet orTreeSet)
For instance :
Set<String> mySet = new HashSet<String>();
Set is an interface. You cannot instantiate an interface, only classes which implement that interface.
The interface specifies behaviour, and that behaviour can be implemented in different ways by different types. If you think about it like that, it makes no sense to instantiate an interface because it's specifying what a thing must do, not how it does it.
HashMap's keySet() method already creates the set you need, so simply:
Set<String> keys = defaults.keySet();
This is a view of the keys in defaults, so its contents will change when changes are made to the underlying (defaults) map. Changes made to keys will be reflected in the map, as well, but you can only remove...not add...keys from the map.
If you need a copy of the keys that doesn't interact with the original map, then use one of the types suggested, as in:
Set<String> keys = new HashSet( defaults.keySet() );

HashMap<String, Object> : How to put Object itself as in place of String

A a = new A(); //classA { }
HashMap<String, Object> hm = new Hashmap<String,Object>();
hm.put("A", a);
My question is, How can i put the Object itself instead of "A" in same declaration?
hm.put(`a??`, a);
You simply cannot do that, the language prohibits it. It would only be possible if your class A is a subclass of String which is not possible, since String is declared as final in Java.
With respect to you interview question: It's not possible due to the generic type parameter that was chosen for the declaration. You can read more about that in Bounded Type Parameters.
A a = new A(); //classA { }
Map<A, A> hm = new Hashmap<A, A>();
hm.put(a, a);
But I do not see any point of putting a->a
If the class held a non-changing decent String field, you could use that.
// the id property must be a String, immutable and unique for each instance!
myMap.put(a.getId(), a);
If you want to make any object as a key in your HashMap, then that object has to be immutable.. Because, you don't want anyone to change your key, after you add them to your HashMap..
Just imagine, if your keys are changed after insertion, you won't ever be able to find your inserted value..
But if your key is immutable, then if anyone tries to change your keys, he will actually create a new one for himself, but you will still have yours..
That is what happens in case you use String as your key in HashMap(They can't be changed).. So, if you want your object to be a key, either you make your class a subclass of String (that you can't do), or, just make your class immutable..
This is actually possible using a raw type, like this:
Object key = ...;
Object value = ...;
Map<String, Integer> map = new HashMap<>();//a normal map
Map rawMap = map; // here is the raw type
rawMap.put(key, value); // it works!
This runs fine, but problems arise when you try to use the generic map later:
Integer value = map.get(key);// ClassCastException (unless value actually is an Integer)
That's why you were told that it's a "dirty trick". You shouldn't use it.

Where better create Map in java?

Code:
public class MyClass {
private Map<Integer,String> myMap;
...........................
void methodFillMap(){
myMap=new HashMap<Integer, String>();
.....................
}
}
Or better like:
public class MyClass {
private Map<Integer,String> myMap=new HashMap<Integer, String>();
...........................
void methodFillMap(){
myMap.put(.....);
.....................
}
}
Are these 2 ways of creating map the same by efficiency and functionality?
No they are different functionally. In your first case every time the method methodFillMap is called a new map is created and you will lose information from the old Map whereas in your second case the object will persist with the information.
The "time penalty" for creating an object (the map) will probably be the same in both cases. The question is - do you want the instantiation to be faster or the cal to methodFillMap?
I believe the best way is the first one, that way you only create a new instance of the map when you really need it, so in case you never call the method "methodFillMap" you wont have an instance of an unused object in memory.

Categories