I have a BiDiMap class. How can I make it generic, by accepting not only String but also Object type of objects as input parameters, with keeping all the original functions working. For example I'd like to be able to use function put() with Object, Object as input parameters instead of String, String. I'd like to change all the input parameters and returning values of String type to Object types.
package MyBiDiMap;
import java.util.HashMap;
import java.util.Map;
public class BiDiMap {
private Map<String, String> keyValue;
private Map<String, String> valueKey;
public BiDiMap() {
this.keyValue = new HashMap<>();
this.valueKey = new HashMap<>();
}
private BiDiMap(Map<String, String> keyValue,
Map<String, String> valueKey) {
this.keyValue = keyValue;
this.valueKey = valueKey;
}
public void put(String key, String value) {
if (this.keyValue.containsKey(key)
|| this.valueKey.containsKey(value)) {
this.remove(key);
this.removeInverse(value);
}
this.keyValue.put(key, value);
this.valueKey.put(value, key);
}
public String get(String key) {
return this.keyValue.get(key);
}
public String getInverse(String value) {
return this.valueKey.get(value);
}
public void remove(String key) {
String value = this.keyValue.remove(key);
this.valueKey.remove(value);
}
public void removeInverse(String value) {
String key = this.valueKey.remove(value);
this.keyValue.remove(key);
}
public int size() {
return this.keyValue.size();
}
public BiDiMap getInverse() {
return new BiDiMap(this.valueKey, this.keyValue);
}
}
The answer is pretty simple: by introducing two generic types, named K and V on your class and by then vigorously replacing all occurance of String with K (where your key type should be used), and similarly with V where values are required.
In other words: don't use specific types when declaring the two maps, but in all places, use the new generic types you added on class level.
Related
Problem statement: We are building a library which has a TypeSafeMap as response. TypeSafeMap is a map which can hold any type of objects.
Now, the client is going to access the typesafemap. We are trying to enforce some level of compile type safety. Below is the code and more explanation.
Response structure:
public Class Response {
private TypeSafeMap t;
public TypeSafeMap getMap() { return t; }
}
//Type safety Map
public class TypeSafeMap
{
private final static Map<String, Object> map = new HashMap<>();
public static <T> T put(String key, T value) {
if (null != key) {
return (T) map.put(key, value);
}
return (T) map;
}
#SuppressWarnings("unchecked")
public static <T> T get(PartyEnums partyEnum)
{
return (T) map.get(partyEnum.PARTY.name());
}
}
//Enum that we expose to client to get the property and the corresponding field type
public enum PartyEnums
{
PARTY("party", new ArrayList<Party>().getClass());
private final String name;
private final Class<?> clzz; //this is the type client should access as field type
PartyEnums(String name,Class<?> clzz)
{
this.name = name;
this.clzz=clzz;
}
public Class<?> getClzz()
{
return clzz;
}
#SuppressWarnings("unchecked")
public <T> T getInstance()
{
T ins = null;
try {
ins = (T) getClzz().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return ins;
}
}
//Client call
public class ClientCall {
Object obj = TypeSafeMap.get(PartyEnums.PARTY); //No error.
String str = TypeSafeMap.get(PartyEnums.PARTY); //No error.
But we want enforce some level of compile type safety as the field type "str" and TypeSafeMap.get() type do not match.
How can we enforce compile type safety?
List<Party> party = TypeSafeMap.get(PartyEnums.PARTY);// OK.
}
TL;DR
This cannot be done using an enum in its current form.
There was a JEP 301: Enhanced Enums that would address the very same problem, but it's been withdrawn.
Your only choice to enforce type-safety (currently) is to simulate an enum by using a final class with predefined typed constants.
I would imagine it to be something like this:
public final class Key<V> {
public static final Key<List<Party>> PARTY_LIST = new Key<>("party list");
public static final Key<Map<Integer, String>> PARTY_MAP = new Key<>("party map");
public static final Key<String> SOME_STRING = new Key<>("party string");
private final String name;
private Key(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
And now we are ready to make your TypeSafeMap really type-safe by modifying both <T> T put(String key, T value) (which would not obviously be safe even if the "enum approach" was theoretically possible) and <T> T get(PartyEnums partyEnum) this way:
public static class TypeSafeMap {
private final static Map<String, Object> MAP = new HashMap<>();
#SuppressWarnings("unchecked")
public static <T> T put(Key<T> key, T value) {
if (null != key) {
return (T) MAP.put(key.getName(), value);
}
return value;
}
#SuppressWarnings("unchecked")
public static <T> T get(Key<T> key) {
return (T) MAP.get(key.getName());
}
}
I still see at least 2 possible issues that:
Multiple constants may share the same name hence effectively overwriting each other once used (can be tackled for instance using an enum instead of String)
put(Key<T> key, T value) passes through the input value back even for the null keys which is (in my opinion) fail-safe but misleading for the caller (who might think the "store" operation was somehow succeeded.
However, given the weak points above this implementation still fits your original intention, so that:
These cases would pass:
map.put(Key.JUST_STRING, "just string");
map.put(Key.PARTY_LIST, Arrays.asList(new Party()));
...
Object resultAsObject = map.get(Key.PARTY_LIST);
List<Party> resultAsList = map.get(Key.PARTY_LIST);
String resultString = map.get(Key.JUST_STRING);
But these fail with compile-time error:
map.put(Key.PARTY_LIST, Arrays.asList(new String()));
...
String stringFromList = map.get(Key.PARTY_LIST);
Note that Object resultAsObject = map.get(<ANYTHING>) will always succeed as any returned value (including nulls) can we represented as an Object variable
I stumbled upon a 400 lines if/else, with about 100 clauses in it. So I feel the urge to refactor that monster.
The method is of this form:
private void updatePaymentField(Payment payment, String fieldName, String value) {
if (Field.FIELD_1.equalsIgnoreCase(fieldName)) {
payment.getField1().setId(value)
} else if (Field.FIELDS_2.equalsIgnoreCase(fieldName)) {
payment.setField2(valueToSet)
} else if (Field.FIELDS_3.equalsIgnoreCase(fieldName)) {
UtilClass::setField3(payment, value)
} else if ... // 100 more of these
}
I changed that into an enum that takes a String (the field name) and a BiConsumer<Payment, String> (the method/lambda that will update the field on the payment)
enum Field {
UNDEFINED("", null),
FIELD1(Fields.FIELD1, (Payment, value) -> payment.getField1().setId(value)),
FIELD2(Fields.FIELD2, Payment::setField2),
FIELD3(Fields.FIELD3, UtilClass::setField3),
// 100 more of these,
private String fieldName;
private BiConsumer<Payment, String> populateFunction;
Field(String fieldName, BiConsumer<Payment, String> populateFunction) {
this.fieldName = fieldName;
this.populateFunction = populateFunction;
}
public static Field getByName(String name) {
return Arrays.stream(values())
.filter(p -> p.getFieldName().equals(name))
.findFirst()
.orElse(UNDEFINED);
}
private String getFieldName() {
return fieldName;
}
public BiConsumer<Payment, String> getPopulateFunction() {
return populateFunction;
}
}
then basically the big if can be replaced by
private void updatePaymentField(Payment payment, String fieldName, String value) {
Field field = Field.getByName(fieldName);
if(field != Field.UNDEFINED) {
field.getPopulateFunction().accept(payment, value);
}
}
That works and is all good, but I am thinking that I might have overdone it can achieve the same thing with a simple Map
Map<String, BiConsumer<Payment, String>> fieldMap = new HashMap<>();
fieldMap.put(Fields.FIELD1, (Payment, value) -> payment.getField1().setId(value))
fieldMap.put(Fields.FIELD2, Payment::setField2)
fieldMap.put(Fields.FIELD3, PaymentUtilClass::setField3)
// 100 more of these,
And use it the same way
private void updatePaymentField(Payment payment, String fieldName, String value) {
BiConsumer<Payment, String> populateFunction = fieldMap.get(fieldName);
if(populateFunction!=null) {
populateFunction.accept(payment, value);
}
}
I feel that accessing the BiConsumer<Payment, String> would be quicker using the Map than using the getByName method on the enum, and the Map notation is also lighter.
So I am thinking the Map might be a better choice.
So the question is: Before I change this to a Map, is there any objective value in using en enum over a Map in that specific use case?
Here is my code:
public enum DecisionType {
REFUSAL,
GRANT_OF_PROTECTION,
PARTIAL_REFUSAL;
}
public class DocumentComposition<T extends Enum<DecisionType>> extends TreeMap<DocumentType, Object> {
#Override
public Object put(DocumentType key, Object value) {
if (key.getDecisionType() != ) {
return null;
}
return value;
}
}
DocumentComposition map = new DocumentComposition<DecisionType.REFUSAL>();
I need my Map to contain only elements that are of a certain value of the DecisionType enum. How do I achieve this? What should my test look like?
Do I understand it right you want to have a DocumentComposition which accepts only DocumentType instances of a specific DecisionType ?
My parts of the solution:
You don't need to use generics for that but rather an internal variable which you provide in the constructor.
In you overridden put method you must not forget to call the super otherwise your TreeMap will never get any elements.
public class DocumentComposition extends TreeMap<DocumentType, Object> {
private DecisionType acceptedDecisionType;
public DocumentComposition(DecisionType acceptedDecisionType)
{
this.acceptedDecisionType = acceptedDecisionType;
}
#Override
public Object put(DocumentType key, Object value) {
if (key.getDecisionType() != acceptedDecisionType) {
return null;
}
return super.put(key, value); // do not forget to call super, otherwise your TreeMap is not filled
}
}
Now you can use your map:
public static void main( String args[])
{
DocumentComposition dc=new DocumentComposition(DecisionType.REFUSAL);
dc.put(new DocumentType(DecisionType.REFUSAL), "refusalDoc");
dc.put(new DocumentType(DecisionType.PARTIAL_REFUSAL), "partialRefusalDoc");
System.out.println(dc);
}
Only refusalDoc will be in the map.
I have to use a map which stores keys of type Integer, String and Long only.
One solution: To store type Object and in put method check with instanceof operator. Is there any better solution, maybe with enum
You can use a map and storing Long as String into it
or you can use two different hashmap and duplicate put/get methods. If you have two types, it is probably for two different things, and having two different map should probably be the correct answer
Create a class that has a map as a member and add methods that will store and retrieve int and long as Strings.
class MyMap {
private Map mabObject = Map<String, Object>;
public void add(long key, Object value) {
mapObject.put(Long.toString(key),value);
}
public void add(String key, Object value) {
mapObject.put(key, value);
}
public Object get(long key) {
return mapObject.get(Long.toString(key));
}
public Object get(String key) {
return mapObject.get(key);
}
}
I agree with Paul Boddington's comment, and the need of such trick shows that code smells.
Just for a funny excercise (not for production code) I've made an example that shows what we can do in compile time for limiting types of keys in a map.
For example we can create a wrapper allowing only values of specific classes.
common/map/Wrap.java
package common.map;
import java.util.Arrays;
import java.util.List;
public class Wrap<T> {
private T value;
private Wrap(T value){
this.value = value;
}
public T get() {
return this.value;
}
/*
* it's important to implement this method
* if we intend to use Wrap instances as map's key
*
* and it's needed to see that hash codes are computing differently in different classes,
* and depending on `allowedClasses` contents we can face some unexpected collisions
* so if you care of performance - test your maps usage accurately
*/
public int hashCode() {
return this.value.hashCode();
}
/*
* static
*/
private static List<Class> allowedClasses = Arrays.asList(Long.class, String.class);
public static <T> Wrap<T> create(Class<? extends T> clazz, T value) {
if (!allowedClasses.contains(clazz)) {
throw new IllegalArgumentException("Unexpected class " + clazz);
}
return new Wrap<>(value);
}
public static <T> Wrap<T> create(AllowedClasses allowedClass, T value) {
return create(allowedClass.clazz, value);
}
public enum AllowedClasses {
LONG(Long.class),
STRING(String.class);
private Class clazz;
AllowedClasses(Class clazz) {
this.clazz = clazz;
}
}
}
And let's run it
common/map/Example.java
package common.map;
import common.map.Wrap.AllowedClasses;
import java.util.HashMap;
import java.util.Map;
public class Example {
public static void main(String... args) {
Map<Wrap, Object> map = new HashMap<>();
// next two lines create wrappers for values of types we added to enum AllowedClasses
// but since enums cannot have type parameters, we are not able to check
// if the second parameter type is compatible with a type associated with given enum value
// so I think usage of enum is useless for your purpose
Wrap<?> valLong0 = Wrap.create(AllowedClasses.LONG, "the string in place of Long is OK");
Wrap<?> valString0 = Wrap.create(AllowedClasses.STRING, 12345);
// from the next lines you can see how we can use the Wrap class to keep
// only allowed types to be associated with the map keys
Wrap<Long> valLong = Wrap.create(Long.class, 1L); // legal
Wrap<String> valString = Wrap.create(String.class, "abc"); // legal
Wrap<String> valWrong = Wrap.create(String.class, 123); // doesn't compile
Wrap<Object> valWrong2 = Wrap.create(Object.class, 123); // compiles but throws exception in runtime
Object obj = ThirdParty.getObjectOfUnknownClass();
Wrap<?> valDynamic = Wrap.create(obj.getClass(), obj); // compiles but MAYBE throws exception in runtime
// so we get to this point only if all the wrappers are legal,
// and we can add them as keys to the map
map.put(valLong, new Object());
map.put(valString, new Object());
map.put(valDynamic, new Object());
}
}
HashMap<DataType1,DataType2>hm = new HashMap<DataType1,DataType2>();
or
Map<DataType1,DataType2> m = new HashMap<DataType1,DataType2>();
m.put(key, value);
Instead of DataType1 & DataType2 you can add Integer,String,Long ,etc. and use the put(key,value) method to enter key and values into the HashMap.
I am using org.apache.commons.lang3.text.StrSubstitutor to parse a String. I have it setup similar to this:
StrSubstitutor sub = new StrSubstitutor(messageValues, "&(", ")");
String format = sub.replace("Information: &(killer) killed &(target)!");
This no longer works if I write the keys in different cases:
"Information: &(KILLER) killed &(TARGET)!"
Is there a way of making the keys for the String Substitutor case-insensitive?
I cannot use toLowerCase() because I only want the keys to be case-insensitive.
StrSubstitutor has a constructor that takes an instance of StrLookup. You can create an implementation of StrLookup that lowercases the keys its looking for before actually looking for them.
Here's how it looks like:
public class CaseInsensitiveStrLookup<V> extends StrLookup<V> {
private final Map<String, V> map;
CaseInsensitiveStrLookup(final Map<String, V> map) {
this.map = map;
}
#Override
public String lookup(final String key) {
String lowercaseKey = key.toLowerCase(); //lowercase the key you're looking for
if (map == null) {
return null;
}
final Object obj = map.get(lowercaseKey);
if (obj == null) {
return null;
}
return obj.toString();
}
}
Using this StrLookup implementation you don't need to worry about what kind of Map you're passing to the constructor.
The following test case returns succesfully, using the above implementation:
import org.apache.commons.lang3.text.StrSubstitutor;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.HashMap;
import java.util.Map;
#Test
public class TestClass {
#Test
public void test() {
Map<String, String> messageValues = new HashMap<String, String>();
messageValues.put("killer", "Johnson");
messageValues.put("target", "Quagmire");
StrSubstitutor sub = new StrSubstitutor(new CaseInsensitiveStrLookup<String>(messageValues), "&(", ")", '\\');
String format2 = sub.replace("Information: &(killer) killed &(target)!");
String format = sub.replace("Information: &(KILLER) killed &(TARGET)!");
Assert.assertEquals(format, "Information: Johnson killed Quagmire!");
Assert.assertEquals(format2, "Information: Johnson killed Quagmire!");
}
}
You don't need to write a custom class. Assuming you can live with the log(n) access times, just use a case-insensitive TreeMap.
public static void main(String[] args) {
Map<String, String> m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
m.put("foo", "bar");
StrSubstitutor sub = new StrSubstitutor(m);
String s = sub.replace("${FOO}");
System.out.println(s);
} // prints "bar"
I think this case-insensitive map would work:
import java.util.HashMap;
import java.util.Map;
public class CaseMap<V> extends HashMap<String, V> {
public CaseMap() {
}
public CaseMap(int capacity) {
super(capacity);
}
public CaseMap(int capacity, float loadFactor) {
super(capacity, loadFactor);
}
public CaseMap(Map<String, ? extends V> map) {
putAll(map);
}
public V put(String key, V value) {
return super.put(key.toUpperCase(), value);
}
public V get(Object key) {
if (!(key instanceof String)) return null;
return super.get(((String)key).toUpperCase());
}
}
If you don't control the creation of the messageValues map, you could build a CaseMap from it like this:
CaseMap<String> caseFreeMessageValues = new CaseMap<String>(messageValues);
And then build your StrSubstitutor like this:
StrSubstitutor sub = new StrSubstitutor(messageValues, "&(", ")");
String format = sub.replace("Information: &(KILLER) killed &(TARGET)!");
You might want to think about other methods of Map that should be overridden as well, such as containsKey.
In case you need flexibility with both the Map and the Tokens being case insensitive AND you are not in control of the map being built you can use something like this.
String replaceTokens(String str, Map<String, String> messageValues) {
if(tokenToValue == null || tokenToValue.size() < 1) return str;
StrSubstitutor caseInsensitiveTokenReplacer = new StrSubstitutor(new CaseInsensitiveStrLookup<>(messageValues),
"&(", ")", '\\');
return caseInsensitiveTokenReplacer.replace(str);
}
StrLookup Implementation
public class CaseInsensitiveStrLookup<V> extends StrLookup<V> {
private final Map<String, V> map = new TreeMap<String, V>(String.CASE_INSENSITIVE_ORDER);
public CaseInsensitiveStrLookup(final Map<String, V> map) throws NullValueKeyNotSupported {
if(map.containsKey(null)) throw new Exception(); // Dont want to support null
this.map.putAll(map);
}
#Override
public String lookup(final String key) {
V value = map.get(key);
if(value == null) return null;
return value.toString();
}}