web-dev-qa-db-fra.com

Pourquoi ne pouvons-nous pas appeler Thread # sleep () directement dans une fonction Lambda?

Le code ci-dessous me donne une erreur de compilation:

Thread t2 = new Thread(() -> {
    try { 
        sleep(1000);
    } 
    catch (InterruptedException e) {}
});

La méthode sleep (int) n'est pas définie pour le type A (où A est le nom de ma classe).

Alors que, quand j'utilise une classe interne anonyme, il n'y a pas d'erreur de compilation:

Thread t1 = new Thread(){
    public void run(){
        try {
            sleep(1000);
        } catch (InterruptedException e) {}
    }
};

Le code ci-dessous fonctionne aussi très bien:

Thread t3 = new Thread(() -> System.out.println("In lambda"));

Comment les choses fonctionnent-elles dans un corps d'expression lambda? S'il vous plaît aider.

De nombreuses réponses, je peux voir que l'erreur peut être résolue en utilisant Thread.sleep(1000) dans ma première approche. Cependant, j'apprécierais vraiment que quelqu'un puisse m'expliquer comment la portée et le contexte fonctionnent dans une expression lambda.

57

Thread.sleep est une méthode statique de la classe Thread.

Si vous pouvez appeler sleep directement sans qualificatif dans une classe anonyme, c'est parce que vous vous trouvez réellement dans le contexte d'une classe qui hérite de Thread. Par conséquent, sleep est accessible ici.

Mais dans le cas lambda, vous n'êtes pas dans une classe qui hérite de Thread. Vous êtes dans la classe qui entoure ce code. Par conséquent, sleep ne peut pas être appelé directement et vous devez dire Thread.sleep. Le documentation supporte également ceci:

Les expressions lambda ont une portée lexicale. Cela signifie qu'ils n'héritent d'aucun nom d'un supertype ni n'introduisent un nouveau niveau de périmètre. Les déclarations dans une expression lambda sont interprétées exactement comme dans l'environnement englobant.

En gros, cela veut dire qu’à l’intérieur du lambda, vous êtes dans la même mesure que si vous étiez en dehors du lambda. Si vous ne pouvez pas accéder à sleep en dehors du lambda, vous ne pouvez pas non plus à l'intérieur.

Notez également que les deux manières de créer un fil de discussion que vous avez présentées ici sont intrinsèquement différentes. Dans le lambda, vous passez un Runnable au constructeur Thread, alors que dans le premier classe anonyme, vous créez un Thread en créant directement une classe anonyme.

83
Sweeper

Dans la première approche, vous passez un Runnable au Thread, vous avez besoin de l'appel Thread.sleep:

Thread t2 = new Thread(() -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
    }
});

c'est la version courte de:

Runnable runnable = new Runnable() {
    public void run(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {}
    }
};

Thread t2 = new Thread(runnable);

Dans la seconde, vous substituez directement la méthode thread.run, vous pouvez donc appeler thread.sleep dans thread.run.

18
xingbin

Cela finit par être un malentendu de la portée.

Lorsque vous transmettez un lambda au thread, vous ne créez pas de sous-classe de Thread, mais vous passez à la FunctionalInterface de Runnable et appelez un constructeur de Thread. Lorsque vous essayez d'appeler Sleep, le contexte de votre portée est une combinaison de Runnable + votre classe (vous pouvez appeler des méthodes par défaut si l'interface Runnable en avait), pas de thread.

Runnable n'a pas défini sleep (), mais Thread.

Lorsque vous créez une classe interne anonyme, vous sous-classez Thread. Par conséquent, sleep () est disponible pour votre appel, car le contexte de Scope est une sous-classe de Thread.

L'appel de méthodes statiques, sans le nom de classe, est déconseillé pour exactement ce genre de malentendu. L'utilisation de Thread.Sleep est à la fois correcte et sans ambiguïté dans toutes les circonstances.

10
Ryan The Leach

Votre doute provient d'un malentendu sur la définition des portées d'une expression lambda et d'une classe anonyme. Ci-dessous, je vais essayer de clarifier cela.

Les expressions lambda n'introduisent pas un nouveau niveau de portée. Cela signifie que, à l'intérieur de celui-ci, vous ne pouvez accéder qu'aux éléments auxquels vous pourriez avoir accès dans le bloc de code immédiatement englobant. Voyez ce que docs disent:

Les expressions lambda ont une portée lexicale. Cela signifie qu'ils n'héritent d'aucun nom d'un supertype ni n'introduisent un nouveau niveau de périmètre. Les déclarations dans une expression lambda sont interprétées exactement comme dans l'environnement englobant.

Les cours anonymes fonctionnent différemment. Ils introduisent un nouveau niveau de cadrage. Ils se comportent beaucoup comme une classe locale (une classe que vous déclarez dans un bloc de code), bien qu'ils ne puissent pas avoir des constructeurs. Voyez ce que docs disent:

Comme les classes locales, les classes anonymes peuvent capturer des variables; ils ont le même accès aux variables locales de la portée englobante:

  • Une classe anonyme a accès aux membres de sa classe englobante.
  • Une classe anonyme ne peut pas accéder aux variables locales de sa portée englobante qui ne sont pas déclarées finales ou finales.
  • Comme une classe imbriquée, une déclaration de type (telle qu'une variable) dans une classe anonyme masque toutes les autres déclarations de la portée englobante qui portent le même nom. Voir Shadowing pour plus d'informations.

Dans ce contexte, la classe anonyme se comportera comme une classe locale à l'intérieur de Thread et pourra ainsi accéder directement à sleep(), car cette méthode sera dans sa portée. Cependant, dans l'expression lambda, sleep() ne sera pas dans son champ d'application (vous ne pouvez pas appeler sleep() dans l'environnement englobant), vous devez donc utiliser Thread.sleep(). Notez que cette méthode est statique et, par conséquent, ne nécessite pas d'instance de sa classe pour être appelée.

7
Talendar

Le code suivant fonctionne:

    Thread t2 = new Thread(() -> {
        try { 
            Thread.sleep(1000);
        } 
        catch (InterruptedException e) {}
    });

En effet, sleep(int milliseconds) est une méthode de Thread class pendant que vous créez et passez une instance Runnable au constructeur Thread class.

Dans la deuxième méthode, vous créez une instance de classe interne anonyme de Thread class et avez ainsi accès à toutes les méthodes Thread class.

5
S.K.

J'aime la réponse qui a été fournie et acceptée, mais en termes beaucoup plus simples, vous pouvez penser que this a changé de classe intérieure anonyme à une lambda.

Dans le cas d'un AIC, this fait référence à une instance d'une classe que vous étendez (dans votre exemple, Thread), dans le cas de lambda expression, this se réfère à une instance de la classe qui entoure l'expression lambda (quelle que soit cette classe dans votre exemple). Et je parie que dans votre classe où vous utilisez l'expression lambda, il n'y a pas de telle sleep définie.

4
Eugene
public void foo() {
    new Thread(() -> { sleep(1000); });
}

est équivalent à

public void foo() {
    new Thread(this::lambda$0);
}
private void lambda$0() {
    sleep(1000);
}

donc le compilateur ne cherchera pas sleep dans Thread

2
yyyy