If
query.list()
returns an untyped List, then the following code will produce an ugly warning: "Unchecked Cast: 'java.util.List' to 'java.util.List<String>'
List<String> tags = null;
try {
...
tags = (List<String>) query.list();
...
Unfortunately, you can only use @SuppressWarnings on a declaration. I don't want to add this annotation to my method declaration because my method does a lot more than this one cast and this is a useful warning to detect in the rest of the method. One solution is to add a new variable to make a declaration to use @SuppressWarnings on:
List<String> tags = null;
try {
...
@SuppressWarnings("unchecked")
List<String> tempTags = (List<String> query.list();
tags = tempTags;
...
Now it produces an inspection warning in IDEA 10.5, "Local variable tempTags is redundant." I should probably just ignore this warning or turn it off altogether, but I wasn't quite comfortable with that either. In situations like these, I naturally turn to
Joshua Bloch for guidance and his item #77 - Eliminate Unchecked Warnings has this advice in bold:
If you can't eliminate a warning, and you can prove that the code that provoked the warning is typesafe, then (and only then) suppress the warning with an @SuppressWarnings("unchecked") annotation.
I could probably prove that my query is only going to return Strings, but it occurred to me that there is a more general way to satisfy all the above requirements:
@SuppressWarnings({"unchecked"})
private List<String> getListOfStringsFromList(List list) {
if ( (list != null) && (list.size() > 0) ) {
for (Object item : list) {
if ( (item != null) && !(item instanceof String) ) {
throw new ClassCastException("List contained non-strings Elements: " + item.getClass().getCanonicalName());
}
}
}
return (List<String>) list;
}
The method does nothing but make a typesafe cast on a list, so I can @SuppressWarnings on the whole thing. It throws an undeclared runtime ClassCastException, but only if the caller makes a programming error and this exception would get thrown anyway wherever the list was eventually used if I didn't throw it here. In short, this method takes a programming error that cannot be caught by the compiler and does the next best thing:
fail-fast, making the error easy to find.
Using the above solution hides all that complexity and preserves the intent of the original line of code:
List<String> tags = null;
try {
...
tags = getListOfStringsFromList(query.list());
...
I thought this was interesting enough to post. Well, at least interesting enough for people who are considering a
Java logo tattoo. If you liked this, you probably want to check out
John Yeary's Response and my
Updated Generic Version (part 2 of this post).
1 comment:
I like the Josh Bloch Kool-Aid, but here are a couple of alternatives.
public List getTypeSafeList(List list, Class clazz) {
for (Object o : list) {
if (!clazz.isAssignableFrom(o.getClass())) {
throw new ClassCastException("List contained non-strings Elements: " + o.getClass().getCanonicalName());
}
}
return (List) list;
}
public List getImmutableTypeSafeList(List list, Class clazz) {
return Collections.unmodifiableList(getTypeSafeList(list, clazz));
}
I also took the liberty of modifying your example since the enhanced for loop will not produce null items.
public List getListOfStringsFromList(List list) {
if ((list != null) && (list.size() > 0)) {
for (Object item : list) {
if (!(item instanceof String)) {
throw new ClassCastException("List contained non-strings Elements: " + item.getClass().getCanonicalName());
}
}
}
return (List) list;
}
Post a Comment