Java 8 pour le développement Android méthodes par défaut et statiques

Java 8 constituait un énorme pas en avant pour le langage de programmation et, avec la sortie d'Android Studio 3.0, les développeurs Android ont enfin accès à la prise en charge intégrée de certaines des fonctionnalités les plus importantes de Java 8..

Dans cette série en trois parties, nous avons exploré les fonctionnalités de Java 8 que vous pouvez commencer à utiliser dans vos projets Android. aujourd'hui. Dans Cleaner Code With Lambda Expressions, nous avons configuré notre développement pour qu'il utilise le support Java 8 fourni par la chaîne d'outils par défaut d'Android, avant d'examiner de manière approfondie les expressions lambda..

Dans cet article, nous examinerons deux manières différentes de déclarer des méthodes non abstraites dans vos interfaces (ce qui n’était pas possible dans les versions précédentes de Java). Nous allons également répondre à la question de, maintenant que les interfaces peuvent implémenter des méthodes, ce exactement est la différence entre les classes abstraites et les interfaces?

Nous allons également couvrir une fonctionnalité Java 8 qui vous donne la liberté d'utiliser la même annotation autant de fois que vous le souhaitez au même endroit, tout en restant compatible avec les versions antérieures d'Android..

Mais tout d’abord, examinons une fonctionnalité Java 8 conçue pour être utilisée en combinaison avec les expressions lambda que nous avons vues dans le post précédent..

Write Cleaner Lambda Expressions, avec références de méthode

Dans le dernier message, vous avez vu comment utiliser les expressions lambda pour supprimer beaucoup de code standard de vos applications Android. Cependant, lorsqu'une expression lambda appelle simplement une méthode qui porte déjà un nom, vous pouvez couper encore plus de code de votre projet en utilisant une référence de méthode..

Par exemple, cette expression lambda ne fait que rediriger le travail vers un serveur existant. handleViewClick méthode:

 FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (view -> handleViewClick (view));  private void handleViewClick (Afficher la vue) 

Dans ce scénario, nous pouvons faire référence à cette méthode par son nom, en utilisant le :: opérateur de référence de méthode. Vous créez ce type de référence de méthode en utilisant le format suivant:

Object / Class / Type :: methodName

Dans notre exemple Floating Action Button, nous pouvons utiliser une référence de méthode comme corps de notre expression lambda:

 FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (this :: handleViewClick); 

Notez que la méthode référencée doit prendre les mêmes paramètres que l’interface. Dans ce cas, c’est Vue.

Vous pouvez utiliser l'opérateur de référence de méthode (::) faire référence à l’un des éléments suivants:

Une méthode statique

Si vous avez une expression lambda qui appelle une méthode statique:

(args) -> Class.staticMethod (args)

Ensuite, vous pouvez le transformer en une référence de méthode:

Classe :: staticMethodName

Par exemple, si vous aviez une méthode statique PrintMessage dans un Ma classe class, votre référence de méthode ressemblerait à ceci:

public class myClass public static void PrintMessage () System.out.println ("Ceci est une méthode statique");  public static void main (String [] args) Thread thread = new Thread (myClass :: PrintMessage); thread.start ();  

Une méthode d'instance d'un objet spécifique

Il s'agit d'une méthode d'instance d'un objet connue à l'avance, vous permettant de remplacer:

(arguments) -> containObject.instanceMethodName (arguments)

Avec:

conteneur :: instanceMethodName

Donc, si vous aviez l'expression lambda suivante:

MyClass.printNames (noms, x -> System.out.println (x));

Ensuite, introduire une référence à une méthode vous donnerait ce qui suit:

MyClass.printNames (names, System.out :: println);

Une méthode d'instance d'un objet arbitraire d'un type particulier

Il s'agit d'une méthode d'instance d'un objet arbitraire qui sera fournie ultérieurement et écrite au format suivant:

ContainingType :: methodName

Références constructeurs

Les références de constructeur sont similaires aux références de méthode, sauf que vous utilisez le mot clé Nouveau invoquer le constructeur. Par exemple, Button :: nouveau est une référence constructeur pour la classe Bouton, bien que le constructeur exact qui est invoqué dépend du contexte.

En utilisant les références de constructeur, vous pouvez activer:

(arguments) -> new ClassName (arguments)

Dans:

ClassName :: new

Par exemple, si vous aviez ce qui suit MonInterface interface:

 interface publique myInterface public abstract Student getStudent (nom de la chaîne, âge entier); 

Ensuite, vous pouvez utiliser les références du constructeur pour créer de nouvelles Étudiant les instances:

myInterface stu1 = Student :: new; Student stu = stu1.getStudent ("John Doe", 27);

Il est également possible de créer des références de constructeur pour les types de tableaux. Par exemple, une référence de constructeur pour un tableau de ints est int [] :: nouveau.

Ajouter des méthodes par défaut à vos interfaces

Avant Java 8, vous ne pouviez inclure que des méthodes abstraites dans vos interfaces (c'est-à-dire des méthodes sans corps), ce qui rendait difficile l'évolution des interfaces, post-publication.

Chaque fois que vous ajoutez une méthode à une définition d'interface, il manquait soudain une implémentation à toutes les classes qui implémentaient cette interface. Par exemple, si vous aviez une interface (MonInterface) qui a été utilisé par Ma classe, puis en ajoutant une méthode à MonInterface romprait la compatibilité avec Ma classe.

Dans le meilleur des cas, vous êtes responsable du petit nombre de classes qui utilisent MonInterface, Ce comportement serait ennuyeux mais gérable - il vous suffirait de réserver un peu de temps pour mettre à jour vos classes avec la nouvelle implémentation. Cependant, les choses pourraient devenir beaucoup plus compliqué si un grand nombre de classes implémentées MonInterface, ou si l'interface a été utilisée dans des classes que vous n'étiez pas responsable de.

Bien qu'il y ait eu plusieurs solutions de contournement pour ce problème, aucune d'entre elles n'était idéale. Par exemple, vous pouvez inclure de nouvelles méthodes dans une classe abstraite, mais cela obligerait tout le monde à mettre à jour son code pour étendre cette classe abstraite. et pendant que vous pourrait étendre l'interface d'origine avec une nouvelle interface, toute personne souhaitant utiliser ces nouvelles méthodes devra alors réécrire tout leurs références d'interface existantes.

Avec l'introduction des méthodes par défaut dans Java 8, il est désormais possible de déclarer des méthodes non abstraites (c'est-à-dire des méthodes avec un corps) dans vos interfaces, afin que vous puissiez enfin créer des implémentations par défaut pour vos méthodes..

Lorsque vous ajoutez une méthode à votre interface en tant que méthode par défaut, toute classe qui l'implémente n'a pas besoin de fournir sa propre implémentation, ce qui vous permet de mettre à jour vos interfaces sans rompre la compatibilité. Si vous ajoutez une nouvelle méthode à une interface en tant que méthode par défaut, alors chaque classe qui utilise cette interface mais ne fournit pas sa propre implémentation héritera simplement de l'implémentation par défaut de la méthode. Comme la classe ne manque pas d'implémentation, elle continuera à fonctionner normalement.

En fait, l'introduction de méthodes par défaut a été la raison pour laquelle Oracle a pu effectuer un nombre d'ajouts aussi important à l'API Collections dans Java 8..

Collection est une interface générique utilisée dans de nombreuses classes différentes, donc l'ajout de nouvelles méthodes à cette interface risquait de briser d'innombrables lignes de code. Plutôt que d’ajouter de nouvelles méthodes à la Collection d’interface et en cassant chaque classe dérivée de cette interface, Oracle a créé la fonctionnalité de méthode par défaut, puis a ajouté ces nouvelles méthodes en tant que méthodes par défaut. Si vous regardez la nouvelle méthode Collection.Stream () (que nous allons explorer en détail dans la troisième partie), vous verrez qu'elle a été ajoutée comme méthode par défaut:

 flux par défaut stream () return StreamSupport.stream (spliterator (), false); 

Créer une méthode par défaut est simple: il suffit d’ajouter le défaut modificateur à votre signature de méthode:

interface publique MyInterface void interfaceMethod (); default voet defaultMethod () Log.i (TAG, "Ceci est une méthode par défaut");

Maintenant si Ma classe les usages MonInterface mais ne fournit pas sa propre mise en œuvre de defaultMethod, ça va juste hériter de l'implémentation par défaut fournie par MonInterface. Par exemple, la classe suivante compilera toujours:

Classe publique MyClass extend AppCompatActivity implémente MyInterface 

Une classe d'implémentation peut remplacer l'implémentation par défaut fournie par l'interface, de sorte que les classes ont toujours le contrôle complet de leurs implémentations..

Les méthodes par défaut sont un ajout bienvenu pour les concepteurs d'API, mais elles peuvent parfois poser problème aux développeurs qui essaient d'utiliser plusieurs interfaces dans la même classe.. 

Imaginez qu’en plus de MonInterface, vous avez le suivant:

interface publique SecondInterface void interfaceMethod (); default void defaultMethod () Log.i (TAG, "Ceci est également une méthode par défaut");

Tous les deux MonInterface et Deuxième interface contient une méthode par défaut avec exactement la même signature (defaultMethod). Maintenant, imaginez que vous essayez d'utiliser ces deux interfaces dans la même classe:

Classe publique MyClass extend AppCompatActivity implémente MyInterface, SecondInterface 

À ce stade, vous avez deux implémentations contradictoires de defaultMethod, et le compilateur n'a aucune idée de la méthode à utiliser, vous allez donc rencontrer une erreur de compilation.

Un moyen de résoudre ce problème consiste à remplacer la méthode en conflit par votre propre implémentation:

Classe publique MyClass extend AppCompatActivity implémente MyInterface, SecondInterface public void defaultMethod () 

L’autre solution consiste à spécifier quelle version de defaultMethod vous souhaitez implémenter, en utilisant le format suivant:

.super.();

Donc si vous vouliez appeler le MyInterface # defaultMethod () mise en œuvre, alors vous utiliseriez ce qui suit:

Classe publique MyClass extended AppCompatActivity implémente MyInterface, SecondInterface public void defaultMethod () MyInterface.super.defaultMethod (); 

Utilisation de méthodes statiques dans vos interfaces Java 8

Semblables aux méthodes par défaut, les méthodes d'interface statique vous permettent de définir des méthodes dans une interface. Cependant, contrairement aux méthodes par défaut, une classe d'implémentation ne peut pas écraser celle d'une interface. statique les méthodes.

Si vous avez des méthodes statiques spécifiques à une interface, les méthodes d'interface statique de Java 8 vous permettent de placer ces méthodes dans l'interface correspondante, sans avoir à les stocker dans une classe séparée..

Vous créez une méthode statique en plaçant le mot clé statique au début de la signature de la méthode, par exemple:

interface publique MyInterface static void staticMethod () System.out.println ("Ceci est une méthode statique"); 

Lorsque vous implémentez une interface contenant une méthode d'interface statique, cette méthode statique fait toujours partie de l'interface et n'est pas héritée par la classe qui l'implémente. Vous devez donc préfixer la méthode avec le nom de l'interface, par exemple:

Classe publique MyClassargis AppCompatActivity implémente MyInterface vide public statique principal (String [] args) MyInterface.staticMethod ();… 

Cela signifie également qu'une classe et une interface peuvent avoir une méthode statique avec la même signature. Par exemple, en utilisant MyClass.staticMethod et MyInterface.staticMethod dans la même classe ne causera pas d'erreur de compilation.

Les interfaces sont donc essentiellement des classes abstraites?

L'ajout de méthodes d'interface statique et de méthodes par défaut a amené certains développeurs à se demander si les interfaces Java ressemblent davantage à des classes abstraites. Cependant, même avec l'ajout de méthodes d'interface par défaut et d'interface statique, il existe encore des différences notables entre les interfaces et les classes abstraites:

  • Les classes abstraites peuvent avoir des variables finales, non finales, statiques et non statiques, alors qu'une interface ne peut avoir que des variables statiques et finales.
  • Les classes abstraites vous permettent de déclarer des champs qui ne sont pas statiques et finaux, alors que les champs d'une interface sont intrinsèquement statiques et finaux..
  • Dans les interfaces, toutes les méthodes que vous déclarez ou définissez comme méthodes par défaut sont par nature publiques, alors que dans les classes abstraites, vous pouvez définir des méthodes concrètes publiques, protégées et privées..
  • Les classes abstraites sont Des classes, et peut donc avoir un état; les interfaces ne peuvent pas être associées à un état.
  • Vous pouvez définir des constructeurs dans une classe abstraite, chose impossible dans les interfaces Java..
  • Java vous permet uniquement d’étendre une classe (qu’elle soit abstraite ou non), mais vous êtes libre d’implémenter autant d’interfaces que vous le souhaitez. Cela signifie que les interfaces ont généralement l'avantage lorsque vous avez besoin de plusieurs héritages, même si vous devez vous méfier du diamant mortel de la mort.!

Appliquez la même annotation autant de fois que vous le souhaitez

Traditionnellement, l’une des limitations des annotations Java est que vous ne pouvez pas appliquer la même annotation plus d’une fois au même endroit. Essayez d’utiliser plusieurs fois la même annotation et vous rencontrerez une erreur lors de la compilation..

Cependant, avec l'introduction des annotations répétées de Java 8, vous êtes maintenant libre d'utiliser la même annotation autant de fois que vous le souhaitez au même endroit..

Afin de vous assurer que votre code reste compatible avec les versions antérieures de Java, vous devez stocker vos annotations répétitives dans une annotation de conteneur..

Vous pouvez indiquer au compilateur de générer ce conteneur en procédant comme suit:

  • Marquez l'annotation en question avec le @ Répétable méta-annotation (une annotation utilisée pour annoter une annotation). Par exemple, si vous vouliez faire la @Faire annotation répétable, vous utiliseriez: @Repeatable (ToDos.class). La valeur entre parenthèses est le type d'annotation de conteneur que le compilateur générera éventuellement..
  • Déclarez le type d'annotation contenant. Cela doit avoir un attribut qui est un tableau du type d'annotation répétitif, par exemple:
public @interface ToDos ToDo [] value (); 

Tenter d'appliquer la même annotation plusieurs fois sans d'abord déclarer qu'elle est répétable entraînera une erreur lors de la compilation. Cependant, une fois que vous avez spécifié qu'il s'agit d'une annotation répétable, vous pouvez utiliser cette annotation plusieurs fois, peu importe l'emplacement où vous utiliseriez une annotation standard..

Conclusion

Dans cette deuxième partie de notre série sur Java 8, nous avons vu comment vous pouvez réduire encore plus le code standard de vos projets Android en combinant des expressions lambda avec des références de méthodes et comment améliorer vos interfaces avec des méthodes par défaut et statiques..

Dans le troisième et dernier volet, nous examinerons une nouvelle API Java 8 qui vous permettra de traiter d’énormes quantités de données de manière plus efficace et déclarative., sans pour autant avoir à se soucier de la concurrence et de la gestion des threads. Nous allons également relier quelques-unes des fonctionnalités décrites dans cette série en explorant le rôle que les interfaces fonctionnelles doivent jouer dans les expressions lambda, les méthodes d’interface statique, les méthodes par défaut, etc..

Enfin, même si nous attendons toujours l'arrivée de la nouvelle API Date et heure de Java 8 sur Android, je vais vous montrer comment utiliser cette nouvelle API dans vos projets Android dès aujourd'hui, avec l'aide de tiers. les bibliothèques.

En attendant, consultez quelques-uns de nos autres articles sur le développement d'applications Java et Android.!