[英]Understanding generics: Incompatible types when class has generic type and implements one of its parametrised superclass
I am implementing a simple app with MVP architecture. 我正在使用MVP架构实现一个简单的应用程序。
Here are my MvpView
and MvpPresenter
interfaces (nothing interesting about MvpModel
, so I am skipping it): 这里是我的
MvpView
和MvpPresenter
接口(没有什么有趣的MvpModel
,所以我跳过吧):
/// MvpView.java
public interface MvpView {
}
/// MvpPresenter.java
public interface MvpPresenter<V extends MvpView> {
void attachView(V view);
void detachView();
}
Now I have a basic MvpView
implementation, which is an Activity
: 现在我有一个基本的
MvpView
实现,这是一个Activity
:
// BaseActivity.java
public abstract class BaseActivity<V extends MvpView, P extends MvpPresenter<V>>
extends AppCompatActivity implements MvpView {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPresenter().attachView(this);
}
public abstract P getPresenter();
// other logic
}
As for me everything looks correct, but there is a compile error in line: 至于我,一切看起来都正确,但是有一个编译错误:
getPresenter().attachView(this);
If I add cast to V
then project compiles and everything works fine: 如果我将演员表添加到
V
然后项目编译,一切正常:
getPresenter().attachView((V) this);
(1 is already answered by Eran ) (1已经由伊兰回答)
V
to BaseActivity
in this example or how it is better to implement this MVP approach? BaseActivity
V
链接到BaseActivity
或如何更好地实现此MVP方法? It is strange, as for me, because my BaseActivity
is extending MvpView
as it is defined by this generic parameter: V extends MvpView
! 对我来说很奇怪,因为我的
BaseActivity
正在扩展MvpView
因为它是由这个通用参数定义的: V extends MvpView
!
getPresenter()
is an instance of type P
, which extends MvpPresenter<V>
. getPresenter()
是P
类型的实例,它扩展了MvpPresenter<V>
。 Therefore getPresenter.attachView()
expects an argument of type V
. 因此,
getPresenter.attachView()
需要一个V
类型的参数。
Now, we know that V
must implement MvpView
, and we also know that BaseActivity
implements MvpView
, but this implementations don't necessarily match. 现在,我们知道
V
必须实现MvpView
,我们也知道BaseActivity
实现了MvpView
,但是这个实现不一定匹配。
For example, you can create a concrete sub-class SubBaseActivity
and instantiate it with: 例如,您可以创建一个具体的子类
SubBaseActivity
并使用以下实例化它:
SubBaseActivity<MvpViewImpl, MvpPresenterImpl<MvpViewImpl>>
activity = new SubBaseActivity<> (); // let's ignore the fact that you are not suppose
// to instantiate Android activities this way
Now getPresenter()
returns a MvpPresenterImpl
and getPresenter().attachView()
expects an argument of type MvpViewImpl
. 现在
getPresenter()
返回一个MvpPresenterImpl
和getPresenter().attachView()
需要一个MvpViewImpl
类型的参数。 But this
is not of type MvpViewImpl
. 但是,
this
并不类型MvpViewImpl
。
When you make the unsafe cast from BaseActivity<V,P>
to V
, you are telling the compiler BaseActivity<V,P>
can be cast to V
. 当您从
BaseActivity<V,P>
到V
进行不安全的BaseActivity<V,P>
,您告诉编译器BaseActivity<V,P>
可以转换为V
However, the reason this works at runtime is that the compiler erases the generic type parameters V
and P
. 但是,这在运行时工作的原因是编译器擦除了泛型类型参数
V
和P
Since the type bound of V
is MvpView
, the casting to V
becomes a casting to MvpView
, which BaseActivity
implements. 由于
V
的类型绑定是MvpView
,因此对V
的转换成为MvpView
, BaseActivity
实现了它。
The problem is that you are assuming that V
makes reference to BaseActivity<V,?>
because you are probably using it only this way in practice. 问题是你假设
V
引用了BaseActivity<V,?>
因为你可能只是在实践中使用它。
Unfortunately currently there is no way to make reference to the class you are declaring in the type arguments bounds as to force such a constraint. 不幸的是,目前没有办法引用你在类型参数边界中声明的类来强制这样的约束。
The simplest fix is to use an unchecked cast (masking the warning if you like): 最简单的解决方法是使用未经检查的强制转换(如果您愿意,可以屏蔽警告):
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@SupressWarning("unchecked")
final V myView = (V) this;
getPresenter().attachView(myView);
}
You don't need to use the local myView
variable but declaring it explicitly allows you to silence only that cast and not any other "unchecked" warning in that method. 您不需要使用本地
myView
变量,但明确声明它只允许您静音该方法而不是任何其他“未经检查”的警告。
In any case you must make sure that any extending class would set their V
type-parameter to themselves as to not break contract: 在任何情况下,您必须确保任何扩展类都将其
V
类型参数设置为自己,以便不破坏合同:
public class ConcreteActivity<V extends MvpView, P extends MvpPresenter<V>> extends BaseActivity<ConcreteActivity<V,P>, P> {
...
}
There is no way you can enforce that at compilation time, instead your test code should use reflection to verify that each extending class complies with such a restriction. 您无法在编译时强制执行此操作,而是您的测试代码应使用反射来验证每个扩展类是否符合此类限制。
You can go a bit further in order to avoid the unchecked cast warning but that would require for you to add a field typed V
pointing to this
set in the BasicActivity constructor passed as a parameter by the extending class constructor.... 您可以更进一步,以避免未经检查的强制转换警告,但这需要您在BasicActivity构造函数中添加一个字段类型
V
指向this
集合,该构造函数由扩展类构造函数作为参数传递....
public abstract class BaseActivity<V extends MvpView, P extends MvpPresenter<V>>
extends AppCompatActivity implements MvpView {
private final V myView;
protected BaseActivity(final V myView) {
if (myView != this) { throw new IllegalArgumentException("you must pass this object"); }
this.myView = myView;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPresenter().attachView(myView);
}
public abstract P getPresenter();
// other logic
}
public class ConcreteActivity<V extends MvpView, P extends MvpPresenter<V> extends BaseActivity<BaseActivity<V, P>, P> {
public ConcreteActivity() {
super(this);
}
...
}
Notice that we double check that myView
is in fact this
in the constructor in order to fail early if the extending class does not comply; 请注意,我们仔细检查
myView
是,其实this
在构造函数,以便如果扩展的类不符合早期失效; you can leave that out if your test code makes sure that this is going to be always the case. 如果您的测试代码确保始终如此,您可以将其保留。
Despite that is preferable to avoid any kind of warning... I would say that in this situation the first alternative is quite acceptable as it needs less code and is more efficient memory-wise. 尽管如此,最好避免任何形式的警告...我会说在这种情况下,第一种选择是完全可以接受的,因为它需要更少的代码并且在内存方面更有效。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.