How do I avoid lots of checking in Java? - java

When writing a Java program, I face a problem as follows:
Map<Object, Map<Object, Map<Object, Object>>> map = new HashMap<>();
/**
* put operations
**/
Map<Object, Map<Object, Object>> a = null;
Map<Object, Object> b = null;
Object c = null;
a = map.get(/*some object*/);
if(a != null) {
b = a.get(/*some object*/);
}
if(b != null) {
c = b.get(/*some object*/);
}
if(c != null) {
/*do what I want to do*/
}
If I want to be sure c is not null, I need to add an "if" three times to ensure that the code can run without exception. Is there a better way to do it?

Encapsulate required functionality in a class implementing something like this:
interface ThreeKeysMap {
void put(Object keyA, Object keyB, Object keyC, Object value);
Object get(Object keyA, Object keyB, Object keyC);
}
Alternatively, you could create a class for your composite key:
public class ThreeKey {
public Object keyA;
public Object keyB;
public Object keyC;
// TODO: MUST override equals and hashCode methods or map won't work!!!!
}
and use it as a key for your map: Map<ThreeKey, Object> map = new HashMap<>();

I think that the getOrDefault-method of the Map interface could work here. Especially if you combine it with the Optional class as follows:
Map<Object, Map<Object, Map<Object, Object>>> map = new HashMap<>();
Map<Object, Map<Object, Object>> a = map.getOrDefault("", Collections.emptyMap());
Map<Object, Object> b = a.getOrDefault("", Collections.emptyMap());
Optional.ofNullable(b.get("")).ifPresent(c -> {/* Do what I want to do */});
This way you simply use getOrDefault to retrieve empty maps (which I personally like better than null values. The Optional.ofNullable is a null-safe way of creating an Optional object and you can do what you want to do in the lambda expression.
Both Optional and Map.getOrDefault are part of Java 8.

You can create a class Key<T,E> that will hold your first pairs of objects, and than create a map
Map<Key<T,E>, Object>
This seems much more neat to me, because it seems you use the first two objects as a key, and the nested dictionary approach is less elegant for such a task.
Remember that if you follow this approach - you have to override equals() and hashCode(), and depending on usages - sometimes also compareTo()

Related

Java 8: How to get a value from a list contained as a map value?

I have the following situation:
I have a LinkedHashMap<> where the key type is a String and the values types varies: double, String, LinkedHashMap, etc.
I am trying to extract a value from a key of one of the LinkedHashMaps values which are a value of the main map.
For example, I'd like to get the result 1 from the following code (obviously it is a mess since it doesn't even compile):
Map<String, Object> input = new HashMap<>();
input.put("a", "1234");
input.put("b", "2345");
input.put("c", "3456");
input.put("d", new HashMap<String, String>());
HashMap<String, Object> input2 = (HashMap<String, Object>)(input.get("d"));
input2.put("d1", 1);
input2.put("d2", 2);
Optional<Integer> result = input.entrySet().stream()
.filter(e -> e.getKey().equals("d"))
.map(Map.Entry::getValue)
.filter(e -> e.getKey().equals("d1"))
.findFirst();
Where do I go wrong, and of course, what is the best way to get the result?
Thanks.
Once you use a Map with different value (and even key) types (and worse, nested maps). Then I suggest taking a step back and try to analyse what you've done. It seems that you're way better with a class than a Map. An example with your keys:
class YourClass {
String a;
String b;
String c;
YourOtherClass d;
}
class YourOtherClass {
Integer d1;
Integer d2;
}
I've omitted getters, setters and constructors for simplicity.
You can then create instances of those objects, like this:
YourOtherClass yoc = new YourOtherClass(1, 2);
YourClass yc = new YourClass("1234", "2345", "3456", yoc);
And then call the specific getter to receive a value with typesafety:
String a = yc.getA(); // works
Integer i = yc.getA(); // doesn't work
Or setting a new value via the setter:
yoc.setD1(4); // works
yoc.setD1("4"); // doesn't work
You're overcomplicating things imo. You could do it in a very straightforward manner. One liners are not always the ideal solutions.
I don't have the possibility to compile it, but it should be ok.
public Optional<Integer> getInnerValue(Map<String, Object> map, String outerKey, String innerKey) {
Object o = map.get(outerKey);
if (!(o instanceof Map)) {
return Optional.empty();
}
return Optional.ofNullable(((Map)o).get(innerKey));
}
Using a one-liner
public Optional<Integer> getInnerValue(Map<String, Object> map, String outerKey, String innerKey) {
return Optional.ofNullable(map.get(outerKey))
.filter(Map.class::isInstance)
.map(Map.class::cast)
.map(m -> m.get(innerKey))
.findFirst();
}

java - to set multiple value in a map

I got a scenario like the following:
Map1 - Map<String, Map<String,List<Vo>>>
Map2 - Map<String, Set<String>
Is it possible to set the same have a same key reference for the above 2 Maps like the following?
Map<String, Collection<?> mapCommon=new HashMap<String, Collection<?>();
Can anyone please give some idea about how to set this?
edit: yes same reference
You are touching here two interesting elements.
Firstly - Map does not belong to Collection. List and Set do belong, but Map is a different one even though it shares some commonalities with Lists and Sets.
Secondly - Mixing the types into one commonMap the way you are trying is doable but it should be avoided as it is generally not considered as best practice. The problem we are dealing with is caused by type erasure. Once compiler compiles the code - it does not pass any information about generic types hold by Map or Set. Effectively your Map<String, List<Vo>> becomes raw-type Map<?> in the compiled code. The problem with that is casting back original values. The compiler will not allow you to check the instance if it is Map<String, List<Vo>> or Set<String>.
The fllowing piece of code will fail:
public static void processElement(Object commonMapObjectEitherMapOrSet) {
if (commonMapObjectEitherMapOrSet instanceof Map<String, List<Vo>>) {
//...
}
}
Error: Cannot perform instanceof check against parameterized type
Map>. Use the form Map instead since further
generic type information will be erased at runtime
The possible workaround would be to forget about generics and check if the instance is a raw-type Set or Map. The code below shows how check if Object is either Map or Set.
public static void processElement(Object commonMapObjectEitherMapOrSet) {
if (commonMapObjectEitherMapOrSet instanceof Map) {
System.out.println("Got map; but types held in the map are not known due to type-erasure");
// This is where things will get messy as you will get warnings:
Map<String, List<Vo>> map = (Map<String, List<Vo>>) commonMapObjectEitherMapOrSet;
// ...
}
if (commonMapObjectEitherMapOrSet instanceof Set) {
System.out.println("Got set; but types held in the set are not known due to type-erasure");
// This is where things will get messy as you will get warnings:
Set<String> set = (Set<String>) commonMapObjectEitherMapOrSet;
// ...
}
}
The problem with the above is casting the value from your commonMap back to your desired types ie. Map<String, List<Vo>> and Set<String>. The compiler won't be able to check if the casting is correct and will issue a warning. You can technically Suppress the warning with (#SuppressWarnings("unchecked") annotation ) but this may not be the best thing to do.
At this stage - it makes sense to consider whether or not to create your own specialized class to manage different types.
Back to your original question - to answer it I am posting the code that maps things to the common map:
package stackoverflow;
import java.util.*;
class Vo {}
public class MultipleRefs {
public static void main(String[] args) {
Map<String, List<Vo>> mapVo = new HashMap<>();
Set<String> set = new HashSet<>();
Map<String, Object> commonMap = new HashMap<>();
//commonMap.put("a", Map)
commonMap.put("mapVoOne", mapVo);
commonMap.put("setOne", set);
commonMap.forEach((key, value) -> processElement(value));
}
public static void processElement(Object commonMapObject) {
if (commonMapObject instanceof Map) {
System.out.println("Got map; but types held in the map are not known due to type-erasure");
// This is where things will get messy:
Map<String, List<Vo>> map = (Map<String, List<Vo>>) commonMapObject;
System.out.println(" processElement prints map: " + map);
}
if (commonMapObject instanceof Set) {
System.out.println("Got set; but types held in the set are not known due to type-erasure");
// This is where things will get messy:
Set<String> set = (Set<String>) commonMapObject;
System.out.println(" processElement prints set: " + set);
}
}
}
If I understand you would want to have the same key to be used for various different types of values.
Why not have a new Class itself that would consists of maps, sets, whose instances could be used as values
class MyClass {
private Map<String, List<Vo>> theMap;
private Set<String> theSet;
...
... // have its own getters and setters
}
And then you can have your top level map defined like this
Map<String, MyClass> myMainMap = new HashMap<String, MyClass>();
Or as an alternative have a tuple
You can check this link further to see how that is done.
What you want to do is impossible because Set and Map do not share any common implementation or super class except Object. You can see it in the official documentation :
Javadoc Map
Javadoc Set
You could do a Map<String, Object> but I strongly not advise you to doing that. How could you know if your object is a map or a set ? It is not possible to do that properly.
In my opinion, the best solution you have is to create a new class to wrap your two collections :
public class YourWrapper {
Map<String, Map<String,List<Vo>>> a;
Map<String, Set<String> b;
// getter setter etc...
}
After that you can create your collection :
Map<String, YourWrapper> myMap = new HashMap<String, YourWrapper>();

How can I make a Map with two indexes?

I have one Map in java like this:
Map<String index1, Map<String index 2, Object obj>> map = new HashMap<>();
I want to get my Object in the map by using index1 and index2 as lookups.
The easiest way to do this would be to use Guava's Table, if you're willing to use a third party library.
It works like this:
Table<String, String, Object> table = HashBasedTable.create();
table.put(index1, index2, obj);
Object retrievedObject = table.get(index1, index2);
You can add it to your project by following these instructions: How to add Guava to Eclipse project
If you don't want to use Guava, you have a big problem. If you try to insert an element with new first key, you have to make sure the innermap already exists. This means, every time you do put, you have to retrieve the innerMap, see if it exists, and then create it if it does not. You will have to do this every time you call Map.put. Also, you risk throwing a NullPointerException if the inner map doesn't exist when you call get on the inner map.
If you do this, should wrap your Map<String, Map<String, Object> in an outer class to manage these problems, or use Java 8's computeIfAbsent. But the easiest way is to just use Table as above.
If you make your own class to use instead of Table, it would be something like:
public class DoubleMap<R, C, V> {
private final Map<R, Map<C, V>> backingMap;
public DoubleMap() {
this.backingMap = new HashMap<>();
}
public V get(R row, C column) {
Map<C, V> innerMap = backingMap.get(row);
if(map == null) return null;
else return innerMap.get(column);
}
public void put(R row, C column, V value) {
Map<C, V> innerMap = backingMap.get(row);
if(innerMap == null) {
innerMap = new HashMap<C, V>();
backingMap.put(row, innerMap);
}
innerMap.put(column, value);
}
}
You would use this class by doing:
DoubleMap<String, String, Object> map = new DoubleMap();
Note that this answer has a lot less features than the Guava version.
Getting a Value from a Map
If I understand your question, then with an index a and b that might look like (guarding against null with a ternary or Conditional Operator ? :),
Object obj = (map.get("a") == null) ? null : map.get("a").get("b");
Using a Generic Type
And you might be more specific, like
Map<String, Map<String, Something>> map = new HashMap<>();
Something s = (map.get("a") == null) ? null : map.get("a").get("b");
Adding values to the Map
Assuming you want to add your Something value to the map that could be done with something like,
Map<String, Map<String, Something>> map = new HashMap<>();
if (map.get("a") == null) {
map.put("a", new HashMap<>());
}
map.get("a").put("b", value);
If you don't need regular access to the entire "row", but just quick access to each cell you can use the built-in Map.Entry as your key:
Map<Map.Entry<String, String>, Object> table = new Map<>();
table.put(new Map.SimpleEntry("index1", "index2"), "Hello world");
Alternatively, if you're willing to go with something third-party, several someones have already implemented tuples for Java.
If you are in a situation where you cannot pull in a third-party library easily, but you don't like the semantics of Map.Entry (which is written in terms of keys and values) you can write your own Pair class to have the same effect.
As my understanding, you can do like:
Map<String, Map<String, Object> map= new HashMap();
Map<String, Object> subMap = map.get("index1");
if(subMap != null) {
Object obj = subMap.get("index2");
}
The best solution probably depends on how this map is intended to be used:
Is it used in a limited scope, or is it part of a public API?
Are the "indices" always of type String, or do they have to be generic?
Will it always be two indices, or may you need more indices later?
...
A pragmatic solution focussed on the question as you described it would be to introduce a StringPair class that can be used for indexing. This saves you from the hassle of doing 2D-lookups of inner maps (and possible cleanups when the inner maps become empty!), does not require any third-party libraries, and is readable and efficient.
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
public class StringPairMapTest
{
public static void main(String[] args)
{
Map<StringPair, Object> map = new LinkedHashMap<StringPair, Object>();
map.put(StringPair.of("A","B"), 12);
map.put(StringPair.of("C","D"), 34);
System.out.println(map.get(StringPair.of("A","B")));
System.out.println(map.get(StringPair.of("C","D")));
System.out.println(map.get(StringPair.of("X","Y")));
}
}
class StringPair
{
private final String s0;
private final String s1;
static StringPair of(String s0, String s1)
{
return new StringPair(s0, s1);
}
private StringPair(String s0, String s1)
{
this.s0 = s0;
this.s1 = s1;
}
#Override
public String toString()
{
return "("+s0+","+s1+")";
}
#Override
public int hashCode()
{
return Objects.hash(s0, s1);
}
#Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
StringPair other = (StringPair) obj;
return Objects.equals(s0, other.s0) && Objects.equals(s1, other.s1);
}
}
Generalizations to a Pair<T> or Tuple<S,T> would be possible, of course, but this did not seem to be what you have been looking for...

Map same key and value using google collections

I need to validate if map (String to String) entry doesn't contain same key and value pair (case-insensitive). For example -
("hello", "helLo") // is not a valid entry
I was wondering if Google collection's Iterable combined with Predicates some how could solve this problem easily.
Yes I could have simple iterator for entries to do it myself, but thinking of any thing already up.
Looking for something in-lined with Iterables.tryFind(fromToMaster, Predicates.isEqualEntry(IGNORE_CASE)).isPresent()
If you want to use guava, you can use the Maps utils, specifically the filterEntries function.
An example to filter only entries where the key does not equal the value (ignoring the case) could look like this
Map<String, String> map = new HashMap<>();
map.put("hello", "helLo");
map.put("Foo", "bar");
Map<String, String> filtered = Maps.filterEntries(map, new Predicate<Map.Entry<String, String>>() {
#Override
public boolean apply(Map.Entry<String, String> input) {
return !input.getKey().equalsIgnoreCase(input.getValue());
}
});
System.out.println(filtered); // will print {Foo=bar}
However there is no default Predicate in guava's Predicates I know of that does what you want.
Addition:
If you want a validation mechanism without creating a new map, you can use Iterables and the any method to iterate over the entry set of the map. To make the condition more readable I would assign the predicate to a variable or a member field of the class you are working in.
Predicate<Map.Entry<String, String>> keyEqualsValueIgnoreCase = new Predicate<Map.Entry<String, String>>() {
#Override
public boolean apply(Map.Entry<String, String> input) {
return input.getKey().equalsIgnoreCase(input.getValue());
}
};
if (Iterables.any(map.entrySet(), keyEqualsValueIgnoreCase)) {
throw new IllegalStateException();
}
or if you need the entry, you can use the Iterables#tryFind method and use the returned Optional
Optional<Map.Entry<String, String>> invalid = Iterables.tryFind(map.entrySet(), keyEqualsValueIgnoreCase);
if(invalid.isPresent()) {
throw new IllegalStateException("Invalid entry " + invalid.get());
}

Rhino: How to get all properties from ScriptableObject?

I am using a Javascript object as an object with configuration properties.
E.g. I have this object in javascript:
var myProps = {prop1: 'prop1', prop2: 'prop2', 'prop3': 'prop3'};
This object (NativeObject) is returned to me in Java function.
E.g.
public Static void jsStaticFunction_test(NativeObject obj) {
//work with object here
}
I want to get all properties from object and build HashMap from it.
Any help will be appreciated.
So, I solved my problem :)
Code:
public static void jsStaticFunction_test(NativeObject obj) {
HashMap<String, String> mapParams = new HashMap<String, String>();
if(obj != null) {
Object[] propIds = NativeObject.getPropertyIds(obj);
for(Object propId: propIds) {
String key = propId.toString();
String value = NativeObject.getProperty(obj, key).toString();
mapParams.put(key, value);
}
}
//work with mapParams next..
}
well, if you looked closer, you would have seen that NativeObject implements the Map interface, so you could have worked very well with the NativeObject.... But to answer your question: you could have used the common approach for getting the key-value pairs of any map
for (Entry<Object, Object> e : obj.entrySet()){
mapParams.put(e.getKey().toString(), e.getValue().toString());
}
A cast would have been enough for your case, because you have only strings as values. So, if you really wanted a HashMap:
HashMap<String, String> mapParams = new HashMap<String, String>((Map<String,String>)obj); //if you wanted a HashMap
But if you just wanted a generic Map, it was even simpler, and less RAM consuming:
Map<String, String> mapParams = (Map<String,String>)obj;

Categories