[JAVA] Lisez la documentation officielle de Dagger2 pour comprendre les bases

Déclencheur

Dagger2 est également présenté dans le Guide des développeurs Android sur l'architecture des applications (https://developer.android.com/topic/libraries/architecture/guide.html). Il est temps ** Je pense qu'il est peu probable que les ingénieurs Android ne comprennent pas Dagger2 ou DI **, alors j'ai cherché. Si vous n'êtes pas sûr, veuillez lire la [Note complémentaire] correspondante. Puisque le code source est affiché, il peut être facile à imaginer. Je n'ai pas pu parler de la relation récemment mise à jour avec Android (dagger.android) cette fois. Audacieux ce qui vous a surpris en tant que débutant Dagger.

C'est presque une traduction des documents suivants. Veuillez indiquer ce qui ne va pas.

User's Guide https://google.github.io/dagger/users-guide.html

User's Guide

Présentation de Dagger2

En tant que bonne classe pour de nombreuses applications, BarcodeDecoder aura probablement des dépendances de classe telles que BarcodeCameraFinder, DefaultPhysicsEngine et HttpStreamer. En revanche, un exemple de mauvaise classe est une classe qui prend de la place sans rien faire, comme BarcodeDecoderFactory. Vous pouvez le remplacer par le modèle de conception d'injection de dépendances. Les tests peuvent être simplifiés en assemblant à l'aide d'annotations standard javax.inject (JSR 330). Il n'y a pas besoin d'une implémentation qui la remplace par une classe pour accéder à l'API Web de Fake. ** L'injection de dépendances n'est pas uniquement destinée aux tests. Vous pouvez créer des modules faciles à réutiliser et à remplacer. ** Par exemple, le module d'authentification peut être partagé et utilisé dans l'application, ou le DevLoggingModule peut être utilisé pour le débogage et le ProdLoggingModule peut être utilisé en production.

Pourquoi Dagger2 est-il différent des autres DI?

Il existe jusqu'à présent de nombreuses bibliothèques DI. Pourquoi avez-vous redéveloppé les roues? Dagger2 est le premier à être entièrement implémenté par génération de code. La règle est de générer une injection de dépendances aussi simple, traçable et exécutable que possible.

Déclaration de dépendance

Dagger Construit une instance de la classe de l'application et remplit ses dépendances. Utilisez annotation javax.inject.Inject pour identifier les constructeurs et les champs. Utilisez un constructeur qui utilise l'annotation @ Inject lorsque Dagger instancie une classe. Lorsqu'une nouvelle instance est nécessaire, Dagger obtient les paramètres requis et appelle le constructeur.

class Thermosiphon implements Pump {
  private final Heater heater;

  @Inject
  Thermosiphon(Heater heater) {
    this.heater = heater;
  }

  ...
}

Dagger peut également injecter des champs (variables membres) directement. Dans cet exemple, nous allons mettre une instance du champ de chauffage et du champ de pompe.

class CoffeeMaker {
  @Inject Heater heater;
  @Inject Pump pump;

  ...
}

S'il y a un champ avec ** @ Inject et aucun constructeur avec @ Inject, Dagger créera le champ si demandé, mais n'essaiera pas de créer l'instance. (Probablement comme ce CoffeeMaker.) En créant un constructeur sans argument avec @ Inject, il créera une instance de la même manière. ** ** Dagger prend également en charge l'injection de méthode, mais il est couramment utilisé dans les champs et les constructeurs. Les classes sans «@ Inject» ne peuvent pas être construites avec Dagger.

[Note supplémentaire] Déclaration de dépendance

C'était un morceau de code, donc c'est difficile à comprendre, n'est-ce pas?

C'est l'image. (Le code est ici. Https://github.com/takahirom/dagger2-sample/commit/4b4e7a047dee0993735c6605bc06bdcd3084c8b8) En ce qui concerne la zone autour de @ Component, je l'introduirai plus tard, mais créerai un composant et appelez maker (). ** Ce qui se passe lorsque vous appelez maker (), c'est qu'il renvoie simplement un nouveau CoffeeMaker () et une nouvelle instance injectée Heater () dans ce champ CoffeeMaker. ** **

Je crée un composant (décrit plus tard)

public class MainActivity extends AppCompatActivity {

    private CoffeeShop coffeeShop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        coffeeShop = DaggerMainActivity_CoffeeShop.create();
        System.out.println(coffeeShop.maker());
    }

    @Component
    interface CoffeeShop {
        CoffeeMaker maker();
    }
}

@ Inject permet au constructeur d'être exécuté à partir de Dagger, et @ Inejct spécifie le champ.

class CoffeeMaker {
    @Inject
    Heater heater;

    @Inject
    public CoffeeMaker() {
    }
}

Un appareil de chauffage est également créé à partir du champ.

class Heater {
    @Inject
    public Heater() {
    }
}

Cette fois, j'ai utilisé @ Inject pour le champ (variable membre), Si vous l'attachez à l'argument du constructeur, l'objet de l'argument sera nouveau en premier, et il sera utilisé pour appeler le constructeur.

Satisfaire les dépendances

Par défaut, Dagger remplit la dépendance en construisant le type demandé. Lorsque vous demandez CoffeeMaker new CoffeeMaker () définit les champs qui peuvent être injectés. Cependant, «@ Inject» ne peut pas être utilisé partout.

Dans de tels cas, une méthode utilisant «@ Provides» peut satisfaire la dépendance. La valeur de retour de la méthode doit être un type qui satisfait la dépendance.

Par exemple, la méthode provideHeader est appelée lorsque Heater est nécessaire.

@Provides static Heater provideHeater() {
  return new ElectricHeater();
}

** De plus, les méthodes avec «@ Provides» peuvent avoir leurs propres dépendances. Voici un exemple de retour de Thermosiphon là où la pompe est nécessaire. ** **

@Provides static Pump providePump(Thermosiphon pump) {
  return pump;
}

Les méthodes avec «@ provide» doivent appartenir à un module. Ajoutez simplement l'annotation @ Module à la classe.

@Module
class DripCoffeeModule {
  @Provides static Heater provideHeater() {
    return new ElectricHeater();
  }

  @Provides static Pump providePump(Thermosiphon pump) {
    return pump;
  }
}

** Par convention, la méthode @ Provides doit avoir un nom de méthode commençant par provide et le module doit avoir un nom de classe qui se termine par Module. ** **

[Note supplémentaire] Satisfaire la dépendance

Je vais le résumer dans le sens de la construction de la relation suivante (graphique).

Construire une relation (graphique)

Classes avec des relations de formulaire @ Injection et @ Provides (Graphiques) construites par des dépendances. Accédez à l'association (Graph) via un "ensemble de routes d'association bien définies" avec un code d'appel tel que la méthode main () de l'application ou la classe Application Android. Dans Dagger2, l'ensemble est déclaré dans une interface où ** la classe souhaitée a une méthode sans argument de retour. ** Appliquez l'annotation @ Component et passez le module aux modules de l'annotation pour créer une implémentation complète en utilisant ce module.

@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
  CoffeeMaker maker();
}

Une classe pour le Dagger et le premier composant sera générée à partir de laquelle vous pourrez créer une instance de CoffeeShop.

CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
    .dripCoffeeModule(new DripCoffeeModule())
    .build();

S'il s'agit d'une classe interne, elle sera générée avec un délimiteur _ comme celui-ci

Dans les cas suivants, il sera nommé DaggerFoo_Bar_BazComponent.

class Foo {
  static class Bar {
    @Component
    interface BazComponent {}
  }
}

Ce dripCoffeeModule () n'est pas nécessaire si la classe DripCoffeeModule a accès (et n'a pas besoin d'être) le constructeur par défaut. De plus, si toutes les méthodes avec «@ Provides» sont statiques, le constructeur n'a pas besoin d'être accessible.

DaggerCoffeeShop.builder()
    .dripCoffeeModule(new DripCoffeeModule())

Si vous n'avez pas besoin d'une instance du module, vous pouvez utiliser la méthode create () comme suit au lieu d'utiliser le générateur.

CoffeeShop coffeeShop = DaggerCoffeeShop.create();

Maintenant, vous pouvez simplement utiliser CoffeeShop avec une implémentation qui permet à Dagger d'obtenir un CoffeeMaker entièrement dépendant.

public class CoffeeApp {
  public static void main(String[] args) {
    CoffeeShop coffeeShop = DaggerCoffeeShop.create();
    coffeeShop.maker().brew();
  }
}

[Note supplémentaire] Satisfaire la dépendance

Cette fois, c'était aussi un fragment de code, donc ça peut être difficile à comprendre, donc je vais mettre tout le code. (Voici le code. Https://github.com/takahirom/dagger2-sample/commit/7adcffb7b4b38d1aede06645df9bbdc37603bd23)

public class MainActivity extends AppCompatActivity {

    private CoffeeShop coffeeShop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        coffeeShop = DaggerMainActivity_CoffeeShop.create();
        System.out.println(coffeeShop.maker());
    }

    @Component(modules = DripCoffeeModule.class)
    interface CoffeeShop {
        CoffeeMaker maker();
    }

    @Module
    static class DripCoffeeModule {
        @Provides static Heater provideHeater() {
            return new ElectricHeater();
        }

        @Provides static Pump providePump(Thermosiphon pump) {
            return pump;
        }
    }

}
class CoffeeMaker {
    @Inject
    Heater heater;
    @Inject
    Pump pump;

    @Inject
    public CoffeeMaker() {
    }
}

Actuellement, il existe des classes vides @ Inject-only telles que Chauffage, Pompe, Chauffage électrique et Thermosiphon.

public class ElectricHeater extends Heater {
    @Inject
    public ElectricHeater(){

    }
}

En utilisant @ Module, vous pouvez voir que chaque instance est utilisée. image.png

Liaison de graphe

Cet exemple montre un exemple de construction d'un composant à l'aide de liaisons typiques, mais il existe différents mécanismes pour aider à la liaison d'association (Graph). Les éléments suivants peuvent être utilisés lors de la création de dépendances et sont utilisés pour créer des composants en bon état.

(Il semble y avoir ce qui suit, mais je ne comprends pas tout)

Liaisons singleton et étendues

@ Singleton est attaché à une méthode avec @ Provides ou une classe injectable. Graphique Utilisez une instance pour tous les utilisateurs.

@Provides @Singleton static Heater provideHeater() {
  return new ElectricHeater();
}

@ Singleton à la classe est également utile comme document. Vous pouvez voir que cette classe peut être partagée par plusieurs threads.

@Singleton
class CoffeeMaker {
  ...
}

** Dagger2 connecte les instances étendues (y compris @ Singleton) dans Graph avec les instances de l'implémentation du composant **, vous devez donc étendre la portée du composant. Par exemple, cela n'a pas de sens d'avoir à la fois la liaison «@ Singleton binding» (qui semble signifier la liaison à un objet dans le graphique) et la liaison «@ RequestScoped» (portée personnalisée) en même temps. Parce qu'il doit être dans un composant avec un cycle de vie différent. Pour créer une portée personnalisée, appliquez simplement les annotations au composant.

@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
  CoffeeMaker maker();
}

[Note supplémentaire] Liaisons singleton et étendues

Comment les instances Singleton sont-elles gérées? (Voici le code https://github.com/takahirom/dagger2-sample/commit/063b7f45c9fb97e54b90ce56a4f96b217e39a6bc )

Dagger2 connecte les instances étendues (y compris @ Singleton) dans Graph avec les instances de l'implémentation du composant. Qu'est-ce que ça veut dire? Comment est-il géré?

À titre d'exemple simple, j'ai expérimenté avec @ Singleton.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final CoffeeShop coffeeShop =  DaggerMainActivity_CoffeeShop.create();
        System.out.println(coffeeShop.maker());
        System.out.println(coffeeShop.maker());
        final CoffeeShop coffeeShop2 = DaggerMainActivity_CoffeeShop.create();
        System.out.println(coffeeShop2.maker());
        System.out.println(coffeeShop2.maker());

    }

    @Singleton
    @Component(modules = DripCoffeeModule.class)
    interface CoffeeShop {
        CoffeeMaker maker();
    }

    
}
@Singleton
class CoffeeMaker {
    @Inject
    Heater heater;
    @Inject
    Pump pump;

    @Inject
    public CoffeeMaker() {
        System.out.println("new CoffeeMaker()");
    }
}

Cela produira ce qui suit: En d'autres termes, «@ Singleton» semble être associé à Coffee Shop.

new CoffeeMaker()
com.github.takahirom.dagger_simple.CoffeeMaker@a69a8a4
com.github.takahirom.dagger_simple.CoffeeMaker@a69a8a4
new CoffeeMaker()
com.github.takahirom.dagger_simple.CoffeeMaker@336f60d
com.github.takahirom.dagger_simple.CoffeeMaker@336f60d

J'ai essayé de vider la mémoire dans cet état. Dans cette implémentation, la référence à CoffeeMaker disparaît, c'est-à-dire que l'instance disparaît lorsque MainActivity.onCreate () se termine.

image.png

Et si j'ai une instance de CoffeeShop dans Actviity?

public class MainActivity extends AppCompatActivity {

    private CoffeeShop coffeeShop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        coffeeShop = DaggerMainActivity_CoffeeShop.create();
        System.out.println(coffeeShop.maker());
        System.out.println(coffeeShop.maker());
    }

Il semble que l'instance soit détenue par le coffeeMakerProvider dans le champ de la classe DaggerMainActivity_CoffeeShop générée automatiquement par Dagger qui implémente l'interface de CoffeeShop.

image.png

** Dagger2 avait en quelque sorte un sens pour ** connecter des instances étendues (y compris @ Singleton) dans Graph avec des instances de l'implémentation du composant. J'ai également expérimenté sans @Singleton, mais l'instance CoffeeMaker a disparu normalement.

Portée réutilisable

Parfois, vous souhaitez limiter le nombre d'instanciations, mais parfois vous n'êtes pas obligé de le limiter à un. L'instanciation est un processus coûteux sur Android, par exemple. Dans de tels cas, vous pouvez utiliser @ Réutilisable. @ Reusable, contrairement aux autres portées, ne se lie pas à un composant. Au lieu de cela, le composant renvoie une instance mise en cache ou l'instancie. ... (Cela semble être une fonctionnalité peu utilisée, je vais donc l'omettre cette fois **)

Références diffusables

... (Il semble être implémenté comme ça, mais il semble qu'il ne soit pas beaucoup utilisé, alors ** je l'omettrai cette fois **)

@Inject @ForReleasableReferences(MyScope.class)
ReleasableReferences myScopeReferences;

void lowMemory() {
  myScopeReferences.releaseStrongReferences();
}

...

Lazy injections Parfois, il peut être nécessaire de retarder l'instanciation. Vous pouvez retarder l'instanciation jusqu'à ce que vous appeliez la méthode get () de Lazy pour toute liaison T. Si T est une seule tonne, tout dans ObjectGraph sera la même instance. S'il ne s'agit pas d'un singleton, il sera instancié à chaque point d'injection. Mais à partir de la deuxième fois, il renverra la même instance.

class GridingCoffeeMaker {
  @Inject Lazy<Grinder> lazyGrinder;

  public void brew() {
    while (needsGrinding()) {
      // Grinder created once on first call to .get() and cached.
      lazyGrinder.get().grind();
    }
  }
}

Provider injections Vous pouvez avoir besoin de plusieurs instances au lieu d'injecter une seule valeur. Il existe plusieurs options, telles que l'utilisation de Factory ou Builder, mais une option consiste à utiliser Provider au lieu d'un simple type T. Le fournisseur appelle la logique de liaison chaque fois que la méthode get () est appelée. Si la logique de liaison est un constructeur avec @Inject, une nouvelle instance sera créée, mais il n'y a pas de telle garantie si l'instance est créée par une méthode avec @ Provides.

class BigCoffeeMaker {
  @Inject Provider<Filter> filterProvider;

  public void brew(int numberOfPots) {
  ...
    for (int p = 0; p < numberOfPots; p++) {
      maker.addFilter(filterProvider.get()); //new filter every time.
      maker.addCoffee(...);
      maker.percolate();
      ...
    }
  }
}

Remarque: le code qui injecte Provider peut créer du code déroutant qui peut entraîner l'utilisation d'objets mal ciblés, mal structurés ou mal structurés pour Graph. Dans de nombreux cas, vous pouvez utiliser des usines, Lazy ou reconfigurer la durée de vie et la structure de votre code pour injecter T. Dans certains cas, le fournisseur peut être mis en œuvre efficacement. Une utilisation courante est lorsque vous devez tirer parti d'une architecture héritée qui ne correspond pas à la durée de vie naturelle de l'objet.

(** Également utilisé dans l'exemple d'application du composant d'architecture ** https://github.com/googlesamples/android-architecture-components/blob/e33782ba54ebe87f7e21e03542230695bc893818/GithubBrowserSample/app/src/main/java/com/android/ exemple / github / viewmodel / GithubViewModelFactory.java # L30)

Qualifiers Parfois, les types seuls ne suffisent pas pour identifier les dépendances. Par exemple, dans une cafetière sophistiquée, je voudrais séparer le chauffe-eau pour l'eau et le radiateur pour la plaque chauffante. Dans ce cas, ajoutez une annotation de qualificatif. Ceci est une annotation avec «@ Qualifier». La déclaration de «@ Named» de l'annotation de qualificatif incluse dans javax.inject est la suivante.

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
  String value() default "";
}

Vous pouvez créer votre propre annotation de qualificatif ou utiliser @Named. Il peut être limité en appliquant des annotations aux champs ou aux arguments. Les types et les annotations de qualificatif sont utilisés pour identifier les dépendances.

class ExpensiveCoffeeMaker {
  @Inject @Named("water") Heater waterHeater;
  @Inject @Named("hot plate") Heater hotPlateHeater;
  ...
}

Fournit la valeur spécifiée en annotant la méthode @Provides correspondante.

@Provides @Named("hot plate") static Heater provideHotPlateHeater() {
  return new ElectricHeater(70);
}

@Provides @Named("water") static Heater provideWaterHeater() {
  return new ElectricHeater(93);
}

Les dépendances peuvent ne pas avoir plusieurs annotations de qualificatif. (?)

[Note supplémentaire] Qualifications

En fait, j'ai essayé de l'utiliser et j'ai essayé de voir ce qui se passerait si j'entrais un nom différent dans named. (Voici le code https://github.com/takahirom/dagger2-sample/commit/fbabf72a454ec7a96628783a81e12bcb765d6cad)

    @Module
    static class DripCoffeeModule {
        @Provides
        @Named("water")
        static Heater provideHeater() {
            return new ElectricHeater();
        }
class CoffeeMaker {
    @Inject
    @Named("hot plate")
    Heater heater;
...
}

Il le détecte correctement comme une erreur et semble m'avertir correctement au moment de la compilation.

MainActivity.java:30:Erreur: @javax.inject.Named("hot plate") com.github.takahirom.dagger_simple.Heater cannot be provided without an @Provides- or @Produces-annotated method.
        CoffeeMaker maker();
                    ^
      @javax.inject.Named("hot plate") com.github.takahirom.dagger_simple.Heater is injected at
          com.github.takahirom.dagger_simple.CoffeeMaker.heater
      com.github.takahirom.dagger_simple.CoffeeMaker is provided at
          com.github.takahirom.dagger_simple.MainActivity.CoffeeShop.maker()
1 erreur

Vous pouvez le compiler si vous le faites arroser correctement comme indiqué ci-dessous.

class CoffeeMaker {
    @Inject
    @Named("water")
    Heater heater;

Optional bindings ... (Cela semble être une fonction lors de l'utilisation de Optional et Dagger de Java 8 ou Guava, donc je vais l'omettre.)

Binding Instances Les données sont souvent disponibles lors de la construction de composants. Par exemple, considérons une application qui utilise des arguments de ligne de commande. Vous souhaiterez peut-être lier ces arguments dans un composant. Supposons que votre application accepte un argument (String) qui représente le nom de l'utilisateur que vous souhaitez injecter avec l'annotation «@ UserName». Vous pouvez ajouter «@ BindsInstance» au Component Builder afin de pouvoir injecter cette instance dans le Component Builder.

@Component(modules = AppModule.class)
interface AppComponent {
  App app();

  @Component.Builder
  interface Builder {
    @BindsInstance Builder userName(@UserName String userName);
    AppComponent build();
  }
}

Et l'application ressemble à ceci.

public static void main(String[] args) {
  if (args.length > 1) { exit(1); }
  App app = DaggerAppComponent
      .builder()
      .userName(args[0])
      .build()
      .app();
  app.run();
}

Dans l'exemple ci-dessus, si vous injectez la chaîne @ UserName dans un composant, l'instance fournie au générateur est utilisée lors de l'appel de cette méthode. (? Je ne savais pas où se trouve cette méthode ...) Vous devez appeler toutes les méthodes @ BindsInstance avant de construire le composant. Les liaisons «@ Nullable» suivantes sont une exception. Si le paramètre de la méthode @ BindsInstance est marqué @ Nullable, la liaison est considérée comme" nullable "de la même manière que la méthode @ Provides. Par conséquent, l'injecteur est également «@ Nullable» et nul doit être accepté. Vous pouvez également omettre l'appel de méthode Builder, où le composant traite l'instance comme nulle. ** La méthode @ BindsInstance doit être utilisée de préférence à la passer à @ Module dans le constructeur et à l'adapter immédiatement. ** **

(Ceci est également un exemple de composants d'architecture https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/di/AppComponent. java # L38 semble être utilisé.)

[Note supplémentaire] Instances contraignantes

C'est presque la même chose, mais jetons un coup d'œil au code (le code est ici https://github.com/takahirom/dagger2-sample/commit/c2cbfdbbf883203378ad51fd1d629ea25a7dbe6f)

Le qualificatif semble être bon pour celui qui était UserName dans l'exemple. Je l'utilise donc chez @Named. Cette fois, j'ai essayé de passer s'il fallait utiliser du sucre lors du démarrage de l'application.

J'ai spécifié d'utiliser du sucre avec du sucre (vrai) (j'ajouterai du sucre)

        coffeeShop = DaggerMainActivity_CoffeeShop.builder().sugar(true).build();
        System.out.println(coffeeShop.maker());

C'est le même que l'exemple.

    @Singleton
    @Component(modules = DripCoffeeModule.class)
    interface CoffeeShop {
        CoffeeMaker maker();

        @Component.Builder
        interface Builder {
            @BindsInstance
            Builder sugar(@Named("sugar") boolean isAddingSugger);
            CoffeeShop build();
        }
    }

Mettez le suger où vous le souhaitez.

class CoffeeMaker {
...
    @Inject
    @Named("sugar")
    boolean isAddSugar;

Validation au moment de la compilation

... (Le processeur d'annotation de Dagger est strict et donnera une erreur s'il est faux, donc je l'omettrai.)

Génération de code au moment de la compilation

Le processeur d'annotations de Dagger génère automatiquement des classes telles que CoffeeMaker_Factory.java et CoffeeMaker_MembersInjector.java. Ces classes sont les détails de l'implémentation de Dagger. C'est utile lors du débogage via des injections, mais vous n'avez pas à en profiter directement. ** Tout ce dont vous avez besoin dans votre code est celui qui commence par Dagger pour le composant. ** **

Comment utiliser Dagger dans la construction

... (Je vais l'omettre car c'est Android après tout.)

Résumé

J'ai senti que ce serait pratique si je pouvais le maîtriser. En particulier, si vous créez une instance d'une certaine classe avec Dagger, s'agit-il d'une chaîne de création d'instances telles que la création d'arguments pour ce champ ou cette méthode? C'était très pratique parce que j'allais le faire. Cependant, au début, j'ai l'impression que le coût d'apprentissage est un peu lourd, alors j'espère qu'il y a des gens qui seront un peu plus faciles avec cette Qiita.

Recommended Posts

Lisez la documentation officielle de Dagger2 pour comprendre les bases
[java8] Pour comprendre l'API Stream
Comprendre les bases de l'enregistrement audio Android
Référence Java à comprendre dans la figure
[Ruby] Des bases à la méthode inject
Pourquoi le code était-il pénible à lire?
[Introduction à Ruby] Comment utiliser la méthode slice
[Pour les débutants] Comprendre rapidement les bases de Java 8 lambda
Comprendre les caractéristiques de Scala en 5 minutes (Introduction à Scala)
Classes et instances Java comprises dans la figure