Développement Java 8 pour Android API de flux et bibliothèques de date et d'heure

Dans cette série en trois parties, nous avons exploré toutes les principales fonctionnalités de Java 8 que vous pouvez commencer à utiliser dans vos projets Android aujourd'hui..

Dans Cleaner Code With Lambda Expressions, nous nous sommes concentrés sur la découpe de votre projet en utilisant des expressions lambda, puis dans les méthodes par défaut et statiques, nous avons vu comment rendre ces expressions plus concises en les combinant avec des références de méthode. Nous avons également abordé Répétition d'annotations et comment déclarer des méthodes non abstraites dans vos interfaces à l'aide de méthodes d'interface par défaut et statiques..

Dans ce dernier article, nous allons nous intéresser aux annotations de types, aux interfaces fonctionnelles et à une approche plus fonctionnelle du traitement des données avec la nouvelle API Stream de Java 8..

Je vais également vous montrer comment accéder à certaines fonctionnalités Java 8 supplémentaires qui ne sont pas actuellement prises en charge par la plateforme Android, à l'aide des bibliothèques Joda-Time et ThreeTenABP..

Annotations de type

Les annotations vous aident à écrire du code plus robuste et moins sujet aux erreurs en informant les outils d'inspection du code tels que Lint des erreurs qu'ils devraient rechercher. Ces outils d'inspection vous avertiront alors si un morceau de code n'est pas conforme aux règles énoncées par ces annotations..

Les annotations ne sont pas une nouveauté (en fait, elles remontent à Java 5.0), mais dans les versions précédentes de Java, il était uniquement possible d'appliquer des annotations aux déclarations..

Avec la sortie de Java 8, vous pouvez désormais utiliser des annotations partout où vous avez utilisé un type, y compris les récepteurs de méthodes; expressions de création d'instances de classe; la mise en place d'interfaces; génériques et tableaux; la spécification de jette et met en oeuvre clauses; et le casting de type.

Frustrement, bien que Java 8 permette d'utiliser des annotations dans plus d'emplacements que jamais auparavant, il ne fournit aucune annotation spécifique aux types..

La bibliothèque de prise en charge des annotations d’Android permet d’accéder à des annotations supplémentaires, telles que @ Nullable, @NonNull, et des annotations pour la validation des types de ressources tels que  @DrawableRes, @DimenRes, @ColorRes, et @StringRes. Toutefois, vous pouvez également utiliser un outil d'analyse statique tiers, tel que le Checker Framework, qui a été développé conjointement avec la spécification JSR 308 (spécification Annotations on Java Types). Ce cadre fournit son propre ensemble d'annotations pouvant être appliquées aux types, ainsi qu'un certain nombre de "vérificateurs" (processeurs d'annotation) qui sont connectés au processus de compilation et effectuent des "vérifications" spécifiques pour chaque annotation de type incluse dans le vérificateur..

Etant donné que les annotations de types n'affectent pas les opérations d'exécution, vous pouvez utiliser les annotations de types de Java 8 dans vos projets tout en restant compatible avec les versions antérieures de Java..

Stream API

L'API de flux offre une approche alternative, «tuyaux et filtres», pour le traitement des collections..

Avant Java 8, vous manipuliez les collections manuellement, généralement en effectuant une itération sur la collection et en agissant successivement sur chaque élément. Cette boucle explicite a nécessité beaucoup de passe-partout, de plus, il est difficile de saisir la structure de la boucle for avant d'atteindre le corps de la boucle..

L'API Stream vous permet de traiter les données plus efficacement en effectuant une seule analyse de ces données, quelle que soit la quantité de données que vous traitez ou que vous effectuiez plusieurs calculs..

En Java 8, chaque classe qui implémente java.util.Collection a un courant méthode qui peut convertir ses instances en Courant objets. Par exemple, si vous avez un Tableau:

String [] myArray = new String [] "A", "B", "C", "D";

Ensuite, vous pouvez le convertir en un flux avec les éléments suivants:

Courant myStream = Arrays.stream (myArray);

L’API Stream traite les données en transmettant les valeurs d’une source, via une série d’étapes de calcul, appelées pipeline de flux. Un pipeline de flux est composé des éléments suivants:

  • Une source, comme un Collection, fonction de tableau ou de générateur.
  • Zéro ou plusieurs opérations «paresseuses» intermédiaires. Les opérations intermédiaires ne démarrent pas le traitement des éléments tant que vous n’avez pas appelé fonctionnement du terminal-c'est pourquoi ils sont considérés comme paresseux.Par exemple, appeler Stream.filter () sur une source de données, configure simplement le pipeline de flux; aucun filtrage ne se produit réellement jusqu'à ce que vous appeliez l'opération de terminal. Cela permet de chaîner plusieurs opérations, puis d'effectuer tous ces calculs en un seul passage des données. Les opérations intermédiaires produisent un nouveau flux (par exemple, filtre produira un flux contenant les éléments filtrés) sans pour autant en modifiant la source de données pour vous permettre d'utiliser les données d'origine ailleurs dans votre projet ou de créer plusieurs flux à partir de la même source.
  • Une opération de terminal, telle que Stream.forEchaque. Lorsque vous appelez l'opération de terminal, toutes vos opérations intermédiaires seront exécutées et produiront un nouveau flux. Un flux n'est pas capable de stocker des éléments. Ainsi, dès que vous appelez une opération de terminal, ce flux est considéré comme "consommé" et n'est plus utilisable. Si vous souhaitez revoir les éléments d'un flux, vous devez générer un nouveau flux à partir de la source de données d'origine..

Créer un flux

Il existe différentes manières d'obtenir un flux à partir d'une source de données, notamment:

  • Flux de()Crée un flux à partir de valeurs individuelles:

Courant stream = Stream.of ("A", "B", "C");
  • IntStream.range () Crée un flux à partir d'une plage de nombres:

IntStream i = IntStream.range (0, 20);
  • Stream.iterate () Crée un flux en appliquant de manière répétée un opérateur à chaque élément. Par exemple, nous créons ici un flux dont la valeur augmente de un élément:

Courant s = Stream.iterate (0, n -> n + 1);

Transformer un flux en opérations

Il existe une tonne d'opérations que vous pouvez utiliser pour effectuer des calculs de style fonctionnel sur vos flux. Dans cette section, je vais aborder quelques-unes des opérations de cours d'eau les plus couramment utilisées..

Carte

le carte() operation prend une expression lambda comme seul argument et utilise cette expression pour transformer la valeur ou le type de chaque élément du flux. Par exemple, ce qui suit nous donne un nouveau flux, où chaque Chaîne a été converti en majuscule:

Courant myNewStream = myStream.map (s -> s.toUpperCase ());

Limite

Cette opération limite la taille d'un flux. Par exemple, si vous souhaitez créer un nouveau flux contenant un maximum de cinq valeurs, utilisez les éléments suivants:

liste number_string = numbers.stream () .limit (5)

Filtre

le filtre (prédicat) opération vous permet de définir des critères de filtrage à l'aide d'une expression lambda. Cette expression lambda doit renvoie une valeur booléenne qui détermine si chaque élément doit être inclus dans le flux résultant. Par exemple, si vous avez un tableau de chaînes et que vous souhaitez filtrer les chaînes contenant moins de trois caractères, vous devez utiliser les éléments suivants:  

Arrays.stream (myArray) .filter (s -> s.length ()> 3) .forEach (System.out :: println); 

Triés

Cette opération trie les éléments d'un flux. Par exemple, ce qui suit retourne un flux de nombres disposés par ordre croissant:

liste list = Arrays.asList (10, 11, 8, 9, 22); list.stream () .sorted () .forEach (System.out :: println);

Traitement parallèle

Toutes les opérations de flux peuvent s'exécuter en série ou en parallèle, bien que les flux soient séquentiels, sauf indication explicite contraire. Par exemple, ce qui suit va traiter chaque élément un par un:

Stream.of ("a", "b", "c", "d", "e") .forEach (System.out :: print);

Pour exécuter un flux en parallèle, vous devez explicitement marquer ce flux en tant que parallèle, à l'aide du parallèle() méthode:

Stream.of ("a", "b", "c", "d", "e") .parallel () .forEach (System.out :: print);

Sous le capot, les flux parallèles utilisent le cadre Fork / Join, de sorte que le nombre de threads disponibles est toujours égal au nombre de cœurs disponibles dans la CPU..

L'inconvénient des flux parallèles est que différents cœurs peuvent être impliqués à chaque exécution du code. Vous obtiendrez ainsi une sortie différente à chaque exécution. Par conséquent, vous ne devez utiliser des flux parallèles que lorsque l'ordre de traitement est sans importance et éviter les flux parallèles lors de l'exécution d'opérations basées sur l'ordre, telles que findFirst ().

Opérations terminales

Vous collectez les résultats d'un flux à l'aide d'une opération de terminal, qui est toujours le dernier élément d'une chaîne de méthodes de flux et renvoie toujours autre chose qu'un flux.

Il existe différents types d'opérations de terminal qui renvoient différents types de données, mais dans cette section, nous allons examiner deux des opérations de terminal les plus couramment utilisées..

Collecte

le Collecte L’opération regroupe tous les éléments traités dans un conteneur, tel qu’un liste ou Ensemble. Java 8 fournit une Collectionneurs classe d’utilité, vous n’avez donc pas à vous soucier de la mise en œuvre de la Collectionneurs interface, ainsi que des usines pour de nombreux collectionneurs communs, y compris lister(), mettre en place(), et toCollection ().

Le code suivant produira un liste contenant uniquement des formes rouges:

formes.stream () .filter (s -> s.getColor (). égal ("rouge")) .collect (Collectors.toList ());

Vous pouvez également collecter ces éléments filtrés dans une Ensemble:

 .collect (Collectors.toSet ());

pour chaque

le pour chaque() opération effectue une action sur chaque élément du flux, ce qui en fait l'équivalent de la déclaration pour-each de l'API de flux.

Si vous aviez un articles liste, alors vous pouvez utiliser pour chaque pour imprimer tous les éléments inclus dans cette liste:

items.forEach (item-> System.out.println (item));

Dans l'exemple ci-dessus, nous utilisons une expression lambda, il est donc possible d'effectuer le même travail avec moins de code, en utilisant une référence de méthode:

items.forEach (System.out :: println);

Interfaces fonctionnelles

Une interface fonctionnelle est une interface qui contient exactement une méthode abstraite, appelée méthode fonctionnelle..

Le concept d'interfaces à méthode unique n'est pas nouveau-Runnable, Comparateur, Callable, et OnClickListener sont tous des exemples de ce type d'interface, bien que, dans les versions précédentes de Java, ils étaient connus sous le nom d'interfaces SAM (Single Abstract Method Interfaces).  

C’est plus qu’un simple changement de nom, car il existe des différences notables dans la façon dont vous travaillez avec des interfaces fonctionnelles (ou SAM) dans Java 8 par rapport aux versions précédentes..

Avant Java 8, vous instanciiez généralement une interface fonctionnelle à l'aide d'une implémentation de classe anonyme volumineuse. Par exemple, nous créons ici une instance de Runnable en utilisant une classe anonyme:

Runnable r = new Runnable () @Override public void run () System.out.println ("Mon Runnable"); ;

Comme nous l'avons vu dans la première partie, lorsque vous avez une interface à méthode unique, vous pouvez instancier cette interface en utilisant une expression lambda, plutôt qu'une classe anonyme. Maintenant, nous pouvons mettre à jour cette règle: vous pouvez instancier interfaces fonctionnelles, en utilisant une expression lambda. Par exemple:

Runnable r = () -> System.out.println ("Mon Runnable");

Java 8 introduit également une @FunctionalInterface annotation qui vous permet de marquer une interface en tant qu'interface fonctionnelle:

@FunctionalInterface interface publique MyFuncInterface public void doSomething (); 

Pour garantir la compatibilité avec les versions antérieures de Java, la @FunctionalInterface l'annotation est facultative; Cependant, il est recommandé de vous assurer que vous implémentez correctement vos interfaces fonctionnelles..

Si vous essayez d'implémenter deux méthodes ou plus dans une interface marquée comme @FunctionalInterface, alors le compilateur se plaindra d'avoir découvert plusieurs méthodes abstraites non principales. Par exemple, les éléments suivants ne seront pas compilés:

@FunctionalInterface interface publique MyFuncInterface void doSomething (); // Définit une seconde méthode abstraite // void doSomethingElse ();  

Et, si vous essayez de compiler un @FunctionInterface interface qui contient zéro méthodes, alors vous allez rencontrer un Aucune méthode cible trouvée Erreur.

Les interfaces fonctionnelles doivent contenir exactement une méthode abstraite, mais comme les méthodes par défaut et les méthodes statiques n'ont pas de corps, elles sont considérées comme non abstraites. Cela signifie que vous pouvez inclure plusieurs méthodes par défaut et statiques dans une interface, la marquer comme @FunctionalInterface, et ça va encore compiler.

Java 8 a également ajouté un package java.util.function qui contient de nombreuses interfaces fonctionnelles. Prenez le temps de vous familiariser avec toutes ces nouvelles interfaces fonctionnelles, pour que vous sachiez exactement ce qui est disponible immédiatement..

JSR-310: Nouvelle API de date et heure de Java

Travailler avec la date et l'heure en Java n'a jamais été aussi simple, de nombreuses API omettant des fonctionnalités importantes, telles que les informations de fuseau horaire.

Java 8 a introduit une nouvelle API Date et heure (JSR-310) qui vise à résoudre ces problèmes, mais malheureusement, au moment de la rédaction de cette API, cette API n'était pas prise en charge sur la plate-forme Android. Cependant, vous pouvez utiliser certaines des nouvelles fonctionnalités de date et heure dans vos projets Android aujourd'hui, en utilisant une bibliothèque tierce..

Dans cette dernière section, je vais vous montrer comment configurer et utiliser deux bibliothèques tierces populaires permettant d'utiliser l'API Date et heure de Java 8 sur Android..

ThreeTen Android Backport

ThreeTen Android Backport (également appelé ThreeTenABP) est une adaptation du projet de backport ThreeTen, très répandu, qui fournit une implémentation de JSR-310 pour Java 6.0 et Java 7.0. ThreeTenABP est conçu pour fournir un accès à toutes les classes de l'API Date et Heure (bien que sous un nom de package différent) sans ajouter un grand nombre de méthodes à vos projets Android..

Pour commencer à utiliser cette bibliothèque, ouvrez votre niveau de module build.gradle fichier et ajouter ThreeTenABP en tant que dépendance de projet:

dependencies // Ajoute la ligne suivante // compile 'com.jakewharton.threetenabp: threetenabp: 1.0.5'

Vous devez ensuite ajouter l'instruction d'importation ThreeTenABP:

import com.jakewharton.threetenabp.AndroidThreeTen;

Et initialisez les informations de fuseau horaire dans votre Application.onCreate () méthode:

@Override public void onCreate () super.onCreate (); AndroidThreeTen.init (this); 

ThreeTenABP contient deux classes qui affichent deux «types» d’informations d’heure et de date:

  • Heure locale, qui stocke une heure et une date dans le format 2017-10-16T13: 17: 57.138
  • ZonedDateTime, qui prend en compte les fuseaux horaires et stocke les informations de date et d'heure au format suivant: 2011-12-03T10: 15: 30 + 01: 00 [Europe / Paris]

Pour vous donner une idée de la façon dont vous utiliseriez cette bibliothèque pour récupérer des informations de date et d’heure, utilisons les Heure locale classe pour afficher la date et l'heure actuelles:

importer android.support.v7.app.AppCompatActivity; importer android.os.Bundle; import com.jakewharton.threetenabp.AndroidThreeTen; importer android.widget.TextView; import org.threeten.bp.LocalDateTime; Classe publique MainActivity étend AppCompatActivity @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); AndroidThreeTen.init (getApplication ()); setContentView (R.layout.activity_main); TextView textView = new TextView (this); textView.setText ("Time:" + LocalDateTime.now ()); setContentView (textView); 

Ce n'est pas le moyen le plus convivial d'afficher la date et l'heure! Pour analyser ces données brutes en quelque chose de plus lisible par l'homme, vous pouvez utiliser le DateTimeFormatter classe et définissez-la sur l'une des valeurs suivantes:

  • BASIC_ISO_DATE. Formate la date comme 2017-1016 + 01.00
  • ISO_LOCAL_DATE. Formate la date comme 2017-10-16
  • ISO_LOCAL_TIME. Formate le temps comme 14: 58: 43.242
  • ISO_LOCAL_DATE_TIME. Formate la date et l'heure comme 2017-10-16T14: 58: 09.616
  • ISO_OFFSET_DATE. Formate la date comme 2017-10-16 + 01.00
  • ISO_OFFSET_TIME.  Formate le temps comme 14: 58: 56.218 + 01: 00
  • ISO_OFFSET_DATE_TIME. Formate la date et l'heure comme 2017-10-16T14: 5836.758 + 01: 00
  • ISO_ZONED_DATE_TIME. Formate la date et l'heure comme 2017-10-16T14: 58: 51.324 + 01: 00 (Europe / Londres)
  • ISO_INSTANT. Formate la date et l'heure comme 2017-10-16T13: 52: 45.246Z
  • ISO_DATE. Formate la date comme 2017-10-16 + 01: 00
  • ISO_TIME. Formate le temps comme 14: 58: 40,945 + 01: 00
  • ISO_DATE_TIME. Formate la date et l'heure comme 2017-10-16T14: 55: 32.263 + 01: 00 (Europe / Londres)
  • ISO_ORDINAL_DATE. Formate la date comme 2017-289 + 01: 00
  • ISO_WEEK_DATE. Formate la date comme 2017-W42-1 + 01: 00
  • RFC_1123_DATE_TIME. Formate la date et l'heure comme Lundi 16 octobre 2017 14h58: 43h + 01h00

Ici, nous mettons à jour notre application pour afficher la date et l'heure avec DateTimeFormatter.ISO_DATE mise en forme:

importer android.support.v7.app.AppCompatActivity; importer android.os.Bundle; import com.jakewharton.threetenabp.AndroidThreeTen; importer android.widget.TextView; // Ajouter l'instruction d'importation DateTimeFormatter // import org.threeten.bp.format.DateTimeFormatter; import org.threeten.bp.ZonedDateTime; Classe publique MainActivity étend AppCompatActivity @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); AndroidThreeTen.init (getApplication ()); setContentView (R.layout.activity_main); TextView textView = new TextView (this); DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE; Chaîne formatéeZonedDate = formatter.format (ZonedDateTime.now ()); textView.setText ("Time:" + formatedZonedDate); setContentView (textView); 

Pour afficher ces informations dans un format différent, remplacez simplement par DateTimeFormatter.ISO_DATE pour une autre valeur. Par exemple:

DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

Joda-Time

Avant Java 8, la bibliothèque Joda-Time était considérée comme la bibliothèque standard de traitement de la date et de l’heure en Java, à tel point que la nouvelle API Date et heure de Java 8 s’appuie en grande partie sur l’expérience tirée du projet Joda-Time.

Alors que le site Web de Joda-Time recommande aux utilisateurs de migrer le plus tôt possible vers Java 8 Date et heure, dans la mesure où Android ne prend actuellement pas en charge cette API, Joda-Time reste une option viable pour le développement Android. Cependant, notez que Joda-Time dispose d'une API volumineuse et charge les informations de fuseau horaire à l'aide d'une ressource JAR, qui peuvent affecter les performances de votre application..

Pour commencer à travailler avec la bibliothèque Joda-Time, ouvrez votre module build.gradle déposer et ajouter ce qui suit:

dependencies compile 'joda-time: joda-time: 2.9.9'… 

La bibliothèque Joda-Time propose six classes principales de date et heure:

  • Instant: Représente un point dans la chronologie; par exemple, vous pouvez obtenir la date et l'heure actuelles en appelant Instant.now ().
  • Date et heure: Remplacement polyvalent des JDK Calendrier classe.
  • Date locale: Une date sans heure ou toute référence à un fuseau horaire.
  • Heure locale: Heure sans date ni référence à un fuseau horaire, par exemple 14:00:00.
  • Heure locale: Une date et une heure locales, toujours sans aucune information de fuseau horaire.
  • ZonedDateTime: Une date et une heure avec un fuseau horaire.

Voyons comment imprimer la date et l'heure avec Joda-Time. Dans l'exemple suivant, je réutilise le code de notre exemple ThreeTenABP. Pour rendre les choses plus intéressantes, j'utilise également avecZone convertir la date et l'heure en un ZonedDateTime valeur.

importer android.support.v7.app.AppCompatActivity; importer android.os.Bundle; importer android.widget.TextView; importer org.joda.time.DateTime; importer org.joda.time.DateTimeZone; Classe publique MainActivity étend AppCompatActivity @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); DateTime aujourd'hui = new DateTime (); // Retourne un nouveau formateur (avec withZone) et spécifiez le fuseau horaire à l'aide de ZoneId // DateTime todayNy = today.withZone (DateTimeZone.forID ("America / New_York")); TextView textView = new TextView (this); textView.setText ("Time:" + todayNy); setContentView (textView); 

Vous trouverez une liste complète des fuseaux horaires pris en charge dans les documents officiels Joda-Time..

Conclusion

Dans cet article, nous avons examiné comment créer un code plus robuste en utilisant des annotations de type et avons exploré l'approche «tuyaux-et-filtres» pour le traitement des données avec la nouvelle API Stream de Java 8..

Nous avons également examiné l'évolution des interfaces dans Java 8 et la manière de les utiliser en combinaison avec d'autres fonctionnalités explorées au cours de cette série, notamment les expressions lambda et les méthodes d'interface statique..

Pour conclure, je vous ai montré comment accéder à certaines fonctionnalités Java 8 supplémentaires qu'Android ne prend actuellement pas en charge par défaut, à l'aide des projets Joda-Time et ThreeTenABP..

Vous pouvez en apprendre plus sur la version Java 8 sur le site Web d'Oracle..

Et pendant que vous êtes ici, consultez certains de nos autres articles sur le développement Java 8 et Android.!