it-swarm.it

Funzioni annidate in Java

Esistono estensioni per il linguaggio di programmazione Java che rendono possibile la creazione di funzioni annidate? Esistono molte situazioni in cui è necessario creare metodi che vengono utilizzati solo una volta nel contesto di un altro metodo o ciclo. Non sono stato in grado di realizzare questo in Java finora, anche se può essere fatto facilmente in Javascript.

Ad esempio, questo non può essere fatto in 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 introduce lambda.

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 sintassi () -> funziona su qualsiasi interfaccia che definisce esattamente un metodo. Quindi puoi usarlo con Runnable ma non funziona con List.

BiConsumer è una delle molte interfacce funzionali fornite da Java.util.function .

Vale la pena notare che sotto il cofano, questo definisce una classe anonima e lo istanzia. times è un riferimento all'istanza.

38
Neal Ehardt

La risposta che segue sta parlando del massimo che si possa ottenere per avere funzioni nidificate in Java prima di Java 8. Non è necessariamente il modo in cui gestirò le stesse attività che potrebbero essere gestite con funzioni annidate in Javascript. Spesso un metodo di helper privato funziona altrettanto bene - forse anche un helper privato type , che crea un'istanza all'interno del metodo, ma che è disponibile per tutti i metodi.

Naturalmente in Java 8 ci sono espressioni lambda che sono una soluzione molto più semplice.


Il più vicino che puoi facilmente raggiungere è con una classe interiore anonima. Questo è il momento in cui Java arriva alle chiusure al momento, anche se si spera che ci sarà più supporto in Java 8.

Le classi interne anonime hanno vari limiti - sono ovviamente piuttosto prolisse rispetto al tuo esempio Javascript (o qualsiasi cosa usi lambdas) e il loro accesso all'ambiente circostante è limitato alle variabili finali.

Quindi per (orribilmente) pervertire il tuo esempio:

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];
        }
    }
}

Produzione:

2
4
10
100

È l'output che ottieni da Javascript?

24
Jon Skeet

Penso che il più vicino possibile ad avere funzioni annidate in Java 7 non è usando una classe interna anonima (la risposta di Jon Skeet sopra) ma usando le classi locali altrimenti molto raramente usate. In questo modo, nemmeno l'interfaccia della classe nidificata è visibile al di fuori dell'ambito previsto ed è anche un po 'meno prolissa.

L'esempio di Jon Skeet implementato con una classe locale sarebbe il seguente:

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];
        }
    }
}

Produzione:

2
4
10
100
21
deinocheirus

Tali metodi sono talvolta chiamati chiusure. Dai un'occhiata a Groovy - forse lo preferiresti a Java. In Java 8 probabilmente ci saranno anche chiusure (vedi JSR335 e lista differita ).

2
dma_k

Prendi in considerazione la possibilità di creare una classe locale anonima e di utilizzare il blocco di inizializzazione per eseguire il lavoro:

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]);
                }
            };
        }
    }
}

Produzione:

2
4
10
100

(Il "trucco finale del wrapper" non è automaticamente richiesto con questa tecnica, ma era necessario qui per gestire il requisito di mutazione.)

Funziona in modo quasi conciso come la versione lambda, ma puoi usare qualunque metodo firme tu voglia, ottengono nomi di parametri reali, ei metodi sono chiamati direttamente con i loro nomi - non c'è bisogno di .apply() o whatnot. (Questo tipo di cose a volte rende gli IDE strumenti che funzionano un po 'meglio.)

0