繁体   English   中英

理解泛型:当类具有泛型类型并实现其参数化超类之一时,不兼容的类型

[英]Understanding generics: Incompatible types when class has generic type and implements one of its parametrised superclass

我正在使用MVP架构实现一个简单的应用程序。

这里是我的MvpViewMvpPresenter接口(没有什么有趣的MvpModel ,所以我跳过吧):

/// MvpView.java
public interface MvpView {
}

/// MvpPresenter.java
public interface MvpPresenter<V extends MvpView> {
    void attachView(V view);

    void detachView();
}

现在我有一个基本的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
}

至于我,一切看起来都正确,但是有一个编译错误:

    getPresenter().attachView(this);

不兼容的类型

如果我将演员表添加到V然后项目编译,一切正常:

    getPresenter().attachView((V) this);

1.问题是为什么我需要这个演员为什么我在没有演员的情况下遇到这种不兼容的类型错误?

(1已经由伊兰回答)

2.如何在此示例BaseActivity V链接到BaseActivity或如何更好地实现此MVP方法?

对我来说很奇怪,因为我的BaseActivity正在扩展MvpView因为它是由这个通用参数定义的: V extends MvpView

getPresenter()P类型的实例,它扩展了MvpPresenter<V> 因此, getPresenter.attachView()需要一个V类型的参数。

现在,我们知道V必须实现MvpView ,我们也知道BaseActivity实现了MvpView ,但是这个实现不一定匹配。

例如,您可以创建一个具体的子类SubBaseActivity并使用以下实例化它:

SubBaseActivity<MvpViewImpl, MvpPresenterImpl<MvpViewImpl>>
    activity = new SubBaseActivity<> (); // let's ignore the fact that you are not suppose
                                         // to instantiate Android activities this way

现在getPresenter()返回一个MvpPresenterImplgetPresenter().attachView()需要一个MvpViewImpl类型的参数。 但是, this 并不类型MvpViewImpl

当您从BaseActivity<V,P>V进行不安全的BaseActivity<V,P> ,您告诉编译器BaseActivity<V,P>可以转换为V 但是,这在运行时工作的原因是编译器擦除了泛型类型参数VP 由于V的类型绑定是MvpView ,因此对V的转换成为MvpViewBaseActivity实现了它。

问题是你假设V引用了BaseActivity<V,?>因为你可能只是在实践中使用它。

不幸的是,目前没有办法引用你在类型参数边界中声明的类来强制这样的约束。

最简单的解决方法是使用未经检查的强制转换(如果您愿意,可以屏蔽警告):

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    @SupressWarning("unchecked")
    final V myView = (V) this;
    getPresenter().attachView(myView);
}

您不需要使用本地myView变量,但明确声明它只允许您静音该方法而不是任何其他“未经检查”的警告。

在任何情况下,您必须确保任何扩展类都将其V类型参数设置为自己,以便不破坏合同:

public class ConcreteActivity<V extends MvpView, P extends MvpPresenter<V>> extends BaseActivity<ConcreteActivity<V,P>, P> {
...
} 

您无法在编译时强制执行此操作,而是您的测试代码应使用反射来验证每个扩展类是否符合此类限制。

您可以更进一步,以避免未经检查的强制转换警告,但这需要您在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);
    }
    ...
}

请注意,我们仔细检查myView是,其实this在构造函数,以便如果扩展的类不符合早期失效; 如果您的测试代码确保始终如此,您可以将其保留。

尽管如此,最好避免任何形式的警告...我会说在这种情况下,第一种选择是完全可以接受的,因为它需要更少的代码并且在内存方面更有效。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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