简体   繁体   中英

Type of a class implementing an abstract class is not compatible with the generic type of the abstract class

I tried to model a Database.

I have three abstract classes for database, schema and table. Each database should be able to return all schemas and each schema should be able to return all tables. Next I created instances for one database with two schemas each containing to tables.

The abstract classes have type parameters to make sure, that schema types of different databases are not mixed. The schemas of all databases should all be schemas, but the specific schemas of a specific database should never be used in another database but its own.

This is the code:

import java.util.List;

public class main
{
  // Abstract

  static abstract class Database
  {
    public abstract List<Schema<? extends Database>> schemas ();
  }

  static abstract class Schema<D extends Database>
  {
    public abstract List<Table<D,? extends Schema<D>>> tables ();
  }

  public abstract class Table<D extends Database,S extends Schema<D>>
  {
  }

  // Concrete

  static class Database_1 extends Database
  {
    @Override
    public List<Schema<Database_1>> schemas () {
      return List.of (new Database_1_Schema_1(),
                      new Database_1_Schema_2());
    }
  }

  static class Database_1_Schema_1 extends Schema<Database_1>
  {
    @Override
    public List<Table<Database_1, Database_1_Schema_1>> tables () {
      return List.of (new Database_1_Schema_1_Table_1(),
                      new Database_1_Schema_1_Table_2());
    }
  }

  static class Database_1_Schema_1_Table_1 extends Table<Database_1, Database_1_Schema_1> { }
  static class Database_1_Schema_1_Table_2 extends Table<Database_1, Database_1_Schema_1> { }

  static class Database_1_Schema_2 extends Schema<Database_1>
  {
    @Override
    public List<Table<Database_1, Database_1_Schema_2>> tables () {
      return List.of (new Database_1_Schema_2_Table_1(),
                      new Database_1_Schema_2_Table_2());
    }
  }

  static class Database_1_Schema_2_Table_1 extends Table<Database_1, Database_1_Schema_2> { }
  static class Database_1_Schema_2_Table_2 extends Table<Database_1, Database_1_Schema_2> { }

  // Main

  public static void main (String ...arguments) throws Exception
  {
  }
}

The code does not compile. When I try to compile it I get several errors. Most of them are consequential errors. The primary error I do not understand is the following:

main.java:26: error: schemas() in Database_1 cannot override schemas() in Database
    public List<Schema<Database_1>> schemas () {
                                    ^
  return type List<Schema<Database_1>> is not compatible with List<Schema<? extends Database>>

The type

  • List<Schema<Database_1>> with Database_1 extends Database

is not compatible with:

  • List<Schema<? extends Database>> List<Schema<? extends Database>> .

Why? The look pretty identical. The first is a subset of the second. Why is it not possible to fulfill the general requirement of the abstract class with the specific type of the concrete class?

Let Cat be a subtype of Animal . We then write Cat ≺ Animal. Let PersianCat be a subtype of Cat , thus PersianCat ≺ Cat.

The relation is transitive . PersianCat ≺ Cat && Cat ≺ Animal => PersianCat ≺ Animal. The relation is reflexive (Cat ≺ Cat), but not symmetric (Cat ≺ Animal does not imply Animal ≺ Cat). Thus, the relation is an order .

Invariance

Generic types are, however, invariant. List<Cat> simply is not a subtype of List<Animal> , and for good reason, considering the following code snippet:

List<Animal> animals = new List<Animal>();
animals.add(new Dog());
animals.add(new Cat());

Lets assume for a moment that List<Cat>List<Animal> and re-consider the code snippet with a slight change:

List<Animal> animals = new List<Cat>(); // valid, since List<Cat> ≺ List<Animal>
animals.add(new Cat());
animals.add(new Dog()); // bäng! But I am allowed to put Dogs into List<Animal>, no?

Thus, generics are invariant. They both guarantee what you can put into them and what you get out of them. You can ease these requirements by using Co-or Contarvariant type bounds.

Contravariance

List<? super Dog> List<? super Dog> is contravariant . We can only put Dogs in, but nothing is said about what we get out. Thus, we get Object out, as that is the root of the type hierarchy (its the largest element in our subtyping relationship, the supremum of the order).

List<? super Dog> dogs = new ArrayList<Animal>(); this works, because we can put a Dog into it. And Animals are Objects, so we can get objects out.

List<? super Dog> dogs = new ArrayList<Animal>();
// dogs.add(new Animal()); // compile error, need to put Dog in
dogs.add(new Dog());
Object obj = dogs.get(0);
// Dog dog = dogs.get(0); // compile error, can only take Object out

Covariance

We can make types covariant using extends . Covariant types make a guarantee of what you can get out of the type.

List<? extends Animal> List<? extends Animal> is covariant . You are guaranteed to get an Animal out .

List<? extends Animal> animals = new ArrayList<Cat>(); is allowed, because Cats are Animals, (Cat ≺ Animal) and get(int) gives you Animals. Granted, they are all Cats, but Cats are Animals, so this works out fine.

Adding stuff is harder, though, since you don't actually have a type that you can put in:

List<? extends Animal> animals = new ArrayList<Cat>();
//animals.add(new Cat()); // compile error
//animals.add(new Animal()); // compile error
Animal animal = animals.get(0);

List<? extends Cat> cats = new ArrayList<Animal>(); is a compiler error , because you can take out any animal - but you require that the only thing that can be taken out is Cats.

Your code and error

return type List<Schema<Database_1>> is not compatible with List<Schema<? extends Database>>

Schema<Database_1> is not a subtype of Schema<? extends Database> Schema<? extends Database> . Schema<? extends Database> Schema<? extends Database> has to accept any type T for which holds T ≺ Database. However, Schema<Database_1> only accepts type for which holds T ≺ Database_1.

Consider the following code snippet:

Schema<? extends Database> schema = new Schema<>(new Database_2()); // perfectly valid
List<Schema<? extends Database>> schemas_N = new ArrayList<>();
schemas_N.add(schema); // perfectly valid

List<Schema<Database_1>> schemas_1 = new ArrayList<>();
schemas_1.add(schema); // error, as expected!

// but this is also expected
schemas_N = schemas_1; // error

The last line should now be clear. We can't assign schemas_N the list schemas_1 , because both expect different types. They simply arent't compatible.

Thus, if your method is expected to return a List<Schema<? extends Database>> List<Schema<? extends Database>> (which means that you can put a Schema<Database_2> into it), you can't return a List<Schema<Database_1>> , because that doesn't accept the above mentioned type. When overriding methods, you can't change them in an incompatible way, and thus you see the following error:

return type List<Schema<Database_1>> is not compatible with List<Schema<? extends Database>>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM