views:

155

answers:

3

Is there a way to avoid using @SuppressWarnings below and to keep the same functionality without the warning 'Type safety: Unchecked cast from AbstractDO[] to E[]':

public MyClass {
  ...
  private Map<Class<? extends AbstractDO>, AbstractDO[]> map;
  ...
  private void saveConcreteDOs(AbstractDO[] theEntities) {        
    entityMap.put(theEntities[0].getClass(), theEntities);
  }

  @SuppressWarnings("unchecked")
  protected <E extends AbstractDO> E[] getConcreteDOs(Class<E> theType) {
    return (E[]) map.get(theType);
  }
  ...
}

Maybe enhance map declaration?

+3  A: 

You have a choice: either suppress the warning for a cast you know will always succeed, or avoid the warning and verify that the cast succeeded with a try/catch block.

There are only those two choices.

maybe there is a way to enhance map declaration?

In your case, I'd say you have a few options.

I think your best bet is to add a throws ClassCastException clause to your getConcreteDOs method and let the caller deal with an invalid cast brought on by an invalid use of the method -- assuming they could get it to compile around your extends AbstractDO clause. This has the unfortunate side effect of forcing the consumer to wrap the call in a try/catch block or declare their own throws clause to force a try/catch block higher up the stack.

You could just swallow the exception with an empty catch block; frankly I'd prefer the @SuppressWarning over that.

Or you could drop the method entirely and only deal in abstract entities, effectively making the consumer of your repository deal with the cast.

Bottom line: you're going to have these issues whenever you try to build generic repositories. You might be better off with a concrete-repository-per-entity-type pattern.

Randolpho
maybe there is a way to enhance map declaration?
grigory
Beat me to it; I was going to say that anytime you cast from a parent to a child you will always get this warning because there's no way for the compiler to guarantee that the types are compatible.
Dan M
You mean "@SuppressWarnings" annotation is gonna affect runtime behaviour? (whether or not "cast succeeded") Do I understand you correctly? Now, that's some news.
Nikita Rybak
@Nikita Rybak: No, @SuppressWarnings does not affect runtime behavior. It's used for those who normally worry about compiler warnings but understand that, in this case, the warning is unwarranted because of something the compiler has no way of understanding. You should only ever suppress such a warning if you *know* the warning is invalid. If the cast has even a remote chance of failing, you should verify that the cast succeeded with a try/catch block, or declare that an invalid cast exception could bubble up (with a throws clause in Java, or with documentation in C#, for example).
Randolpho
@grigory: I responded to your question by editing my answer.
Randolpho
You are mistaken. For example, `<T> T cast(Object o){return (T)o;}` doesn't really do anything at runtime. It won't throw ClassCastException at that spot. When the caller tries to use the returned object as a `T`, then exception occurs. If the caller ignores the return value, there's no exception.
irreputable
The compiler will convert the cast '(E[])' to '(Object[])' since all arrays can be cast to an object array this will never fail. Which is the reason why the compiler gives the unchecked warning. Adding a 'throws' clause will do nothing as the class cast never fails at runtime.
josefx
@Randolpho: actually adding `throws ClassCastException` doesn't eliminate the need in `@SuppressWarnings("unchecked")`. I'd have used this option since the class is a test helper class and throwing exception doesn't hurt a bit.
grigory
+1  A: 

You can avoid the unchecked cast by making your class generic, e.g.

public class MyClass<E extends AbstractDO> {

    private Map<Class<? extends AbstractDO>, E[]> map;

    public void saveConcreteDOs(E[] theEntities) {        
      map.put(theEntities[0].getClass(), theEntities);
    }

    public E[] getConcreteDOs(Class<E> theType) {
      return map.get(theType);
    }
}
Péter Török
I think that the intention is to return a X[] when I call it with X.class as an argument, so the map will contain one entry with each class and corresponding array. You proposal means forcing all the arrays subclass E...
helios
That's right, helios.
grigory
@grigory, then you can't get away without an unchecked cast.
Péter Török
+1  A: 

First, your code is not type safe. It can throw class cast exception at runtime. You should have

  private void saveConcreteDOs(AbstractDO[] theEntities) {        
    entityMap.put(theEntities.getClass().getComponentType(), theEntities);
  }

You may have only homogeneous arrays at runtime, and element[0] has the same type as the array component type. However, there's no way to know that by examing this class alone.

With this corrected code, a super smart compiler can prove that getConcreteDOs() is type safe. However javac is not that smart. It's required by language spec to throw a warning.

Generally, there is no way to express more sophisticated relations between keys and values in Java. The invariant, that a value is an array whose component type is the key, is only maintained in your head.

Now, look at this non-array version:

private Map<Class<? extends AbstractDO>, AbstractDO> map;

protected <E extends AbstractDO> E getConcreteDOs(Class<E> theType) 
{
  AbstractDO obj = map.get(theType);
  return theType.cast(obj);
}

This has no warning, but it's kind of cheating. Class.cast() hides the warning for us, that's all.

It doesn't help the array version, there is no T[] castArray(Object[]) in Class<T>. You can make one method yourself, effectively hide the warning in it.

Or you can do this, but it's really unnecessarily tacky. Don't be afraid of unchecked cast warning if you know what you are doing and you examined carefully your program to ensure type safety.

protected <E extends AbstractDO> E[] getConcreteDOs(Class<E[]> arrayType) 
{
  AbstractDO[] array = map.get(arrayType.getComponentType());
  return arrayType.cast(array);
}
...
X[] array = getConcreteDOs(X[].class);
irreputable
I agree that avoiding warning and `@SupressWarnings` is not a goal by itself. The goal is to have clear, sufficient and self-explanatory code. That's why I like your 'Don't be afraid of unchecked cast warning if you know what you are doing and you examined carefully your program to ensure type safety.'
grigory
The first note about type safety is not clear to me. How is it possible that `theEntities[0].getClass()` may not cast to `Class<? extends AbstractDO>`?
grigory
@grigory say B is subclass of A, if `array = new A[]{ new B(), new A() }`, your code would think that the array is a `B[]`
irreputable
I see your point. But my code makes no assumptions of the type of *array* - just of its *first element*. I appreciate your answer because it's most helpful that far.
grigory
When I tried this: `map.put(theEntities.getClass().getComponentType(), theEntities);` I get compile error: **The method put(Class<? extends AbstractDO>, AbstractDO[]) in the type Map<Class<? extends AbstractDO>,AbstractDO[]> is not applicable for the arguments (Class<capture#3-of ?>, E[])**
grigory
in your code, `getConcreteDOs(B.class)` will try to cast the `array` into a `B[]` which will fail. for the compile error - you need a cast, compiler doesn't know that X[].class.getComponentClass() is X
irreputable
@irreputable - I am just using **your** code...
grigory