25 votos

Lambdas, múltiples forEach con bastidor

Necesitan un poco de ayuda pensar en las lambdas de mis compañeros de StackOverflow luminarias.

Caso estándar de picking a través de una lista de una lista de una lista de recoger a los niños de profundidad en un gráfico. Lo impresionante maneras podría Lambdas ayuda con este estándar?

public List<ContextInfo> list() {
    final List<ContextInfo> list = new ArrayList<ContextInfo>();
    final StandardServer server = getServer();

    for (final Service service : server.findServices()) {
        if (service.getContainer() instanceof Engine) {
            final Engine engine = (Engine) service.getContainer();
            for (final Container possibleHost : engine.findChildren()) {
                if (possibleHost instanceof Host) {
                    final Host host = (Host) possibleHost;
                    for (final Container possibleContext : host.findChildren()) {
                        if (possibleContext instanceof Context) {
                            final Context context = (Context) possibleContext;
                            // copy to another object -- not the important part
                            final ContextInfo info = new ContextInfo(context.getPath());
                            info.setThisPart(context.getThisPart());
                            info.setNotImportant(context.getNotImportant());
                            list.add(info);
                        }
                    }
                }
            }
        }
    }
    return list;
}

Nota: la lista se va para el cliente como JSON, por lo que no se centran en lo que se devuelve. Debe ser un par de cuidada formas que puede cortar los lazos.

Interesado en ver lo que mis compañeros expertos en crear. Los múltiples enfoques de anima.

EDITAR

El findServices y el dos findChildren métodos devuelven matrices

EDITAR - BONO RETO

"No importante parte" volvió a ser importante. Yo en realidad necesita una copia de un valor sólo disponible en la host de instancia. Esto parece a la ruina a todos los hermosos ejemplos. ¿Cómo se podía llevar a estado de avance?

final ContextInfo info = new ContextInfo(context.getPath());
info.setHostname(host.getName()); // The Bonus Challenge

26voto

Stuart Marks Puntos 8927

Es bastante profundamente anidadas pero no parece excepcionalmente difícil.

La primera observación es que, si una de bucle se traduce en una secuencia anidada de bucles puede ser "aplanado" en una secuencia en el uso de flatMap. Esta operación tiene un solo elemento y devuelve un número arbitrario de elementos en una secuencia. Miré hacia arriba y se encontró que StandardServer.findServices() devuelve una matriz de Service así que convertir esto en una secuencia mediante Arrays.stream(). (Puedo hacer suposiciones para Engine.findChildren() y Host.findChildren().

Siguiente, la lógica dentro de cada bucle hace un instanceof de verificación y de un reparto. Esto puede ser modelado mediante corrientes como filter operación para realizar el instanceof , seguido por un map operación que simplemente moldes y devuelve la misma referencia. Esto es en realidad un no-op pero permite a la estática de sistema de tipificación de convertir un Stream<Container> a Stream<Host> , por ejemplo.

La aplicación de estas transformaciones a los bucles anidados, se obtiene el siguiente:

public List<ContextInfo> list() {
    final List<ContextInfo> list = new ArrayList<ContextInfo>();
    final StandardServer server = getServer();

    Arrays.stream(server.findServices())
        .filter(service -> service.getContainer() instanceof Engine)
        .map(service -> (Engine)service.getContainer())
        .flatMap(engine -> Arrays.stream(engine.findChildren()))
        .filter(possibleHost -> possibleHost instanceof Host)
        .map(possibleHost -> (Host)possibleHost)
        .flatMap(host -> Arrays.stream(host.findChildren()))
        .filter(possibleContext -> possibleContext instanceof Context)
        .map(possibleContext -> (Context)possibleContext)
        .forEach(context -> {
            // copy to another object -- not the important part
            final ContextInfo info = new ContextInfo(context.getPath());
            info.setThisPart(context.getThisPart());
            info.setNotImportant(context.getNotImportant());
            list.add(info);
        });
    return list;
}

Pero espera, hay más.

El final de la forEach de la operación es un poco más complicado, map operación que convierte un Context a una ContextInfo. Además, estas sólo son recopilados en un List por lo que podemos utilizar colectores para ello, en lugar de la creación y la lista vacía en la delantera y, a continuación, rellenar. La aplicación de estos refactorings resultados en las siguientes:

public List<ContextInfo> list() {
    final StandardServer server = getServer();

    return Arrays.stream(server.findServices())
        .filter(service -> service.getContainer() instanceof Engine)
        .map(service -> (Engine)service.getContainer())
        .flatMap(engine -> Arrays.stream(engine.findChildren()))
        .filter(possibleHost -> possibleHost instanceof Host)
        .map(possibleHost -> (Host)possibleHost)
        .flatMap(host -> Arrays.stream(host.findChildren()))
        .filter(possibleContext -> possibleContext instanceof Context)
        .map(possibleContext -> (Context)possibleContext)
        .map(context -> {
            // copy to another object -- not the important part
            final ContextInfo info = new ContextInfo(context.getPath());
            info.setThisPart(context.getThisPart());
            info.setNotImportant(context.getNotImportant());
            return info;
        })
        .collect(Collectors.toList());
}

Por lo general tratan de evitar la multi-línea de lambdas (como en la final map de la operación) así que me gustaría refactorizar en un pequeño ayudante método que toma un Context y devuelve un ContextInfo. Esto no acortar el código, pero creo que lo hace más claro.

ACTUALIZACIÓN

Pero espera, aún hay más.

Vamos a extraer la llamada a service.getContainer() en su propio canalización elemento:

    return Arrays.stream(server.findServices())
        .map(service -> service.getContainer())
        .filter(container -> container instanceof Engine)
        .map(container -> (Engine)container)
        .flatMap(engine -> Arrays.stream(engine.findChildren()))
        // ...

Esto expone la repetición de filtrado en instanceof seguido por una asignación con un yeso. Esto se hace tres veces en total. Parece probable que otros de código se va a necesitar para hacer cosas similares, por lo que sería bueno para extraer este poco de lógica en un método auxiliar. El problema es que filter puede cambiar el número de elementos en la secuencia de la caída que no coinciden), pero no puede cambiar sus tipos. Y map puede cambiar los tipos de elementos, pero no puede cambiar su número. Puede que algo cambie tanto el número y los tipos? Sí, es nuestro viejo amigo flatMap de nuevo! Así que nuestro método auxiliar necesita tomar un elemento y el retorno de una secuencia de elementos de un tipo diferente. Que la secuencia de retorno contiene un único elemento fundido (si corresponde) o vacío (si no coincide). La función auxiliar tendría este aspecto:

<T,U> Stream<U> toType(T t, Class<U> clazz) {
    if (clazz.isInstance(t)) {
        return Stream.of(clazz.cast(t));
    } else {
        return Stream.empty();
    }
}

(Esto se basa libremente en C#'s OfType construir menciona en algunos de los comentarios.)

Mientras estamos en ello, vamos a extraer un método para crear un ContextInfo:

ContextInfo makeContextInfo(Context context) {
    // copy to another object -- not the important part
    final ContextInfo info = new ContextInfo(context.getPath());
    info.setThisPart(context.getThisPart());
    info.setNotImportant(context.getNotImportant());
    return info;
}

Después de estas extracciones, la tubería se parece a esto:

    return Arrays.stream(server.findServices())
        .map(service -> service.getContainer())
        .flatMap(container -> toType(container, Engine.class))
        .flatMap(engine -> Arrays.stream(engine.findChildren()))
        .flatMap(possibleHost -> toType(possibleHost, Host.class))
        .flatMap(host -> Arrays.stream(host.findChildren()))
        .flatMap(possibleContext -> toType(possibleContext, Context.class))
        .map(this::makeContextInfo)
        .collect(Collectors.toList());

Mejor, creo yo, y hemos quitado el temido multi-línea de lambda.

ACTUALIZACIÓN: EL BONUS DE DESAFÍO

Una vez más, flatMap es tu amigo. Tomar la cola de la corriente y migrar a la última flatMap antes de la cola. De esa manera el host variable es todavía en el ámbito, y se puede pasar a un makeContextInfo método auxiliar que ha sido modificado para tener en host como bueno.

    return Arrays.stream(server.findServices())
        .map(service -> service.getContainer())
        .flatMap(container -> toType(container, Engine.class))
        .flatMap(engine -> Arrays.stream(engine.findChildren()))
        .flatMap(possibleHost -> toType(possibleHost, Host.class))
        .flatMap(host -> Arrays.stream(host.findChildren())
                               .flatMap(possibleContext -> toType(possibleContext, Context.class))
                               .map(ctx -> makeContextInfo(ctx, host)))
        .collect(Collectors.toList());

22voto

Edwin Dalorzo Puntos 19899

Esta sería mi versión de su código utilizando el JDK 8 arroyos, método de referencias y expresiones lambda:

server.findServices()
    .stream()
    .map(Service::getContainer)
    .filter(Engine.class::isInstance)
    .map(Engine.class::cast)
    .flatMap(engine -> Arrays.stream(engine.findChildren()))
    .filter(Host.class::isInstance)
    .map(Host.class::cast)
    .flatMap(host -> Arrays.stream(host.findChildren()))
    .filter(Context.class::isInstance)
    .map(Context.class::cast)
    .map(context -> {
        ContextInfo info = new ContextInfo(context.getPath());
        info.setThisPart(context.getThisPart());
        info.setNotImportant(context.getNotImportant());
        return info;
    })
    .collect(Collectors.toList());

En este enfoque puedo reemplazar su si-instrucciones para los predicados de filtro. Tener en cuenta que un instanceof cheque puede ser reemplazado con un Predicate<T>

Predicate<Object> isEngine = someObject -> someObject instanceof Engine;

que también puede ser expresada como

Predicate<Object> isEngine = Engine.class::isInstance

Del mismo modo, los moldes pueden ser reemplazados por Function<T,R>.

Function<Object,Engine> castToEngine = someObject -> (Engine) someObject;

Que es casi lo mismo que

Function<Object,Engine> castToEngine = Engine.class::cast;

Y la adición de elementos manualmente en una lista en el bucle for puede ser reemplazado con un colector. En el código de producción, la lambda que transforma a un Context a una ContextInfo puede (y debe) ser extraído en un método independiente, y se utiliza como un método de referencia.

2voto

user11153 Puntos 375

Solución al reto del bono

Inspirado por la respuesta @EdwinDalorzo.

public List<ContextInfo> list() {
    final List<ContextInfo> list = new ArrayList<>();
    final StandardServer server = getServer();

    return server.findServices()
            .stream()
            .map(Service::getContainer)
            .filter(Engine.class::isInstance)
            .map(Engine.class::cast)
            .flatMap(engine -> Arrays.stream(engine.findChildren()))
            .filter(Host.class::isInstance)
            .map(Host.class::cast)
            .flatMap(host -> mapContainers(
                Arrays.stream(host.findChildren()), host.getName())
            )
            .collect(Collectors.toList());
}

private static Stream<ContextInfo> mapContainers(Stream<Container> containers,
    String hostname) {
    return containers
            .filter(Context.class::isInstance)
            .map(Context.class::cast)
            .map(context -> {
                ContextInfo info = new ContextInfo(context.getPath());
                info.setThisPart(context.getThisPart());
                info.setNotImportant(context.getNotImportant());
                info.setHostname(hostname); // The Bonus Challenge
                return info;
            });
}

1voto

David Blevins Puntos 10502

Primer intento más allá de feo. Pasarán años antes de que esto me parece legible. Tiene que haber una mejor manera.

Nota: el findChildren métodos devuelven matrices de los trabajos del curso con for (N n: array) de sintaxis, pero no con el nuevo Iterable.forEach método. Tuvieron que envolver con Arrays.asList

public List<ContextInfo> list() {
    final List<ContextInfo> list = new ArrayList<ContextInfo>();
    final StandardServer server = getServer();

    asList(server.findServices()).forEach(service -> {

        if (!(service.getContainer() instanceof Engine)) return;

        final Engine engine = (Engine) service.getContainer();

        instanceOf(Host.class, asList(engine.findChildren())).forEach(host -> {

            instanceOf(Context.class, asList(host.findChildren())).forEach(context -> {

                // copy to another object -- not the important part
                final ContextInfo info = new ContextInfo(context.getPath());
                info.setThisPart(context.getThisPart());
                info.setNotImportant(context.getNotImportant());
                list.add(info);
            });
        });
    });

    return list;
}

La utilidad de los métodos de

public static <T> Iterable<T> instanceOf(final Class<T> type, final Collection collection) {
    final Iterator iterator = collection.iterator();
    return () -> new SlambdaIterator<>(() -> {
        while (iterator.hasNext()) {
            final Object object = iterator.next();
            if (object != null && type.isAssignableFrom(object.getClass())) {
                return (T) object;
            }
        }
        throw new NoSuchElementException();
    });
}

Y, finalmente, un Lambda-powerable implementación de Iterable

public static class SlambdaIterator<T> implements Iterator<T> {
    // Ya put your Lambdas in there
    public static interface Advancer<T> {
        T advance() throws NoSuchElementException;
    }
    private final Advancer<T> advancer;
    private T next;

    protected SlambdaIterator(final Advancer<T> advancer) {
        this.advancer = advancer;
    }

    @Override
    public boolean hasNext() {
        if (next != null) return true;

        try {
            next = advancer.advance();

            return next != null;
        } catch (final NoSuchElementException e) {
            return false;
        }
    }

    @Override
    public T next() {
        if (!hasNext()) throw new NoSuchElementException();

        final T v = next;
        next = null;
        return v;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }
}

Un montón de plomería y sin duda 5x el código de bytes. Debe haber una mejor manera.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X