Quand utilisez-vous map vs flatMap dans RxJava?
Quand utilisez-vous map vs flatMap dans RxJava?
dire par exemple, nous voulons mapper les fichiers contenant JSON dans des chaînes qui contiennent le JSON--
en utilisant la carte, nous devons faire face à l'Exception d'une façon ou d'une autre. Mais comment?:
Observable.from(jsonFile).map(new Func1<File, String>() {
@Override public String call(File file) {
try {
return new Gson().toJson(new FileReader(file), Object.class);
} catch (FileNotFoundException e) {
// So Exception. What to do ?
}
return null; // Not good :(
}
});
en utilisant flatMap, c'est beaucoup plus verbeux, mais nous pouvons avancer le problème le long de la chaîne des Observables et gérer l'erreur si nous choisissons ailleurs et même réessayer:
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
@Override public Observable<String> call(final File file) {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override public void call(Subscriber<? super String> subscriber) {
try {
String json = new Gson().toJson(new FileReader(file), Object.class);
subscriber.onNext(json);
subscriber.onCompleted();
} catch (FileNotFoundException e) {
subscriber.onError(e);
}
}
});
}
});
j'aime la simplicité de la carte, mais la manipulation d'erreur de flatmap (pas la verbosité). Je n'ai pas vu de bonnes pratiques dans ce domaine et je suis curieux de savoir comment cela est utilisé dans la pratique.
9 réponses
map
transformer un événement en un autre.
flatMap
transformer un événement en un événement zéro ou plus. (extrait de IntroToRx )
comme vous voulez transformer votre json en objet, utiliser map devrait suffire.
traiter avec FileNotFoundException est un autre problème (utiliser map ou flatmap ne résoudrait pas ce problème).
pour résoudre votre problème D'Exception, jetez-le avec un Non coché exception: RX appellera le gestionnaire onError pour vous.
Observable.from(jsonFile).map(new Func1<File, String>() {
@Override public String call(File file) {
try {
return new Gson().toJson(new FileReader(file), Object.class);
} catch (FileNotFoundException e) {
// this exception is a part of rx-java
throw OnErrorThrowable.addValueAsLastCause(e, file);
}
}
});
la même version avec flatmap:
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
@Override public Observable<String> call(File file) {
try {
return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
} catch (FileNotFoundException e) {
// this static method is a part of rx-java. It will return an exception which is associated to the value.
throw OnErrorThrowable.addValueAsLastCause(e, file);
// alternatively, you can return Obersable.empty(); instead of throwing exception
}
}
});
vous pouvez aussi retourner, dans la version flatMap, un nouvel Observable qui n'est qu'une erreur.
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
@Override public Observable<String> call(File file) {
try {
return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
} catch (FileNotFoundException e) {
return Observable.error(OnErrorThrowable.addValueAsLastCause(e, file));
}
}
});
FlatMap se comporte très bien comme map, la différence est que la fonction qu'il applique renvoie un observable lui-même, il est donc parfaitement adapté à la cartographie sur les opérations asynchrones.
dans le sens pratique, la fonction Map applies ne fait qu'effectuer une transformation sur la réponse enchaînée( ne retournant pas un Observable); tandis que la fonction FlatMap applies renvoie un Observable<T>
, C'est pourquoi FlatMap est recommandé si vous prévoyez de faire un appel asynchrone à l'intérieur de la méthode.
résumé:
- la carte retourne un objet de type T
- FlatMap retourne un Observable.
un exemple clair peut être vu ici: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .
Couchbase Java 2.Le Client X utilise Rx pour fournir des appels asynchrones d'une manière pratique. Depuis il utilise Rx, il a les méthodes carte et FlatMap, l'explication dans leur documentation pourrait être utile pour comprendre le concept général.
pour gérer les erreurs, outrepasser onError sur votre susbcripteur.
Subscriber<String> mySubscriber = new Subscriber<String>() {
@Override
public void onNext(String s) { System.out.println(s); }
@Override
public void onCompleted() { }
@Override
public void onError(Throwable e) { }
};
il pourrait être utile de regarder ce document: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1 /
une bonne source sur la façon de gérer les erreurs avec RX peut être trouvée à: https://gist.github.com/daschl/db9fcc9d2b932115b679
dans votre cas, je pense que vous avez besoin de carte, puisqu'il n'y a qu'une entrée et une sortie.
fonction map-supplied accepte simplement un article et renvoie un article qui sera émis plus loin (une seule fois) vers le bas.
la fonction fournie par flatMap accepte un élément puis renvoie un" Observable", ce qui signifie que chaque élément du nouveau" Observable " sera émis séparément plus bas.
peut-être que le code éclaircira les choses pour vous.
//START DIFFERENCE BETWEEN MAP AND FLATMAP
Observable.just("item1")
.map( str -> {
System.out.println("inside the map " + str);
return str;
})
.subscribe(System.out::println);
Observable.just("item2")
.flatMap( str -> {
System.out.println("inside the flatMap " + str);
return Observable.just(str + "+", str + "++" , str + "+++");
})
.subscribe(System.out::println);
//END DIFFERENCE BETWEEN MAP AND FLATMAP
sortie:
inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++
la façon dont je pense à cela est que vous utilisez flatMap
quand la fonction que vous vouliez mettre à l'intérieur de map()
renvoie un Observable
. Dans ce cas, vous pouvez toujours essayer d'utiliser map()
mais ce serait peu pratique. Laissez-moi vous expliquer pourquoi.
si dans un tel cas vous avez décidé de vous en tenir à map
, vous obtiendriez un Observable<Observable<Something>>
. Par exemple, dans votre cas, si nous avons utilisé une bibliothèque rxgson imaginaire, qui a retourné un Observable<String>
de son toJson()
méthode (au lieu de simplement retourner un String
) il ressemblerait à ceci:
Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
@Override public Observable<String>> call(File file) {
return new RxGson().toJson(new FileReader(file), Object.class);
}
}); // you get Observable<Observable<String>> here
à ce point, il serait assez difficile de subscribe()
à un tel observable. A l'intérieur, vous obtiendrez une Observable<String>
à laquelle vous auriez encore besoin de subscribe()
pour obtenir la valeur. Ce qui n'est pas pratique et agréable à regarder.
ainsi pour le rendre utile une idée est de "aplatir" cet observable des observables (vous pourriez commencer à voir d'où vient le nom _flat_Map). RxJava fournit quelques façons d'aplatir observables et par souci de simplicité laisse supposer fusionner est ce que nous voulons. La fusion prend un tas d'observables et émet chaque fois que l'un d'eux émet. (Beaucoup de gens diraient que switch serait un meilleur défaut. Mais si tu n'émets qu'une seule valeur, ça n'a pas d'importance de toute façon.)
donc en modifiant notre extrait précédent nous obtiendrions:
Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
@Override public Observable<String>> call(File file) {
return new RxGson().toJson(new FileReader(file), Object.class);
}
}).merge(); // you get Observable<String> here
C'est beaucoup plus utile, parce que souscrire à cela (ou cartographie, ou filtrage, ou...) vous obtenez juste la valeur String
. (Aussi, vous l'esprit, une telle variante de merge()
n'existe pas dans RxJava, mais si vous comprenez l'idée de fusionner alors j'espère que vous comprenez comment cela pourrait fonctionner.)
donc essentiellement parce que tel merge()
ne devrait probablement jamais être utile quand il succède à un map()
retournant un observables et si vous n'avez pas à taper encore et encore, flatMap()
a été créé comme une abréviation. Il applique la fonction de mapping comme une normale map()
le ferait, mais plus tard au lieu d'émettre les valeurs retournées il les "aplatit" (ou fusionne).
c'est le cas d'usage général. Il est le plus utile dans une base de code qui utilise RX allover l'endroit et vous avez beaucoup de méthodes retournant observables, que vous voulez enchaîner avec d'autres méthodes retournant observable.
dans votre cas d'utilisation, il se trouve que c'est aussi utile, parce que map()
ne peut transformer qu'une valeur émise dans onNext()
en une autre valeur émise dans onNext()
. Mais il ne peut pas le transformer en valeurs multiples, aucune valeur ou une erreur. Et comme akarnokd a écrit dans sa réponse (et rappelez-vous il est beaucoup plus intelligent que moi, probablement en général, mais au moins quand il s'agit de RxJava) vous ne devriez pas jeter des exceptions de votre map()
. Vous pouvez donc utiliser flatMap()
et
return Observable.just(value);
quand tout va bien, mais
return Observable.error(exception);
quand quelque chose échoue.
Voir sa réponse pour un extrait complet: https://stackoverflow.com/a/30330772/1402641
Ici est un simple pouce-règle que j'utilise m'aider à décider quand utiliser flatMap()
plus map()
Rx Observable
.
une fois que vous avez pris la décision d'employer une transformation map
, vous écririez votre code de transformation pour rendre un objet correct?
si ce que vous retournez comme résultat final de votre transformation est:
-
un objet non observable alors vous n'utiliserez que
map()
. Etmap()
enveloppe cet objet dans un Observable et l'émet. -
an
Observable
objet, puis vous utilisezflatMap()
. EtflatMap()
déballe L'Observable, ramasse l'objet retourné, l'enveloppe avec son propre Observable et l'émet.
dire par exemple nous avons une méthode titleCase(String inputParam) qui renvoie L'objet String Cased du paramètre d'entrée. Le type de retour de cette méthode peut être String
ou Observable<String>
.
-
si le type de retour de
titleCase(..)
n'était queString
, alors vous utiliseriezmap(s -> titleCase(s))
-
si le type de retour de
titleCase(..)
devait êtreObservable<String>
, alors vous utiliseriezflatMap(s -> titleCase(s))
Espère que précise.
je voulais juste ajouter qu'avec flatMap
, vous n'avez pas vraiment besoin d'utiliser votre propre Observable personnalisé à l'intérieur de la fonction et vous pouvez compter sur les méthodes/opérateurs d'usine standard:
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
@Override public Observable<String> call(final File file) {
try {
String json = new Gson().toJson(new FileReader(file), Object.class);
return Observable.just(json);
} catch (FileNotFoundException ex) {
return Observable.<String>error(ex);
}
}
});
généralement, vous devez éviter de lancer des exceptions (Runtime-) des méthodes onXXX et des callbacks si possible, même si nous avons placé autant de sauvegardes que nous avons pu dans RxJava.
la question Est Quand utilisez-vous map vs flatMap dans RxJava? . Et je pense qu'une simple démo est plus spécifique.
lorsque vous voulez convertir un élément émis à un autre type , dans votre cas convertir le fichier en chaîne, la carte et flatMap peut à la fois fonctionner. Mais je préfère map operator parce que c'est plus clair.
cependant dans un certain endroit, flatMap
peut faire le travail magique mais map
ne peut pas. Par exemple, je veux obtenir un info mais je dois d'abord obtenir son id lorsque l'utilisateur se connecter. Évidemment, j'ai besoin de deux requêtes et elles sont en ordre.
commençons.
Observable<LoginResponse> login(String email, String password);
Observable<UserInfo> fetchUserInfo(String userId);
voici deux méthodes, une pour la connexion retournée Response
, et une autre pour la recherche des informations utilisateur.
login(email, password)
.flatMap(response ->
fetchUserInfo(response.id))
.subscribe(userInfo -> {
// get user info and you update ui now
});
comme vous le voyez, dans la fonction flatMap s'applique, d'abord j'obtiens le nom d'utilisateur de Response
puis je récupère les informations d'utilisateur. Lorsque deux demandes sont finis, nous pouvons faire notre travail comme mise à jour de L'interface utilisateur ou sauvegarde des données dans la base de données.
cependant si vous utilisez map
vous ne pouvez pas écrire un tel code agréable. En un mot, flatMap
peut nous aider à sérialiser les requêtes.
dans ce scénario utilisez la carte, vous n'avez pas besoin D'un nouveau Observable pour elle.
vous devez utiliser des Exceptions.propager, qui est un wrapper afin que vous puissiez envoyer ces exceptions vérifiées au mécanisme RX
Observable<String> obs = Observable.from(jsonFile).map(new Func1<File, String>() {
@Override public String call(File file) {
try {
return new Gson().toJson(new FileReader(file), Object.class);
} catch (FileNotFoundException e) {
throw Exceptions.propagate(t); /will propagate it as error
}
}
});
vous devez alors gérer cette erreur dans l'abonné
obs.subscribe(new Subscriber<String>() {
@Override
public void onNext(String s) { //valid result }
@Override
public void onCompleted() { }
@Override
public void onError(Throwable e) { //e might be the FileNotFoundException you got }
};);
il y a un excellent billet pour cela: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/
dans certains cas, vous pourriez finir par avoir une chaîne d'observables, où votre observable retournerait un autre observable. "flatmap" sorte de déballer le deuxième observable qui est enfoui dans le premier et vous permet d'accéder directement les données deuxième observable est cracher tout en souscrivant.