I'm working on an assignment where I have to sort integers and string type variables.
So I have a method declared as
public void quickSort(ArrayList<Entry<Integer, String>> list) {
but if I want to overload the method and use
public void quickSort(ArrayList<Entry<String, Integer>> list) {
It is recognized as a duplicate.
Is there a way to check what my variable types are?
Alternatively, is there a way to sort strings and integers the same way so that I can do something like
public void quickSort(ArrayList<Entry<K, V>> list) {
that will work on both date types?
You probably need to provide an explicit comparator, since there is no natural ordering of Entrys:
public <T> void quickSort(ArrayList<T> list, Comparator<? super T> comparator)
and then use that comparator inside the method for your comparisons.
The fact that you are trying to sort something with two type parameters (Map.Entry<String, Integer> or Map.Entry<Integer, String>) is not relevant from a generics perspective: it's just a single type, hence you can replace the whole thing with T.
What also may be helpful for you is that you can check the datatype of a variable with instanceof keyword. E.g.
if (variable instanceof String) {
//do stuff
}
Related
I am writing the below code to sort the hash map values :
private static HashMap sortByValues(HashMap map) {
List list = new LinkedList(map.entrySet());
Collections.sort(list, new Comparator() {
public int compare(Object o1, Object o2) {
return (((Map.Entry) (o1)).getValue()).compareTo(((Map.Entry) (o2)).getValue());
}
});
}
However When I execute this, it throws an error stating cannot find symbol compareTo. But isnt the method in the String class which is in the lang package?
Also when i replace it by adding a Comparable typecast, it runs fine
private static HashMap sortByValues(HashMap map) {
List list = new LinkedList(map.entrySet());
Collections.sort(list, new Comparator() {
public int compare(Object o1, Object o2) {
return ((Comparable) ((Map.Entry) (o1)).getValue()).compareTo(((Map.Entry) (o2)).getValue());
}
});
}
Can someone please help, I am a beginner in Collections.
All of Java's collection classes and interfaces are generics, which means they are intended to be used with type parameters. For historical reasons it's possible to use them without type parameters, which is what you've done here. However, it's a bad idea to do that, because
you sacrifice a lot of the type safety that the compiler gives you,
you end up casting objects to whatever type you need them to be, which is prone to error, and such errors usually only reveal themselves at run time.
Finding errors at compile time is better than finding them at run time. The compiler is your friend - it gives you messages to help you find your errors.
Now in this particular case, you seem to be building a sorted list of values from your map. But it only makes sense to do this if the values in your map belong to some type that can be sorted. There's no general way of sorting Object, so for this to make sense, you want to restrict your parameter to be a map whose values can be sorted, or to put it another way, can be compared to other objects of the same type.
The generic interface that tells you that one object can be compared to another is Comparable. So if you have a type V, then writing V extends Comparable<V> means that one object of type V can be compared to other objects of type V, using the compareTo method. This is the condition that you want the type of the values in your map to obey. You don't need any such condition on the type of the keys in your map.
Therefore, you could write your method as generic, which means that its signature will list some type parameters, inside < > characters, possibly with some conditions on those type parameters. In your case, you'd give your method a signature like this, assuming it's going to return a List.
private static <K, V extends Comparable<V>> List<V> sortAndListValues(Map<K,V> map)
Of course, if you really intend to return some kind of sorted map, then it might be more like
private static <K, V extends Comparable<V>> Map<K,V> sortByValues(Map<K,V> map)
but you need to remember that it's not possible to sort HashMap objects. They're naturally sorted in an order that's implied by the hashCode function of the key class, and by the current size of the map. This is generally not a very useful order. There are other types of map in the JDK, such as
TreeMap, which sorts its entries according to the key - not what you want here
LinkedHashMap, which sorts its entries according to the order they were inserted - and you could probably make use of this here.
For the sake of answering your question though, I'm just going to write the List version of your method.
private static <K, V extends Comparable<V>> List<V> sortAndListValues(Map<K,V> map) {
List<V> toReturn = new LinkedList<>(map.values());
Collections.sort(toReturn, new Comparator<V>() {
public int compare(V first, V second) {
return first.compareTo(second);
}
});
return toReturn;
}
Note that by using the type parameters K and V wherever it's appropriate to do so, there's no need for any kind of casting. The compiler will also warn you if you try to use any of the objects in the map in a way that's inappropriate for their type.
There are shorter ways of writing this of course, using the "functional style" that comes with Java 8. But that's a topic for another post entirely.
#ghostrider - you have removed generics from HashMap so both key and value are of Object type. Inside contents of map are Comparable type but the reference is of Entry<Object, Object> not Entry<Object, Comparable>. Look into the below example.
Object obj = new Integer(5);
int i = obj.intValue(); // Error
int i = ((Integer)obj).intValue(); // Success
Here int i = obj.intValue(); fails but int i = ((Integer)obj).intValue(); get success because i am explicitly type casting because of reference is of Object type.
You can do this by following
private static Map<String, Integer> sortByValue(Map<String, Integer> unsortMap) {
// 1. Convert Map to List of Map
List<Map.Entry<String, Integer>> list =
new LinkedList<Map.Entry<String, Integer>>(unsortMap.entrySet());
// 2. Sort list with Collections.sort(), provide a custom Comparator
// Try switch the o1 o2 position for a different order
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
public int compare(Map.Entry<String, Integer> o1,
Map.Entry<String, Integer> o2) {
return (o1.getValue()).compareTo(o2.getValue());
}
});
// 3. Loop the sorted list and put it into a new insertion order Map LinkedHashMap
Map<String, Integer> sortedMap = new LinkedHashMap<String, Integer>();
for (Map.Entry<String, Integer> entry : list) {
sortedMap.put(entry.getKey(), entry.getValue());
}
return sortedMap;
}
I defined a generic function in java with the signature
<V> List<V> sortedValuesFromMap(Map<?, Collection<V>> keysValues, Comparator<V> comp)
which takes a Map mapping any type of keys to a Collection of some defined type V, and a comparator of type V. The method works great and the Java compiler does not complain about type incompatibility.
But now when I want to apply this method to a map of the type Map<String, Set<String>> and the AlphanumComparator (see here) the compiler says :
The method sortedValuesFromMap(Map<?,Collection<V>>, Comparator<V>)
in the type MyUtils is not applicable for the arguments
(Map<String,Set<String>, AlphanumComparator)
Turning Collection to Set in the signature of sortedValuesFromMap would fix it – but I do not want to do that. So why is Java forcing me to do so, although Set<E> is implementing Collection<E>?
PS: If someone is interested in my code:
public static <V> List<V> sortedValuesFromMap(Map<?, Collection<V>> keysValues,
Comparator<V> comp) {
List<V> values = new LinkedList<V>();
for (Collection<V> col : keysValues.values()) {
values.addAll(col);
}
Collections.sort(values, comp);
return values;
}
Just as a List<Dog> is not a List<Animal>, a Map<String, Set<String>> is not a Map<String, Collection<String>> and it's not a Map<?, Collection<String>>.
The solution here is to add a wildcard in place of Set to allow a subclass in the generic type parameter.
// Add v
public static <V> List<V> sortedValuesFromMap(Map<?, ? extends Collection<V>> keysValues,
Comparator<V> comp) {
Your problem is that Map<String,Set<V>> is not a subtype of Map<String,Collection<V>>. Think about what type x can be, if it's legal to write x.put("Hello",new ArrayList<V>());
In this case, x could be a Map<String,Collection<V>>, since an ArrayList<V> is certainly a Collection<V>. But it couldn't be a Map<String,Set<V>>, since an ArrayList<V> is not a Set<V>. Therefore it's untrue to say that any Map<String,Set<V>> "is a" Map<String,Collection<V>>.
The type that you want is either Map<String,? extends Collection<V>> or Map<?,? extends Collection<V>>. Both Map<String,Set<V>> and Map<String,Collection<V>> are subtypes of these types.
// I know that this method will generated duplicate
// trim keys for the same value but I am just
// trying to understand why we have a compile error:
// The method put(String, capture#11-of ?) in the type
// Map<String,capture#11-of ?> is not applicable for the arguments
// (String, capture#12-of ?)
void trimKeyMap(Map<String, ?> map){
for (String key : map.keySet()) {
map.put(StringUtils.trim(key), map.get(key)); // compile error
}
}
How come the value that we want to put map.get(key) can be from a different type?
The problem is that the compiler only knows the key type is "unknown", but doesn't know it's the same unknown type for the type of the Map's key and the type returned from get() (even though we as humans realize that it's the same).
If you want to make it work, you must tell the compiler it's the same unknown type by typing your method, for example:
void <V> trimKeyMap(Map<String, V> map) {
for (String key : map.keySet()) {
map.put(StringUtils.trim(key), map.get(key));
}
}
It might have been possible for the Java 5 expert group, to add a little more power to the specification of wildcards in generic method argument types. I suspect the enhancement didn't make it due to lack of time in the specs phase. See my own recent question here: How does the JLS specify that wildcards cannot be formally used within methods?
Short of a better solution, you have to make your method a generic method:
<V> void trimKeyMap(Map<String, V> map){
for (String key : map.keySet()) {
map.put(StringUtils.trim(key), map.get(key));
}
}
You cannot put anything in a Map<String, ?> type, except for the null literal:
Map<String, ?> map = new HashMap<String, String>();
map.put("A", null); // Works
map.put("B", "X"); // Doesn't work.
The compiler doesn't know that the map's value argument type was String. So it cannot allow you to add anything to the map, even if you got the value from the map itself.
You can use type inference in a helper method, if you want to keep the clean method signature (the client of trimKeyMap shouldn't have to use a generic method):
void trimKeyMap(final Map<String, ?> map) {
for (final String key : map.keySet()) {
trim(map, key);
}
}
private <T> void trim(final Map<String, T> map, final String key) {
map.put(StringUtils.trim(key), map.get(key));
}
This is called wildcard capture and is discussed more here: http://www.ibm.com/developerworks/library/j-jtp04298/
? is not a wildcard for any type but the unknown type. So the map only accepts ?-type objects and not String. One conclusion from this (rather strange) fact: We can't add values to collections that are parametized with ?.
? is different from Object.
A Map<String, ?> is a generic map where you don't know the element type, but it is still being enforced, i.e. it does not allow you to put anything.
You cannot add values (that are not null) to Collections and Maps parametrized with a wildcard ?. That is because a wildcard is not a substitute for any object, it's just an unknown object. So you cannot add any (lets put it that way) known object in a map that accepts only unknown.
You can only read values.
I am trying to sort and get the top 3 maxium values for the VALUE(?)
Map<String,?> points = map
Tried using Map<String, String> and it works fine, but in this case I need Map<String, ?>.
The type of the values will need to implement Comparable.
If the size of the map is not too large, you could try something like this:
private <T extends Comparable> SortedSet<T> sortValues(final Map<?, T> m)
{
final SortedSet<T> result = new TreeSet<>();
result.addAll(m.values());
return result;
}
I haven't tested this, but I think I have all of the type declarations correct. Then just take the first, or last as the case may be, elements from the sorted set. I believe you can optionally provide your own Comparator to the set to choose how to order the objects.
You cannot sort non-Comparable objects, to begin with, so you'll need a map with Comparable values, not ?. Having that, however, you could do something like this:
public static <T extends Comparable<? super T>> List<T> sortValues(Map<?, T> map) {
List<T> buffer = new ArrayList<T>(map.values());
Collections.sort(buffer);
return(buffer);
}
Then you can do whatever you want with the return value of sortValues, such as picking the first three objects in it.
I ran into a bug in my code where I was using the wrong key to fetch something from a Java map that I believed was strongly typed using Java generics. When looking at the Map Javadocs, many of the methods, including get and remove, take an Object as the parameter instead of type K (for a Map defined as Map). Why is this? Is there a good reason or is it an API design flaw?
I think this is for backwards compatibility with older versions of the Map interface. It's unfortunate that this is the case however as you're right, it would be much better if this took the correct type.
Because the map will return a value if the object passed to the get method is equal to any key stored in the map. Equal does not mean that they have to be of the same type, but that the key's and the passed object's equal methods are implemented in such a way, that the different object types are mutually recognized as equal.
The same applies of course to the remove method.
Example of valid code, which would break (not compile) if the get method only allowed parameters of type K:
LinkedList<Number> k1 = new LinkedList<Number>();
k1.add(10);
ArrayList<Integer> k2 = new ArrayList<Integer>();
k2.add(10);
Map<LinkedList<Number>, String> map = new HashMap<LinkedList<Number>, String>();
map.put(k1, "foo");
System.out.println(map.get(k2));
This was done so that if the type parameter is a wildcard, these methods can still be called.
If you have a Map<?, ?>, Java won't allow you to call any methods that are declared with the generic types as arguments. This prevents you from violating the type constraints so you cannot, for instance, call put(key, value) with the wrong types.
If get() were defined as get(K key) instead of the current get(Object key), it too would have been excluded due to this same rule. This would make a wildcarded Map practically unusable.
In theory, the same applies to remove(), as removing an object can never violate the type constraints either.
Here is an example of code that would not have been possible if get had been declared as get(T key):
public static <K,V> Map<K, V> intersect(Map<? extends K, ? extends V> m1, Map<? extends K, ? extends V> m2) {
Map<K,V> result = new HashMap<K, V>();
for (Map.Entry<? extends K, ? extends V> e1 : m1.entrySet()) {
V value = m2.get(e1.getKey()); // this would not work in case of Map.get(K key)
if (e1.getValue().equals(value)) {
result.put(e1.getKey(), e1.getValue());
}
}
return result;
}
e1.getKey() returns an object of some unknown subtype of K (the subtype used by m1), but m2 uses a potentially different subtype of K. Had Map.get() been declared as get(K key), this usage would not have been allowed.