实现抽象类的类的类型与抽象类的泛型类型不兼容。

huangapple go评论78阅读模式
英文:

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

问题

以下是翻译好的内容:

我尝试对数据库建模。

我为数据库、模式和表创建了三个抽象类。每个数据库应能够返回所有模式,每个模式应能够返回所有表。接下来,我为一个包含两个表的模式的一个数据库创建了实例。

抽象类具有类型参数,以确保不同数据库的模式类型不会混合。所有数据库的模式都应该是模式,但特定数据库的特定模式不应在其他数据库中使用,而只应在其自己的数据库中使用。

这是代码:

import java.util.List;

public class main {
  // 抽象

  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>> {
  }

  // 具体

  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> {
  }

  // 主函数

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

该代码无法编译。尝试编译时会出现多个错误,其中大部分是连续错误。我不理解的主要错误如下:

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>>

类型:

  • List<Schema<Database_1>>,其中Database_1继承自Database

与:

  • List<Schema<? extends Database>>

为什么?它们看起来非常相似。第一个是第二个的子集。为什么不能通过具体类的特定类型来满足抽象类的一般要求?

英文:

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&lt;Schema&lt;? extends Database&gt;&gt; schemas ();
}
static abstract class Schema&lt;D extends Database&gt;
{
public abstract List&lt;Table&lt;D,? extends Schema&lt;D&gt;&gt;&gt; tables ();
}
public abstract class Table&lt;D extends Database,S extends Schema&lt;D&gt;&gt;
{
}
// Concrete
static class Database_1 extends Database
{
@Override
public List&lt;Schema&lt;Database_1&gt;&gt; schemas () {
return List.of (new Database_1_Schema_1(),
new Database_1_Schema_2());
}
}
static class Database_1_Schema_1 extends Schema&lt;Database_1&gt;
{
@Override
public List&lt;Table&lt;Database_1, Database_1_Schema_1&gt;&gt; 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&lt;Database_1, Database_1_Schema_1&gt; { }
static class Database_1_Schema_1_Table_2 extends Table&lt;Database_1, Database_1_Schema_1&gt; { }
static class Database_1_Schema_2 extends Schema&lt;Database_1&gt;
{
@Override
public List&lt;Table&lt;Database_1, Database_1_Schema_2&gt;&gt; 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&lt;Database_1, Database_1_Schema_2&gt; { }
static class Database_1_Schema_2_Table_2 extends Table&lt;Database_1, Database_1_Schema_2&gt; { }
// 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:

<!-- language: none -->

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

The type

  • List&lt;Schema&lt;Database_1&gt;&gt; with Database_1 extends Database

is not compatible with:

  • List&lt;Schema&lt;? extends Database&gt;&gt;.

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?

答案1

得分: 6

Cat成为Animal的子类型。我们写 Cat ≺ Animal。让PersianCat成为Cat的子类型,因此 PersianCat ≺ Cat。

这个关系是传递的。如果 PersianCat ≺ Cat 且 Cat ≺ Animal,则 PersianCat ≺ Animal。这个关系是自反的(Cat ≺ Cat),但不是对称的(Cat ≺ Animal 不意味着 Animal ≺ Cat)。因此,这个关系是一个

不变性

然而,泛型类型是不变的。List<Cat> 简单地不是 List<Animal> 的子类型,原因很好理解,考虑以下代码片段:

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

让我们假设一会儿 List<Cat>List<Animal> 并重新考虑带有轻微更改的代码片段:

List<Animal> animals = new List<Cat>(); // 有效,因为 List<Cat> ≺ List<Animal>
animals.add(new Cat());
animals.add(new Dog()); // 错误!但我可以把 Dog 放入 List<Animal> 中,不是吗?

因此,泛型是不变的。它们同时保证了你可以放入其中的内容以及你可以从其中获取的内容。你可以通过使用协变或逆变类型边界来放宽这些要求。

逆变性

List<? super Dog>逆变的。我们只能放入 Dog,但对于我们能够取出什么内容并没有限制。因此,我们取出的是 Object,因为它是类型层次结构的根(是子类型关系中最大的元素,是序的最大元素)。

List<? super Dog> dogs = new ArrayList<Animal>(); 这是有效的,因为我们可以放入一个 Dog。而且 Animals 是 Objects,所以我们可以取出对象。

List<? super Dog> dogs = new ArrayList<Animal>();
// dogs.add(new Animal()); // 编译错误,需要放入 Dog
dogs.add(new Dog());
Object obj = dogs.get(0);
// Dog dog = dogs.get(0); // 编译错误,只能取出 Object

协变性

我们可以使用extends使类型变为协变。协变类型对你能从类型中取出什么做出了保证。

List<? extends Animal>协变的。你保证可以从中取出 Animal。

List<? extends Animal> animals = new ArrayList<Cat>(); 是允许的,因为 Cats 是 Animals(Cat ≺ Animal),而且 get(int) 会给你 Animals。虽然它们都是 Cats,但 Cats 是 Animals,所以没问题。

然而,添加内容就比较难,因为你实际上没有一个能够放入其中的类型:

List<? extends Animal> animals = new ArrayList<Cat>();
// animals.add(new Cat()); // 编译错误
// animals.add(new Animal()); // 编译错误
Animal animal = animals.get(0);

List<? extends Cat> cats = new ArrayList<Animal>();编译错误,因为你可以取出任何动物,但你要求只能取出 Cats。

你的代码和错误

返回类型 List<Schema<Database_1>>List<Schema<? extends Database>> 不兼容。

Schema<Database_1> 不是 Schema<? extends Database> 的子类型。Schema<? extends Database> 必须接受对于 T ≺ Database 成立的任何类型 T。然而,Schema<Database_1> 仅接受对于 T ≺ Database_1 成立的类型。

考虑以下代码片段:

Schema<? extends Database> schema = new Schema<>(new Database_2()); // 完全有效
List<Schema<? extends Database>> schemas_N = new ArrayList<>();
schemas_N.add(schema); // 完全有效

List<Schema<Database_1>> schemas_1 = new ArrayList<>();
schemas_1.add(schema); // 错误,如预期所示!

// 但这也是预期的错误
schemas_N = schemas_1; // 错误

最后一行现在应该很清楚了。我们不能将 schemas_1 列表分配给 schemas_N,因为它们都期望不同的类型。它们简单地不兼容。

因此,如果你的方法预期返回一个 List<Schema<? extends Database>>(这意味着你可以将 Schema<Database_2> 放入其中),你不能返回一个 List<Schema<Database_1>>,因为它不接受上述提到的类型。在重写方法时,你不能以不兼容的方式进行更改,因此你会看到以下错误:

返回类型 List<Schema<Database_1>>List<Schema<? extends Database>> 不兼容。

英文:

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&lt;Cat&gt; simply is not a subtype of List&lt;Animal&gt;, and for good reason, considering the following code snippet:

List&lt;Animal&gt; animals = new List&lt;Animal&gt;();
animals.add(new Dog());
animals.add(new Cat());

Lets assume for a moment that List&lt;Cat&gt;List&lt;Animal&gt; and re-consider the code snippet with a slight change:

List&lt;Animal&gt; animals = new List&lt;Cat&gt;(); // valid, since List&lt;Cat&gt; ≺ List&lt;Animal&gt;
animals.add(new Cat());
animals.add(new Dog()); // b&#228;ng! But I am allowed to put Dogs into List&lt;Animal&gt;, 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&lt;? super Dog&gt; 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&lt;? super Dog&gt; dogs = new ArrayList&lt;Animal&gt;(); this works, because we can put a Dog into it. And Animals are Objects, so we can get objects out.

List&lt;? super Dog&gt; dogs = new ArrayList&lt;Animal&gt;();
// 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&lt;? extends Animal&gt; is covariant. You are guaranteed to get an Animal out.

List&lt;? extends Animal&gt; animals = new ArrayList&lt;Cat&gt;(); 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&lt;? extends Animal&gt; animals = new ArrayList&lt;Cat&gt;();
//animals.add(new Cat()); // compile error
//animals.add(new Animal()); // compile error
Animal animal = animals.get(0);

List&lt;? extends Cat&gt; cats = new ArrayList&lt;Animal&gt;(); 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&lt;Schema&lt;Database_1&gt;&gt; is not compatible with List&lt;Schema&lt;? extends Database&gt;&gt;

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

Consider the following code snippet:

Schema&lt;? extends Database&gt; schema = new Schema&lt;&gt;(new Database_2()); // perfectly valid
List&lt;Schema&lt;? extends Database&gt;&gt; schemas_N = new ArrayList&lt;&gt;();
schemas_N.add(schema); // perfectly valid

List&lt;Schema&lt;Database_1&gt;&gt; schemas_1 = new ArrayList&lt;&gt;();
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&lt;Schema&lt;? extends Database&gt;&gt; (which means that you can put a Schema&lt;Database_2&gt; into it), you can't return a List&lt;Schema&lt;Database_1&gt;&gt;, 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&lt;Schema&lt;Database_1&gt;&gt; is not compatible with List&lt;Schema&lt;? extends Database&gt;&gt;

huangapple
  • 本文由 发表于 2020年9月6日 01:56:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/63756957.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定