web-dev-qa-db-fra.com

Comment déboguer stream (). Map (...) avec des expressions lambda?

Dans notre projet, nous migrons vers Java 8 et nous en testons les nouvelles fonctionnalités.

Sur mon projet, j'utilise des prédicats et des fonctions Guava pour filtrer et transformer certaines collections en utilisant Collections2.transform Et Collections2.filter.

Lors de cette migration, je dois par exemple modifier le code de goyave en Java 8 modifications. Ainsi, les modifications que je suis en train de faire sont les suivantes:

List<Integer> naturals = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10,11,12,13);

Function <Integer, Integer> duplicate = new Function<Integer, Integer>(){
    @Override
    public Integer apply(Integer n)
    {
        return n * 2;
    }
};

Collection result = Collections2.transform(naturals, duplicate);

À...

List<Integer> result2 = naturals.stream()
    .map(n -> n * 2)
    .collect(Collectors.toList());

J'utilisais beaucoup de goyave pour déboguer le code car je pouvais déboguer chaque processus de transformation, mais je me demandais comment déboguer, par exemple .map(n -> n*2).

En utilisant le débogueur, je peux voir du code comme:

@Hidden
@DontInline
/** Interpretively invoke this form on the given arguments. */
Object interpretWithArguments(Object... argumentValues) throws Throwable {
    if (TRACE_INTERPRETER)
        return interpretWithArgumentsTracing(argumentValues);
    checkInvocationCounter();
    assert(arityCheck(argumentValues));
    Object[] values = Arrays.copyOf(argumentValues, names.length);
    for (int i = argumentValues.length; i < values.length; i++) {
        values[i] = interpretName(names[i], values);
    }
    return (result < 0) ? null : values[result];
}

Mais ce n'est pas aussi simple que Guava de déboguer le code, en fait je ne pouvais pas trouver la transformation n * 2.

Existe-t-il un moyen de voir cette transformation ou un moyen de déboguer facilement ce code?

EDIT: J'ai ajouté la réponse de différents commentaires et réponses postées

Merci à Holger commentaire qui a répondu à ma question, l'approche du bloc lambda m'a permis de voir le processus de transformation et de déboguer ce qui s'est passé dans le corps de lambda:

.map(
    n -> {
        Integer nr = n * 2;
        return nr;
    }
)

Grâce à Stuart Marks, L'approche consistant à avoir des références de méthodes m'a également permis de déboguer le processus de transformation:

static int timesTwo(int n) {
    Integer result = n * 2;
    return result;
}
...
List<Integer> result2 = naturals.stream()
    .map(Java8Test::timesTwo)
    .collect(Collectors.toList());
...

Grâce à la réponse Marlon Bernardes, J'ai remarqué que mon Eclipse n'indiquait pas ce qu'il devait afficher et que l'utilisation de peek () aidait à afficher les résultats.

97
Federico Piazza

Je n'ai généralement aucun problème à déboguer les expressions lambda lors de l'utilisation d'Eclipse ou d'IntelliJ IDEA. Il suffit de définir un point d'arrêt et de ne pas inspecter l'expression lambda entière (inspectez uniquement le corps lambda).

Debugging Lambdas

Une autre approche consiste à utiliser peek pour inspecter les éléments du flux:

List<Integer> naturals = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13);
naturals.stream()
    .map(n -> n * 2)
    .peek(System.out::println)
    .collect(Collectors.toList());

UPDATE:

Je pense que vous êtes en train de devenir confus parce que map est un intermediate operation, Autrement dit: il s’agit d’une opération lazy qui ne sera exécutée qu’après l’exécution de terminal operation. Ainsi, lorsque vous appelez stream.map(n -> n * 2), le corps lambda n'est pas exécuté pour le moment. Vous devez définir un point d'arrêt et l'inspecter après l'appel d'une opération de terminal (collect, dans ce cas).

Vérifiez Opérations de flux pour plus d'explications.

UPDATE 2:

Citant Holger commentaire:

Ce qui rend la tâche difficile ici, c’est que l’appel à la carte et l’expression lambda sont sur une seule ligne, de sorte que le point d’arrêt de la ligne s’arrête sur deux actions totalement indépendantes.

Insérer un saut de ligne juste après map( Vous permettrait de définir un point de rupture pour l'expression lambda uniquement. Et il n’est pas rare que les débogueurs n’affichent pas les valeurs intermédiaires d’une instruction return. Changer le lambda en n -> { int result=n * 2; return result; } Vous permettrait de contrôler le résultat. Encore une fois, insérez les sauts de ligne de manière appropriée lorsque vous marchez ligne par ligne…

71
Marlon Bernardes

IntelliJ a un tel plugin Nice pour ce cas comme un plugin Java Stream Debugger . Vous devriez le vérifier: https://plugins.jetbrains.com/plugin/9696-Java-stream-debugger?platform=hootsuite

Il étend la fenêtre IDEA du débogueur en ajoutant le bouton Tracer la chaîne de flux en cours, qui devient actif lorsque le débogueur s’arrête dans une chaîne d’appels d’API de flux.

Il a une interface agréable pour travailler avec des opérations de flux séparées et vous donne l’occasion de suivre certaines valeurs que vous devez déboguer.

Java Stream Debugger

Vous pouvez le lancer manuellement à partir de la fenêtre de débogage en cliquant ici:

enter image description here

25
Dmytro Melnychuk

Le débogage de Lambdas fonctionne également bien avec NetBeans. J'utilise NetBeans 8 et JDK 8u5.

Si vous définissez un point d'arrêt sur une ligne comportant un lambda, vous frapperez une fois lors de la configuration du pipeline, puis une fois pour chaque élément de flux. En utilisant votre exemple, la première fois que vous atteignez le point d'arrêt sera l'appel map() qui configure le pipeline de flux:

first breakpoint

Vous pouvez voir la pile d'appels et les variables locales et les valeurs de paramètre pour main comme vous le souhaitiez. Si vous continuez à marcher, le "même" point d'arrêt est touché à nouveau, sauf que cette fois, il est dans l'appel du lambda:

enter image description here

Notez que cette fois, la pile d'appels se trouve au cœur de la machine des flux et que les variables locales sont les locales de la méthode lambda elle-même, et non la méthode main englobante. (J'ai modifié les valeurs de la liste naturals pour que cela soit clair.)

Comme Marlon Bernardes a souligné (+1), vous pouvez utiliser peek pour inspecter les valeurs au fur et à mesure de leur progression dans le pipeline. Soyez prudent si vous utilisez ceci à partir d'un flux parallèle. Les valeurs peuvent être imprimées dans un ordre imprévisible sur différents threads. Si vous stockez des valeurs dans une structure de données de débogage à partir de peek, cette structure de données devra bien sûr être thread-safe.

Enfin, si vous effectuez beaucoup de débogage de lambdas (en particulier les lambdas d’instruction multiligne), il peut être préférable d’extraire le lambda dans une méthode nommée, puis de vous y reporter en utilisant une référence de méthode. Par exemple,

static int timesTwo(int n) {
    return n * 2;
}

public static void main(String[] args) {
    List<Integer> naturals = Arrays.asList(3247,92837,123);
    List<Integer> result =
        naturals.stream()
            .map(DebugLambda::timesTwo)
            .collect(toList());
}

Cela pourrait vous aider à voir ce qui se passe pendant le débogage. De plus, extraire les méthodes de cette manière facilite le test unitaire. Si votre lambda est si compliqué que vous avez besoin de ne le faire que d'une seule étape, vous voudrez probablement faire une série de tests unitaires.

21
Stuart Marks

Intellij IDEA 15 semble rendre la tâche encore plus facile, cela permet de s’arrêter dans une partie de la ligne où se trouve lambda, voir la première fonctionnalité: http: //blog.jetbrains. com/idea/2015/06/intellij-idea-15-eap-is-open /

6
Egor