Java 8: formateo de lambda con nuevas líneas y sangría

82

Lo que me gustaría lograr con la sangría lambda es lo siguiente:

Declaración de varias líneas:

String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)" };
List<String> strings = Arrays.stream(ppl)
                         .filter(
                             (x) -> 
                             {
                                 return x.contains("(M)");
                             }
                         ).collect(Collectors.toList());
strings.stream().forEach(System.out::println);

Declaración de una sola línea:

List<String> strings = Arrays.stream(ppl)
                         .map((x) -> x.toUpperCase())
                         .filter((x) -> x.contains("(M)"))
                         .collect(Collectors.toList());



Actualmente, Eclipse se está formateando automáticamente a lo siguiente:

Declaración de varias líneas:

String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)" };
List<String> strings = Arrays.stream(ppl).filter((x) ->
{
    return x.contains("(M)");
}).collect(Collectors.toList());
strings.stream().forEach(System.out::println);

Declaración de una sola línea:

String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des(M)", "Rick (M)" };
List<String> strings = Arrays.stream(ppl).map((x) -> x.toUpperCase())
        .filter((x) -> x.contains("(M)")).collect(Collectors.toList());
strings.stream().forEach(System.out::println);

Y encuentro esto realmente complicado, debido a que la collectllamada está directamente debajo del returny no hay espacio entre ellos. Preferiría que pudiera iniciar el lambda en una nueva línea con sangría, y que la .filter(llamada estuviera justo encima de la .collect(llamada. Sin embargo, lo único que se puede personalizar con Java-8 Eclipse Formatter estándar es la llave al comienzo del cuerpo lambda, pero nada para los ()corchetes de antemano, ni la sangría.

Y en el caso de las llamadas de una sola línea, solo usa el ajuste de línea básico y hace que sea un desastre encadenado. No creo que deba explicar por qué esto es difícil de descifrar después.

¿Hay alguna forma de personalizar más el formato de alguna manera y lograr el primer tipo de formato en Eclipse? (O, opcionalmente, en otro IDE como IntelliJ IDEA).



EDITAR: Lo más cercano que pude estar fue con IntelliJ IDEA 13 Community Edition (lea: edición gratuita: P) que era lo siguiente (definido por una sangría continua que en este caso es 8):

public static void main(String[] args)
{
    int[] x = new int[] {1, 2, 3, 4, 5, 6, 7};
    int sum = Arrays.stream(x)
            .map((n) -> n * 5)
            .filter((n) -> {
                System.out.println("Filtering: " + n);
                return n % 3 != 0;
            })
            .reduce(0, Integer::sum);

    List<Integer> list = Arrays.stream(x)
            .filter((n) -> n % 2 == 0)
            .map((n) -> n * 4)
            .boxed()
            .collect(Collectors.toList());
    list.forEach(System.out::println);
    System.out.println(sum);    

También permite "alinear" la invocación del método encadenado de esta manera:

    int sum = Arrays.stream(x)
                    .map((n) -> n * 5)
                    .filter((n) -> {
                        System.out.println("Filtering: " + n);
                        return n % 3 != 0;
                    })
                    .reduce(0, Integer::sum);


    List<Integer> list = Arrays.stream(x)
                               .filter((n) -> n % 2 == 0)
                               .map((n) -> n * 4)
                               .boxed()
                               .collect(Collectors.toList());
    list.forEach(System.out::println);
    System.out.println(sum);
}

Personalmente, encuentro que si bien tiene más sentido, la segunda versión lo aleja demasiado, así que prefiero la primera.

La configuración responsable de la primera configuración es la siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<code_scheme name="Zhuinden">
  <option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
  <option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
  <option name="JD_ADD_BLANK_AFTER_PARM_COMMENTS" value="true" />
  <option name="JD_ADD_BLANK_AFTER_RETURN" value="true" />
  <option name="JD_P_AT_EMPTY_LINES" value="false" />
  <option name="JD_PARAM_DESCRIPTION_ON_NEW_LINE" value="true" />
  <option name="WRAP_COMMENTS" value="true" />
  <codeStyleSettings language="JAVA">
    <option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
    <option name="BRACE_STYLE" value="2" />
    <option name="CLASS_BRACE_STYLE" value="2" />
    <option name="METHOD_BRACE_STYLE" value="2" />
    <option name="ELSE_ON_NEW_LINE" value="true" />
    <option name="WHILE_ON_NEW_LINE" value="true" />
    <option name="CATCH_ON_NEW_LINE" value="true" />
    <option name="FINALLY_ON_NEW_LINE" value="true" />
    <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
    <option name="SPACE_WITHIN_BRACES" value="true" />
    <option name="SPACE_BEFORE_IF_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_WHILE_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_TRY_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_CATCH_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_SWITCH_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
    <option name="METHOD_PARAMETERS_WRAP" value="1" />
    <option name="EXTENDS_LIST_WRAP" value="1" />
    <option name="THROWS_LIST_WRAP" value="1" />
    <option name="EXTENDS_KEYWORD_WRAP" value="1" />
    <option name="THROWS_KEYWORD_WRAP" value="1" />
    <option name="METHOD_CALL_CHAIN_WRAP" value="2" />
    <option name="BINARY_OPERATION_WRAP" value="1" />
    <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
    <option name="ASSIGNMENT_WRAP" value="1" />
    <option name="IF_BRACE_FORCE" value="3" />
    <option name="DOWHILE_BRACE_FORCE" value="3" />
    <option name="WHILE_BRACE_FORCE" value="3" />
    <option name="FOR_BRACE_FORCE" value="3" />
    <option name="PARAMETER_ANNOTATION_WRAP" value="1" />
    <option name="VARIABLE_ANNOTATION_WRAP" value="1" />
    <option name="ENUM_CONSTANTS_WRAP" value="2" />
  </codeStyleSettings>
</code_scheme>

Traté de asegurarme de que todo fuera razonable, pero es posible que haya estropeado algo, por lo que es posible que necesite ajustes menores.

Si eres húngaro como yo y estás usando un diseño húngaro, entonces este mapa de teclas podría ser útil para ti, para que no termines sin poder usar AltGR + F, AltGR + G, AltGR + B , AltGR + N y AltGR + M (que corresponden a Ctrl + Alt).

<?xml version="1.0" encoding="UTF-8"?>
<keymap version="1" name="Default copy" parent="$default">
  <action id="ExtractMethod">
    <keyboard-shortcut first-keystroke="shift control M" />
  </action>
  <action id="GotoImplementation">
    <mouse-shortcut keystroke="control alt button1" />
  </action>
  <action id="GotoLine">
    <keyboard-shortcut first-keystroke="shift control G" />
  </action>
  <action id="Inline">
    <keyboard-shortcut first-keystroke="shift control O" />
  </action>
  <action id="IntroduceField">
    <keyboard-shortcut first-keystroke="shift control D" />
  </action>
  <action id="Mvc.RunTarget">
    <keyboard-shortcut first-keystroke="shift control P" />
  </action>
  <action id="StructuralSearchPlugin.StructuralReplaceAction" />
  <action id="Synchronize">
    <keyboard-shortcut first-keystroke="shift control Y" />
  </action>
</keymap>

Si bien IntelliJ no parece proporcionar una forma de poner la llave de apertura de la lambda en una nueva línea, de lo contrario es una forma bastante razonable de formatear, así que marcaré esto como aceptado.

EpicPandaForce
fuente
10
¿No puedes usar .filter(x -> x.contains("(M)"))? Mucho más simple ... Si realmente está hablando de lugares donde necesita múltiples declaraciones, sería mejor dar un ejemplo que lo necesite.
Jon Skeet
@JonSkeet Realmente no podía pensar en un caso de uso realmente bueno para varias líneas en este momento, pero agregué ambos casos.
EpicPandaForce
2
Honestamente, la versión 'Netbeans 8.0' que alguien publicó a continuación y eliminó estaba muy cerca de la verdad, aunque Netbeans es conocido por no permitir formatear correctamente las llaves de apertura de las declaraciones al estilo de Allman.
EpicPandaForce
3
Supongo que la solución actual es habilitar "nunca unir líneas ajustadas" y formatear a mano, pero eso parece algo tedioso en comparación con presionar Ctrl+Alt+Fsi está formateando automáticamente el código de otra persona.
EpicPandaForce
Y por alguna razón, Eclipse tenía problemas para averiguar el tipo de xsin abrir la llave y cerrarla, por eso usé una declaración en lugar de la versión simple al principio.
EpicPandaForce

Respuestas:

29

IntelliJ 13 listo para usar probablemente funcionará para usted.

Si lo escribo de esta manera:

// Mulit-Line Statement
String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)" };
List<String> strings = Arrays.stream(ppl)
        .filter(
                (x) ->
                {
                    return x.contains("(M)");
                }
        ).collect(Collectors.toList());
strings.stream().forEach(System.out::println);

Y luego aplique el formateador automático (sin cambios):

// Mulit-Line Statement
String[] ppl = new String[]{"Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)"};
List<String> strings = Arrays.stream(ppl)
        .filter(
                (x) ->
                {
                    return x.contains("(M)");
                }
        ).collect(Collectors.toList());
strings.stream().forEach(System.out::println);

Lo mismo es cierto para su declaración de una sola línea. Según mi experiencia, IntelliJ es más flexible en cómo se aplica su formateo automático. Es menos probable que IntelliJ elimine o agregue devoluciones de línea, si lo coloca allí, entonces asume que tenía la intención de colocarlo allí. IntelliJ ajustará felizmente su espacio de pestañas por usted.


IntelliJ también se puede configurar para hacer algo de esto por usted. En "configuración" -> "estilo de código" -> "java", en la pestaña "Envoltura y llaves" puede establecer "llamadas de método de cadena" a "envolver siempre".

Antes de formatear automáticamente

// Mulit-Line Statement
List<String> strings = Arrays.stream(ppl).filter((x) -> { return x.contains("(M)"); }).collect(Collectors.toList());

// Single-Line Statement
List<String> strings = Arrays.stream(ppl).map((x) -> x.toUpperCase()).filter((x) -> x.contains("(M)")).collect(Collectors.toList());

Después del formateo automático

// Mulit-Line Statement
List<String> strings = Arrays.stream(ppl)
        .filter((x) -> {
            return x.contains("(M)");
        })
        .collect(Collectors.toList());

// Single-Line Statement
List<String> strings = Arrays.stream(ppl)
        .map((x) -> x.toUpperCase())
        .filter((x) -> x.contains("(M)"))
        .collect(Collectors.toList());
Mike Rylander
fuente
La pregunta es, ¿es capaz de formatearlo de esta manera automáticamente? Simplifica el problema si el formateo automático hace un buen trabajo al formatearlo de la forma en que me gusta diseñar el código, de esa manera cualquier código es más fácil de leer, incluso el código de cualquier otra persona.
EpicPandaForce
@Zhuinden He actualizado la respuesta con un ejemplo sobre cómo hacer esto para una declaración de una sola línea. Como se muestra, también funciona hasta cierto punto para la declaración de varias líneas. Hay muchas opciones para configurar el formateador automático de IntelliJ, probablemente podría refinarlo para que aparezca exactamente como lo tiene en su pregunta.
Mike Rylander
Configuré las cosas de acuerdo con el XML que pegué arriba, supongo que usaré IntelliJ en lugar de Eclipse para cualquier cosa que no sea Android. Gracias.
EpicPandaForce
1
@EpicPandaForce, ¿por qué no lo usarías también para Android?
herman
@herman técnicamente también comencé a usar Android Studio para Android desde entonces. Tuve que instalar algunos gigas adicionales de RAM, pero la empresa tenía algunos por ahí, así que ahora con 8 GB, Gradle no congela toda la computadora mientras se compila: P
EpicPandaForce
59

En Eclipse, para las declaraciones de una sola línea:

En su proyecto o preferencias globales, vaya a Java -> Code Style -> Formatter -> Edit -> Line Wrapping -> Function Calls -> Qualified Invocations, configure Wrap all elements, except first if not necessaryy marque Force split, even if line shorter than maximum line width.

Arend
fuente
Oye, esto parece estar bien, excepto que me veo obligado a sangrar, ya sea con la indención predeterminada o la columna o 1. Eso no tiene sentido. Mi sangría difiere según la corriente. Por ejemplo: final List <DataRow> días = MappedSelect.query ("gameDays", DataRow.class) .param ("seasonId", 20142015) .select (contexto); Para lo anterior, quiero que las siguientes 2 líneas tengan sangría exactamente en el punto de la línea anterior.
Saravana Kumar M
1
¡Casi perfecto! :-) A veces, sin embargo, se ajusta demasiado para mi preferencia: las declaraciones dentro de lambda, por ejemplo .filter(c -> c.getValue().equals(newValue)), también se ajustarán justo antes de .equals. Sería bueno si tal configuración solo se aplicara al método relacionado con lambda; Sin embargo, veo que esto podría ser difícil de implementar ...
Dominic
24

Eclipse (Mars) tiene una opción para el formateador de expresiones lambda.

Ir Window > Preferences > Java > Code Style > Formatter

ingrese la descripción de la imagen aquí

Haga clic en el Editbotón, vaya a la Bracesetiqueta y establecer el Lambda BodyaNext Line Indented

ingrese la descripción de la imagen aquí

Otra opción es actualizar estas propiedades en la configuración de su proyecto. ( yourWorkspace > yourProject > .settings > org.eclipse.jdt.core.prefs)

org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=next_line_shifted
Diego D
fuente
2
esto solo parece ayudar con lambdas entre corchetes.
Nick
actualizar estas propiedades no ayuda
Piotr Wittchen
19

Esta pregunta ahora es antigua y, desafortunadamente, la configuración predeterminada del formateador Eclipse sigue sin ser fácil de usar para escribir código funcional de una manera legible.

Probé todas las cosas mencionadas en todas las demás respuestas y nadie se adapta a la mayoría de los casos de uso.
Puede estar bien para algunos pero desagradable para otros.

Encontré una forma que se adapta a mí la mayor parte del tiempo.
Lo comparto pensando que podría ayudar a otros.

Tenga en cuenta que mi método tiene una compensación: aceptar que cada invocación calificada esté siempre en una línea distinta.
Quizás sea la opción que falta en la configuración del formateador: indicar el umbral en términos de invocaciones para envolver la línea en lugar de usar la 1invocación por defecto.

Aquí están mis 2 herramientas combinadas para manejarlo correctamente:

  • Personalización de la configuración del formateador Eclipse para la mayoría de los casos

  • Creación de una plantilla de código //@formatter:off ... //@formatter:onpara casos de esquina.


Personalización de la configuración del formateador de Eclipse
Los valores que se van a cambiar están rodeados de rojo en la captura.

Paso 1) Cree su propio formateador de estilo de código Java

Preferencesmenú y en el árbol, vaya a Java -> Code Style -> Formatter.
Haga clic en "Nuevo" para crear uno nuevo Profile(inicialícelo con las "convenciones de Java").

Formateador de eclipse

Los dos siguientes pasos deben realizarse en su perfil de formateador personalizado.

Paso 2) Cambie la configuración de sangría para líneas envueltas

Configuración de identación

La modificación permite utilizar espacios en blanco en lugar de tabulaciones.
Importará en el siguiente paso, ya que configuramos la política de ajuste de línea con la opción de sangría de columna.
Evitará de hecho crea espacios desagradables.

Paso 3) Cambie la sangría predeterminada para las líneas envueltas y la política de ajuste de línea para la invocación calificada

Tamaño de línea envuelto


Aquí hay un formato de prueba con el código de la pregunta.

Antes de formatear:

void multiLineStatements() {
    String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)" };
    List<String> strings = Arrays.stream(ppl).filter((x) ->
    {
        return x.contains("(M)");
    }).collect(Collectors.toList());
    strings.stream().forEach(System.out::println);
}

void singleLineStatements() {
    String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des(M)", "Rick (M)" };
    List<String> strings = Arrays.stream(ppl).map((x) -> x.toUpperCase())
            .filter((x) -> x.contains("(M)")).collect(Collectors.toList());
    strings.stream().forEach(System.out::println);
}

Después de formatear:

void multiLineStatements() {
    String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)" };
    List<String> strings = Arrays.stream(ppl)
                                 .filter((x) -> {
                                     return x.contains("(M)");
                                 })
                                 .collect(Collectors.toList());
    strings.stream()
           .forEach(System.out::println);
}

void singleLineStatements() {
    String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des(M)", "Rick (M)" };
    List<String> strings = Arrays.stream(ppl)
                                 .map((x) -> x.toUpperCase())
                                 .filter((x) -> x.contains("(M)"))
                                 .collect(Collectors.toList());
    strings.stream()
           .forEach(System.out::println);
}

Creación de plantillas de código //@formatter:off ... //@formatter:onpara casos de esquina.

Escribir manualmente o copiar y pegar //@formatter:ony //@formatter:offestá bien, ya que rara vez lo escribe.
Pero si tiene que escribirlo varias veces a la semana o incluso peor durante el día, una forma más automática es bienvenida.

Paso 1) Vaya a la plantilla del Editor de Java

Preferencesmenú y en el árbol, vaya a Java ->Editor -> Template.
ingrese la descripción de la imagen aquí

Paso 2) Cree una plantilla para deshabilitar el formato del código seleccionado

Formateador de plantilla desactivado activado

Ahora puedes probarlo.
Seleccione las líneas que desea deshabilitar el formato.
Ahora ingrese ctrl+spacedos veces (la primera es "propuestas de Java" y la segunda es "propuestas de plantilla").
Deberías obtener algo como:

propuesta de plantilla

Seleccione la fmtplantilla como en la captura de pantalla y haga clic en "Entrar". ¡Hecho!

resultado después de la aplicación de la plantilla

davidxxx
fuente
16

Doy formato a la declaración de una sola línea agregando un comentario vacío "//" después de las funciones.

List<Integer> list = Arrays.stream(x) //
                           .filter((n) -> n % 2 == 0) //
                           .map((n) -> n * 4) //
                           .boxed() //
                           .collect(Collectors.toList());
Saljack
fuente
3
Fácil y eficaz.
Steve
12
Creo que hay ruido en su base de código solo por no establecer una configuración de estilo IDE. Puede resultar confuso para las personas que miran su código base por primera vez.
Marc Bouvier
9

No es ideal, pero puede desactivar el formateador solo para aquellas secciones que son un poco densas. Por ejemplo

  //@formatter:off
  int sum = Arrays.stream(x)
        .map((n) -> n * 5)
        .filter((n) -> {
            System.out.println("Filtering: " + n);
            return n % 3 != 0;
        })
        .reduce(0, Integer::sum);
  //@formatter:on

Vaya a "Ventana> Preferencias> Java> Estilo de código> Formateador". Haga clic en el botón "Editar ...", vaya a la pestaña "Desactivar / Activar etiquetas" y habilite las etiquetas.

trotar
fuente
3
Esta parecía una opción poco elegante pero más simple para que mi formateo fuera correcto
smc
2

La opción que funcionó para mí fue marcar la Never join already wrapped linesopción en la sección Ajuste de línea del Formateador en Preferencias.

Nosrep
fuente
1

Si aún no tiene un formateador de Eclipse personalizado:

Preferencias de Eclipse Java> Estilo de código> Formateador Nuevo Ingrese un nombre Haga clic en Aceptar Saltos de línea de control

Editar el perfil Pestaña de ajuste de línea Marque "Nunca unir líneas ya ajustadas"

Terminará así

    String phrase = employeeList
            .stream()
            .filter(p -> p.getAge() >= 33)
            .map(p -> p.getFirstName())
            .collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));

Necesito escribir cada. empezar en la siguiente línea

créditos - https://www.selikoff.net/2017/07/02/eclipse-and-line-wrapping/

Somendu
fuente
-1

La última versión de Eclipse tiene un formateador integrado de formato java 8, goto

Preferences -> Java -> Code Style -> Formatter -> Active profile

and Select Eclipse 2.1[built-in] profile
Yuvaraj Chitela
fuente