Extend HashMap and LinkedHashMap at the Same Time? - java

I want to extend HashMap to add the method putIfGreaterThan which basically retrieves the value for a given key and if the new value is greater than the old value we update the old value with the new value. Like this:
public void putIfGreaterThan(String key, Double value )
{
if (containsKey(key ) != true) {
put( key , value );
} else {
if (get(key ) < value) {
System. out .println("Adding new value: " + value + " to map" );
put( key , value );
} else {
System. out .println("Did not add value: " + value + " to map" );
}
}
}
The program above works fine - however I would like to add this method to both HashMap and LinkedHashMap. In other words, if someone instantiates:
HashMap hashmap = new HashMap();
They should be able to access the method:
hashmap.putIfGreaterThan();
And if someone instantiates:
LinkedHashMap linkedhashmap = new LinkedHashMap();
They should be able to access the method:
linkedhashmap .putIfGreaterThan();
If I create a new class as follows:
MyHashMap extends HashMap<String, Double> and add the previously mentioned method - I am only extending HashMap not LinkedHashMap. This would not allow me to access the method if I instantiate a LinkedHashMap.
I was thinking of modifying the source code in the original HashMap class (by adding the method putIfGreaterThan) however I am unable to modify the source code unless I de-compile the entire class (and when I try doing this I get a bunch of other errors so I figured it would be easier just to extend the HashMap class but doing this means I cannot use the method putIfGreaterThan on both HashMap and LinkedHashMap).
Further, if I had added the method to the original class one would be able to call this method on any HashMap (even if the map contains two Strings) but the method is only applicable on a HashMap that contains String and Double or String and Int. Hence, I think it makes more sense to extend the original class and customize the current class with methods related to a HashMap of String and Double.
Any suggestions?
Thanks

Let your custom map implement the Map interface instead and wrap a concrete map with it that is provided by the user of the class via the constructor:
public class MyMap implements Map<String, Double>{
private final Map<String, Double> map;
public MyMap(Map<String, Double> map){
this.map = map;
}
public void putIfGreaterThan(String key, Double value ){...}
#Override
public int size() {
return map.size();
}
//inherited methods
}
This class can be used like this:
MyMap map = new MyMap(new LinkedHashMap<>());
or:
MyMap map = new MyMap(new HashMap<>());

Java don't support multi inheritance, but you can be done using Interfaces.
Java Multiple Inheritance

You cannot add your method to the existing HashMap or LinkedHashMap class.
Only way is to create a custom class MyHashMap<K,V> which implements the Map interface and put your logic there and compose that class with HashMap and(or) LinkedHashMap and let your clients operate on MyHashMap.

Related

Copying keys from one Map to another

I have two maps. One with default values of 0 and one with set values.
For example:
Map<String,Integer> defaultTaskScores = new HashMap<String, Integer>(){{
put("Phalanxing", 0);
put("Shieldwalling",0);
put("Tercioing",0);
put("Wedging",0);
}};
Map<String,Integer> taskScores = new HashMap<String, Integer>(){{
put("Phalanxing", 90);
put("Shieldwalling",56);
put("Wedging",24);
}};
I want to put into taskScores a pair of key-value from defaultTaskScores which key's isn't in taskScores. For this example it's putting Tercioing with value of 0.
taskScores maps are in the list
List<CourseResult> courseResultList;
public class CourseResult {
private final Person person;
private final Map<String, Integer> taskResults;
public CourseResult(final Person person, final Map<String, Integer> taskResults) {
this.person = person;
this.taskResults = taskResults;
}
}
You could iterate over defaultTaskScores and use putIfAbsent to add the missing keys to taskScores:
defaultTaskScores.keySet().forEach(k -> taskScores.putIfAbsent(k, 0));
EDIT:
An alternate approach could be to apply the default value when retrieving a score from the map. Instead of calling taskScaores.get(someKey), you could use taskScores.getOrDefault(someKey, 0).
Only put in the Map those values if the key is not already there:
for(Map.Entry<String, Integer> entry : defaultTaskScores.entrySet()) {
taskScores.putIfAbsent( entry.getKey(), entry.getValue() );
}
You can create iterate over the entries of defaultTaskScores with enhanced for-loop using entry set as a source, or by invoking forEach() method on thedefaultTaskScores map directly.
To update taskScores you can use Java 8 methods:
putIfAbsent():
defaultTaskScores.forEach(taskScores::putIfAbsent);
or computeIfAbsent():
defaultTaskScores.forEach((k, v) -> taskScores.computeIfAbsent(k, (key) -> v));
In case if you're not comfortable with lambda expressions and method references, have a look at this tutorial created by Oracle.
Sidenote: avoid using obsolete double brace initialization, it creates an anonymous class under the hood and might cause memory leaks. Since Java 9 we have a family of overloaded factory methods Map.of() and method Map.ofEntries() which produce an immutable map. When you need a map which is mutable like in this case, you can wrap it with a HashMap, like that:
Map<String, Integer> taskScores = new HashMap<>(Map.of(
"Phalanxing", 90, "Shieldwalling",56,"Wedging",24
));

Typesafe Hetereogeneous Container design pattern with typesafe collections as value

I want to create a typesafe collection which can store multiple collections of the same type but with typesafe parameters. The standart way:
Map<Key<?>, Object> container = new HashMap<>();
The key contains the type of the object and the get method casts to the correct type (standart typesafe hetereogeneous container pattern). But i need something like this:
container.put(Key, new HashMap<Long, String>);
The type itself would be safe (Map.class) but i don't know how to ensure that key and value of the map are of the type long and string. How can i do that with java?
EDIT
To make things clearer:
Map<Class<?>, Object> container = new HashMap<>();
Now the typesafe implementation of this map:
public <T> void put(Class<T> key, T value) {
container.put(key, value);
}
public <T> T get(Class<T> key) {
return key.cast(container.get(key));
}
I can to this in a typesafe way now:
containerClass.put(Double.class, 2.0);
containerClass.put(Integer.class, 3);
And of course:
containerClass.put(MyObject.class, myObject);
If i want to store multiple values of the same type than i could use a generic list instead of the object as a value or a specific key class which has an identifier as a field.
BUT
What happens now?
Map<String, Integer> map = new HashMap<>();
containerClass.put(Map.class, map);
With this implementation it is not safe that it is a map with String as a key and integer as the value. I want to store all kinds of objects and collections but the collections itself must be typesafe too.
Not sure what your Key class is but the following is perhaps what you are looking for, just replace String with your Key class
Map<String, Map<? extends Long, ? extends String>> container = new HashMap<>();
container.put("", new HashMap<Long, String>());
container.put("", new HashMap<Long, Long>());
if you try to compile that code, you will find the first put is okay but the 2nd will not compile due to non compliance to the Map type. That type states only an object that extends Long is accepted as the 1st parameter and the 2nd only accepts objects that extend String.

How to pass in initialized HashMap as param?

How can I pass in a new HashMap in the most canonical (simplest, shortest hand) form?
// 1. ? (of course this doesn't work)
passMyHashMap(new HashMap<String, String>().put("key", "val"));
// 2. ? (of course this doesn't work)
passMyHashMap(new HashMap<String, String>(){"key", "val"});
void passMyHashMap(HashMap<?, ?> hm) {
// do stuff witih my hashMap
}
Create it, initialize it, then pass it:
Map<String,String> myMap = new HashMap<String,String>();
myMap.put("key", "val");
passMyHashMap(myMap);
You could use the "double curly" style that David Wallace mentions in a comment, I suppose:
passMyHashMap(new HashMap<String,String>(){{
put("x", "y");
put("a", "b");
}});
This essentially derives a new class from HashMap and sets up values in the initializer block. I don't particularly care for it (hence originally not mentioning it), but it doesn't really cause any problems per se, it's more of a style preference (it does spit out an extra .class file, although in most cases that's not a big deal). You could compress it all to one line if you'd like, but readability will suffer.
You can't call put and pass the HashMap into the method at the same time, because the put method doesn't return the HashMap. It returns the old value from the old mapping, if it existed.
You must create the map, populate it separately, then pass it in. It's more readable that way anyway.
HashMap<String, String> map = new HashMap<>();
map.put("key", "val");
passMyHashMap(map);
HashMap< K,V>.put
public **V** put(K key,V value)
Associates the specified value with the specified key in this map. If
the map previously contained a mapping for the key, the old value is
replaced.
Returns the previous value associated with key, or null if there was
no mapping for key. (A null return can also indicate that the map
previously associated null with key.)
As you can see, it does not return the type HashMap<?, ?>
You can't do that. What you can do is create a factory that allow you to do so.
public class MapFactory{
public static Map<String, String> put(final Map<String, String> map, final String key, final String valeu){
map.put(key, value);
return map;
}
}
passMyHashMap(MapFactory.put(new HashMap<String, String>(),"key", "value"));
Although I can't image a approach that would need such implementation, also I kinda don't like it. I would recommend you to create your map, pass the values and just then send to your method.
Map<String, String> map = new HashMap<String, String>();
map.put("key","value");
passMyHashMap(map);

Adding an Object to a collection

I am learning Hashmaps in Java, so I have a simple java program that creates an account. My problem is when it comes to storing the new accounts in a collection, I am trying to do it using a hashmap but just can't figure out where to go.
HashMap<String,CurrentAccount> m = new HashMap<String,String>();
if (Account.validateID(accountID)) {
CurrentAccount ca = new CurrentAccount(cl,accountID, sortCode, 0);
I am unsure of the next stage to add this account to the hashmap I have tried a couple of different ways but always end up with an error.
You have an error with your instantiation statement. The map's type is HashMap<String, CurrentAccount>, but you are instantiating HashMap<String,String>.
To fix this, change your instantiation statement to correspond to the map's type, like the following:
HashMap<String, CurrentAccount> m = new HashMap<String, CurrentAccount>();
Or if you are using JDK 1.7+, you could use diamond notation instead (see Generic Types for more information):
HashMap<String, CurrentAccount> m = new HashMap<>();
In order to add items to the map, you can use Map#put(K, V):
m.put(accountID, ca);
In order to get a value, you can use Map#get(Object):
CurrentAccount ca = m.get(accountID);
See JDK 1.7 Map documentation for more information about maps.
As for the question made by the OP in the comments of this answer, in order to access the map (or any other type) in multiple methods, it has to be declared as a class field:
public class TestClass {
Map<String, CurrentAccount> accountMap;
public TestClass() {
accountMap = new HashMap<String, CurrentAccount>();
}
public void method1() {
// You can access the map as accountMap
}
public void method2() {
// You can also acces it here
}
}
The map declaration is incorrect, as you're typing the value to two different objects. Change the declaration to:
Map<String,CurrentAccount> m = new HashMap<String,CurrentAccount>();
Then, presuming the accountID value is a string, it should be as simple as...
m.put( accountID, ca );
Altogether you'll have:
Map<String,CurrentAccount> m = new HashMap<String,CurrentAccount>();
if (Account.validateID(accountID)) {
CurrentAccount ca = new CurrentAccount(cl,accountID, sortCode, 0);
m.put( accountID, ca );
}
Use put(key, value); See HashMap javadoc
m.put("SomeIdentifierString", ca);
Then whenever you want to access that particular object. Use the key to obtain it
CurrentAccount account = m.get("SomeIdentifierString");
If you want to iterate through the entire map to get key and values you can do this
for (Map.Entry<String, CurrentAccount> entry : m.entrySet()){
String s = entry.getKey();
CurrentAccount accuont = entry.getValue();
// do something with them
}
Your code does not compile because you try to initialize the hasmap bound to the type String and Account to an hashmap of type String String for (key and value type)
HashMap<String, CurrentAccount> accountsMap = new Hashmap<String, String>()
should be
HashMap<String, CurrentAccount> accountsMap = new Hashmap<String, CurrentAccount>()
The first argument is the type for the key value the second is the type of the associated value for the key
To find a value within your hashmap you can use the following code snipped
for (String key : accountsMap.keySet().iterator()) {
CurrentAccount current = accounts.get(key);
}
where accountsMap is your HashMap.

What is the best datastructure to use in Java for a "multidimensional" list?

I need a dictionary-like data structure that stores information as follows:
key [value 1] [value 2] ...
I need to be able to look up a given value by supplying the key and the value I desire (the number of values is constant). A hash table is the first thing that came to my mind but I don't think it can be used for multiple values. Is there any way to do this with a single datastrucuture rather than splitting each key-value pair into a separate list (or hash table)? Also I'd rather not use a multi-dimensional array as the number of entries is not known in advance. Thanks
I'm not sure what you mean about your list of values, and looking up a given value. Is this basically a keyed list of name-value pairs? Or do you want to specify the values by index?
If the latter, you could use a HashMap which contains ArrayLists - I'm assuming these values are String, and if the key was also a String, it would look something like this:
HashMap<String, ArrayList<String>> hkansDictionary = new HashMap<String, ArrayList<String>>();
public String getValue (String key, int valueIdx) {
ArrayList<String> valueSet = hkansDictionary.get(key);
return valueSet.get(valueIdx);
}
If the former, you could use a HashMap which contains HashMaps. That would look more like this:
HashMap<String, HashMap<String, String>> hkansDictionary
= new HashMap<String, HashMap<String, String>>();
----
public String getValue (String key, String name) {
HashMap<String, String> valueSet = hkansDictionary.get(key);
return valueSet.get(name);
}
You could make a class that holds the two key values you want to look up, implement equals() and hashcode() to check/combine calls to the underlying values, and use this new class as the key to your Map.
I would use
Map<Key,ArrayList<String>> map = new HashMap<Key,ArrayList<String>>
where you define Key as
public class Key{
private String key;
private String value;
//getters,setters,constructor
//implement equals and hashcode and tostring
}
then you can do
Key myKey = new Key("value","key");
map.get(myKey);
which would return a list of N items
You can create a multidimensional array by first declaring it, then creating a method to ensure that new value keys are initialized before the put. This example uses a Map with an embedded List, but you can have Maps of Maps, or whatever your heart desires.
I.e., you must define your own put method that handles new value initialization like so:
private static Map<String, List<USHCommandMap>> uSHCommandMaps = new HashMap<String, List<USHCommandMap>>();
public void putMemory() {
if (!uSHCommandMaps.containsKey(getuAtom().getUAtomTypeName()))
uSHCommandMaps.put(getuAtom().getUAtomTypeName(), new ArrayList<USHCommandMap>());
uSHCommandMaps.get(getuAtom().getUAtomTypeName()).add(this);
}

Categories

Resources