Suspicious call to 'LinkedHashMap.get' - java

Hello I have the following code
public static LinkedHashMap<Object, String[]> dataMap = new LinkedHashMap<>();
public static void parseDataset(int line){
String[] dataArr = dataMap.get(dataMap.keySet().toArray()[line]);
}
Since the Object I use as a Key is dynamically generated I have no knowledge about it so I have to find it before I can use it to get its value.
This code gives me the warning Suspicious call to 'LinkedHashMap.get', is that a problem and how would I get rid of the warning?

You don't need to use get: instead of converting the keys to an array, use values() instead. This works because values() iterates in the same order as the corresponding keys():
String[] dataArr = (String[]) dataMap.values().toArray()[line];
But you don't need to use toArray() either, which wastefully allocates an array containing all values, from which you only want one: you can just iterate through the values to get the thing you want:
static String[] nthItem(int n) {
int i = 0;
for (String[] value : dataMap.values()) {
if (i == n) return value;
++i;
}
throw new ArrayIndexOutOfBoundsException();
}
Or:
String[] dataArr = dataMap.values().stream().skip(line).findFirst().orElseThrow();
(Existing implementations of this sort of thing can be found in commonly-used libraries, e.g. Guava's Iterables.get)

Related

Can an array be used as a HashMap key?

If a HashMap's key is a String[] array:
HashMap<String[], String> pathMap;
Can you access the map by using a newly created String[] array, or does it have to be the same String[] object?
pathMap = new HashMap<>(new String[]{"korey", "docs"}, "/home/korey/docs");
String path = pathMap.get(new String[]{"korey", "docs"});
It will have to be the same object. A HashMap compares keys using equals() and two arrays in Java are equal only if they are the same object.
If you want value equality, then write your own container class that wraps a String[] and provides the appropriate semantics for equals() and hashCode(). In this case, it would be best to make the container immutable, as changing the hash code for an object plays havoc with the hash-based container classes.
EDIT
As others have pointed out, List<String> has the semantics you seem to want for a container object. So you could do something like this:
HashMap<List<String>, String> pathMap;
pathMap.put(
// unmodifiable so key cannot change hash code
Collections.unmodifiableList(Arrays.asList("korey", "docs")),
"/home/korey/docs"
);
// later:
String dir = pathMap.get(Arrays.asList("korey", "docs"));
No, but you can use List<String> which will work as you expect!
Arrays in Java use Object's hashCode() and don't override it (the same thing with equals() and toString()). So no, you cannot shouldn't use arrays as a hashmap key.
You cannot use a plain Java Array as a key in a HashMap. (Well you can, but it won't work as expected.)
But you could write a wrapper class that has a reference to the Array and that also overrides hashCode() and equals().
In most cases, where the Strings inside your array are not pathological and do not include commas followed by a space, you can use Arrays.toString() as a unique key. i.e. your Map would be a Map<String, T>. And the get/put for an array myKeys[] would be
T t = myMap.get(Arrays.toString(myKeys));
myMap.put(Arrays.toString(myKeys), myT);
Obviously you could put in some wrapper code if desired.
A nice side effect is that your key is now immutable. Of course, of you change your array myKeys and then try a get(), you won't find it.
Hashing of Strings is highly optimized. So my guess is that this solution, though it feels a bit slow and kludgy, will be both faster and more memory efficient (less object allocations) than #Ted Hopp solution using an immutable List. Just think about whether Arrays.toString() is unique for your keys. If not, or if there is any doubt, (e.g. the String[] comes from user input) use the List.
Like said you need a wrapper class around your array which overrides equality and hashCode.
e.g.
/**
* We can use this instance as HashKey,
* the same anagram string will refer the same value in the map.
*/
class Anagram implements CharSequence {
private final char[] anagram;
public Anagram(String anagram) {
this.anagram = anagram.toCharArray();
Arrays.sort(this.anagram);
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Anagram that = (Anagram) o;
return Arrays.equals(this.anagram, that.anagram);
}
#Override
public int hashCode() {
return Arrays.hashCode(this.anagram);
}
#Override
public int length() {
return anagram.length;
}
#Override
public char charAt(int index) {
return anagram[index];
}
#Override
public CharSequence subSequence(int start, int end) {
return new String(anagram).subSequence(start, end);
}
#Override
public String toString() {
return Arrays.toString(anagram);
}
}
Otherwise declare your map as IdentityHashMap, then the user knows we need to use the same instance for your CRUD.
Ted Hopp is right it will have to be same object.
For information see this example:
public static void main(String[] args) {
HashMap<String[], String> pathMap;
pathMap = new HashMap<String[], String>();
String[] data = new String[]{"korey", "docs"};
pathMap.put(data, "/home/korey/docs");
String path = pathMap.get(data);
System.out.println(path);
}
When you run the above code, it will print "docs".
Since Java 9, you can use Arrays::compare method as a comparator for TreeMap that compares the contents of arrays.
Map<String[], String> map = new TreeMap<>(Arrays::compare);
String[] key1 = {"one", "two"};
String[] key2 = {"one", "two"};
String[] key3 = {"one", "two"};
map.put(key1, "value1");
map.put(key2, "value2");
System.out.println(map.size()); // 1
System.out.println(map.get(key1)); // value2
System.out.println(map.get(key2)); // value2
System.out.println(map.get(key3)); // value2
See also: How to make a Set of arrays in Java?
A running example using the Arrays utility and the hash code it provides:
String[] key1 = { "korey", "docs" };
String value1 = "/home/korey/docs";
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(Arrays.hashCode(key1), value1);
System.out.println(map);
{-1122550406=/home/korey/docs}
This approach is useful if your focus is in storing only. Retrieving using the readable (original) key is simple:
String retrievedValue = map.get(Arrays.hashCode(key1));
System.out.println(retrievedValue);
/home/korey/docs

Is it possible to avoid loop which will cast one object type to another in split() or Arrays.asList()?

I'm trying to simplify my code and I have a question: is it's possible to convert string of IDs separated by coma to specific collection type?
So, my code now is:
String [] condition_list_id_tmp = rule.getContractRuleConditions().split(",");
List<String> condition_list_id = Arrays.asList(condition_list_id_tmp);
List<Long> condition_ids = new ArrayList<Long>();
for (String str_id : condition_list_id){
condition_ids.add(Long.parseLong(str_id));
}
Can I simplify this code by using for example Type collectionType = new TypeToken<List<Long>>() {}.getType(); like in gson?
Have you considered LambdaJ?
class StringToLong implements Converter<String, Long> {
public Long convert(String str) {
return Long.parseLong(str);
}
}
String [] condition_list_id_tmp = rule.getContractRuleConditions().split(",");
List<String> condition_list_id = Arrays.asList(condition_list_id_tmp);
List<Long> condition_ids = convert(condition_list_id, new StringToLong());
or using some libraries, like guava? so that there is no looping in your codes (but in theirs) ?
I saw that your mentioned your goal is "to simplify my code"
e.g.
final List<String> strList = Arrays.asList("1,2,3,4,5".split(","));
final List<Long> l = Lists.transform(strList, new Function<String, Long>() {
#Override
public Long apply(final String input) {
return Long.parseLong(input);
}
});
I don't think so with out looping you can do this. How come your collection's content type be changed with out casting explicitly .
There is no method available without looping. Even if a method is available it will look like to you as a single operation but obviously it has to
loop internally.
For eg: Arrays.fill(arrayname, intval);
This is a single method to fill the array with any integer value. But internally it will also run a loop on the array.
java 8 can do this :
String [] condition_list_id_tmp = rule.getContractRuleConditions().split(",");
List<String> condition_list_id = Arrays.asList(condition_list_id_tmp);
List<Long> condition_ids = condition_list_id.map(c -> Long.parseLong(c))
The solution for your problem until Java 8 pop to the market could be project Guava, with their support for Functional Idioms.
Then you could perform that operation in different way, but as i wrote in the comment. At the end you will have same operation.
public static List<Long> splitToLong(String list, String token) {
StringTokenizer tokenizer= new StringTokenizer(list, token);
List<Long> result = new ArrayList<Long>();
while(tokenizer.hasMoreTokens() {
result.add(Long.parseLong(tokenizer.nextToken()));
}
}
If you put this method in some Util class, then you can enjoy clean code
//....
for(Long mLong : StringHelper.splitToLong(message,",")) {
//Do something with mLong
}
//....

Convert List<String> to List<Integer> directly

After parsing my file " s" contains AttributeGet:1,16,10106,10111
So I need to get all the numbers after colon in the attributeIDGet List. I know there are several ways to do it. But is there any way we can Directly convert List<String> to List<Integer>.
As the below code complains about Type mismatch, so I tried to do the Integer.parseInt, but I guess this will not work for List. Here s is String.
private static List<Integer> attributeIDGet = new ArrayList<Integer>();
if(s.contains("AttributeGet:")) {
attributeIDGet = Arrays.asList(s.split(":")[1].split(","));
}
Using Java8:
stringList.stream().map(Integer::parseInt).collect(Collectors.toList());
No, you need to loop over the array
for(String s : strList) intList.add(Integer.valueOf(s));
Using lambda:
strList.stream().map(org.apache.commons.lang3.math.NumberUtils::toInt).collect(Collectors.toList());
You can use the Lambda functions of Java 8 to achieve this without looping
String string = "1, 2, 3, 4";
List<Integer> list = Arrays.asList(string.split(",")).stream().map(s -> Integer.parseInt(s.trim())).collect(Collectors.toList());
Guava Converters do the trick.
import com.google.common.base.Splitter;
import com.google.common.primitives.Longs;
final Iterable<Long> longIds =
Longs.stringConverter().convertAll(
Splitter.on(',').trimResults().omitEmptyStrings()
.splitToList("1,2,3"));
No, you will have to iterate over each element:
for(String number : numbers) {
numberList.add(Integer.parseInt(number));
}
The reason this happens is that there is no straightforward way to convert a list of one type into any other type. Some conversions are not possible, or need to be done in a specific way. Essentially the conversion depends on the objects involved and the context of the conversion so there is no "one size fits all" solution. For example, what if you had a Car object and a Person object. You can't convert a List<Car> into a List<Person> directly since it doesn't really make sense.
If you use Google Guava library this is what you can do, see Lists#transform
String s = "AttributeGet:1,16,10106,10111";
List<Integer> attributeIDGet = new ArrayList<Integer>();
if(s.contains("AttributeGet:")) {
List<String> attributeIDGetS = Arrays.asList(s.split(":")[1].split(","));
attributeIDGet =
Lists.transform(attributeIDGetS, new Function<String, Integer>() {
public Integer apply(String e) {
return Integer.parseInt(e);
};
});
}
Yep, agree with above answer that's it's bloated, but stylish. But it's just another way.
Why don't you use stream to convert List of Strings to List of integers?
like below
List<String> stringList = new ArrayList<String>(Arrays.asList("10", "30", "40",
"50", "60", "70"));
List<Integer> integerList = stringList.stream()
.map(Integer::valueOf).collect(Collectors.toList());
complete operation could be something like this
String s = "AttributeGet:1,16,10106,10111";
List<Integer> integerList = (s.startsWith("AttributeGet:")) ?
Arrays.asList(s.replace("AttributeGet:", "").split(","))
.stream().map(Integer::valueOf).collect(Collectors.toList())
: new ArrayList<Integer>();
If you're allowed to use lambdas from Java 8, you can use the following code sample.
final String text = "1:2:3:4:5";
final List<Integer> list = Arrays.asList(text.split(":")).stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
System.out.println(list);
No use of external libraries. Plain old new Java!
Using Streams and Lambda:
newIntegerlist = listName.stream().map(x->
Integer.valueOf(x)).collect(Collectors.toList());
The above line of code will convert the List of type List<String> to List<Integer>.
I hope it was helpful.
No, there is no way (that I know of), of doing that in Java.
Basically you'll have to transform each entry from String to Integer.
What you're looking for could be achieved in a more functional language, where you could pass a transformation function and apply it to every element of the list... but such is not possible (it would still apply to every element in the list).
Overkill:
You can, however use a Function from Google Guava (http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Function.html) to simulate a more functional approach, if that is what you're looking for.
If you're worried about iterating over the list twice, then instead of split use a Tokenizer and transform each integer token to Integer before adding to the list.
Here is another example to show power of Guava. Although, this is not the way I write code, I wanted to pack it all together to show what kind of functional programming Guava provides for Java.
Function<String, Integer> strToInt=new Function<String, Integer>() {
public Integer apply(String e) {
return Integer.parseInt(e);
}
};
String s = "AttributeGet:1,16,10106,10111";
List<Integer> attributeIDGet =(s.contains("AttributeGet:"))?
FluentIterable
.from(Iterables.skip(Splitter.on(CharMatcher.anyOf(";,")).split(s)), 1))
.transform(strToInt)
.toImmutableList():
new ArrayList<Integer>();
Use Guava transform method as below,
List intList = Lists.transform(stringList, Integer::parseInt);
import java.util.Arrays;
import java.util.Scanner;
public class reto1 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
double suma = 0, promedio = 0;
String IRCA = "";
int long_vector = Integer.parseInt(input.nextLine());
int[] Lista_Entero = new int[long_vector]; // INSTANCE INTEGER LIST
String[] lista_string = new String[long_vector]; // INSTANCE STRING LIST
Double[] lista_double = new Double[long_vector];
lista_string = input.nextLine().split(" "); // INPUT STRING LIST
input.close();
for (int i = 0; i < long_vector; i++) {
Lista_Entero[i] = Integer.parseInt(lista_string[i]); // CONVERT INDEX TO INDEX FROM STRING UNTIL INTEGER AND ASSIGNED TO NEW INTEGER LIST
suma = suma + Lista_Entero[i];
}

How do I grab an index from an array in a HashMap?

I've got a HashMap<Object, String[]> I just want to grab the 0 index position from the String[]. How do I do that?
Can I just do this? mMap.get(position)[0]?
Yes, you can do what you've indicated, provided position is a key in the map.
HashMap doesn't have a 0index and it doesn't have a String[]
You cannot do what you ask because it doesn't make sense.
Can I just do this? mMap.get(position)[0]?
You can. Have you tried this to see if it works? Note: it will fail if map.get() returns null
A map is not an array. It's a dictionary of keys that map to items. So there is no ordered index in the Map part, there's a lookup key. This means that a Map has no "next" item.
In the event that you stored a String[] in the map, then you could get the first element of the String array like so:
String first = ((String[])mMap.get(lookupKey))[0];
Since you are using generics, your compiler will make the casting unnecessary, simplifying the answer to
String first = mMap.get(lookupKey)[0];
Note that this is not using the position to access the item stored in the Map, it's using the lookup key. In addition, there is a casting of the returned Object into a String[] (because we stored a String[] in the map earlier), and then there is a dereferncing of the first ('0') element.
Here is a little demo program that does what you're asking.
import java.util.Map;
import java.util.HashMap;
public class FirstElementInHashMap {
public static void main(String[] args) {
Map<Object,String[]> mMap = new HashMap<Object,String[]>();
mMap.put("myKeyA", new String[] { "myValue1", "myValue2", "myValue3" });
mMap.put("myKeyB", new String[] { "myValue4", "myValue5", "myValue6" });
mMap.put("myKeyC", new String[] { "myValue7", "myValue8", "myValue9" });
Object position = "myKeyB";
String[] strings = mMap.get(position);
// make sure position exists in the Map and contains a non-empty array
// so we don't throw an NullPointerException
String firstStringInArray = null;
if (strings != null && strings.length > 0) {
firstStringInArray = strings[0];
}
System.out.println(firstStringInArray);
}
}
The output of the above program is:
myValue4

List Sorting puzzle

Assuming I have
final Iterable<String> unsorted = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");
What can I do to transform this unsorted list into this:
[PREFZ, PREFA, BAR, FOO, PREFOO, ZOO]
(a list which begin with known values that must appears first (here "PREFA" and "PREFZ") and the rest is alphabetically sorted)
I think there are some usefull classes in guava that can make the job (Ordering, Predicates...), but I have not yet found a solution...
I would keep separate lists.
One for known values and unknown values. And sort them separately, when you need them in a one list you can just concatenate them.
knownUnsorted.addAll(unsorted.size - 1, unknonwUnsorted);
I suggest filling List with your values and using Collections.sort(...).
Something like
Collections.sort(myList, new FunkyComparator());
using this:
class FunkyComparator implements Comparator {
private static Map<String,Integer> orderedExceptions =
new HashMap<String,Integer>(){{
put("PREFZ", Integer.valueOf(1));
put("PREFA", Integer.valueOf(2));
}};
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
Integer i1 = orderedExceptions.get(s1);
Integer i2 = orderedExceptions.get(s2);
if (i1 != null && i2 != null) {
return i1 - i2;
}
if (i1 != null) {
return -1;
}
if (i2 != null) {
return +1;
}
return s1.compareTo(s2);
}
}
Note: This is not the most efficient solution. It is just a simple, straightforward solution that gets the job done.
I would first use Collections.sort(list) to sort the list.
Then, I would remove the known items, and add them to the front.
String special = "PREFA";
if (list.remove(special)
list.add(0, special);
Or, if you have a list of array of these values you need in the front you could do:
String[] knownValues = {};
for (String s: knownValues) {
if (list.remove(s))
list.add(0, s);
}
Since I'm a fan of the guava lib, I wanted to find a solution using it. I don't know if it's efficient, neither if you find it as simple as others solution, but it's here:
final Iterable<String> all = asList("FOO", "BAR", "PREFA", "ZOO", "PREFOO", "PREFZ");
final List<String> mustAppearFirst = asList("PREFZ", "PREFA");
final Iterable<String> sorted =
concat(
Ordering.explicit(mustAppearFirst).sortedCopy(filter(all, in(mustAppearFirst))),
Ordering.<String>natural().sortedCopy(filter(all, not(in(mustAppearFirst)))));
You specifically mentioned guava; along with Sylvain M's answer, here's another way (more as an academic exercise and demonstration of guava's flexibility than anything else)
// List is not efficient here; for large problems, something like SkipList
// is more suitable
private static final List<String> KNOWN_INDEXES = asList("PREFZ", "PREFA");
private static final Function<Object, Integer> POSITION_IN_KNOWN_INDEXES
= new Function<Object, Integer>() {
public Integer apply(Object in) {
int index = KNOWN_INDEXES.indexOf(in);
return index == -1 ? null : index;
}
};
...
List<String> values = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");
Collections.sort(values,
Ordering.natural().nullsLast().onResultOf(POSITION_IN_KNOWN_INDEXES).compound(Ordering.natural())
);
So, in other words, sort on natural order of the Integer returned by List.indexOf(), then break ties with natural order of the object itself.
Messy, perhaps, but fun.
I would also use Collections.sort(list) but I think I would use a Comparator and within the comparator you could define your own rules, e.g.
class MyComparator implements Comparator<String> {
public int compare(String o1, String o2) {
// Now you can define the behaviour for your sorting.
// For example your special cases should always come first,
// but if it is not a special case then just use the normal string comparison.
if (o1.equals(SPECIAL_CASE)) {
// Do something special
}
// etc.
return o1.compareTo(o2);
}
}
Then sort by doing:
Collections.sort(list, new MyComparator());

Categories