1

The following gives this error:

public class Account
{
    ...
}

public class DB_Account extends Account implements DBObject
{
    ...
}

public class Cache<E extends DBObject>
{
    protected Map<Long,E> m_contents;

    ...

    public Collection<E> values()
    {
        return m_contents.values();
    }
}

public class DB_Imp implements Database
{
    protected Cache<DB_Account> m_accounts;

    ...

    @Override
    public Collection<Account> getAccounts()
    {
        if (m_accounts.isValid())
            /* Compiler gives error here */
            return m_accounts.values();
        ...
    }
}

I've currently worked around the compiler error in the DB_Imp class by adding a Collections.class.cast() around the call to accounts.values() and added a @SuppressWarnings. There must be a better way. The other way is to modify the Cache class to:

    @SuppressWarnings("unchecked")
    public <T> Collection<T> values()
    {
        return Collections.class.cast(m_contents.values());
    }
Laura
  • 21
  • 4
  • What version of Java? They've improved type inference in Java 8, and the above gives no warnings here. – Elliott Frisch Aug 02 '15 at 21:32
  • javac 1.8.0_45 Without the cast the compiler gives an error not a warning. However with the cast the `Collection` could be assigned to an `Collection` – Laura Aug 03 '15 at 08:19
  • possible duplicate of [Is List a subclass of List? Why aren't Java's generics implicitly polymorphic?](http://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-arent-javas-generics-implicitly-p) – Tom Aug 03 '15 at 12:19

2 Answers2

2

The problem is your trying to return a Collection<DB_Account> as a Collection<Account> an the compiler wont' let you. Collection is not covariant, so Collection<DB_Account> is not a subtype of Collection<Account>.

The way to fix this would be to modify the method values in Database to:

Collection<? extends Account> values();

EDIT: If you can't make that change, what I'd do instead is:

@Override
public Collection<Account> getAccounts() {
    if (m_accounts.isValid())
      return Collections.unmodifiableCollection(m_accounts.values());
    ...
}

This creates an unmodifiable view of values() with the right parameter type (Collection<Account>). And it gives you the extra security (at runtime) that your Cache can't be modified by client code. values() returns a view of the Cache map, so every change made to it by someone calling getAccounts() would reflect on the Cache. This is something you'd generally want to avoid.

Chirlo
  • 5,989
  • 1
  • 29
  • 45
0

First look at the error. It is saying that:

  • For Cache<DB_Account> m_accounts, the method call m_accounts.values() returns a Collection<DB_Account>.
  • Ands getAccounts() returns Collection<Account>
  • But these are not polymorphic: Collection<Account> is not a supertype of Collection<DB_Account>, and so the compiler cannot convert them.

This is completely correct and expected.

  • For a supertype X
  • And subtype Y
  • Foo<Y> is not a subtype of Foo<X>

This is a crucial concept in Java's generics, and worth understanding. You should read The Java Tutorials > Generics, Inheritance and Subtypes.

Possible solutions

It is hard to suggest a solution without knowing the exact context. One possible approach, if you are able to change the Database interface, would be to change getAccounts to use a wildcard

public Collection<? extends Account> getAccounts();

However this would only allow you to get items out of the returned Collection, not set them into it (maybe this is what you want). To understand why you should read "Upper Bounded Wildcards" and "Guidelines for Wildcard Use".

Andy Brown
  • 18,961
  • 3
  • 52
  • 62