Lambda Expression

di il
4 risposte

Lambda Expression

Buon pomeriggio,

mi sono ritrovato ad affrontare un'esercizio dove posso usare le lambda.
La richiesta è di eliminare tutti i Mandarini che non hanno i semi.

la mia implementazione sarebbe stata questa:

public void mangiaMandariniSenzaSemi() {
		for (Agrume a : agrumi) {
			if (a instanceof Mandarino) {
				Mandarino m = (Mandarino) a;
				agrumi.removeIf(Mandarino -> m.hasSemi() == false);
			}
		}
	}
questo codice elimina tutti i gli Elementi di tipo Mandarino, anche se non ne capisco il motivo.

La mia seconda implementazione, senza lambda (per una mia incomprensione di queste), è stata la seguente:

public void mangiaMandariniSenzaSemi() {
		for (Agrume a : agrumi) {
			if (a instanceof Mandarino && ((Mandarino)a).hasSemi()==false) {
				agrumi.remove(a);
			}
		}
	}
L'esecuzione di questo metodo porta ad un'eccezione (java.util.ConcurrentModificationException).

Sono andato a sbirciare nell'esercizio del professore e c'è solo questa riga:

agrumi.removeIf(m -> (m instanceof Mandarino) && (!((Mandarino) m).hasSemi()));
ciò che non capisco io è: perché scriviamo (m instanceof Mandarino) dentro la lambda e non fuori (naturalmente in un if)? Non si cambia l'interfaccia in BiPredicate, con quest'ultima implementazione? Non dovremmo fare un confronto del tipo ((Mandarino) m).hasSemi()==false) per stabilire che il valore di ritorno sia falso?

Sono molto confuso.

Vi ringrazio per l'attenzione.

4 Risposte

  • Re: Lambda Expression

    ^Matteo^ ha scritto:


    la mia implementazione sarebbe stata questa:
    
    public void mangiaMandariniSenzaSemi() {
    		for (Agrume a : agrumi) {
    			if (a instanceof Mandarino) {
    				Mandarino m = (Mandarino) a;
    				agrumi.removeIf(Mandarino -> m.hasSemi() == false);
    			}
    		}
    	}
    
    questo codice elimina tutti i gli Elementi di tipo Mandarino, anche se non ne capisco il motivo.

    La mia seconda implementazione, senza lambda (per una mia incomprensione di queste), è stata la seguente:
    
    public void mangiaMandariniSenzaSemi() {
    		for (Agrume a : agrumi) {
    			if (a instanceof Mandarino && ((Mandarino)a).hasSemi()==false) {
    				agrumi.remove(a);
    			}
    		}
    	}
    
    L'esecuzione di questo metodo porta ad un'eccezione (java.util.ConcurrentModificationException).
    Nessuna delle due è giusta.
    La seconda è proprio sbagliata. Durante la iterazione vai a fare un remove direttamente sulla collezione. Questa è la causa del ConcurrentModificationException.
    Le collezioni standard (es. ArrayList) hanno un iteratore che ha un comportamento che si dice "fail-fast". Durante la iterazione con l'Iterator fornito dalla collezione, la collezione NON si può modificare. Con l'unica eccezione possibile del remove() del Iterator stesso (e non quello della collezione).
    Tutto questo è ben documentato (es. nel javadoc del framework).

    La prima non ha senso, fa "troppo".

    ^Matteo^ ha scritto:


    Sono andato a sbirciare nell'esercizio del professore e c'è solo questa riga:
    
    agrumi.removeIf(m -> (m instanceof Mandarino) && (!((Mandarino) m).hasSemi()));
    
    Questo ha più senso.

    Il removeIf() ha già DENTRO la iterazione sulla collezione!! E per ciascun elemento invoca il predicato che gli hai passato e in base al risultato del predicato elimina (nota: con il remove() del Iterator !) oppure no l'oggetto i-esimo.

    Ecco perché un TUO ciclo sulla collezione dove usi il removeIf() semplicemente NON ha senso.

    ^Matteo^ ha scritto:


    ciò che non capisco io è: perché scriviamo (m instanceof Mandarino) dentro la lambda e non fuori (naturalmente in un if)? Non si cambia l'interfaccia in BiPredicate, con quest'ultima implementazione? Non dovremmo fare un confronto del tipo ((Mandarino) m).hasSemi()==false) per stabilire che il valore di ritorno sia falso?
    La collezione (non l'hai precisato) è sicuramente parametrizzata <Agrume>. Quindi Mandarino è un sotto-tipo e potrebbero (non lo so nel tuo caso) esserci altri sotto-tipi (es. Arancia).
    Quindi è giusto/sensato testare prima se è un oggetto Mandarino e poi testare se non ha semi (se è questo che si vuole: eliminare "solo i Mandarini senza semi").


    P.S. leggi sempre bene la documentazione:

    default boolean removeIf(Predicate<? super E> filter)

    Removes all of the elements of this collection that satisfy the given predicate.
  • Re: Lambda Expression

    Grazie, per i consigli, ne trarrò beneficio.
  • Re: Lambda Expression

    ^Matteo^ ha scritto:


    Una domanda: non sarebbe un risparmio di confronti se noi, prima di passare al removeIf, facessimo l'instanceOf per m su Mandarino?
    No non ha senso, perché (ribadendo quanto detto prima) il instanceOf va fatto per ciascun elemento. Ed è già il removeIf() che fa al suo interno la iterazione e PASSA al predicato ciascun elemento.

    Il removeIf() è fatto così (preso direttamente dai sorgenti del JDK8):
        default boolean removeIf(Predicate<? super E> filter) {
            Objects.requireNonNull(filter);
            boolean removed = false;
            final Iterator<E> each = iterator();
            while (each.hasNext()) {
                if (filter.test(each.next())) {
                    each.remove();
                    removed = true;
                }
            }
            return removed;
        }
    Vedi bene che il ciclo è già DENTRO il removeIf. Prende il Iterator esplicitamente (non usa il "for-each" perché altrimenti non avrebbe accesso al Iterator!), cicla e ciascun elemento lo passa al test(T) del Predicate. Se il Predicate dice "sì" (=true), allora lo rimuove.
  • Re: Lambda Expression

    Si, avevo letto frettolosamente ciò che avevi scritto.

    Grazie per l'attenzione.
Devi accedere o registrarti per scrivere nel forum
4 risposte