HashMap with different possible value type - java

Imagine a situation where you have something like this :
class AbstractClass{void sharedMethod()}
class ClassOne extends AbstractClass{void sharedMethod(); void specificMethod()}
class ClassTwo extends AbstractClass{void sharedMethod(); void specificMethod2()}
I want to store in a Map something like this :
HashMap<String, List<SOMETHING_I_DON'T_KNOW_IN_ADVANCE>> hm = new HashMap<>();
hm.put("blabla", List<ClassOne>);
hm.put("blublu", List<ClassTwo>);
I know that I can put AbstractClass as the value type for my map but I want to have access to my specific methods. I already did some research to try to dynamic cast my object when I get them, but I didn't find anything satisfying.
Thanks in advance

Would creating "abstract void specificMethod()" in your AbstractClass, and then making concrete versions of specificMethod() in your concrete classes work for you?
That way you don't need to worry about whether to call specificMethodOne() or specificMethodTwo() ... the AbstractClass object already knows which version of specificMethod() it's supposed to use.

I know that I can put AbstractClass as the value type for my map but I want to have access to my specific methods.
I am assuming that you want to do this:
List<ClassOne> one = hm.get("blabla");
one.specificMethod();
// or
List<ClassTwo> two = hm.get("blublu");
two.specificMethod2();
Unfortunately you can't do this automatically because of type erasure. The Map and Lists have no idea what types they are storing so there is no way for them to help here. To them you are storing Objects.
Storing different types of objects in a Collection is a difficult pattern to support correctly and requires you to do your own casting once you get the values out of the Map.
List<AbstractClass> list = hm.get("blabla");
if (list instanceof ClassOne) {
ClassOne one = (ClassOne)list;
one.specificMethod();
} else if (list instanceof ClassTwo) {
ClassTwo two = (ClassTwo)list;
two.specificMethod2();
}
...
Yes this is gross and error prone but there is no easy way to do this using Java generics.

I would consider either encapsulating the data in a class, such as
private static class MyMessages
{
final List<ClassOne> typeOneMessages = new ArrayList<>();
final List<ClassTwo> typeTwoMessages = new ArrayList<>();
public void addTypeOne(ClassOne c) {
typeOneMessages.add(e);
}
// add appropriate getter methods; or a stream
public Stream<ClassOne> streamOne()
{
return typeOneMessages.stream();
}
// same for type two
}
Or follow what Rick Stabile suggested, and have a single method defined in the abstract class.
If you wish to keep them together, you will have to cast on retrieval, such as:
public static void main(String[] args)
{
final Map<String, List<? extends AbstractClass>> map = new HashMap<>();
List<ClassOne> ones = new ArrayList<>();
for (int i = 0; i < 4; i++) {
ones.add(new ClassOne());
}
List<ClassTwo> twos = new ArrayList<>();
for (int i = 0; i < 8; i++) {
twos.add(new ClassTwo());
}
map.put("ones", ones);
map.put("twos", twos);
List<? extends AbstractClass> out = map.get("ones");
// can call shared method on the contents
out.stream().forEach(a -> a.sharedMethod());
// will have to cast to the specific type
out.stream().filter(a -> a instanceof ClassOne)
.forEach(c1 -> ((ClassOne)c1).specificMethod());
List<? extends AbstractClass> out2 = map.get("twos");
// can call shared method on the contents
out2.stream().forEach(a -> a.sharedMethod());
// will have to cast to the specific type 2
out2.stream().filter(a -> a instanceof ClassTwo)
.forEach(c2 -> ((ClassTwo)c2).specificMethod2());
}

You can achieve that by using "wildcard(?) arguments" and because of the "Type Safety" mechanism a cast is inevitable.
This works for me:
HashMap<String, List<? extends AbstractClass>> hm = new HashMap<>();
ClassOne class1 = new ClassOne();
ClassTwo class2 = new ClassTwo();
List<ClassOne> c1l = new ArrayList<>();
c1l.add(class1);
List<ClassTwo> c2l = new ArrayList<>();
c2l.add(class2);
hm.put("blabla", c1l);
hm.put("blublu", c2l);
#SuppressWarnings("unchecked")
List<ClassOne> xyy = (List<ClassOne>)hm.get("blabla");
ClassOne c1 = xyy.get(0);
c1.specificMethod1();
and my output was:
Hello! from SpecificMethod1

Related

How can I make a new HashMap that won't change the one I've copied from?

I have a HashMap called the Item Database, which stores data about all of the items.
However, these items can have modifiers (in this case, the stat multiplier is important). Whenever I change an item to just one specific item drop, it ends up changing the base item from the HashMap.
For example, whenever a player creates a Katana, it does something like this.
HashMap<String, CustomItem> db = new HashMap<String, CustomItem>();
db.putAll(ItemDatabase.database);
CustomItem ci = db.get("KATANA");
From there, modifiers are applied via a getBukkitItem function on the CustomItem ci, basically multiplying a lot of the stats on that CustomItem and applying it.
baseHealth = (int) ((abbaseHealth / 100.0) * multiplier);
and other stats like that.
However, whenever I make changes to this new CustomItem, it also applies to the ItemDatabase hashmap. This means that whenever somebody makes another Katana, those multiplied stats become the new base stats to be multiplied.
TL;DR Whenever I'm changing a variable I got from a HashMap (db), that change also applies to the HashMap (itemdb). This happens even if the HashMap (db) it's from, is a copy of another HashMap (itemdb)
I have tried the method above, and using .clone() on a HashMap and casting it back to HashMap. Unfortunately I'm not really sure what else to try.
you should create a new object of deep clone. Using orika framework like below.
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
#Test
public void t() {
Map<Integer, User> map = new HashMap<>();
User one = new User();
one.setName("one");
System.out.println(one);
User two = new User();
two.setName("two");
System.out.println(two);
map.put(1,one);
map.put(2,two);
TypeBuilder<Map<Integer,User>> typeBuilder = new TypeBuilder<Map<Integer,User>>() {
};
Type<Map<Integer,User>> type = typeBuilder.build();
Map<Integer,User> copyMap = mapperFactory.getMapperFacade().mapAsMap(map, type,type);
System.out.println(copyMap.get(1));
System.out.println(copyMap.get(2));
}
You need to make new CustomItem instances. If you only make a copy of the Map, you’re just copying the references stored in the Map; they’ll still refer to the same CustomItem instances.
You can make this easier by adding a copy constructor or clone() method to CustomItem. Example of a copy constructor:
public class CustomItem {
public CustomItem(CustomItem other) {
this.name = other.name;
this.baseHealth = other.baseHealth;
this.multiplier = other.multiplier;
// Don't want two instances to refer to the same List!
this.inventoryList = new ArrayList<>(other.inventoryList);
// etc.
}
}
Example of a clone() method:
public class CustomItem
implements Cloneable {
#Override
public CustomItem clone()() {
try {
CustomItem copy = (CustomItem) super.clone();
// Don't want two instances to refer to the same List!
copy.inventoryList = new ArrayList<>(copy.inventoryList);
// etc.
return copy;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
Once you have a way to copy CustomItem instances, you need to use it in your new Map:
Map<String, CustomItem> newMap = new HashMap<>();
for (Map.Entry<String, CustomItem> entry : db) {
String key = entry.getKey();
CustomItem value = entry.getValue()
value = value.clone();
newMap.put(key, value);
}
A shorter way:
Map<String, CustomItem> newMap = new HashMap<>(db);
newMap.replaceAll((k, v) -> v.clone());

Iterate over Two Class with constants and load their fields into a HashMap

I have two java class with constants, For Ex:
public class FirstClass {
public static final String STRING_A = "STRING_A";
public static final String STRING_B = "STRING_B";
public static final String STRING_C = "STRING_C";
...
}
public class SecondClass {
public static final String STRING_AA = "STRING_AA";
public static final String STRING_BA = "STRING_BA";
public static final String STRING_CA = "STRING_CA";
...
}
Now, I want to load these constants into a,
Map< String,String> classPropertyMap = new HashMap<>(); in Such a way that, Key for this map must be a constant from FirstClass and corresponding value must be a constant from SecondClass.
If it was just one class I could use reflection to load the fields, now since the constants are from two class, how could this be done?
Finally after loading the map, the contents of the map must be something like this:
First element : key and value is < STRING_A, STRING_AA>
Second element : key and value is < STRING_B, STRING_BA>
Third element : key and value is < STRING_C, STRING_CA>
I think if you really need it, the most robust way is to put the properties to a map explicitly.
I mean map.put(FirstClass.STRING_A, SecondClass.STRING_AA); and so on.
If you use reflection you rely on the properties and their declaration order never changes. If some new property is introduced in the library, it can break your code.
Try something like below, seems you can achieve.
FirstClass first = new FirstClass();
Field[] fields = first.getClass().getFields();
SecondClass second = new SecondClass();
Field[] fields1 = second.getClass().getFields();
Try This:
FirstClass first = new FirstClass();
String[] firstStrings = first.getStrings();
//Makes a String array, and fills it with the strings from the first class.
SecondClass second = new SecondClass();
String[] secondStrings = second.getStrings();
//Makes a String array, and fills it with the strings from the second class.
HashMap<String, String> classPropertyMap = new HashMap<String, String>();
int i = 0;
//Now put strings with the same index number from both arrays into the
//HashMap
while(i <= firstStrings.length()){
classPropertyMap.put(firstStrings.get(i), secondStrings.get(i);
i++;
}
Hope that works!

How to reference a variable using a string

I am attempting to reference a variable using a certain string but have no idea how to do it. I know that I can use if statements if I really had to but I am sure that there is a simple way. An example is a Integer named dog. I would try to access the Integer using another string that contained the text dog.
private int dog;
String anything = "dog";
Is there anyway this is possible? Thanks!
Try this:
// use a map for referring to a value given its name
Map<String, Integer> vars = new HashMap<String, Integer>();
// for example, let's use these values
String anything = "dog";
int dog = 10;
// bind a value to a name
vars.put(anything, dog);
// retrieve the value, given its name
vars.get(anything);
=> 10
http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html
public static void main(String[] args) {
Map<String, MyObject> mapping = new HashMap<>();
}
Or new HashMap<String, MyObject>(); for pre java 7
You should use a Map of String to Integer. For example,
public static void main(String[] args) {
java.util.Map<String, Integer> dogMap = new java.util.HashMap<String, Integer>();
dogMap.put("Snoop", 10);
dogMap.put("doggy", 15);
dogMap.put("dog", 20);
System.out.println(dogMap);
}
Which outputs
{doggy=15, Snoop=10, dog=20}
Two options: create a Map<String, Object> that connects the two, or use reflection. I prefer reflection.
in order to get the field:
public class Test {
private int dog = 10;
private String anything = "dog";
public static void main(String[] args){
Test obj = new Test();
Object field = obj.getClass()
.getDeclaredField(obj.anything)
.get(obj);
System.out.println(field);
}
}
Output:
10
Create an object of the class that you will use. Then use the getDeclaredField() method on the class of that object. This will look into the private fields that are set, getField() holds only the public fields. That's it.
I've removed the try-catch from the post because it just clutters it.

Keying a collection by type

I'm looking to have a collection of objects that implement a certain interface, but I'd like to only have one per concrete type within the collection.
collection of implementers of dog:
- instance of dachshund
- instance of beagle
- instance of corgi
In .NET, there's a "KeyedByTypeCollection". Does something similar exist in Java in such a way that I could use it on Android?
Thanks!
If you're willing to use third-party libraries -- and if you don't care about maintaining order -- Guava's ClassToInstanceMap seems applicable here.
ClassToInstanceMap<Dog> map = MutableClassToInstanceMap.create();
map.putInstance(Corgi.class, new Corgi("Spot"));
map.putInstance(Beagle.class, new Beagle("Lady"));
Corgi corgi = map.getInstance(Corgi.class); // no cast required
(Disclosure: I contribute to Guava.)
You should look at generics. E.g.:
List<Dogs> dogList = new ArrayList<Dogs>();
EDIT: to have only unique instances in your collection, you should use Set<Dogs> dogList = new HashSet<Dogs>();
this might be what you are looking for:
see the comments in codes
// two Dog(interface) implementations
// Beagle, Dachshund implements Interface Dog.
final Dog d1 = new Beagle();
final Dog d2 = new Dachshund();
// here is your collection with type <Dog>
final Set<Dog> set = new HashSet<Dog>();
set.add(d1);
set.add(d2);
// see output here
for (final Dog d : set) {
System.out.println(d.getClass());
}
// you can fill them into a map
final Map<Class, Dog> dogMap = new HashMap<Class, Dog>();
for (final Dog d : set) {
// dog instances with same class would be overwritten, so that only one instance per type(class)
dogMap.put(d.getClass(), d);
}
the output of system.out.println line would be something like:
class test.Beagle
class test.Dachshund
I think you need a custom HaspMap that will maintain multiple values with same key,
So, create an simple class that extends HashMap and put values into it.
public class MyHashMap extends LinkedHashMap<String, List<String>> {
public void put(String key, String value) {
List<String> current = get(key);
if (current == null) {
current = new ArrayList<String>();
super.put(key, current);
}
current.add(value);
}
}
Now, create the instance of MyHashMap and put values into it as below,
MyHashMap hashMap = new MyHashMap();
hashMap.put("dog", "dachshund");
hashMap.put("dog", "beagle");
hashMap.put("dog", "corgi");
Log.d("output", String.valueOf(hashMap));
OUTPUT
{dog=[dachshund, beagle, corgi]}

Determining an object's variable name dynamically?

Let's say I have some objects:
ArrayList<SomeObject> list = new ArrayList<SomeObject>();
SomeObject A = new SomeObject();
SomeObject B = new SomeObject();
SomeObject C = new SomeObject();
SomeObject D = new SomeObject();
These constructors automatically add each object to the ArrayList so I can iterate over them but still maintain the variable names for direct access:
public SomeObject(){
// init stuff here
list.add(this);
}
But then, let's say I want to output some debug info, and iterate through list and print out the NAME of each object? How can I do that? Essentially, when "SomeObject A = new SomeObject();" is executed, I want to use reflection (if possible) to determine that this variable's name is "A" (a String) and either store that in the object when the constructor executes, or determine it dynamically through reflection when referencing this object with the variable named "A". Does that make sense? How can I do this?
Thanks!
The compiler doesn't keep the variable names you define in your code, so this isn't possible at run-time.
I don't see why you insist on working with List, as it seems that what you're looking for is a Map. An entry in a map is a named object, e.g. it has a key used to look up the entry in the map.
Map<String,SomeObject> map = new HashMap<String,SomeObject>();
map.put("A",new SomeObject());
map.put("B",new SomeObject());
If your objects have names or need to know their names, then the object should have the name as a property. Using variable names or map keys for object identification is not good.
Map<String,SomeObject> map = new HashMap<String,SomeObject>();
map.put("A",new SomeObject("A"));
map.put("B",new SomeObject("B"));
However, this is repetitive and you may want to refactor that into a more expressive design by introducing new classes:
SomeObjects objects = new SomeObjects();
SomeObject objectA = objects.create("A");
SomeObject objectB = objects.create("B");
// The container can manage references if you like to
SomeObject objectA = objects.get("A");
SomeObjects may use a Map internally to manage the objects:
class SomeObjects {
Map<String,SomeObject> objects = ...;
public SomeObject create(String name) {
SomeObject newObject = new SomeObject(name);
objects.put(name,newObject);
return newObject;
}
public SomeObject get(String name) {
return objects.get(name);
}
}
To iterate over either the object names or over the objects, the container can simply provide iterators for the keys of the map or the values of the map:
public class SomeObjects {
Map<String,SomeObject> objects = ...;
public Iterator<SomeObject> objects() {
return objects.values().iterator();
}
public Iterator<String> names() {
return objects.keySet().iterator();
}
}
To use these iterators, you can do:
public void test() {
SomeObjects objects = ...;
for(SomeObject obj : objects.objects()) {
// Do something with the object
}
for(String objName : objects.names()) {
// Do something with the object name
}
}
If you directly use a Map, you can use the Map's Entry class, which is a key and value pair:
public void test() {
Map<String,SomeObject> objects = new HashMap<String,SomeObject>();
objects.put("A",new SomeObject());
for(Entry entry : objects.entrySet()) {
System.out.printlnt("Processing object with name: " + entry.getKey());
SomeObject obj = entry.getValue();
doSomethingWith(obj);
}
}
you could just create a String field in SomeObject called name and store the name there.
OR
As much as I hate this answer:
Assuming you are creating the same objects every time, you could create a method which checks references, like so:
public void referenceChecker(SomeObject thing){
if( A == thing) System.out.println("A");
else if(B == thing) System.out.println("B");
//etc etc
}
Its not pretty and it's annoying to maintain, but it works

Categories