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