2015-10-13 1 views
2

Avertissement: Cette question contient du code en utilisant la bibliothèque rxjava, mais le problème n'est pas lié à celle-ci. Je fournis toutes les informations nécessaires pour permettre à ceux qui ne sont pas familiers avec cette bibliothèque de répondre.Impossible de passer la classe interne d'une sous-classe générique en utilisant un caractère générique délimité

je la méthode suivante qui sélectionne une source de données basée sur si certaines données sont mises en cache ou non:

public Observable<? extends MetricDataSource> createForOwners() { 
    return Observable.defer(new Func0<Observable<? extends MetricDataSource>>() { 
     @Override 
     public Observable<? extends MetricDataSource> call() { 
      if (ownersCache.isCached()) { 
       return cacheMetricDataSource; 
      } else { 
       return androidApiMetricDataSource; 
      } 
     } 
    }); 
} 

Il ne peut pas compiler avec l'erreur suivante:

Error:(29, 26) error: method defer in class Observable<T#2> cannot be applied to given types; 
required: Func0<Observable<T#1>> 
found: <anonymous Func0<Observable<? extends MetricDataSource>>> 
reason: cannot infer type-variable(s) T#1 
(argument mismatch; <anonymous Func0<Observable<? extends MetricDataSource>>> cannot be converted to Func0<Observable<T#1>>) 
where T#1,T#2 are type-variables: 
T#1 extends Object declared in method <T#1>defer(Func0<Observable<T#1>>) 
T#2 extends Object declared in class Observable 

La signature de Observable.defer est:

public final static <T> Observable<T> defer(Func0<Observable<T>> observableFactory) 

Func0 essentiellement co ntains une méthode call() qui renvoie un objet du type générique:

public interface Func0<T> { T call(); } 

Les sources de données sont déclarées comme:

private final Observable<AndroidApiMetricDataSource> androidApiMetricDataSource; 
private final Observable<CacheMetricDataSource> cacheMetricDataSource; 

Alors, pourquoi il échoue? defer attend un Observable<T> et je lui donne un Observable<? extends MetricDataSource>, qui devrait bien entrer dans ce T.

Modifier: Si je remplace la méthode defer par un privé dans la classe actuelle puis remplacer Observable<T>-T à la fois l'argument et le type de retour, il compile. Cependant, je dois utiliser la méthode originale Observable.

Ainsi, de cette façon il échoue:

private static <T> Observable<T> defer(Func0<Observable<T>> observableFactory) { 
    return null; 
} 

Et celui-ci compile:

private static <T> T defer(Func0<T> observableFactory) { 
    return null; 
} 

Répondre

1

(pensez Func0 comme Supplier et Observable comme Iterable si cela rend la question plus familier)

Ici, Func0 et Observable sont intuitivement covariant; en Java, ils devraient presque toujours être utilisés avec des jokers, sinon des problèmes surgiront tôt ou tard.

La signature de defter peut certainement être blâmé ici

<T> Observable<T> defer(Func0<Observable<T>> observableFactory) 

il aurait pu être plus générale, comme

<T> Observable<? extends T> defer(Func0<? extends Observable<? extends T>> observableFactory) 

Mais cela conduit aussi à l'enfer ... wildcard Je ne vois pas les types sur le son du cri générique. Nous pourrions à la place, vivre dangereusement, et juste omettre les caractères génériques - quand des problèmes surviennent, faites un casting pour contourner le problème. Dans votre cas, nous devons convertir Observable<Subtype> en Observable<Supertype>. C'est évidemment sûr, et il n'y a pas de culpabilité pour le faire.

+0

mon écriture sur joker - [wildcard hell] (http://bayou.io/draft/Capturing_Wildcards.html#Wildcard_Hell), [wildcard manquant] (http://bayou.io/draft/Wildcard_Case_Studies.html#Missing_Wildcards) – ZhongYu

+0

Réponse très utile, même si je l'ai déjà résolu autrement (voir ma réponse). J'ai une question sur votre réponse. La distribution fonctionne si je l'entoure d'une méthode similaire à votre exemple 'vary()' sur le second lien. Il émet uniquement un avertissement de distribution non contrôlée. Mais si j'essaie la distribution directement sans la méthode, elle ne compile pas avec une erreur 'Incompatible types'. Pourquoi, s'il fait la même chose? –

+0

oui, votre solution fonctionne (mais alors peut-être que ça va bomber à des problèmes aussi et nécessiter un casting). voir [casting entre les types concrets] (http://bayou.io/draft/Wildcard_Case_Studies.html#Casting_Between_Concrete_Types) – ZhongYu

2

Pouvez-vous essayer à la place?

public <T extends MetricDataSource> Observable<T> createForOwners() { 
    return Observable.defer(new Func0<Observable<T>>() { 
     @Override 
     public Observable<T> call() { 
     if (ownersCache.isCached()) { 
      return cacheMetricDataSource; 
     } else { 
      return androidApiMetricDataSource; 
     } 
     } 
    }); 
} 

Je ne suis pas certain, mais je suppose que c'est parce que si vous n'êtes pas lier à T, il considère qu'il est fait référence à différents types qui pourraient changer de façon indépendante et ne sont donc pas égaux. Mais pas sûr

+0

Merci! Je ne l'avais pas essayé, mais il échoue avec cette erreur: Erreur: (34, 28) erreur: types incompatibles: Observable ne peut pas être converti en Observable où T est une variable de type: T extends MetricDataSource déclaré dans la méthode createForOwners() ' –

+0

C'est bizarre, cela a fonctionné sur mon ordinateur –

+0

@ ÁlvaroGutiérrez Cela fonctionne aussi sur mon ordinateur, essayez d'utiliser javac pour compiler directement et voir ce qui se passe. –

1

Eh bien, on dirait que si je change la déclaration des sources de données de

private final Observable<AndroidApiMetricDataSource> androidApiMetricDataSource; 
private final Observable<CacheMetricDataSource> cacheMetricDataSource; 

à

private final Observable<MetricDataSource> androidApiMetricDataSource; 
private final Observable<MetricDataSource> cacheMetricDataSource; 

Je peux ensuite déclarer la méthode à

public Observable<MetricDataSource> createForOwners() { 
    return Observable.defer(new Func0<Observable<MetricDataSource>>() { 
     @Override 
     public Observable<MetricDataSource> call() { 
      if (ownersCache.isCached()) { 
       return cacheMetricDataSource; 
      } else { 
       return androidApiMetricDataSource; 
      } 
     } 
    }); 
} 

et maintenant il compile, comme toujours un Observable<MetricDataSource> est retourné.