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.