【问题标题】:Understanding generics: Incompatible types when class has generic type and implements one of its parametrised superclass理解泛型:当类具有泛型类型并实现其参数化超类之一时,类型不兼容
【发布时间】:2017-07-12 11:09:11
【问题描述】:

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

这是我的MvpViewMvpPresenter 接口(@98​​7654326@ 没什么有趣的,所以我跳过它):

/// 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 已被Eran 回答)

2。在此示例中,我如何将 V 链接到 BaseActivity,或者如何更好地实施这种 MVP 方法?

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

【问题讨论】:

    标签: java android generics types casting


    【解决方案1】:

    getPresenter()P 类型的实例,它扩展了MvpPresenter&lt;V&gt;。因此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() 返回一个MvpPresenterImpl 并且getPresenter().attachView() 需要一个MvpViewImpl 类型的参数。但是this 属于MvpViewImpl

    当您从BaseActivity&lt;V,P&gt;V 进行不安全转换时,您是在告诉编译器BaseActivity&lt;V,P&gt; 可以转换为V。但是,这在运行时起作用的原因是编译器会擦除泛型类型参数VP。由于V 的类型绑定是MvpView,所以对V 的强制转换成为对MvpView 的强制转换,这是BaseActivity 实现的。

    【讨论】:

    • 感谢您的详细解释。你说的一切都完全有道理。您是否看到任何正确实施此逻辑的解决方案?有什么办法可以链接V和BaseActivity,强制他们拥有相同的base父接口?
    • @Oleksandr 我目前没有建议。对不起。
    【解决方案2】:

    问题在于您假设V 引用了BaseActivity&lt;V,?&gt;,因为您在实践中可能只是以这种方式使用它。

    不幸的是,目前无法引用您在类型参数边界中声明的类来强制执行此类约束。

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

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

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

    您可以更进一步以避免未经检查的强制转换警告,但这需要您添加一个类型为 V 的字段,指向在扩展类构造函数作为参数传递的 BasicActivity 构造函数中设置的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,以便在扩展类不符合时提前失败;如果您的测试代码确保始终如此,则可以将其省略。

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

    【讨论】:

    • 谢谢@valentin-ruano。我担心这种方法会使代码的可读性降低,我宁愿寻求一种解决方法,将其强制转换为 V getPresenter().attachView((V) this),而不是强制具体的 Activity 实现具有这个不明显的构造函数。在我的实现中,ConcreteActivity 将实现ConcreteView 并包含ConcretePresenter,而不是泛型中的BaseActivity。但再次感谢您的回答。
    猜你喜欢
    • 1970-01-01
    • 2018-03-20
    • 1970-01-01
    • 1970-01-01
    • 2011-10-09
    • 2018-08-16
    • 2015-08-11
    • 1970-01-01
    相关资源
    最近更新 更多