TreeMap generic argument warning - java

Map<Date, Integer> m = new HashMap<Date, Integer>(); // line 1
Map<Date, Integer> sMap = new TreeMap(m); // line 2
Line 2 gives this error:
Type safety: The expression of type TreeMap needs unchecked conversion
to conform to Map
The solution I found is this: How do I fix "The expression of type List needs unchecked conversion...'?
But, is it safe to simply #SuppressWarnings("unchecked") or is there a scenario when my code will failed. More generally, when can we safely add the #SuppressWarnings("unchecked")?

Try specifying the generic types of the TreeMap when you instantiate it.
Map<Date, Integer> m = new HashMap<Date, Integer>(); // line 1
Map<Date, Integer> sMap = new TreeMap<Date,Integer>(m);
This answer assumes you are using java.util.TreeMap. See: http://docs.oracle.com/javase/7/docs/api/

If you use Java 7, you can use the diamond syntax:
Map<Date, Integer> m = new HashMap<>();
Map<Date, Integer> sMap = new TreeMap<>(m);

The correct way is:
1. Map<Date, Integer> m = new HashMap<Date, Integer>();
2. Map<Date, Integer> sMap = new TreeMap<Date, Integer>(m);
You can also supress the "unchecked" warnings if you are sure about the generic type. In this case, you are.

As already said, adding generic parameters to the TreeMap resolves the problem, as the compiler can now guarantee that no invalid casts will happen. If you omit the generic parameters the compiler cannot give you this guarantee and therefore warns you. Then it is your responsibility that only objects of the correct type are stored in the TreeMap.
If you are you sure that only the correct objects are put into the TreeMap you can safely ignore the warning. If it fails and you encounter class cast exceptions on runtime, it is your own fault (:
Generally speaking if you supress a warning you are effectively ignoring a hint of the compiler that there may be a problem in your code. But the compiler is dumb and there are situations where you can safely say that no problems will occur (e.g. you have other checks in place or a design that does not permit the errors to happen). If that is the case you can suppress the warning.

Related

Java HashMap initialization?

I already know how to initialize Java HashMap by using one of the following 2 ways
// way 1: apply generic type saftey
HashMap<String, Integer> hashMap1 = new HashMap<String, Integer>();
// way 2: general without apply generic type saftey
HashMap<String, Integer> hashMap2 = new HashMap();
My problem
What is the best practice
According to Eclipse Marker
Type safety: The expression of type HashMap needs unchecked conversion
to conform to HashMap
So Its recommend to use
new HashMap<String, Integer>();
But according to Sonar Linter
Replace the type specification in this constructor call with the
diamond operator ("<>").
So Its recommend to use
new HashMap();
Which one is the best? Why?
Use the Java 7 diamond operator:
HashMap<String, Integer> hashMap2 = new HashMap<>();
Diamond <> allows the compiler to infer types implicitly
See: Type Inference for Generic Instance Creation

Unchecked cast to generic class implementing Map<String, V>

I'm trying to understand why this code has an unchecked cast warning. The first two casts have no warning, but the third does:
class StringMap<V> extends HashMap<String, V> {
}
class StringToIntegerMap extends HashMap<String, Integer> {
}
Map<?, ?> map1 = new StringToIntegerMap();
if (map1 instanceof StringToIntegerMap) {
StringToIntegerMap stringMap1 = (StringToIntegerMap)map1; //no unchecked cast warning
}
Map<String, Integer> map2 = new StringMap<>();
if (map2 instanceof StringMap) {
StringMap<Integer> stringMap2 = (StringMap<Integer>)map2; //no unchecked cast warning
}
Map<?, Integer> map3 = new StringMap<>();
if (map3 instanceof StringMap) {
StringMap<Integer> stringMap3 = (StringMap<Integer>)map3; //unchecked cast warning
}
This is the full warning for the stringMap3 cast:
Type safety: Unchecked cast from Map<capture#3-of ?,Integer> to StringMap<Integer>
However, the StringMap class declaration specifies the first type parameter of Map (i.e., String), and both map3 and the StringMap<Integer> cast use the same type for the second type parameter of Map (i.e., Integer). From what I understand, as long as the cast doesn't throw ClassCastException (and it shouldn't since there is an instanceof check), stringMap3 would be a valid Map<String, Integer>.
Is this a limitation of the Java compiler? Or is there a scenario where calling methods of either map3 or stringMap3 with certain arguments may result in an unexpected ClassCastException if the warning is ignored?
The behavior is as specified. In Section 5.5.2 of the Java Language Specification an unchecked cast is defined as:
A cast from a type S to a parameterized type T is unchecked unless at least one of the following is true:
S <: T
All of the type arguments of T are unbounded wildcards
T <: S and S has no subtype X other than T where the type arguments of X are not contained in the type arguments of T.
(where A <: B means: "A is a subtype of B").
In your first example, the target type has no wildcards (and thus all of them are unbounded). In your second example, StringMap<Integer> is actually a subtype of Map<String, Integer> (and there is no subtype X as mentioned in the third condition).
In your third example, however, you have a cast from Map<?, Integer> to StringMap<Integer>, and, because of the wildcard ?, neither is a subtype of the other. Also, obviously, not all type parameters are unbounded wildcards, so none of the conditions apply: it is an unchecked exception.
If an unchecked cast occurs in the code, a conforming Java compiler is required to issue a warning.
Like you, I do not see any scenario where the cast would be invalid, so you could argue that it is a limitation of the Java compiler, but at least it is a specified limitation.
This cast isn't safe. Let's say you have:
Map<?, Integer> map3 = new HashMap<String,Integer>();
StringMap<Integer> stringMap3 = (StringMap<Integer>)map3;
That's going to throw an exception. It doesn't matter that you know you newed up a StringMap<Integer> and assigned it to the map3. What you're doing is known as down-casting Downcasting in Java for more info.
EDIT: You're also over complicating the problem with all the generics, you will have the exact same issue without any generic types.
Actually the answer is in the Java Language Specification.
Section 5.1.10 mentions that if you use a wildcard, then it will be a fresh capture type.
This implies that, Map<String, Integer> is not a subclass of Map<?, Integer>, and therefore even if the StringMap<Integer> is assignable to a Map<?, Integer> type, because Map<?, Integer> represents a map with some key type and Integer values, which is true to StringMap<Integer>, the cast is unsafe. So this is why you get an unchecked cast warning.
From the compiler point of view between the assignment, and the cast there could be anything, so even if map3 is an instance of StringMap<Integer> at the cast operation, map3 has Map<?, Integer> as its type, so the warning is completely legit.
And to answer for your question: yes, until the map3 instance has only strings as keys, and your code could not possibly guarantee that.
See why not:
Map<?, Integer> map3 = new StringMap<Integer>();
// valid operation to use null as a key. Possible NPE depending on the map implementation
// you choose as superclass of the StringMap<V>.
// if you do not cast map3, you can use only null as the key.
map3.put(null, 2);
// but after an other unchecked cast if I do not want to create an other storage, I can use
// any key type in the same map like it would be a raw type.
((Map<Double, Integer>) map3).put(Double.valueOf(0.3), 2);
// map3 is still an instance of StringMap
if (map3 instanceof StringMap) {
StringMap<Integer> stringMap3 = (StringMap<Integer>) map3; // unchecked cast warning
stringMap3.put("foo", 0);
for (String s : stringMap3.keySet()){
System.out.println(s+":"+map3.get(s));
}
}
The result:
null:2
foo:0
Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
Of course, it is not possible to provide an answer which is "more correct" (correctier?) than one which explains the observed behaviour in terms of the Java Language Specification. However:
An explanation of the observed behaviour given in practical terms can be easier to follow and easier to remember than an explanation which just throws the JLS at you. As a result, a practical explanation is often more usable than an explanation in terms of the JLS.
The JLS is precisely the way it is and not in any other way, because it needs to satisfy practical constraints. Given the fundamental choices made by the language, quite often the details of the JLS could not have been any other way than the way it is. This means that the practical reasons for a specific behaviour can be thought of as more important than the JLS, because they shaped the JLS, the JLS did not shape them.
So, a practical explanation of what is happening follows.
The following:
Map<?, ?> map1 = ...;
StringToIntegerMap stringMap1 = (StringToIntegerMap)map1;
gives no unchecked cast warning because you are not casting to a generic type. It is the same as doing the following:
Map map4 = ...; //gives warning "raw use of generic type"; bear with me.
StringToIntegerMap stringMap4 = (StringToIntegerMap)map4; //no unchecked warning!
The following:
Map<String, Integer> map2 = ...;
StringMap<Integer> stringMap2 = (StringMap<Integer>)map2;
gives no unchecked cast warning because generic arguments of left hand side match generic arguments of right hand side. (Both are <String,Integer>)
The following:
Map<?, Integer> map3 = ...;
StringMap<Integer> stringMap3 = (StringMap<Integer>)map3;
does give an unchecked cast warning because left hand side is <String,Integer> but right hand side is <?,Integer> and you can always expect such a warning when you cast a wildcard to specific type. (Or a bounded type to a more strictly bounded, more specific type.) Note that "Integer" in this case is a red herring, you would get the same thing with Map<?,?> map3 = new HashMap<>();

Coding Map values to interface - Declare Map <String, List<>> and assign HashMap<String, ArrayList<>>()

I use this common initialization format when I anticipate changing the implementation of the List interface at a later time:
List<Foo> foos = new ArrayList<Foos>();
In an effort to gain the same utility for the values within a Map, I attempted the following but my compiler whines about List<> and ArrayList<> being incompatible types.
Map<String, List<Foo>> fooMap = new HashMap<String, ArrayList<Foo>>;
I've been unable to find an explanation for why I cannot initialize the map in this manner and I'd like to understand the reasoning.
And, sure, this works...
Map<String, List<Foo>> foosMap = new HashMap<String, List<Foo>>;
// ... populate map
ArrayList<Foo> foosAryLst = (ArrayList)foosMap.get("key1");
... but I'm a curious castaphobe. I'd rather fix compile-time errors than runtime errors, things like this aggravate my OCD and the smell of casting conjures an odor similar to the urinal trough after free deep-fried asparagus night at the stadium.
My questions come down to:
Why can I not code my map values to an interface.
Is there a workaround that doesn't require casting?
Any input will be appreciated, thanks!
Sure, there's a workaround that doesn't require casting: don't cast; write
List<Foo> foosLst = foosMap.get("key1");
...and code to the interface with the List as well as the Map.
The root issue, though, is that a Map<String, ArrayList<Foo>> isn't substitutable wherever you'd use Map<String, List<Foo>>. In particular,
Map<String, List<Foo>> map = new HashMap<>();
map.put("foo", new LinkedList<Foo>());
works, but not if map is a Map<String, ArrayList<Foo>>. So one isn't a drop-in substitute for the other.
The declaration that you proposed
Map<String, List<Foos>> fooMap = new HashMap<String, ArrayList<Foos>>();
simply does not make sense: The variable fooMap has the type Map<String, List<Foos>>. This means:
every value that you obtain from this map is a List<Foos>
you may put every value into this list that is (of a subtype of) List<Foos>
If you wanted a map that has ArrayLists as its values, then you would declare it as
Map<String, ArrayList<Foos>> fooMap = new HashMap<String, ArrayList<Foos>>();
If you don't care about the list type, then you can say
Map<String, List<Foos>> fooMap = new HashMap<String, List<Foos>>();
But there's no sensible meaning of mixing the two. Even if you could write what you proposed, then you could still not obtain an ArrayList from this map, because this is simply not the type that fooMap was declared with.
In most cases,
Map<String, List<Foos>> fooMap = new HashMap<String, List<Foos>>();
should be appropriate. Depending on the use case, one could possibly go further by saying
Map<String, List<? extends Foos>> fooMap = new HashMap<String, List<? extends Foos>>();
This way, you can also put lists into the map that contain sublcasses of Foos, like
List<SpecialFoos> specialFoos = ...
fooMap.put("special", specialFoos);
But of course, it's up to you to decide whether this is necessary or not.
The core of the problem is that the compiler cannot keep track of what fooMap may have been assigned to at any particular point in the execution of your code, so there is no way for the compiler to know that
fooMap.put("abc", new ArrayList<Foo>())
should be legal, but that
fooMap.put("abc", new LinkedList<Foo>())
should not be.
All that the compiler knows about the typing of fooMap is its declared type Map<String, List<Foo>>. So, it enforces that whatever object to which you assign fooMap must be able to support all of the operations which a generic Map<String, List<Foo>> is capable of executing. The second line of code above is clearly legal for a Map<String, List<Foo>>, but not legal for a Map<String, ArrayList<Foo>>, so the compiler forbids you from assigning fooMap to a Map<String, ArrayList<Foo>>.

Understanding Generics in Object Initialization

I have a basic question about generics in Java: what is difference between the following two initializations of a map?
Map<String, String> maplet1 = new HashMap<String, String>();
Map<String, String> maplet2 = new HashMap();
I understand the the first initialization is specifying the generics in the object construction, but I don't understand the underlying ramifications of doing this, rather than the latter object construction (maplet2). In practice, I've always seen code use the maplet1 construction, but I don't understand where it would be beneficial to do that over the other.
The second Map is assigned to a raw type and will cause a compiler warning. You can simply use the first version to eliminate the warning.
For more see: What is a raw type and why shouldn't we use it?
The first one is type-safe.
You can shorthand the right side by using the diamond operator <>. This operator infers the type parameters from the left side of the assignment.
Map<String, String> maplet2 = new HashMap<>();
Lets understand the concept of Erasure. At RUNTIME HashMap<String, String>() and HashMap() are the same represented by HashMap.
The process of converting HashMap<String,String> to HashMap (Raw Type) is called Erasure.
Without the use of Generics , you have to cast , say the value in the Map , to String Explicitly every time.
The use of Generics forces you to Eliminate cast.
If you don't use Generics , there will be high probability that a Future Developer might insert another type of Object which will cause ClassCastException

Eclipse Warning with Java HashMap

Eclipse is saying "HashMap is a raw type" When I use the following code
HashMap = new HashMap();
Any idea what could be wrong?
Eclipse will give you that warning when you use a non-Generic HashMap using Java 5 or newer.
See Also: The Generics Lesson in Sun's Java Tutorials.
Edit: Actually, here, I'll give an example too:
Say I want to map someone's name to their Person object:
Map<String, Person> map = new HashMap<String, Person>();
// The map.get method now returns a Person
// The map.put method now requires a String and a Person
These are checked at compile-time; the type information is lost at run-time due to how Java implements Generics.
Nothing wrong exactly, but you are missing out on the wonderful world of generics. Depending on what constraints you want to place on the types used in your map, you should add type parameters. For example:
Map<String, Integer> map = new HashMap<String, Integer>();
That is missing generics, i.e. . If you don't know thise then set the eclipse compiler to java 1.4
Try
HashMap<String,Integer> map = new HashMap<String,Integer>();
instead (obviously replacing the key type (String) and value type (Integer)).
That usually means you're mixing generic code with non-generic code.
But as your example wont even compile its rather hard to tell....
It's missing the generic type. You should specify the key-value generic pair for your map. For instance, the following is a declaration that instantiates a HashMap with String type key and Integer type value.
Map<String, Integer> map = new HashMap<String, Integer>();
All of these are valid answers, you could also use the #SurpressWarnings annotation to get the same result, without having to resort to actual generics. ;)
hashmap is a raw type and hence should be parameterised ie
what ever the data we get through the haspmap function their type must be declared for getting its functions
for example
HashMap<String, Integer> map = new HashMap<String, Integer>();
With the latest Java, you do not have to explicitly mention the variable types in declaration. You can simply put:
= new HashMap<>();

Categories

Resources