web-dev-qa-db-fra.com

Fonctions imbriquées en Java

Existe-t-il des extensions pour le langage de programmation Java permettant de créer des fonctions imbriquées? Il existe de nombreuses situations où je dois créer des méthodes qui ne sont utilisées qu'une seule fois dans le contexte d'une autre méthode ou d'une boucle for. Je n'ai pas encore réussi à accomplir cela en Java, même si cela peut se faire facilement en Javascript.

Par exemple, cela ne peut pas être fait en Java standard:

for(int i = 1; i < 100; i++){
    times(2); //multiply i by 2 and print i
    times(i); //square i and then print the result
    public void times(int num){

        i *= num;
        System.out.println(i);
    }    
}
41
Anderson Green

Java 8 introduit les lambdas.

Java.util.function.BiConsumer<Integer, Integer> times = (i, num) -> {
    i *= num;
    System.out.println(i);
};
for (int i = 1; i < 100; i++) {
    times.accept(i, 2); //multiply i by 2 and print i
    times.accept(i, i); //square i and then print the result
}

La syntaxe () -> fonctionne sur toute interface définissant exactement une méthode. Vous pouvez donc l’utiliser avec Runnable mais cela ne fonctionne pas avec List.

BiConsumer est l’une des nombreuses interfaces fonctionnelles fournies par Java.util.function .

Il est à noter que sous le capot, cela définit une classe anonyme et l'instancie. times est une référence à l'instance.

38
Neal Ehardt

La réponse ci-dessous parle du plus proche possible des fonctions imbriquées dans Java avant Java 8. Ce n'est pas nécessairement comme cela que je gèrerais les mêmes tâches qui pourraient être traitées avec des fonctions imbriquées en Javascript. Souvent, une méthode d'assistance privée conviendra tout aussi bien - éventuellement même une assistance privée type , pour laquelle vous créez une instance dans la méthode, mais qui est disponible pour toutes les méthodes.

Bien entendu, dans Java 8, il existe des expressions lambda qui constituent une solution beaucoup plus simple.


Le plus proche, vous pouvez facilement venir est avec une classe interne anonyme. C’est au plus près de la fermeture de Java pour le moment, mais j’espère qu’il y aura plus de support dans Java 8.

Les classes internes anonymes ont diverses limitations - elles sont évidemment assez verbeuses comparées à votre exemple Javascript (ou à tout ce qui utilise lambdas) et leur accès à l'environnement englobant est limité aux variables finales.

Donc, pour (horriblement) pervertir votre exemple:

interface Foo {
    void bar(int x);
}

public class Test {
    public static void main(String[] args) {
        // Hack to give us a mutable variable we can
        // change from the closure.
        final int[] mutableWrapper = { 0 };

        Foo times = new Foo() {
            @Override public void bar(int num) {
                mutableWrapper[0] *= num;
                System.out.println(mutableWrapper[0]);
            }
        };

        for (int i = 1; i < 100; i++) {
            mutableWrapper[0] = i;
            times.bar(2);
            i = mutableWrapper[0];

            times.bar(i);
            i = mutableWrapper[0];
        }
    }
}

Sortie:

2
4
10
100

Est-ce la sortie que vous obtenez du Javascript?

24
Jon Skeet

Je pense que le plus proche des fonctions imbriquées dans Java 7 ne consiste pas à utiliser une classe interne anonyme (réponse de Jon Skeet ci-dessus), mais à utiliser les classes locales, par ailleurs très rarement utilisées. De cette façon, même l'interface de la classe imbriquée n'est pas visible en dehors de sa portée prévue et elle est aussi un peu moins verbeuse.

L'exemple de Jon Skeet implémenté avec une classe locale ressemblerait à ceci:

public class Test {
    public static void main(String[] args) {
        // Hack to give us a mutable variable we can
        // change from the closure.
        final int[] mutableWrapper = { 0 };

        class Foo {
            public void bar(int num) {
                mutableWrapper[0] *= num;
                System.out.println(mutableWrapper[0]);
            }
        };

        Foo times = new Foo();

        for (int i = 1; i < 100; i++) {
            mutableWrapper[0] = i;
            times.bar(2);
            i = mutableWrapper[0];

            times.bar(i);
            i = mutableWrapper[0];
        }
    }
}

Sortie:

2
4
10
100
21
deinocheirus

De telles méthodes sont parfois appelées fermetures. Jetez un oeil à Groovy - Peut-être préférerez-vous Java. En Java 8, il y aura probablement aussi des fermetures (voir JSR335 et liste différée ).

2
dma_k

Envisagez de créer une classe locale anonyme et d'utiliser son bloc d'initialisation pour effectuer le travail:

public class LocalFunctionExample {
    public static void main(final String[] args) {
        for (final int i[] = new int[] { 1 }; i[0] < 100; i[0]++) {
            new Object() {
                {
                    times(2); //multiply i by 2 and print i
                    times(i[0]); //square i and then print the result
                }

                public void times(final int num) {
                    i[0] *= num;
                    System.out.println(i[0]);
                }
            };
        }
    }
}

Sortie:

2
4
10
100

(Le "tour final d'emballage" n'est pas automatiquement requis avec cette technique, mais était nécessaire ici pour gérer l'exigence de mutation.)

Cela semble être presque aussi concis que la version lambda, mais vous devez utiliser les signatures de méthode que vous voulez, elles ont un nom de paramètre réel, et les méthodes sont appelées directement par leur nom - pas besoin de .apply() ou autre. (Ce genre de chose améliore parfois le fonctionnement de l'outillage IDE.)

0
Woody Zenfell III