Genéricos, expresiones lambda y flujos en Java

Escribe clases y métodos Java que funcionen con cualquier tipo mientras el compilador comprueba cada uso. Después, pasa comportamiento a métodos como un valor mediante expresiones lambda y referencias a métodos. Ordena objetos según cualquier criterio con comparadores y procesa colecciones con la API Stream: filtra, transforma, ordena, cuenta, suma, agrupa y recopila datos en unos pocos pasos fáciles de leer. Cada ejemplo es un fragmento de código pequeño y completo con su resultado exacto, y a lo largo del curso se explican los errores de compilación y las excepciones en tiempo de ejecución habituales.

PrincipianteUnidad 11 de 11 del curso Curso de Programación orientada a objetos con Java

Lo que aprenderás

  1. Utiliza parámetros de tipo para escribir clases, interfaces y métodos que funcionen con muchos tipos, mientras el compilador comprueba cada uso.
    • Define y utiliza clases genéricas
      Declara clases e interfaces con parámetros de tipo, crea objetos con argumentos de tipo y explica la seguridad de tipos que esto aporta frente a almacenar Object.
    • Escribe métodos genéricos
      Declara métodos con sus propios parámetros de tipo, llámalos con argumentos de tipo inferidos o explícitos y decide cuándo debe ser genérico un método en lugar de una clase.
    • Restringe los parámetros de tipo con cotas y comodines
      Utiliza parámetros de tipo con cotas para llamar a métodos sobre un tipo genérico, explica por qué una List<Integer> no es una List<Number> y elige entre ? extends y ? super.
    • Trabaja dentro de las limitaciones del borrado de tipos
      Explica cómo el compilador borra los argumentos de tipo, qué operaciones no permiten por ello los genéricos y por qué deben evitarse los tipos sin parametrizar.
  2. Escribe expresiones lambda y referencias a métodos para interfaces funcionales, utiliza las interfaces funcionales estándar de Java y pasa comportamiento a métodos como un valor.
    • Escribe expresiones lambda para interfaces funcionales
      Reconoce interfaces funcionales, escribe expresiones lambda en sus formas breve y de bloque, y explica cómo el tipo de destino da significado a una expresión lambda.
    • Utiliza las interfaces funcionales estándar
      Elige entre Predicate, Function, Consumer, Supplier y sus variantes, combínalas y pásalas a métodos de colección como removeIf y replaceAll.
    • Captura variables y escribe referencias a métodos
      Utiliza variables del código circundante dentro de una expresión lambda, aplica la regla de efectivamente final y sustituye expresiones lambda sencillas por los cuatro tipos de referencias a métodos.
  3. Ordena listas según cualquier criterio mediante comparadores, desde un método compare escrito a mano hasta cadenas construidas con comparing, thenComparing y reversed.
    • Escribe un comparador y utilízalo para ordenar
      Implementa correctamente el contrato de compare, ordena listas y arreglos con un comparador y decide entre Comparable y Comparator.
    • Combina comparadores con comparing, thenComparing y reversed
      Construye comparadores a partir de claves de ordenación, añade criterios de desempate, invierte órdenes, maneja valores nulos y ordena las entradas de un mapa.
  4. Construye secuencias de procesamiento con la API Stream que filtren, transformen, ordenen y reduzcan datos, y explica cómo se ejecuta una secuencia.
    • Construye una secuencia de procesamiento de flujo y comprende cómo se ejecuta
      Crea flujos a partir de colecciones, arreglos y rangos, distingue las operaciones intermedias de las terminales y explica la evaluación diferida, el uso único y por qué la fuente permanece intacta.
    • Filtra, transforma y reordena elementos
      Utiliza filter, map, mapToInt, flatMap, distinct, sorted, limit y skip, y pasa entre flujos de objetos y flujos de tipos primitivos.
    • Reduce un flujo a un único resultado
      Calcula recuentos, sumas, medias, valores extremos y respuestas verdaderas o falsas con operaciones terminales, y maneja los resultados ausentes con Optional.
  5. Reúne los resultados de los flujos en listas, conjuntos, mapas y cadenas, agrupa y divide datos en particiones con recolectores y decide cuándo es más adecuado un flujo o un bucle.
    • Recopila resultados en listas, conjuntos, mapas y cadenas
      Utiliza toList, Collectors.toList, toSet, toCollection, joining y toMap, y maneja los resultados no modificables y las claves duplicadas.
    • Agrupa y divide datos en particiones con recolectores
      Divide datos en grupos con groupingBy y partitioningBy y resume cada grupo con un recolector posterior.
    • Elige entre flujos y bucles para resolver una tarea completa
      Resuelve una tarea de procesamiento de datos de principio a fin, compara una solución con flujos con otra con bucles y mantén las secuencias de procesamiento libres de efectos secundarios.

Guía de estudio de Genéricos y expresiones lambda en Java

5 capítulos

1

Escribe clases y métodos genéricos

El código genérico mantiene una relación útil entre entradas, valores almacenados y resultados. Una clase puede mantener esa relación para un objeto, mientras que un método puede elegirla de nuevo para cada llamada. Las cotas y los comodines permiten que las API acepten los tipos que necesitan sin permitir escrituras inseguras. El compilador comprueba esas promesas antes de borrar los parámetros de tipo, lo que explica tanto las ventajas de los genéricos como las operaciones en tiempo de ejecución que no pueden admitir.

1.1 Define y utiliza clases genéricas

Un contenedor debe indicar qué contiene. Con un parámetro de tipo, una misma clase puede contener cadenas, números u objetos propios, y el compilador comprueba cada uso antes de que se ejecute el programa.

Da un parámetro de tipo a una clase

En class Box<T>, T es un parámetro de tipo: un nombre para un tipo que proporcionará quien utilice la clase. Úsalo para campos, parámetros de métodos y resultados.

class Box<T> {
    private T value;

    public Box(T value) { this.value = value; }
    public T get() { return value; }
    public void set(T value) { this.value = value; }
}

El nombre del constructor es Box, sin <T>. La declaración después del nombre de la clase introduce T para el cuerpo de la clase. Ahora crea una caja:

Box<String> b = new Box<>("tea");
String drink = b.get();
b.set("water");
System.out.println(drink);   // tea
System.out.println(b.get()); // water

String es el argumento de tipo. Para esta variable, get() devuelve un String y set acepta un String. No hay conversión explícita de tipo al leer. El intento de llamar a b.set(42) falla en la compilación: «tipos incompatibles: int no se puede convertir a String». El error señala la escritura incorrecta antes de que una lectura posterior pueda fallar.

¿Por qué no almacenar Object?

Un campo Object puede hacer referencia tanto a una cadena como a un entero. El código que necesita una cadena debe convertir explícitamente el tipo del objeto devuelto. Si se almacenó un entero, esa conversión lanza ClassCastException mientras se ejecuta el programa. Un Box<String> incorpora la relación deseada al tipo declarado.

El diamante <> en new Box<>("tea") pide al compilador que infiera los argumentos de tipo. Conserva las comprobaciones. Omitir por completo los corchetes angulares, como en new Box("tea"), utiliza un tipo sin parametrizar y pierde las comprobaciones relacionadas con el parámetro de tipo. Asignar una construcción sin parametrizar a una variable tipada produce advertencias de operación no comprobada; la variable tipada sigue haciendo cumplir su propio argumento declarado en llamadas posteriores.

Utiliza clases envoltorio y varios parámetros

Un argumento de tipo debe ser un tipo de referencia. Utiliza Box<Integer>, no Box<int>. Java empaqueta el literal 5 como un Integer al construir new Box<Integer>(5) y lo desempaqueta cuando la aritmética necesita un int. Si n contiene esa caja, int next = n.get() + 1; da 6.

Dos tipos independientes necesitan dos parámetros. Un Pair<K, V> puede tener un campo K first y un campo V second. Para un Pair<String, Integer>, sus métodos de acceso devuelven un String y un Integer. Los nombres habituales son T para un tipo, E para un elemento, K y V para una clave y un valor, y R para un resultado.

Pon parámetros de tipo en las interfaces
interface Container<T> {
    void put(T item);
    T take();
}

Una clase puede conservar la elección con class Shelf<T> implements Container<T>, o fijarla con class NameList implements Container<String>. List<E> y Comparable<T> utilizan la misma idea. Una clase genérica sigue siendo una sola clase: Box<String> y Box<Integer> describen usos distintos en tiempo de compilación, no dos declaraciones de clase. No pueden asignarse entre sí porque sus promesas sobre el tipo de elemento son distintas.

Puntos clave

  • Un parámetro de tipo nombra un tipo dentro de una declaración; un argumento de tipo lo proporciona al utilizarla.
  • Un método de acceso genérico devuelve el tipo de elemento declarado sin conversión explícita de tipo.
  • El diamante conserva las comprobaciones de tipo; un tipo sin parametrizar pierde las comprobaciones genéricas.
  • Utiliza clases envoltorio como Integer como argumentos de tipo.
  • Las clases y las interfaces pueden tener más de un parámetro de tipo.

1.2 Escribe métodos genéricos

Algunas operaciones necesitan elegir un tipo para una llamada, no para todo el tiempo de vida de un objeto. Un método genérico introduce su propio parámetro de tipo y mantiene una relación entre su entrada y su resultado.

Declara el tipo antes del tipo de retorno
import java.util.List;

class Lists {
    public static <T> T last(List<T> list) {
        return list.get(list.size() - 1);
    }

    public static void main(String[] args) {
        int number = last(List.of(4, 8, 15));
        String word = Lists.<String>last(List.of("x", "y"));
        System.out.println(number); // 15
        System.out.println(word);   // y
    }
}

Lee la declaración por partes. static lo convierte en un método de clase. <T> introduce un parámetro de tipo que pertenece al método. El siguiente T es el tipo de retorno. List<T> indica que la lista de entrada y el resultado comparten el tipo de elemento.

Para la primera llamada, Java infiere T como Integer. El resultado puede desempaquetarse después en un int. En la segunda llamada, <String> es un argumento de tipo explícito de método, situado entre el punto y el nombre del método. Aquí Lists.last(...) inferiría el mismo tipo. Un argumento explícito necesita un receptor antes del punto: un nombre de clase para un método estático o un objeto como this para un método de instancia.

Este método espera una lista no vacía. En una lista vacía, size() - 1 es -1, así que get lanza IndexOutOfBoundsException. Los genéricos comprueban tipos; no demuestran que exista un índice.

Relaciona varios tipos

Un método genérico puede introducir varios parámetros. Dada una clase Pair<K, V> con un constructor y métodos de acceso, este método completo invierte los papeles:

static <K, V> Pair<V, K> swap(Pair<K, V> p) {
    return new Pair<>(p.getSecond(), p.getFirst());
}

Para un par de entrada ("Ada", 36) de tipo Pair<String, Integer>, el resultado es (36, "Ada") de tipo Pair<Integer, String>. La declaración de retorno conserva la relación; un tipo de retorno Object la ocultaría y obligaría a quien llama al método a realizar una conversión explícita de tipo.

Un método como <T> int countEqual(List<T> list, T target) también relaciona entradas. Su bucle puede comparar cada elemento con target mediante equals. Con ["a", "b", "a", "c", "a"] y "a", el recuento es 3. La inferencia puede elegir un supertipo común para los argumentos, así que repetir T no siempre obliga a que dos expresiones de argumento tengan tipos declarados idénticos.

Decide a quién pertenece el parámetro de tipo

Un Box<String> conserva su argumento de tipo para el uso de ese objeto. Lists.last elige un argumento de tipo de nuevo para cada llamada. Por tanto, una clase de utilidad ordinaria puede contener métodos estáticos genéricos sin ser genérica ella misma.

Un método estático dentro de Box<T> no puede utilizar el T de la clase: el método estático pertenece a la clase compartida, no a una caja concreta. Declara un parámetro independiente, por ejemplo static <E> Box<E> of(E value). Si una declaración de método comienza con static T first(...) sin un <T> del método, el compilador indica que no puede encontrar T en una clase ordinaria. Añade la declaración antes del tipo de retorno; cambiar el tipo de retorno a Object descartaría la relación útil.

Puntos clave

  • Un método genérico declara sus propios parámetros antes de su tipo de retorno.
  • La inferencia se realiza por separado para cada llamada.
  • Un argumento de tipo explícito de método aparece después de un punto y antes del nombre del método.
  • Utiliza varios parámetros de tipo cuando la entrada y la salida tengan varios papeles relacionados.
  • Un método estático debe declarar su propio parámetro de tipo.
  • La seguridad de tipos no comprueba el requisito de entrada no vacía de un método.

1.3 Restringe los parámetros de tipo con cotas y comodines

Un T sin restricciones solo admite las operaciones disponibles en Object. Las cotas añaden una garantía útil. Los comodines permiten que un parámetro acepte una familia de tipos genéricos sin suponer que todos sus tipos de elemento son idénticos.

Exige las operaciones que necesitas
static <T extends Comparable<T>> T largest(List<T> list) {
    T best = list.get(0);
    for (T x : list) {
        if (x.compareTo(best) > 0) {
            best = x;
        }
    }
    return best;
}

La cota indica que un T puede compararse con otro T. Partiendo de 3, la llamada largest(List.of(3, 11, 7)) sustituye el mejor valor por 11 y después lo deja sin cambios para 7. Devuelve 11. Para ["pear", "apple", "plum"], la palabra mayor es "plum" en orden natural. Una lista vacía falla en get(0).

extends también introduce cotas para interfaces. <T extends Number> permite utilizar doubleValue(). Las cotas múltiples utilizan &, con una clase primero si la hay: <T extends Number & Comparable<T>>.

Por qué List no es List

Integer extiende Number, pero List<Integer> no es un subtipo de List<Number>. Los tipos genéricos son invariantes. Si se permitiera esa asignación, el código que utiliza la referencia List<Number> podría añadir un Double; el propietario original lo leería entonces como un Integer. Un parámetro List<Number> sí acepta implementaciones como ArrayList<Number>, pero rechaza List<Integer>.

Lee con extends; añade con super
static double sum(List<? extends Number> nums) {
    double total = 0;
    for (Number n : nums) total += n.doubleValue();
    return total;
}

static void addOneToThree(List<? super Integer> target) {
    target.add(1);
    target.add(2);
    target.add(3);
}

? extends Number significa un tipo desconocido que es Number o un subtipo. sum acepta listas de enteros, números de doble precisión o números. Lee cada elemento como un Number. sum(List.of(1.5, 2.5)) da 4,0. No puede añadir 3: el tipo de elemento desconocido podría ser Double. Las reglas de tipos permiten añadir null, aunque una lista concreta puede rechazarlo en tiempo de ejecución.

? super Integer significa Integer o un supertipo. El segundo método puede añadir enteros a una List<Integer>, List<Number> o List<Object> modificable. Una lectura mediante ese parámetro solo tiene tipo Object: los elementos existentes podrían ser otros números u objetos.

ParámetroOperación segura sobre elementos
List<? extends T>Leer un elemento como T
List<? super T>Añadir un T; leer como Object
List<T>Leer y escribir utilizando el mismo tipo nombrado
List<?>Leer como Object; consultar el tamaño

La regla para recordarlo es productor extends, consumidor super. Describe la dirección de transferencia de los elementos, no si toda la colección es inmutable. Por ejemplo, puede seguir siendo posible eliminar elementos mediante una vista con extends.

Utiliza un parámetro con nombre cuando un tipo deba conectar varias posiciones, especialmente una entrada y un tipo de retorno. Utiliza un comodín cuando solo importe una variedad de entradas aceptadas. Collections.copy lee de List<? extends T> y escribe en List<? super T>; su destino debe tener ya suficientes posiciones que sustituir.

Puntos clave

  • Las cotas permiten utilizar en T los métodos del tipo que actúa como cota.
  • La invariancia genérica protege una lista de escrituras con el tipo de elemento incorrecto.
  • Utiliza ? extends T para leer elementos como T.
  • Utiliza ? super T para añadir valores de tipo T; las lecturas tienen tipo Object.
  • Un comodín restringe las operaciones mediante una referencia; no hace inmutable al objeto.
  • Utiliza un parámetro de tipo con nombre para relacionar los tipos de entrada y resultado.
  • Collections.copy sustituye posiciones existentes del destino.

1.4 Trabaja dentro de las limitaciones del borrado de tipos

Los genéricos proporcionan al compilador más información que una comprobación ordinaria de clase en tiempo de ejecución. Comprender qué queda después de la compilación explica las advertencias sobre tipos sin parametrizar y varias operaciones que Java rechaza.

Qué elimina el borrado de tipos

Java comprueba los argumentos de tipo y después borra los parámetros de tipo sustituyéndolos por sus cotas. Un T sin cota se convierte en Object; un T extends Number se convierte en Number. El compilador inserta conversiones explícitas de tipo donde son necesarias al leer valores. Hay una sola clase Box en tiempo de ejecución, así que esto imprime true:

System.out.println(
    new Box<String>("a").getClass()
    == new Box<Integer>(1).getClass()
);

La declaración de Box puede ser la clase genérica con un campo de valor, un constructor y un método de acceso. Ambas construcciones tienen la misma clase en tiempo de ejecución, aunque sus usos declarados tienen comprobaciones distintas en tiempo de compilación.

Operaciones que necesitan un tipo conocido en tiempo de ejecución

Dentro de una clase genérica, new T() no compila. Java no sabe qué constructor tiene el tipo de quien llama. Acepta una fábrica para crear el valor:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

class Factory {
    static <T> T create(Supplier<T> factory) {
        return factory.get();
    }

    public static void main(String[] args) {
        List<String> words = create(ArrayList::new);
        words.add("ready");
        System.out.println(words); // [ready]
    }
}

El proveedor aporta el comportamiento de creación. new T[10] y new List<Integer>[2] también se rechazan como creación de arreglos genéricos. Almacena una secuencia en ArrayList<T> cuando no necesites el comportamiento de un arreglo.

Para un Object obj, obj instanceof List<String> no puede comprobar de forma segura el argumento de elemento. Utiliza obj instanceof List<?> para comprobar si es una lista y después trata sus elementos como desconocidos. Esa comprobación no establece que el tipo de elemento sea una cadena.

Miembros estáticos compartidos y sobrecargas tras el borrado

Un campo como static T shared no puede utilizar el T de la clase que lo contiene. Todas las parametrizaciones comparten el miembro estático. Un método estático puede introducir su propio <E>, independiente del parámetro de la clase.

Dos métodos sobrecargados print(Set<String> s) y print(Set<Integer> s) no pueden coexistir en una clase. Ambos se borran hasta quedar como un método que acepta Set, lo que deja un conflicto de nombres. Da nombres distintos a las operaciones o diseña un método que maneje el comportamiento común.

Sigue una advertencia de operación no comprobada hasta la lectura incorrecta
List raw = new ArrayList();
raw.add("text");
raw.add(42);
for (Object value : raw) {
    String text = (String) value;
    System.out.println(text);
}

Esto imprime text y después la conversión explícita de 42 lanza ClassCastException. La declaración sin parametrizar puede generar un diagnóstico de tipo sin parametrizar; las adiciones generan advertencias de llamada no comprobada porque se han eludido las comprobaciones genéricas. Sustituirla por List<String> traslada el fallo a add(42) en tiempo de compilación. Una advertencia de operación no comprobada señala una garantía perdida y un riesgo de fallo de ejecución. Corrige el uso sin parametrizar o la conversión insegura en lugar de suponer que una lectura posterior será segura.

Puntos clave

  • El borrado de tipos sustituye los parámetros de tipo por sus cotas, o por Object cuando no tienen cota.
  • Los usos parametrizados comparten una sola clase en tiempo de ejecución.
  • Utiliza una fábrica proporcionada en lugar de new T().
  • Los tipos de parámetros tras el borrado no pueden distinguir sobrecargas genéricas.
2

Pasa comportamiento mediante expresiones lambda

Un método puede aceptar tanto comportamiento como datos. Las interfaces funcionales describen la forma de ese comportamiento, y las expresiones lambda proporcionan el trabajo que se realiza. Las interfaces estándar dan nombres claros a tareas habituales: comprobar, transformar, actuar y proporcionar valores. Las variables capturadas permiten que una regla utilice información circundante, mientras que las referencias a métodos nombran comportamientos existentes. La correspondencia con el tipo de destino mantiene cada elemento de sintaxis ligado a una llamada ordinaria a un método.

2.1 Escribe expresiones lambda para interfaces funcionales

Un método puede aceptar una regla como argumento. Una expresión lambda escribe esa regla donde se utiliza, mientras que una interfaz funcional indica a Java qué entradas recibe la regla y qué devuelve.

Ajustarse a un método abstracto

Una interfaz funcional tiene un método abstracto que implementar. Las declaraciones que coinciden con métodos públicos de instancia de Object, como equals(Object), no cuentan. Los métodos predeterminados y estáticos no añaden trabajo abstracto. Comparator<T> y Runnable son interfaces funcionales.

@FunctionalInterface
interface Operation {
    int apply(int a, int b);
}

class Combine {
    static int combine(int a, int b, Operation op) {
        return op.apply(a, b);
    }

    public static void main(String[] args) {
        Operation add = (a, b) -> a + b;
        System.out.println(add.apply(3, 4)); // 7
        System.out.println(combine(3, 4, (x, y) -> x * y)); // 12
    }
}

La interfaz proporciona el nombre de método apply, los dos tipos de parámetro int y el tipo de resultado int. La expresión lambda proporciona los nombres de los parámetros y el cuerpo. Crea una implementación utilizable mediante Operation; no llama a apply hasta que el programa lo invoca.

combine siempre pasa sus dos números a op. Quien llama cambia el comportamiento al proporcionar una suma o una multiplicación. Así es también como sort recibe una regla de ordenación y filter recibe una condición.

Lee la sintaxis

Una expresión lambda tiene parámetros, -> y después una expresión o un bloque. Un solo parámetro inferido puede omitir los paréntesis: w -> w.length() > 5. Ningún parámetro requiere (), y dos o más requieren paréntesis, como en (a, b) -> a * b. Se permiten tipos explícitos: (int a, int b) -> a + b.

Un cuerpo de expresión devuelve su resultado automáticamente. Un bloque que produce un valor debe devolverlo:

Operation max = (a, b) -> {
    if (a > b) {
        return a;
    }
    return b;
};
System.out.println(max.apply(3, 9)); // 9

Cada ruta por este bloque devuelve un int. Añadir llaves alrededor de a + b sin return no crea un bloque válido que devuelva un valor.

Deja que el destino aporte el significado

El tipo de destino es la interfaz funcional esperada en una asignación, un argumento o una posición de retorno. El texto x -> x * 2 puede corresponder a Function<Integer, Integer> o a UnaryOperator<Integer>. Sus tipos de parámetro y resultado proceden de ese destino. Object o = () -> 1; falla porque Object no proporciona ningún método funcional.

@FunctionalInterface pide al compilador que compruebe la definición de la interfaz. Es una protección útil si alguien añade después un segundo método abstracto distinto. Sin la anotación, esa interfaz puede seguir declarándose, pero una expresión lambda no puede implementar sus dos requisitos independientes.

Una expresión lambda también puede entrar en conflicto con nombres locales. Si el método que la contiene ya declara int a, no puede declarar un parámetro lambda llamado a en ese ámbito. Elige otro nombre de parámetro. En comparación con una clase anónima que implementa Comparator<String>, una expresión lambda elimina la declaración repetida de clase y método; el cálculo de ordenación sigue teniendo que cumplir el contrato del comparador.

Puntos clave

  • Una expresión lambda proporciona la implementación del método abstracto de una interfaz funcional.
  • La interfaz de destino determina los tipos de entrada, el tipo de resultado y el nombre del método.
  • Los cuerpos de expresión devuelven un valor directamente; los bloques que devuelven un valor necesitan return.
  • @FunctionalInterface comprueba la definición de la interfaz.
  • Un parámetro lambda no puede volver a declarar una variable local del ámbito que lo contiene.

2.2 Utiliza las interfaces funcionales estándar

Java ya da nombre a las formas habituales de comportamiento. Elige una interfaz según el número de entradas y el tipo de resultado, y después llama a su método funcional para ejecutar el comportamiento.

Elige una forma de entrada y salida
InterfazMétodo funcionalFunción
Predicate<T>boolean test(T t)Comprobar una condición
Function<T, R>R apply(T t)Transformar un valor
Consumer<T>void accept(T t)Realizar una acción
Supplier<T>T get()Proporcionar un valor sin entrada

Estas interfaces están en java.util.function. Para cadenas, w -> w.length() > 5 corresponde a un predicado, String::length corresponde a una función que devuelve Integer y System.out::println corresponde a un consumidor. Un proveedor como () -> new ArrayList<String>() crea una lista nueva cada vez que se ejecuta get(). Una condición utilizada por filter necesita Predicate<T>, aunque una Function<T, Boolean> también podría calcular un valor booleano en otro código.

UnaryOperator<T> es una función de T al mismo T. BinaryOperator<T> combina dos valores de tipo T en un T. BiFunction<T, U, R>, BiPredicate<T, U> y BiConsumer<T, U> reciben dos entradas. Las formas para tipos primitivos evitan el empaquetado: IntPredicate.test(int), IntBinaryOperator.applyAsInt(int, int) y ToIntFunction<T>.applyAsInt(T).

Combina reglas en un orden determinado
import java.util.function.Function;
import java.util.function.Predicate;

class Rules {
    public static void main(String[] args) {
        Predicate<String> isLong = w -> w.length() > 5;
        Predicate<String> longB = isLong.and(w -> w.startsWith("b"));
        System.out.println(longB.test("banana")); // true
        System.out.println(isLong.negate().test("kiwi")); // true

        Function<Integer, Integer> twice = x -> x * 2;
        Function<Integer, Integer> inc = x -> x + 1;
        System.out.println(twice.andThen(inc).apply(5)); // 11
        System.out.println(twice.compose(inc).apply(5)); // 12
    }
}

and requiere ambas condiciones, or requiere al menos una y negate invierte la respuesta. Predicate.not(p) también niega un predicado. andThen ejecuta primero el receptor; compose ejecuta primero su argumento. Aquí la primera cadena calcula 10 y después 11, mientras que la segunda calcula 6 y después 12.

Pasa una regla a un método
static <T> List<T> keep(List<T> list, Predicate<T> test) {
    List<T> kept = new ArrayList<>();
    for (T x : list) {
        if (test.test(x)) kept.add(x);
    }
    return kept;
}

keep(List.of(4, 9, 12, 15), n -> n % 3 == 0) da [9, 12, 15]. T permite reutilizar el método con distintos tipos de elemento; el predicado permite reutilizar el comportamiento de selección.

Para una lista modificable [3, 8, 1, 10, 6], removeIf(n -> n < 5) deja [8, 10, 6]. Después, replaceAll(n -> n * 2) da [16, 20, 12]. Estos métodos modifican la lista; forEach acepta un consumidor para acciones como imprimir. Copia el resultado de List.of(...) en un ArrayList antes de utilizar operaciones que lo modifiquen.

Para mapas, forEach((key, value) -> ...) recibe un BiConsumer. merge(word, 1, Integer::sum) almacena 1 para una palabra nueva e incrementa un recuento existente. computeIfAbsent(key, k -> new ArrayList<>()) crea un valor para una clave ausente o asociada a null y devuelve el valor resultante. Estos métodos mantienen juntas la búsqueda y la regla de actualización.

Puntos clave

  • Elige una interfaz funcional según la forma de sus entradas y su resultado.
  • andThen ejecuta primero el receptor; compose ejecuta primero su argumento.
  • removeIf comprueba un predicado; replaceAll aplica un operador unario.
  • merge combina un valor existente de un mapa con un valor proporcionado.
  • computeIfAbsent puede crear y almacenar un valor ausente en un mapa.
  • Las especializaciones para tipos primitivos trabajan directamente con valores primitivos.

2.3 Captura variables y escribe referencias a métodos

Una expresión lambda puede utilizar información del método que la contiene. Cuando su cuerpo solo llama a un método existente, una referencia a método suele expresar el mismo comportamiento de forma más directa.

Captura una variable local
int limit = 10;
Predicate<Integer> small = n -> n < limit;
System.out.println(small.test(7)); // true

La expresión lambda captura limit. Una variable local capturada debe ser final o efectivamente final: se le asigna un valor una vez y nunca se reasigna. Añadir limit = 20; en cualquier lugar después de esa primera asignación hace que la captura no compile, incluso si la reasignación aparece antes de la expresión lambda.

La regla se aplica a la variable local, no a todo el estado de su objeto. Esto compila:

StringBuilder log = new StringBuilder();
Runnable action = () -> log.append("ran");
action.run();
System.out.println(log); // ran

log sigue haciendo referencia al mismo constructor de cadenas. También se permite actualizar un campo del objeto que contiene la expresión, pero los cambios ocultos pueden dificultar el seguimiento del comportamiento.

Intentar numbers.forEach(n -> total += n) con una variable local int total = 0 falla porque cambia la variable capturada. Utiliza un bucle cuando la tarea sea una actualización, o calcula un resultado directamente con numbers.stream().mapToInt(n -> n).sum().

Lee las cuatro formas de referencia

Una referencia a método utiliza ::, sin lista de argumentos. La interfaz funcional de destino proporciona las entradas y el resultado esperado.

TipoReferenciaExpresión lambda equivalente
Método estáticoInteger::parseInts -> Integer.parseInt(s)
Instancia vinculadaSystem.out::printlnx -> System.out.println(x)
Instancia sin receptor vinculadoString::lengths -> s.length()
ConstructorArrayList::new() -> new ArrayList<>()

En la referencia vinculada, el receptor ya está fijado: el PrintStream al que hace referencia System.out. En la referencia sin receptor vinculado, el primer argumento funcional se convierte en el receptor. Así, String::compareToIgnoreCase equivale a (a, b) -> a.compareToIgnoreCase(b): la primera cadena es el receptor y la segunda es el argumento del método.

Ajusta la referencia a un destino
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

class References {
    public static void main(String[] args) {
        Function<String, Integer> parse = Integer::parseInt;
        Function<String, Integer> length = String::length;
        Supplier<List<String>> fresh = ArrayList::new;
        System.out.println(parse.apply("42")); // 42
        System.out.println(length.apply("stream")); // 6
        System.out.println(fresh.get()); // []
    }
}

Una referencia a constructor también puede recibir argumentos. Student::new corresponde a una Function<String, Student> cuando un constructor compatible recibe un String. El destino determina qué constructor sobrecargado se selecciona.

Student::getName hace referencia a un método de acceso de instancia sobre cada estudiante proporcionado; no requiere un método de acceso estático. Escribe una expresión lambda cuando haya trabajo adicional, como en s -> s.getPoints() * 2 o w -> w.length() > 5. Escribir String::length() no es válido: la referencia nombra el comportamiento sin invocarlo en ese punto.

Puntos clave

  • Una variable local capturada debe ser final o efectivamente final.
  • Una referencia final puede seguir haciendo referencia a un objeto mutable.
  • Una referencia vinculada fija su receptor; una referencia sin receptor vinculado lo toma del primer argumento.
  • Las referencias a constructores utilizan ClassName::new.
  • Las referencias a métodos no tienen paréntesis de invocación.
3

Ordena objetos con Comparator

Los mismos objetos pueden necesitar varios órdenes útiles: nombres alfabéticamente, puntos de mayor a menor o grupos con un criterio de desempate por puntuación. Los comparadores mantienen estas reglas separadas del orden natural de la clase. Una comparación correcta debe mantener la consistencia de su signo y de las relaciones de orden. Los métodos de construcción hacen visibles las claves, pero la posición de una inversión y el manejo de empates o valores nulos siguen determinando el resultado.

3.1 Escribe un comparador y utilízalo para ordenar

El orden natural de un objeto no siempre es el que necesita una tarea. Un Comparator<T> independiente te permite ordenar los mismos objetos según distintas reglas sin modificar su clase.

Lee el signo de compare

compare(a, b) devuelve un número negativo cuando a va primero, cero cuando ambos ocupan la misma posición y un número positivo cuando a va después de b. Solo importa el signo. Un comparador no devuelve un valor booleano.

Comparable<T> pertenece a la clase y proporciona su orden natural mediante compareTo. Un comparador es un valor independiente que se pasa a un método de ordenación. Las cadenas tienen un orden natural de caracteres, pero un comparador puede ordenarlas por longitud.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class SortWords {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>(List.of("ccc", "a", "bb"));
        Comparator<String> byLength =
            (a, b) -> Integer.compare(a.length(), b.length());
        words.sort(byLength);
        System.out.println(words); // [a, bb, ccc]
    }
}

El comparador extrae las longitudes y compara los números. words.sort modifica la lista modificable y devuelve void; no devuelve una lista nueva. Ordenar un resultado de List.of(...) lanza UnsupportedOperationException, así que el ejemplo primero crea una copia ArrayList.

Compara números de forma segura

Utiliza Integer.compare o Double.compare. La resta puede invertir el signo por desbordamiento: 2_000_000_000 - (-2_000_000_000) se evalúa como −294 967 296 en un int. Esto coloca incorrectamente el número mayor primero en orden ascendente. Integer.compare(2_000_000_000, -2_000_000_000) devuelve correctamente 1.

Convertir explícitamente una diferencia de números de doble precisión a int también pierde información. (int) (1.2 - 0.7) es 0, aunque el primer valor es mayor. Double.compare(1.2, 0.7) indica un resultado positivo.

Mantén la consistencia del orden

Invertir las entradas debe invertir el signo de la comparación. Si a va antes que b y b antes que c, a debe ir antes que c. Los valores cuya comparación da cero deben ocupar la misma posición con respecto a cualquier otro valor. Incumplir estas reglas puede producir un orden incorrecto o hacer que la ordenación lance IllegalArgumentException con «¡El método de comparación incumple su contrato general!». Devolver solo 0 o 1 no puede expresar ambas direcciones.

Las ordenaciones de listas y las ordenaciones de arreglos de objetos basadas en comparadores de Java son estables: los elementos empatados conservan su orden original. Para los estudiantes Ada 85, Ben 72, Chen 85, Dara 90 y Eli 64, ordenar por puntos ascendentes da Eli, Ben, Ada, Chen, Dara. Ada sigue antes que Chen porque empatan y Ada aparecía primero.

Los ejemplos de estudiantes utilizan esta clase y una lista modificable:

class Student {
    private final String name;
    private final String group;
    private final int points;

    Student(String name, String group, int points) {
        this.name = name;
        this.group = group;
        this.points = points;
    }
    String getName() { return name; }
    String getGroup() { return group; }
    int getPoints() { return points; }
}
List<Student> students = new ArrayList<>(List.of(
    new Student("Ada", "A", 85), new Student("Ben", "B", 72),
    new Student("Chen", "A", 85), new Student("Dara", "B", 90),
    new Student("Eli", "A", 64)));
students.sort(Comparator.comparingInt(Student::getPoints));
students.forEach(s -> System.out.println(s.getName()));
// Eli, Ben, Ada, Chen, Dara, each on a separate line
Elige cómo ordenar

list.sort(cmp) y Collections.sort(list, cmp) modifican una lista modificable. list.sort(null) solicita el orden natural. Arrays.sort(array, cmp) acepta un arreglo de objetos como Integer[]; un int[] no tiene una sobrecarga que acepte un comparador.

El orden natural de cadenas compara unidades de código UTF-16. Las mayúsculas A–Z preceden a las minúsculas a–z, así que ["banana", "Cherry", "apple"] se convierte en [Cherry, apple, banana]. String.CASE_INSENSITIVE_ORDER da [apple, banana, Cherry] para esas palabras. Elige la regla de texto explícitamente cuando las mayúsculas y minúsculas no deban decidir el orden.

Puntos clave

  • Un comparador devuelve un resultado de comparación con signo, no un valor booleano.
  • Comparable proporciona el orden natural; un comparador proporciona un orden independiente.
  • Utiliza métodos de comparación numérica para evitar el desbordamiento y el truncamiento.
  • Las ordenaciones estables conservan el orden original de los elementos empatados.
  • La ordenación de listas modifica una lista modificable y devuelve void.
  • Los arreglos de tipos primitivos no pueden recibir un comparador.

3.2 Combina comparadores con comparing, thenComparing y reversed

Un orden con varias claves se lee mejor como una secuencia de decisiones: compara la clave principal y después compara una segunda clave solo si la primera empata. Los métodos de construcción de comparadores expresan esas decisiones directamente.

Extrae una clave y resuelve empates

Para un Student con getName(), getGroup() y getPoints(), Comparator.comparing(Student::getName) ordena por nombre. comparingInt(Student::getPoints) extrae un int sin empaquetarlo.

Comparator<Student> ranking =
    Comparator.comparingInt(Student::getPoints)
        .reversed()
        .thenComparing(Student::getName);

Esto coloca primero los puntos más altos y después ordena los nombres de forma ascendente cuando los puntos son iguales. Para Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64), da Dara, Ada, Chen, Ben, Eli. La comparación de nombres solo se ejecuta cuando hay un empate en puntos.

Coloca reversed en el nivel adecuado

reversed() invierte todo lo construido antes. Invertir la clasificación después de thenComparing también invertiría los nombres dentro de cada empate de puntuación. Para ordenar los grupos de forma ascendente y los puntos de forma descendente dentro de cada grupo, invierte solo el comparador de puntos:

Comparator<Student> byGroup =
    Comparator.comparing(Student::getGroup)
        .thenComparing(
            Comparator.comparingInt(Student::getPoints).reversed());

El resultado es Ada, Chen, Eli, Dara, Ben: el grupo A precede a B; dentro de A, 85 precede a 64. Poner .reversed() al final invertiría tanto el orden de los grupos como el de los puntos respecto a la cadena anterior.

Elige reglas para texto y valores nulos

Comparator.naturalOrder() sigue Comparable; reverseOrder() lo invierte. Proporciona un comparador de claves cuando una clave necesite un manejo especial, como en Comparator.comparing(Student::getName, String.CASE_INSENSITIVE_ORDER).

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class SortNullable {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>();
        words.add("b");
        words.add(null);
        words.add("a");
        words.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
        System.out.println(words); // [null, a, b]
    }
}

Envuelve la comparación en el nivel donde aparece null. nullsLast alrededor de todo el comparador de estudiantes maneja objetos de estudiante nulos. Un extractor de nombres que devuelve null necesita un comparador que admita valores nulos para la propia clave de nombre. Los comparadores de orden natural y los comparadores de claves ordinarios no manejan automáticamente las claves nulas.

Ordena entradas de mapa y resuelve la inferencia

Un HashMap no garantiza un orden de encuentro. Procesa sus entradas como flujo y elige un orden explícitamente:

stock.entrySet().stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
    .forEach(System.out::println);

Para rice=12, oats=3 y flour=7, esto imprime rice=12, flour=7, oats=3, en líneas separadas. Ordena las entradas del flujo sin reorganizar stock. Añade thenComparing(Map.Entry.comparingByKey()) si los valores empatados necesitan un orden predecible por clave.

El <String, Integer> explícito ayuda a la inferencia en la llamada encadenada. Del mismo modo, Comparator.comparing(s -> s.getName()).reversed() puede inferir s como Object. Utiliza Student::getName o (Student s) -> s.getName() para proporcionar el tipo que falta. Un comparador con nombre puede reutilizarse para ordenar listas, en sorted, min y max de flujos, o en un mapa o conjunto ordenado.

Puntos clave

  • comparing extrae una clave; los métodos de construcción para tipos primitivos evitan empaquetar las claves numéricas.
  • thenComparing solo se utiliza cuando la comparación anterior da un empate.
  • reversed() invierte el comparador ya construido.
  • Maneja los valores nulos donde aparecen: en objetos completos o en claves extraídas.
  • Ordenar un flujo de entradas no reordena su mapa de origen.
4

Procesa colecciones con flujos

Una secuencia de procesamiento de flujo convierte una colección en una serie de decisiones de procesamiento. Sigue los valores y sus tipos cuando un filtro selecciona, una transformación los modifica y una operación terminal crea la respuesta. La ejecución diferida significa que algunos elementos pueden no llegar a examinarse, mientras que la ordenación necesita toda la entrada. Los flujos numéricos y los valores opcionales hacen explícitos los tipos de resultado, incluido lo que sucede cuando no queda ningún valor o un total numérico es demasiado grande.

4.1 Construye una secuencia de procesamiento de flujo y comprende cómo se ejecuta

Un flujo describe cómo procesar una secuencia de valores. Lee de una fuente, pasa los elementos por operaciones intermedias y termina con una operación terminal que produce un resultado o realiza una acción.

Identifica las tres partes
import java.util.List;

class CountPasses {
    public static void main(String[] args) {
        List<Integer> points = List.of(72, 45, 90, 58, 81);
        long passed = points.stream()
            .filter(p -> p >= 60)
            .count();
        System.out.println(passed); // 3
        System.out.println(points); // [72, 45, 90, 58, 81]
    }
}

points.stream() es la fuente. filter es una operación intermedia: devuelve otro flujo. count es la operación terminal: devuelve un long. Los valores 72, 90 y 81 superan el filtro. La lectura mediante el flujo no ha eliminado 45 ni 58 de points.

Elige una fuente

Las colecciones ofrecen stream(). Un mapa ofrece flujos mediante entrySet(), keySet() o values(). Arrays.stream(array) crea un flujo de elementos de un arreglo; un int[] produce un IntStream. Stream.of("a", "b", "c") proporciona los valores enumerados.

IntStream.range(1, 5) contiene 1, 2, 3, 4, con el extremo final excluido. rangeClosed(1, 5) incluye 5 y suma 15. Stream.iterate(1, n -> n * 2) no tiene un extremo fijo; .limit(5).toList() recopila [1, 2, 4, 8, 16]. Un resultado acotado es esencial al consumir esa secuencia infinita.

Sigue la ejecución diferida

Las operaciones intermedias son diferidas. Escribir simplemente names.stream().map(String::toUpperCase); no realiza las transformaciones. Una operación terminal inicia el procesamiento y los elementos se consumen solo cuando se necesitan.

String first = java.util.stream.Stream.of("one", "two", "three", "four")
    .filter(w -> w.length() > 3)
    .map(String::toUpperCase)
    .findFirst()
    .orElse("none");
System.out.println(first); // THREE

Las dos primeras palabras no superan el filtro. "three" lo supera, se convierte en "THREE" y satisface findFirst. La última palabra no es necesaria. Esto es cortocircuito: la respuesta se conoce antes de leer toda la fuente.

sorted necesita toda la entrada antes de poder emitir el primer valor ordenado. Algunas otras operaciones con estado, como distinct secuencial, pueden emitir valores a medida que llega la entrada. La posición de una operación en la cadena afecta a la cantidad de trabajo necesaria.

Crea un flujo nuevo para otro resultado

Después de una operación terminal, el flujo queda consumido. Reutilizar ese flujo suele lanzar IllegalStateException con «el flujo ya se ha utilizado o cerrado». Vuelve a llamar a points.stream() para otra secuencia de procesamiento. Una colección es almacenamiento reutilizable; un flujo es una ruta de procesamiento.

Crear un flujo no copia sus elementos. Mantén la fuente sin cambios mientras la procesas. map produce valores transformados; no los escribe de nuevo en la lista. Para modificar una lista de forma intencionada, métodos como replaceAll y removeIf expresan esa tarea directamente. Utiliza una operación por línea para que el orden de procesamiento y el tipo de resultado sigan siendo visibles.

Puntos clave

  • Una secuencia de procesamiento tiene una fuente, operaciones intermedias y una operación terminal.
  • Las operaciones intermedias son diferidas.
  • El cortocircuito puede evitar leer el resto de una fuente.
  • sorted debe ver toda la entrada antes de emitir una salida ordenada.
  • Un flujo consumido debe sustituirse por un flujo nuevo de la fuente.
  • Crear un flujo no copia ni reescribe su fuente.

4.2 Filtra, transforma y reordena elementos

Sigue tanto los valores como su tipo después de cada operación: una expresión lambda posterior recibe el elemento actual del flujo, no el objeto original de la fuente.

Selecciona antes de transformar

filter conserva los elementos que cumplen un predicado. map sustituye cada elemento por un valor calculado. Dados estudiantes con métodos de acceso al nombre y a los puntos, la selección por puntos debe realizarse antes de transformar los elementos en nombres:

List<String> names = students.stream()
    .filter(s -> s.getPoints() >= 80)
    .map(Student::getName)
    .sorted()
    .toList();

Para Ada 85, Ben 72, Chen 85, Dara 90 y Eli 64, esto produce [Ada, Chen, Dara]. Antes de map, el tipo de elemento es Student. Después, el tipo es String, por lo que ya no está disponible un método de acceso a los puntos.

Pasa a un flujo de tipos primitivos

Stream<Integer> contiene referencias a objetos envoltorio y no tiene método sum(). mapToInt(Integer::intValue) produce un IntStream, que proporciona operaciones numéricas. Del mismo modo, mapToInt(String::length) mide cadenas como longitudes primitivas.

import java.util.List;
import java.util.stream.IntStream;

class Windows {
    public static void main(String[] args) {
        List<Integer> window = IntStream.rangeClosed(1, 10)
            .skip(2)
            .limit(3)
            .boxed()
            .toList();
        System.out.println(window); // [3, 4, 5]
    }
}

skip(2) elimina 1 y 2 del flujo. limit(3) conserva los tres valores siguientes. Invertir esas llamadas deja solo 3. boxed() vuelve a Stream<Integer> para que los elementos puedan recopilarse como objetos. mapToObj crea otros objetos a partir de valores primitivos; los flujos de long y double tienen operaciones de conversión correspondientes.

Elimina duplicados y elige un orden

distinct utiliza la igualdad de objetos. ["map", "filter", "map", "sorted", "filter"] se convierte en [map, filter, sorted], y después sorted() da [filter, map, sorted]. Para tus propias clases, implementa equals y hashCode de forma consistente si los valores de campo coincidentes deben contar como duplicados.

sorted() utiliza el orden natural. Proporciona un comparador para otro orden o para objetos sin orden natural. La ordenación de un flujo ordenado es estable. Ordenar los cinco estudiantes por puntos descendentes y después tomar dos selecciona a Dara y Ada: Ada precede a Chen en la lista original con su puntuación empatada. Llamar a limit(2) antes de ordenar examinaría solo los dos primeros estudiantes originales.

Aplana secuencias anidadas
List<Integer> all = List.of(
        List.of(1, 2), List.of(3), List.of(4, 5, 6))
    .stream()
    .flatMap(List::stream)
    .toList();
System.out.println(all); // [1, 2, 3, 4, 5, 6]

Cada lista interna se convierte en un flujo, y flatMap une sus elementos en un solo flujo. Sustituir aquí flatMap por map da un Stream<Stream<Integer>>: un objeto de flujo por cada lista interna, sin aplanarla.

peek pasa un valor sin modificarlo y puede imprimirlo para depurar. Su acción solo se ejecuta para los elementos realmente recorridos, y una implementación puede omitir una etapa innecesaria, por ejemplo al optimizar count. No lo utilices para realizar trabajo necesario. Para obtener un único valor máximo, utiliza max(comparator) en lugar de ordenar todos los elementos y tomar uno.

Puntos clave

  • filter conserva el tipo de elemento; map puede cambiarlo.
  • Los flujos de tipos primitivos proporcionan operaciones numéricas que no están en Stream<Integer>.
  • El orden de skip, limit y la ordenación cambia los elementos seleccionados.
  • distinct sigue la igualdad; la ordenación de un flujo ordenado conserva los empates.
  • flatMap aplana los flujos producidos a partir de los elementos de entrada.
  • peek sirve para inspeccionar, no para efectos necesarios del programa.

4.3 Reduce un flujo a un único resultado

Elige la operación terminal según el resultado que necesites: un recuento, un número, un valor de verdad, un elemento o una combinación general. La entrada vacía importa siempre que no se pueda elegir un valor de resultado.

Calcula un número

count() devuelve un long. sum() de un flujo de tipos primitivos devuelve un número del tipo primitivo del flujo. Para los puntos [72, 45, 90, 58, 81], filtrar para obtener al menos 60 y después sumar da 243. La media de esos tres valores es 81,0.

import java.util.List;

class PointResults {
    public static void main(String[] args) {
        List<Integer> points = List.of(72, 45, 90, 58, 81);
        int sum = points.stream().filter(p -> p >= 60)
            .mapToInt(Integer::intValue).sum();
        double average = points.stream().filter(p -> p >= 60)
            .mapToInt(Integer::intValue).average().orElse(0.0);
        System.out.println(sum);     // 243
        System.out.println(average); // 81.0
    }
}

La segunda secuencia de procesamiento utiliza un flujo nuevo. Una suma vacía es 0, pero una media vacía no tiene valor. average() devuelve OptionalDouble, y orElse(0.0) proporciona un valor alternativo elegido. Ese valor predeterminado es una decisión del programa, no la media matemática de un conjunto vacío.

Una suma int puede desbordarse. Sumar dos valores de 2 000 000 000 produce −294 967 296 como int. Amplía el tipo antes de sumar: mapToInt(...).asLongStream().sum() da 4 000 000 000 como long.

Maneja un valor o ninguno

Un Optional<T> contiene un valor o ningún valor. min, max, findFirst, findAny y la reducción sin elemento neutro de un flujo de objetos devuelven valores opcionales. points.stream().max(Integer::compare) da Optional[90]; imprimir el contenedor incluye el texto que envuelve el valor.

Utiliza orElse para un valor alternativo, orElseGet para un proveedor alternativo, ifPresent para una acción o map para transformar un valor presente. Filtrar para encontrar un estudiante con más de 95 puntos, transformar la primera coincidencia en un nombre y aplicar orElse("nobody") da "nobody" para los datos de los cinco estudiantes. Llamar a get() o orElseThrow() sobre un valor opcional vacío lanza NoSuchElementException.

Haz una pregunta verdadera o falsa

anyMatch pregunta si al menos un valor cumple la condición, allMatch si todos la cumplen y noneMatch si ninguno la cumple. Pueden detenerse en cuanto se conoce la respuesta. Con una entrada vacía, anyMatch es falso, mientras que allMatch y noneMatch son verdaderos: no hay ningún valor que incumpla ninguna de las dos afirmaciones universales.

Combina con un elemento neutro

Stream.of(3, 5, 7).reduce(0, Integer::sum) acumula 0, 3, 8, 15. Para la multiplicación, empieza con 1 y utiliza (a, b) -> a * b para obtener 105. Un elemento neutro deja un valor sin cambios bajo la operación; 10 no es un elemento neutro de la suma y desplazaría incorrectamente el total a 25. El acumulador debe ser asociativo para que la agrupación no cambie el resultado.

Sin elemento neutro, reduce(Integer::max) da Optional[7] y un valor opcional vacío para una entrada vacía. forEach, en cambio, realiza una acción y devuelve void; es adecuado para imprimir. Utiliza una reducción o recopilación cuando la tarea sea producir un valor de resultado.

Puntos clave

  • Los recuentos utilizan long; los resultados de flujos numéricos siguen su tipo primitivo.
  • Una suma vacía es cero; una media o un valor extremo de una entrada vacía no tiene valor.
  • Utiliza las operaciones para el caso vacío de un valor opcional antes de leer su valor.
  • allMatch y noneMatch son verdaderos con una entrada vacía; anyMatch es falso.
  • Una reducción necesita un elemento neutro y un acumulador asociativo.
  • Amplía a long antes de sumar valores cuyo total supere el rango de int.
5

Recopila y utiliza los resultados de los flujos

Un resultado útil de un flujo puede ser una lista, un conjunto, un mapa, una cadena o un resumen para cada grupo. El recolector decide qué se almacena y qué sucede cuando se repiten las claves. La agrupación combina reglas de selección con resúmenes posteriores; la división en particiones produce dos grupos booleanos. Una tarea completa puede después consultar el mapa resultante. Comparar esa secuencia de procesamiento con un bucle te ayuda a elegir código fácil de leer y a mantener la construcción del resultado separada de los cambios en el estado externo.

5.1 Recopila resultados en listas, conjuntos, mapas y cadenas

Un recolector convierte un flujo en una estructura de resultado útil. Decide qué debe admitir el resultado: duplicados, orden, cambios posteriores o claves de mapa duplicadas. Los distintos recolectores ofrecen garantías diferentes.

Elige una lista o un conjunto

stream.toList() devuelve una lista no modificable en orden de encuentro. Añadir o eliminar elementos lanza UnsupportedOperationException. collect(Collectors.toList()) también recopila una lista, pero no garantiza una implementación concreta ni que sea modificable. Para una lista que vayas a modificar, solicítala explícitamente:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Gather {
    public static void main(String[] args) {
        List<String> words = Stream.of("a", "b", "a")
            .collect(Collectors.toCollection(ArrayList::new));
        words.add("c");
        System.out.println(words); // [a, b, a, c]
        System.out.println(Stream.of("a", "b", "a")
            .collect(Collectors.toSet()).size()); // 2
    }
}

toSet elimina duplicados y no garantiza ningún orden. toCollection(TreeSet::new) solicita un conjunto ordenado. Una lista conserva elementos duplicados; elige un conjunto cuando el resultado represente valores distintos.

Une texto

Collectors.joining(", ") une cadenas con un separador, sin un separador final. Su forma de tres argumentos también añade un prefijo y un sufijo:

String names = Stream.of("Ada", "Chen", "Dara")
    .collect(Collectors.joining(", ", "[", "]"));
System.out.println(names); // [Ada, Chen, Dara]

Los elementos deben ser valores CharSequence, normalmente cadenas. Transforma estudiantes con Student::getName o números con String::valueOf antes de unirlos. La unión devuelve una cadena, no una lista que simplemente se imprime de forma similar.

Elige claves y valores de mapa

Collectors.toMap(keyFunction, valueFunction) calcula una entrada a partir de cada elemento. Con los cinco estudiantes Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64), asociar nombres con puntos da cinco entradas como Ada=85 y Eli=64.

Las claves de grupo son diferentes: varios estudiantes tienen el mismo grupo. toMap(Student::getGroup, Student::getPoints) con dos argumentos lanza IllegalStateException cuando se repite una clave, incluso si ambos valores son 85. Un mapa necesita un único valor resultante por clave, y esta forma no proporciona ninguna regla para elegirlo.

Proporciona un tercer argumento para combinar duplicados:

Map<String, Integer> totals = students.stream()
    .collect(Collectors.toMap(
        Student::getGroup, Student::getPoints, Integer::sum));

El grupo A recibe 85 + 85 + 64 = 234; el grupo B recibe 72 + 90 = 162. Sustituir Integer::sum por Math::max conserva la puntuación más alta por grupo, A=85 y B=90. Utiliza la regla que responda a la tarea, no un primer valor arbitrario.

toMap no garantiza ningún orden concreto del mapa. Un cuarto argumento puede solicitar uno, por ejemplo TreeMap::new para claves ordenadas. toMap(Student::getName, Student::getPoints, (a, b) -> a, TreeMap::new) conserva el primer valor cuando hay nombres duplicados y ordena por nombre. Un recolector también evita crear una lista externa y modificarla en forEach; el resultado lo construye la operación diseñada para recopilarlo.

Puntos clave

  • Stream.toList() es no modificable.
  • Collectors.toList() no garantiza una implementación modificable.
  • Utiliza toCollection cuando se requiera un tipo de colección concreto.
  • joining reúne texto con un separador, un prefijo y un sufijo opcionales.
  • toMap con dos argumentos rechaza claves duplicadas, incluso con valores duplicados iguales.
  • Una función de combinación resuelve las claves de mapa repetidas.
  • Solicita una fábrica de mapas cuando su orden importe.

5.2 Agrupa y divide datos en particiones con recolectores

Un resultado agrupado responde a una pregunta para cada clave. Una partición responde a una pregunta de sí o no con dos grupos. Un recolector posterior determina qué almacena cada grupo.

Agrupa por una clave calculada

groupingBy(classifier) produce un mapa que asocia cada clave encontrada con una lista de sus elementos. Agrupar ["fig", "kiwi", "pear", "plum", "apple", "mango", "banana"] por longitud de palabra asigna a la longitud 4 la lista [kiwi, pear, plum]. Las longitudes ausentes no tienen entrada; buscarlas devuelve null, así que utiliza getOrDefault(key, List.of()) cuando sea útil una lista vacía.

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

class WordGroups {
    public static void main(String[] args) {
        List<String> words = List.of(
            "fig", "kiwi", "pear", "plum", "apple", "mango", "banana");
        Map<Integer, Long> counts = words.stream()
            .collect(Collectors.groupingBy(
                String::length, TreeMap::new, Collectors.counting()));
        System.out.println(counts); // {3=1, 4=3, 5=2, 6=1}
    }
}

La función clasificadora elige claves enteras. TreeMap::new ordena esas claves. counting() sustituye la lista de cada grupo por su tamaño como un Long. El tipo de resultado es, por tanto, Map<Integer, Long>, no un mapa de enteros a enteros.

Cambia lo que almacena un grupo

Con los estudiantes Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64), agrupar por Student::getGroup ofrece estas posibilidades:

Recolector posteriorValor bajo ATipo de valor
counting()3Long
summingInt(Student::getPoints)234Integer
averagingInt(Student::getPoints)78,0Double
mapping(Student::getName, Collectors.toList())[Ada, Chen, Eli]List<String>

Un recolector posterior trabaja dentro de cada grupo. Para A, la media es 234 dividido entre 3, o 78,0; la media de B es 81,0. maxBy y minBy almacenan un Optional que contiene el valor extremo de cada grupo. groupingBy simple no garantiza un tipo de mapa ni un orden de claves concretos; su forma con fábrica de mapas permite elegirlos explícitamente.

Divide en dos respuestas
Map<Boolean, List<String>> partition = students.stream()
    .collect(Collectors.partitioningBy(
        s -> s.getPoints() >= 80,
        Collectors.mapping(Student::getName, Collectors.toList())));

La lista verdadera es [Ada, Chen, Dara] y la lista falsa es [Ben, Eli]. Ambas claves booleanas están presentes, aunque uno de los lados no tenga estudiantes. Con counting() como recolector posterior, los lados vacíos contienen 0 como un Long. En cambio, groupingBy con una función clasificadora booleana solo crea las claves que aparecen.

Procesa el mapa que has creado

Un resultado agrupado es un mapa ordinario. Recórrelo con forEach((group, value) -> ...) o procesa sus entradas como flujo. Para las medias A=78,0 y B=81,0, averages.entrySet().stream().max(Map.Entry.comparingByValue()) devuelve un Optional<Map.Entry<String, Double>> que contiene B=81,0. El valor opcional envuelve la entrada ganadora; no es el valor simple 81,0. Un paso independiente puede transformar esa entrada en su clave o valor y manejar un mapa vacío.

Puntos clave

  • Una función clasificadora elige las claves de grupo; la agrupación simple almacena listas de elementos.
  • Un recolector posterior cambia la estructura del resultado de cada grupo.
  • counting produce Long; los recolectores de medias producen Double.
  • Una división en particiones siempre contiene ambas claves booleanas.
  • Una clave de agrupación ausente devuelve null, no un grupo vacío.

5.3 Elige entre flujos y bucles para resolver una tarea completa

Una tarea completa de datos suele tener dos etapas: resumir la entrada y después consultar el resumen. Elige flujos cuando estas etapas se lean de forma natural como operaciones estándar, y bucles cuando el control explícito haga el trabajo más claro.

Calcula la media de las lecturas y selecciona ciudades

Supón que un Reading tiene getCity() y getCelsius(). Las lecturas son Oslo 4,5, Lima 19,0, Oslo 6,0, Cairo 27,5 y Lima 21,0, en grados Celsius.

class Reading {
    private final String city;
    private final double celsius;
    Reading(String city, double celsius) {
        this.city = city;
        this.celsius = celsius;
    }
    String getCity() { return city; }
    double getCelsius() { return celsius; }
}

Crea la lista de entrada antes de cualquiera de las dos implementaciones:

List<Reading> readings = List.of(
    new Reading("Oslo", 4.5), new Reading("Lima", 19.0),
    new Reading("Oslo", 6.0), new Reading("Cairo", 27.5),
    new Reading("Lima", 21.0));
Map<String, Double> averages = readings.stream()
    .collect(Collectors.groupingBy(
        Reading::getCity,
        TreeMap::new,
        Collectors.averagingDouble(Reading::getCelsius)));

List<String> warmCities = averages.entrySet().stream()
    .filter(e -> e.getValue() > 15)
    .map(Map.Entry::getKey)
    .toList();

El primer resultado es {Cairo=27.5, Lima=20.0, Oslo=5.25}. La media de Oslo es (4.5 + 6.0) / 2, o 5,25. La de Lima es 20,0. El segundo resultado es [Cairo, Lima]; el TreeMap proporciona un orden ascendente de ciudades. Filtra las medias después de agrupar: filtrar primero las lecturas individuales responde a una pregunta distinta.

Para la lectura individual más alta, utiliza readings.stream().max(Comparator.comparingDouble(Reading::getCelsius)). Devuelve un valor opcional que contiene la lectura de Cairo, no necesariamente la ciudad con la media más alta. Estas tareas pueden tener respuestas diferentes con otros datos.

Expresa el mismo cálculo con bucles
Map<String, List<Double>> byCity = new TreeMap<>();
for (Reading r : readings) {
    byCity.computeIfAbsent(r.getCity(), k -> new ArrayList<>())
        .add(r.getCelsius());
}
Map<String, Double> averages = new TreeMap<>();
for (Map.Entry<String, List<Double>> e : byCity.entrySet()) {
    double sum = 0;
    for (double value : e.getValue()) sum += value;
    averages.put(e.getKey(), sum / e.getValue().size());
}
System.out.println(averages);
// {Cairo=27.5, Lima=20.0, Oslo=5.25}

Estas son implementaciones alternativas del mismo resultado; cada grupo no está vacío porque una lectura lo creó. El flujo expresa la agrupación y la media en un solo recolector. Los bucles muestran las listas intermedias y la suma.

Mantén predecible el comportamiento del procesamiento

Una expresión lambda de una secuencia de procesamiento debe calcular a partir de su entrada sin modificar el estado externo. Para los puntos [72, 45, 90, 58, 81], filter(p -> p >= 60).toList() produce directamente [72, 90, 81]. Añadir elementos a una lista externa dentro de forEach oculta esa tarea de construcción del resultado en una acción.

No modifiques una fuente ArrayList dentro de su secuencia de procesamiento: añadir elementos desde un forEach puede lanzar ConcurrentModificationException. Primero recopila los elementos nuevos por separado y después añádelos cuando termine el procesamiento. Las reglas de tipos de Java pueden permitir la mutación mediante una referencia final capturada; eso no hace que la mutación sea adecuada dentro de un flujo.

Elige el flujo de control más claro

Utiliza un bucle cuando necesites índices adyacentes, una condición de parada complicada o varias actualizaciones coordinadas. Utiliza flujos para una selección, transformación y recopilación fáciles de leer. La sintaxis de flujos por sí sola no garantiza velocidad. Da nombre a un predicado o comparador largo y divide una cadena larga en un resultado intermedio significativo. Para depurar, inspecciona con peek o recopila una etapa en una lista e imprímela; continúa desde un flujo nuevo de esa lista en lugar de reutilizar una secuencia consumida.

Puntos clave

  • Agrupa antes de filtrar cuando la condición se refiera a medias de grupo.
  • Una lectura individual máxima y una media de grupo máxima responden a preguntas distintas.
  • Un recolector expresa directamente la tarea de construir el resultado.
  • Mantén el comportamiento de la secuencia de procesamiento libre de actualizaciones externas y de modificaciones de la fuente.
  • Utiliza bucles cuando los índices o el control coordinado hagan más clara la tarea.
  • Inspecciona una etapa recopilada y utiliza un flujo nuevo para el trabajo posterior.

Ejemplos de Genéricos y expresiones lambda en Java

5 Lecciones Interactivas

1

Construye una API reutilizable con seguridad de tipos

17 pasos
Sigue una caja tipada, un método genérico y una transferencia entre listas para ver qué puede comprobar Java antes de la ejecución.
  1. 1
    IntroducciónConserva la relación entre tipos

    Un contenedor reutilizable necesita recordar qué pueden introducir quienes lo utilizan y qué pueden obtener. Un parámetro de tipo mantiene esa relación a lo largo de una declaración.

    Construirás una caja, trasladarás la elección de tipo a un método y después comprobarás qué cambia cuando un parámetro utiliza un comodín. En cada paso, pregúntate qué sabe el compilador sobre un valor antes de que se ejecute el programa.

  2. 2
    ExplicaciónDeclara la caja

    T nombra el tipo elegido por quien utiliza esta clase:

    class Box<T> {
        private T value;
        Box(T value) { this.value = value; }
        T get() { return value; }
        void set(T value) { this.value = value; }
    }
    

    El campo, la entrada del constructor, el resultado del método de acceso y la entrada del método modificador utilizan el mismo T. La declaración introduce un marcador de posición; todavía no se ha elegido ningún tipo concreto de almacenamiento.

  3. 3
    EjemploUtiliza un tipo concreto

    Crea una caja y lee su valor:

    Box<String> b = new Box<>("tea");
    String drink = b.get();
    b.set("water");
    

    String proporciona el argumento de tipo. El método de acceso devuelve una cadena sin conversión explícita de tipo, y el método modificador acepta cadenas. El diamante de la derecha pide a Java que infiera los argumentos. No elimina la comprobación; omitir por completo los corchetes angulares crearía un uso sin parametrizar.

  4. 4
    DesafíoPredice la escritura rechazada

    El Box<T> genérico tiene void set(T value). Después de Box<Integer> b = new Box<>(8);, ¿compilará b.set("nine")? Explica dónde falla.

    ---reveal---

    Falla en la escritura porque esta referencia espera un Integer, no un String. No se necesita ninguna lectura posterior para descubrir el error. El literal 8 puede empaquetarse en Integer; los propios argumentos de tipo deben seguir siendo tipos de referencia, así que Box<int> no es válido.

  5. 5
    ConexiónElige un tipo para cada llamada

    Un método de utilidad puede introducir su propio parámetro de tipo:

    static <T> T last(List<T> list) {
        return list.get(list.size() - 1);
    }
    

    El primer <T> declara el parámetro y el siguiente T nombra el tipo de resultado. Llamarlo sobre [4, 8, 15] devuelve el Integer 15; llamarlo sobre ["x", "y"] devuelve la cadena "y". El método requiere una lista no vacía, independientemente de sus comprobaciones de tipo.

  6. 6
    ConsejoEscribe un argumento de tipo explícito después del punto

    Normalmente Java infiere el argumento de tipo del método a partir de la lista proporcionada. Una llamada también puede indicarlo:

    String word = Lists.<String>last(words);
    

    El argumento de tipo explícito de método sigue al punto y precede a last. Un método estático dentro de una clase genérica sigue necesitando su propio parámetro: static <E> Box<E> of(E value). El T de la clase pertenece a los usos parametrizados de instancia, mientras que el método estático pertenece a la clase compartida.

  7. 7
    DesafíoRelaciona entrada y salida

    Un método de utilidad debe devolver un elemento de una List<E> sin requerir una conversión explícita de tipo escrita por quien lo llama. Un autor escribe static E first(List<E> xs). ¿Qué declaración falta?

    ---reveal---

    Añade <E> antes del tipo de retorno: static <E> E first(List<E> xs). Esto introduce un parámetro propio del método y relaciona sus elementos de entrada con su resultado. Escribir Object como resultado perdería esa relación útil en lugar de corregirla.

  8. 8
    ExplicaciónPermite la comparación

    Un T sin restricciones no tiene ninguna operación compareTo conocida. Una cota puede proporcionar esa garantía:

    <T extends Comparable<T>>
    

    Un método largest(List<T>) puede entonces mantener un T best, comparar cada x con best y sustituirlo cuando x.compareTo(best) > 0. Para 3, 11 y 7, los mejores valores son 3, después 11 y después 11. El retorno sigue siendo del propio tipo de elemento de la lista.

  9. 9
    Idea claveProtege una lista de enteros

    Un Integer es un Number, pero una List<Integer> no es una List<Number>.

    Si se permitiera esa asignación de lista, el código que utiliza la referencia de lista de números podría añadir un Double. El propietario original lo leería entonces como un Integer. La invariancia genérica impide la asignación para que tanto las lecturas como las escrituras mantengan sus garantías. ArrayList<Number> se acepta como List<Number> porque su argumento de elemento sigue coincidiendo.

  10. 10
    EjemploTransfiere en la dirección segura

    Un comodín extends permite al código leer un tipo superior conocido; un comodín super permite insertar un tipo inferior conocido.

    static <T> void append(
            List<? super T> dst,
            List<? extends T> src) {
        dst.addAll(src);
    }
    

    Con un destino List<Number> modificable y una fuente List<Integer>, Java puede elegir T como Integer. Cada elemento de la fuente es aceptable para el destino, sin afirmar que ambas listas tengan argumentos de elemento idénticos.

  11. 11
    DesafíoElige la lectura segura

    Un parámetro tiene el tipo List<? super Integer>. ¿Puede asignarse su primer elemento directamente a una variable Integer? ¿Qué tipo está garantizado en su lugar?

    ---reveal---

    Solo está garantizado Object. La lista real puede ser una List<Number> o una List<Object> y contener valores existentes distintos de enteros. Añadir un entero es seguro mediante este tipo, pero el permiso de escritura no describe todos los elementos ya almacenados.

  12. 12
    ExplicaciónSepara la comprobación de las clases en tiempo de ejecución

    El borrado de tipos sustituye los parámetros de tipo por sus cotas, utilizando Object cuando no se indica ninguna cota. Java ya ha comprobado los usos genéricos e inserta las conversiones explícitas de tipo necesarias en las lecturas.

    Un Box<String> y un Box<Integer> comparten la misma clase Box en tiempo de ejecución. No hay una clase de caja de enteros independiente que pueda identificar una comprobación en tiempo de ejecución. Los genéricos conservan las relaciones de tipo en la comprobación del código fuente, por lo que eliminan muchas conversiones explícitas visibles sin especializar la clase para cada argumento.

  13. 13
    EjemploProporciona el comportamiento de creación

    No se puede crear una instancia de un parámetro de tipo con new T(). En su lugar, pide a quien llama que proporcione una fábrica:

    static <T> T create(Supplier<T> factory) {
        return factory.get();
    }
    List<String> xs = create(ArrayList::new);
    

    La referencia elige un constructor de lista mediante el tipo de proveedor de destino. Los arreglos genéricos como new T[10] también se rechazan; una colección como ArrayList<T> suele servir para almacenar los elementos.

  14. 14
    ConsejoSigue la advertencia de tipo sin parametrizar

    Una List sin parametrizar puede aceptar "text" y 42 con advertencias de llamada no comprobada. Una conversión explícita posterior a (String) falla cuando llega a 42.

    Cambiar la declaración a List<String> traslada el problema a la adición no válida. Una advertencia de operación no comprobada significa que el compilador ha perdido una garantía; la ejecución aún puede tener éxito con algunos datos. Comprueba el uso sin parametrizar o la conversión que causó la advertencia, porque la lectura que finalmente falla puede estar muy lejos.

  15. 15
    DesafíoComprueba la sobrecarga tras el borrado

    Una clase declara show(Set<String> xs) y show(Set<Integer> xs). ¿Por qué Java no puede utilizar los argumentos de elemento para elegir una sobrecarga?

    ---reveal---

    Ambos tipos de parámetro se borran hasta quedar como Set, así que las declaraciones tienen la misma firma tras el borrado de tipos. Dales nombres distintos o una operación común. Una comprobación en tiempo de ejecución de Object frente a List<String> tiene una limitación relacionada: comprueba List<?> sin afirmar cuál es su argumento de elemento.

  16. 16
    DesafíoRevisa una API de transferencia reutilizable

    Una API utiliza una lista de origen sin parametrizar, convierte explícitamente cada elemento a Number y lo añade a una lista de números. Debe aceptar enteros y números de doble precisión como fuentes. ¿Qué parámetro de origen elimina esa conversión y conserva lecturas numéricas seguras?

    ---reveal---

    Utiliza List<? extends Number>. Cada elemento puede leerse como Number, y tanto las listas de enteros como las de números de doble precisión son compatibles. List<Number> rechazaría esos argumentos de lista más restringidos. El destino debe ser modificable; su tipo genérico por sí solo no puede garantizar que las escrituras tengan éxito.

  17. 17
    RepasoInterpreta una API a partir de sus garantías

    Al comprobar una declaración genérica, identifica dónde se introduce cada parámetro de tipo, qué posiciones relaciona y qué operaciones proporciona su cota.

    Para una transferencia, identifica la fuente como productora y el destino como consumidor. Para un paso de creación, busca el comportamiento de construcción proporcionado. Ante una advertencia o una operación rechazada, pregúntate si se ha eludido la información genérica o si la operación requeriría conocer un tipo de elemento en tiempo de ejecución.

2

Convierte el comportamiento en un argumento

14 pasos
Construye y combina reglas funcionales y después interpreta correctamente las variables capturadas y las referencias a métodos.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónEmpieza por la forma del comportamiento
  2. 2
    ExplicaciónIdentifica el método que hay que implementar
  3. 3
    EjemploUtiliza un bloque cuando una decisión lo requiera
  4. 4
    DesafíoCorrige el resultado que falta
  5. 5
    ExplicaciónRelaciona cuatro tareas habituales
  6. 6
    EjemploObserva el orden de composición
  7. 7
    ConexiónDeja que las colecciones ejecuten la regla
  8. 8
    DesafíoDistingue la eliminación de la selección
  9. 9
    ExplicaciónCaptura un valor sin reasignarlo
  10. 10
    EjemploIdentifica el receptor de la referencia
  11. 11
    DesafíoComprueba qué protege final
  12. 12
    EjemploCuenta con una regla de actualización de mapa
  13. 13
    DesafíoCombina el tipo de destino y la captura
  14. 14
    RepasoSigue una llamada a través de la regla
3

Construye un orden decisión a decisión

12 pasos
Sigue los signos de comparación, los empates y las inversiones antes de utilizar un comparador en listas o entradas de mapa.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónNombra el orden que necesita la tarea
  2. 2
    ExplicaciónInterpreta un resultado de comparación
  3. 3
    EjemploEvita la resta en comparaciones numéricas
  4. 4
    EjemploConserva el orden original de las puntuaciones iguales
  5. 5
    DesafíoSepara el orden natural del orden específico de la tarea
  6. 6
    ExplicaciónAñade una regla solo para empates
  7. 7
    Idea claveInvierte solo las decisiones deseadas
  8. 8
    ConsejoManeja los valores nulos en el nivel correspondiente
  9. 9
    DesafíoInterpreta la inversión final
  10. 10
    ConexiónOrdena las entradas, no el mapa
  11. 11
    DesafíoHaz predecibles las entradas de mapa empatadas
  12. 12
    RepasoComprueba el árbol de comparación
4

Sigue los valores a través de un flujo

16 pasos
Sigue los valores y tipos de una secuencia de procesamiento y después elige un resultado terminal y maneja los valores ausentes.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónSigue los valores y los tipos a la vez
  2. 2
    ExplicaciónIdentifica el resultado terminal
  3. 3
    EjemploDetente después de la primera coincidencia
  4. 4
    DesafíoEjecuta otro cálculo
  5. 5
    EjemploSelecciona estudiantes antes de transformar en nombres
  6. 6
    ConexiónPasa a las operaciones numéricas
  7. 7
    EjemploAplana antes de eliminar duplicados
  8. 8
    DesafíoInterpreta el orden de las operaciones
  9. 9
    EjemploCalcula una suma y una media
  10. 10
    ExplicaciónMantén separada la ausencia de un valor
  11. 11
    DesafíoManeja un máximo ausente
  12. 12
    Idea claveComprueba las pruebas universales con una entrada vacía
  13. 13
    EjemploElige un valor inicial neutro
  14. 14
    ConsejoAmplía el tipo antes de que ocurra el desbordamiento
  15. 15
    DesafíoCombina aplanado, selección y resultado
  16. 16
    RepasoComprueba los casos extremos del resultado
5

Construye un resumen útil de datos

15 pasos
Elige contenedores de resultados, resuelve claves duplicadas y consulta resúmenes por grupo sin actualizaciones ocultas.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónParte del resultado deseado
  2. 2
    ExplicaciónEstablece los cambios posteriores como requisito explícito
  3. 3
    EjemploResuelve colisiones con una regla de combinación
  4. 4
    DesafíoElige un valor por cada clave repetida
  5. 5
    ConexiónUne valores como texto
  6. 6
    ExplicaciónElige una clave y después un resultado de grupo
  7. 7
    EjemploResume dentro de los grupos de estudiantes
  8. 8
    Idea claveConserva ambos lados de una partición
  9. 9
    DesafíoPredice un lado vacío
  10. 10
    EjemploCalcula primero las medias por ciudad
  11. 11
    EjemploConsulta el resumen que has construido
  12. 12
    ConsejoMuestra el flujo de control cuando ayude
  13. 13
    DesafíoProtege la fuente durante el procesamiento
  14. 14
    DesafíoEncuentra el grupo ganador de forma segura
  15. 15
    RepasoRevisa toda la tarea de datos

Ejercicios de Genéricos y expresiones lambda en Java

50 Preguntas de Práctica en 5 sets

Set 1: Utiliza los fundamentos10 Preguntas de Práctica

Pregunta 1 de 10

Para un orden ascendente, ¿qué indica un resultado negativo de compare(a, b)?
  1. a va antes que b.
  2. a va después de b.
  3. a y b son objetos iguales.
  4. a es menor por exactamente una posición.
Mostrar respuestaOcultar respuesta
Respuesta: A
El signo de la comparación determina la posición relativa: un resultado negativo coloca el primer argumento antes que el segundo. Cero indica un empate en la ordenación, mientras que un resultado positivo coloca el primer argumento después del segundo. La magnitud del resultado negativo no representa una distancia ni un número de posiciones.

Pregunta 2 de 10

Un Box<T> genérico tiene T get() y void set(T value). Para Box<String> label = new Box<>("north");, ¿cuál es el tipo de resultado declarado de label.get()?
  1. Object
  2. String
  3. Box<String>
  4. CharSequence
Mostrar respuestaOcultar respuesta
Respuesta: B
El argumento de tipo String sustituye al parámetro de clase T en el resultado declarado del método de acceso. Por tanto, el compilador trata la llamada como una que devuelve una cadena, sin una conversión explícita de tipo escrita por quien la llama. El borrado de tipos no convierte el resultado a nivel de código fuente en Object; la comprobación en tiempo de compilación sigue utilizando el argumento.

Pregunta 3 de 10

Una clase de utilidad ordinaria necesita un método estático que devuelva el primer elemento de cualquier lista no vacía, conservando su tipo de elemento. ¿Qué declaración es adecuada?
  1. static E first(List<E> xs)
  2. static <E> Object first(List<Object> xs)
  3. static <E> List<E> first(E xs)
  4. static <E> E first(List<E> xs)
Mostrar respuestaOcultar respuesta
Respuesta: D
El método introduce E antes de su tipo de retorno y lo repite para los elementos de entrada y el resultado. Esto da a cada llamada su propio argumento de tipo inferido. Escribir simplemente E en las posiciones de parámetro y retorno no lo declara, así que la versión sin <E> falla en una clase de utilidad ordinaria.

Pregunta 4 de 10

Un método lee una lista numérica solo para llamar a doubleValue() sobre cada elemento. Debe aceptar tanto List<Integer> como List<Double>. ¿Qué tipo de parámetro es adecuado?
  1. List<? extends Number>
  2. List<Number>
  3. List<? super Number>
  4. List<Object>
Mostrar respuestaOcultar respuesta
Respuesta: A
Un comodín extends acepta un subtipo desconocido de Number, por lo que cada elemento leído puede tratarse como Number. List<Number> no puede aceptar List<Integer> porque los tipos genéricos son invariantes. Un comodín super permitiría tipos de elemento más amplios, pero las lecturas solo tendrían tipo Object.

Pregunta 5 de 10

Una Function<Integer, Integer> debe duplicar su entrada mediante un cuerpo de bloque. ¿Qué expresión lambda es adecuada?
  1. x -> { x * 2; }
  2. x -> { return; }
  3. x -> { return x * 2; }
  4. () -> { return x * 2; }
Mostrar respuestaOcultar respuesta
Respuesta: C
Un bloque que devuelve un valor debe devolverlo explícitamente. return x * 2; proporciona el resultado entero requerido por la función. Una expresión lambda con cuerpo de expresión podría utilizar simplemente x -> x * 2, pero añadir llaves no conserva ese resultado automático.

Pregunta 6 de 10

Un predicado debe registrar cada cadena comprobada y utilizar el umbral inicial, mientras un contador independiente llamado limit puede seguir cambiando. El código captura ambas variables locales: int limit = 4; List<String> notes = new ArrayList<>(); Predicate<String> p = s -> { notes.add(s); return s.length() > limit; }; limit++;. ¿Qué corrección conserva esos requisitos?
  1. Hacer final la referencia a la lista y seguir capturando limit, que cambia.
  2. Mover el incremento antes de la expresión lambda y seguir capturando limit.
  3. Capturar un umbral fijo independiente y conservar la misma referencia a la lista.
  4. Sustituir la lista por una no modificable y seguir capturando limit, que cambia.
Mostrar respuestaOcultar respuesta
Respuesta: C
El incremento impide capturar limit, así que copia su valor inicial en otra variable local efectivamente final y captura ese umbral. La referencia notes, que no cambia, puede seguir apuntando a una lista que recibe entradas de registro. Mover el incremento antes no restablecería la condición de efectivamente final después de la reasignación.

Pregunta 7 de 10

Un programa construye names.stream().filter(s -> s.length() > 2) y no hace nada con el flujo devuelto. ¿Cuándo se realizan las llamadas al predicado?
  1. Todas se realizan durante la llamada a stream().
  2. Todas se realizan durante la llamada a filter().
  3. Se realizan la próxima vez que se lee la lista de origen.
  4. No se realizan sin una operación terminal.
Mostrar respuestaOcultar respuesta
Respuesta: D
filter es una operación intermedia y registra una etapa de procesamiento diferida. Aquí ninguna operación terminal inicia el recorrido, por lo que no se llama al predicado. Crear un flujo no copia ni filtra de inmediato la lista de origen, y una lectura posterior de la lista sin relación con el flujo no ejecuta esta secuencia de procesamiento.

Pregunta 8 de 10

Un flujo no vacío contiene 2, 4 y 6. Se calcula un producto con reduce(0, (a, b) -> a * b). ¿Qué causa el resultado incorrecto?
  1. El acumulador debe recibir un argumento en lugar de dos.
  2. Cero no es el elemento neutro de la multiplicación y mantiene el acumulador en cero.
  3. Un flujo de enteros no puede reducirse mediante multiplicación.
  4. El primer valor de entrada debe servir siempre como elemento neutro explícito.
Mostrar respuestaOcultar respuesta
Respuesta: B
Multiplicar el cero inicial por 2 da cero, y cada multiplicación posterior sigue dando cero. Utiliza 1 como elemento neutro para que el acumulador conserve el primer valor y después calcule el producto 48. Un elemento neutro se elige para la operación, no se toma arbitrariamente de los valores de entrada.

Pregunta 9 de 10

Una lista de resultados debe permitir una llamada posterior a add. ¿Qué expresión terminal proporciona explícitamente un ArrayList?
  1. toList()
  2. collect(Collectors.toList())
  3. collect(Collectors.toCollection(ArrayList::new))
  4. collect(Collectors.toSet())
Mostrar respuestaOcultar respuesta
Respuesta: C
toCollection(ArrayList::new) solicita una implementación de lista modificable conocida. Stream.toList() es no modificable y Collectors.toList() no garantiza que sea modificable, aunque una implementación concreta permita cambios. Un recolector de conjuntos también cambia la estructura del resultado y el tratamiento de duplicados.

Pregunta 10 de 10

Una secuencia de procesamiento sobre un ArrayList<String> words llama a forEach(w -> words.add(w + "!")). ¿Cómo debe añadir el programa de forma segura una copia transformada de cada palabra original?
  1. Recopilar las copias por separado y después llamar a words.addAll(copies).
  2. Añadir peek(w -> words.add(w + "!")) antes de forEach.
  3. Sustituir forEach por map y mantener words.add dentro.
  4. Guardar words en otra variable y añadir mediante esa referencia.
Mostrar respuestaOcultar respuesta
Respuesta: A
Recopilar primero las copias permite que termine el recorrido antes de modificar la lista original. Añadir elementos a un ArrayList mientras su flujo lo lee interfiere con el recorrido y puede lanzar ConcurrentModificationException. Otra referencia sigue apuntando a la misma fuente; no separa las etapas de lectura y escritura.
Set 2: Sigue los tipos y los resultados10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un Box<Integer> contiene 12. El programa asigna int next = box.get() + 3;. ¿Cómo puede compilar y dar 15?
  1. El método de acceso devuelve int, porque los argumentos genéricos son primitivos.
  2. El método de acceso devuelve Object, que la aritmética convierte automáticamente.
  3. El método de acceso devuelve Integer, que la aritmética desempaqueta.
  4. El método de acceso devuelve Double, que la aritmética reduce automáticamente.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un método estático pick pertenece a la clase Items y declara <T> T pick(List<T> xs). ¿Cómo se escribe un argumento de tipo String explícito en una llamada?
  1. Items.<String>pick(words)
  2. Items.pick<String>(words)
  3. <String>Items.pick(words)
  4. Items.String.pick(words)
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Dentro de un método genérico sin cota, ¿qué sustituye a T durante el borrado de tipos?
  1. String
  2. Object
  3. Class<T>
  4. Number
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Una interfaz declara int run(int x), un método predeterminado reset() y un método estático help(). ¿Puede x -> x + 2 implementarla?
  1. No; tres métodos declarados requieren tres cuerpos de expresiones lambda.
  2. Sí; una expresión lambda también proporciona los cuerpos predeterminado y estático.
  3. No; un método funcional debe llamarse apply.
  4. Sí; solo run necesita una implementación abstracta.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Una regla reutilizable recibe un String y decide si termina en ".csv". ¿Qué interfaz estándar es adecuada para la condición que espera filter?
  1. Predicate<String>
  2. Consumer<String>
  3. Supplier<Boolean>
  4. Function<String, Boolean>
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Los elementos son Mira (grupo A, 7), Niko (B, 9), Oren (A, 4), Pia (B, 6). Se ordenan mediante comparing(Item::getGroup).thenComparingInt(Item::getScore).reversed(). ¿Qué orden de nombres resulta?
  1. [Pia, Niko, Oren, Mira]
  2. [Niko, Pia, Mira, Oren]
  3. [Mira, Oren, Niko, Pia]
  4. [Oren, Mira, Pia, Niko]
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

¿Qué lista resulta de IntStream.rangeClosed(2, 9).skip(3).limit(2).boxed().toList()?
  1. [2, 3]
  2. [4, 5]
  3. [5, 6]
  4. [7, 8]
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Para Stream.<Integer>empty(), ¿cuáles son los resultados de llamar por separado, cada vez con un flujo nuevo, a anyMatch(n -> n > 0), allMatch(n -> n > 0) y noneMatch(n -> n > 0), en ese orden?
  1. false, false, true
  2. true, true, false
  3. false, false, false
  4. false, true, true
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Un toMap(Item::getCode, Item::getCount) con dos argumentos encuentra dos elementos con el código "R", ambos con recuento 3. ¿Qué sucede?
  1. Almacena R=3 porque los valores repetidos son iguales.
  2. Almacena R=6 al sumar automáticamente los valores iguales.
  3. Almacena ambas entradas bajo dos copias de la clave R.
  4. Lanza IllegalStateException por la clave repetida.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Un flujo vacío se recopila con partitioningBy(x -> x > 10, Collectors.counting()). ¿Qué resultado está garantizado?
  1. El mapa está vacío porque no se ejecutó ninguna función clasificadora.
  2. Ambas claves booleanas se asocian a 0L.
  3. Solo está presente false y se asocia a 0L.
  4. Ambas claves booleanas se asocian a valores opcionales vacíos.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 3: Elige una implementación segura10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un Pair<L, R> genérico tiene métodos de acceso que devuelven L y R. Para Pair<String, Integer> p = new Pair<>("zone", 4);, ¿cuáles son el tipo de p.getFirst() y el valor de p.getSecond() + 2?
  1. String y 6
  2. Object y 6
  3. String y 42
  4. Integer y 6
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un parámetro se declara como List<? super Integer> sink. ¿Cuál es el tipo más específico garantizado para sink.get(0)?
  1. Integer
  2. Number
  3. Object
  4. Comparable<Integer>
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Una clase declara tanto show(Set<Long> xs) como show(Set<String> xs). ¿Por qué falla la compilación?
  1. Todo método genérico debe devolver su tipo de elemento.
  2. Set solo permite un argumento de tipo en toda una clase.
  3. Las sobrecargas de Java nunca pueden utilizar parámetros de colección.
  4. Ambos tipos de parámetro se borran hasta quedar como Set.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Un método debe ejecutar una acción proporcionada sobre cada cadena sin recibir un resultado de esa acción. ¿Qué interfaz de parámetro es adecuada?
  1. Supplier<String>
  2. Consumer<String>
  3. Predicate<String>
  4. UnaryOperator<String>
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Una clase Badge tiene un constructor Badge(String text). Un método acepta Function<String, Badge> factory. ¿Qué referencia crea el mismo comportamiento que s -> new Badge(s)?
  1. Badge::toString
  2. Badge::new
  3. new Badge::new
  4. Badge::Badge
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Una lista es ["z", null, "a"]. Se ordena con Comparator.nullsLast(Comparator.reverseOrder()). ¿Qué orden resulta?
  1. [null, z, a]
  2. [a, z, null]
  3. [null, a, z]
  4. [z, a, null]
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Una variable de flujo s se utiliza para s.count() y después para s.toList(). ¿Cómo debe obtener el programa ambos resultados de una lista reutilizable data?
  1. Iniciar un data.stream() nuevo para cada operación terminal.
  2. Llamar a s.close() entre las operaciones terminales.
  3. Guardar s en otra variable antes de llamar a count().
  4. Insertar s.limit(data.size()) después de llamar a count().
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

¿Qué lista resulta de Stream.of("mint", "ash", "mint", "elm").distinct().map(String::length).sorted().toList()?
  1. [3, 4]
  2. [3, 3, 4, 4]
  3. [3, 3, 4]
  4. [4, 3, 3]
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Los elementos tienen códigos y cantidades P=2, Q=6, P=5, Q=1. El programa debe devolver una lista modificable de códigos distintos en orden ascendente. ¿Qué secuencia de procesamiento es adecuada?
  1. items.stream().map(Item::getCode).distinct().sorted().toList()
  2. items.stream().map(Item::getCode).distinct().sorted(Comparator.reverseOrder()).collect(Collectors.toCollection(ArrayList::new))
  3. items.stream().map(Item::getCode).distinct().sorted().collect(Collectors.toCollection(ArrayList::new))
  4. items.stream().map(Item::getCode).sorted().collect(Collectors.toCollection(ArrayList::new))
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Una tarea compara cada elemento de una lista con el que lo precede inmediatamente y actualiza dos contadores bajo varias condiciones. ¿Qué hace que un bucle con índice sea una elección razonable?
  1. Muestra los índices vecinos y las actualizaciones coordinadas de los contadores.
  2. Es obligatorio porque los flujos no pueden inspeccionar posiciones vecinas de una lista.
  3. Garantiza un procesamiento más rápido que un flujo sobre la misma lista.
  4. Permite añadir elementos a la fuente dentro de un recorrido for-each ordinario.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 4: Sigue la tarea completa10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Una clase genérica Slot<T> necesita una fábrica estática que cree un objeto de tipo específico a partir de cualquier valor. ¿Qué declaración evita utilizar el parámetro de la clase desde un contexto estático?
  1. static Slot<T> of(T value)
  2. static <E> Slot<T> of(E value)
  3. static Slot<Object> of(T value)
  4. static <E> Slot<E> of(E value)
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un método transfiere cualquier tipo de elemento elegido desde una lista de origen a una lista de destino modificable mediante addAll. Debe copiar enteros en una lista de números o de objetos y conservar la seguridad de tipos. ¿Qué firma es adecuada?
  1. <T> void append(List<? extends T> dst, List<? super T> src)
  2. <T> void append(List<T> dst, List<T> src)
  3. <T> void append(List<? super T> dst, List<? extends T> src)
  4. <T> void append(List<Object> dst, List<T> src)
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Una interfaz declara boolean check(String s) y boolean equals(Object other), sin otros métodos abstractos. ¿Puede implementarla una expresión lambda y por qué?
  1. Sí; la coincidencia con un método de Object no añade trabajo de implementación abstracta.
  2. No; cuenta cada método abstracto declarado, así que la interfaz tiene dos.
  3. Sí; Java siempre proporciona un método ausente para cualquier interfaz de dos métodos.
  4. No; un método funcional que devuelve un booleano debe llamarse test.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

¿Qué expresión lambda tiene el mismo comportamiento que la referencia sin receptor vinculado String::compareToIgnoreCase cuando se utiliza como un Comparator<String>?
  1. (a, b) -> b.compareToIgnoreCase(a)
  2. (a, b) -> a.compareToIgnoreCase(b)
  3. a -> a.compareToIgnoreCase(a)
  4. (a, b) -> String.compareToIgnoreCase(a, b)
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Una lista modificable contiene X con puntuación 8, Y con puntuación 3, Z con puntuación 8 y W con puntuación 3, en ese orden. Se ordena únicamente por puntuación ascendente con un comparador válido. ¿Qué orden de nombres está garantizado?
  1. [Y, W, X, Z]
  2. [W, Y, Z, X]
  3. [Y, W, Z, X]
  4. [X, Z, Y, W]
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Un comparador de mediciones de tipo double utiliza (int) (a - b). ¿Qué sucede al comparar 3,6 con 3,1?
  1. Indica correctamente que el primer valor es mayor.
  2. Indica correctamente que el primer valor es menor.
  3. Indica un empate aunque el primer valor es mayor.
  4. Lanza una excepción por el resultado fraccionario.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

¿Cuál es el tipo de resultado de List.of(List.of("red"), List.of("blue", "gold")).stream().map(List::stream)?
  1. Stream<String>
  2. Stream<Stream<String>>
  3. Stream<List<String>>
  4. List<Stream<String>>
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Un método devuelve el entero más grande de una lista cualquiera, o -1 cuando la lista está vacía. ¿Qué expresión cumple ese requisito?
  1. xs.stream().reduce(-1, Integer::max)
  2. xs.stream().max(Integer::compare).get()
  3. xs.stream().findFirst().orElse(-1)
  4. xs.stream().max(Integer::compare).orElse(-1)
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Las palabras son "reed", "oak", "fern", "elm", "cedar". Después de agruparlas por longitud con counting() en un TreeMap, ¿qué mapa se imprime?
  1. {3=1, 4=2, 5=2}
  2. {3=2, 4=1, 5=1}
  3. {3=2, 4=2, 5=1}
  4. {2=3, 1=5}
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Las lecturas son Delta 6 °C, Delta 18 °C, Cove 14 °C y Cove 16 °C. La tarea pide las ciudades cuya media supera los 13 °C. ¿Qué resultado es correcto?
  1. Solo Cove cumple la condición.
  2. Cove y Delta cumplen la condición.
  3. Solo Delta cumple la condición.
  4. Ninguna ciudad cumple la condición.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 5: Aplica las ideas a tareas nuevas10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un método tiene los parámetros List<? super Integer> dst, List<? extends Number> src e intenta ejecutar dst.add(src.get(0)). Debe aceptar fuentes de enteros o de números de doble precisión y destinos modificables de números u objetos. ¿Cómo deben cambiar sus parámetros?
  1. List<? super Integer> dst, List<? extends Integer> src
  2. List<? super Number> dst, List<? extends Number> src
  3. List<Number> dst, List<? extends Number> src
  4. List<? extends Number> dst, List<? super Number> src
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Una fábrica genérica debe crear un valor nuevo del tipo de referencia elegido por quien la llama, sin suponer que existe un constructor sin argumentos. ¿Qué diseño es adecuado?
  1. Usar new T() dentro de un método que declara <T>.
  2. Usar new Object() y devolverlo directamente como T.
  3. Aceptar Supplier<T> y llamar a su método get().
  4. Usar T.class para encontrar el constructor de quien llama.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Para Function<Integer, Integer> triple = x -> x * 3; y shift = x -> x + 2;, ¿qué devuelve triple.andThen(shift).apply(4)?
  1. 18
  2. 12
  3. 20
  4. 14
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Un HashMap<String, Integer> contiene oak=6, ash=6, elm=2. ¿Qué comparador de entradas garantiza valores descendentes y claves ascendentes en caso de empate?
  1. Map.Entry.<String, Integer>comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey())
  2. Map.Entry.<String, Integer>comparingByValue().thenComparing(Map.Entry.comparingByKey()).reversed()
  3. Map.Entry.<String, Integer>comparingByKey().thenComparing(Map.Entry.comparingByValue())
  4. Map.Entry.<String, Integer>comparingByValue().reversed()
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Los registros son Ivo (2, 5), June (1, 7), Kai (1, 4), Luz (2, 5), donde cada par indica grupo y puntuación. ¿Qué orden resulta de ordenar por grupo ascendente, puntuación descendente y después nombre ascendente?
  1. [Kai, June, Ivo, Luz]
  2. [Ivo, Luz, June, Kai]
  3. [June, Kai, Ivo, Luz]
  4. [June, Kai, Luz, Ivo]
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

¿Qué produce esto y qué palabras comprueba el filtro? Stream.of("ash", "cedar", "elm", "pine").filter(w -> w.length() > 3).map(String::length).findFirst()
  1. Optional[5]; se comprueban ash y cedar.
  2. Optional[4]; se comprueban las cuatro palabras.
  3. Optional[5]; se comprueban las cuatro palabras.
  4. Optional[3]; solo se comprueba ash.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Una lista tiene los grupos anidados [2, 5], [] y [5, 8]. ¿Qué devuelve groups.stream().flatMap(List::stream).distinct().filter(n -> n > 3).mapToInt(n -> n).sum()?
  1. 18
  2. 15
  3. 20
  4. 13
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Los valores son -9, -4 y -12. ¿Qué devuelve values.stream().filter(n -> n > -8).max(Integer::compare).map(n -> n * 2).orElse(0)?
  1. 0
  2. -8
  3. -4
  4. -18
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Un Sale tiene un equipo y unidades enteras. Ignora las ventas con cero unidades o negativas, calcula la media de cada equipo a partir de sus ventas restantes y devuelve el equipo con la media más alta o «none» si la entrada está vacía. Supón que no hay medias empatadas. ¿Qué secuencia de procesamiento es adecuada? Los métodos de recolectores están importados estáticamente.
  1. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, summingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  2. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  3. sales.stream().collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().filter(e -> e.getValue() > 0).max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  4. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByKey()).map(Map.Entry::getKey).orElse("none")
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Para las palabras "an", "oak", "to", "elm", ¿cuál es el valor del lado verdadero de partitioningBy(w -> w.length() == 2, mapping(String::toUpperCase, toList()))?
  1. [AN, TO]
  2. [OAK, ELM]
  3. [AN, TO, ELM]
  4. [AN, OAK, TO, ELM]
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

¿Te atascas con una pregunta tuya? Ayuda con Tareas

Términos Clave

87 Términos Clave, cada uno con su definición

A

Advertencia de operación no comprobada
Una advertencia de operación no comprobada señala una operación cuya seguridad de tipos genéricos el compilador no puede establecer por completo, a menudo debido a un uso sin parametrizar o una conversión explícita de tipo.
Una advertencia de operación no comprobada en el add de una lista sin parametrizar señala una posible lectura posterior de tipo incorrecto.
Argumento de tipo
Un argumento de tipo es el tipo de referencia concreto que se proporciona al utilizar una clase, interfaz o método genérico.
El argumento de tipo String hace que el método de acceso de un Box<String> devuelva texto sin una conversión explícita de tipo.
Argumento de tipo explícito de método
Un argumento de tipo explícito de método es un argumento de tipo escrito entre el punto de un receptor y el nombre del método en una llamada.
El argumento de tipo explícito de método en Lists.<String>last(words) indica explícitamente la elección de tipo del método.
Asociatividad
La asociatividad significa que reagrupar las entradas de una operación no cambia su resultado, como se requiere para el acumulador de una reducción de flujo.
La asociatividad hace que la suma sea adecuada para una reducción de enteros, mientras que la resta no lo es.

B

Borrado de tipos
El borrado de tipos sustituye los parámetros de tipo genéricos por sus cotas, o por Object, después de la comprobación; el compilador inserta las conversiones explícitas necesarias.
El borrado de tipos hace que los objetos de cajas de cadenas y de enteros compartan la misma clase Box en tiempo de ejecución.
Bucle con índice
Un bucle con índice utiliza una variable de posición explícita para acceder a los elementos de una secuencia, lo que hace visibles las posiciones vecinas y las decisiones de control.
Un bucle con índice puede comparar list.get(i) con list.get(i - 1) mientras actualiza dos contadores.

C

Captura de variables
La captura de variables ocurre cuando una expresión lambda utiliza una variable local del código que la rodea, sujeta a las restricciones de Java sobre variables finales.
La captura de variables permite que un predicado compare cada entrada con un valor limit del código circundante.
Clase envoltorio
Una clase envoltorio es un tipo de referencia que representa un valor primitivo y permite utilizar valores como los enteros como argumentos genéricos.
La clase envoltorio Integer hace que List<Integer> sea válido, mientras que List<int> se rechaza.
Clase genérica
Una clase genérica declara uno o más parámetros de tipo para que sus campos y métodos puedan conservar las relaciones entre tipos elegidas por quien la utiliza.
La clase genérica Box<T> permite crear una caja de cadenas y otra de enteros con una sola declaración.
Clase Optional
Un Optional es un contenedor que representa un valor o ningún valor y permite que un método exprese un resultado potencialmente ausente.
Un Optional obtenido de un máximo filtrado puede estar vacío, por lo que una alternativa maneja el caso ausente.
Clase OptionalDouble
Un OptionalDouble representa un valor primitivo double que puede estar ausente, como sucede con la media de un flujo numérico vacío.
Un OptionalDouble obtenido de average() puede proporcionar un valor alternativo elegido de 0,0 mediante orElse.
Clave duplicada
Una clave duplicada aparece cuando distintos elementos de entrada producen claves de mapa iguales, lo que requiere una regla para determinar el único valor almacenado.
Una clave duplicada hace que toMap con dos argumentos falle incluso cuando ambos valores asociados son 85.
Comodín con cota inferior
Un comodín con cota inferior como ? super Integer representa un tipo desconocido que es el tipo indicado o uno de sus supertipos.
Un comodín con cota inferior permite que un método que escribe enteros acepte un destino List<Number> modificable.
Comodín con cota superior
Un comodín con cota superior como ? extends Number representa un tipo desconocido que es el tipo indicado o uno de sus subtipos.
Un comodín con cota superior permite que un cálculo de total numérico lea tanto listas de enteros como listas de números de doble precisión.
Comodín sin cota
Un comodín sin cota es ? sin ninguna cota; representa un argumento desconocido y solo permite leer elementos como Object.
Un comodín sin cota en List<?> permite que un método de presentación consulte el tamaño de una lista sin conocer su tipo de elemento.
Comparador
Un comparador es una regla de ordenación independiente cuyo método compare devuelve un resultado negativo, cero o positivo para dos valores de entrada.
Un Comparator<String> puede ordenar palabras por longitud sin cambiar su orden natural como cadenas.
Comparador que admite valores nulos
Un comparador que admite valores nulos da una posición explícita a los valores null, normalmente envolviendo un orden para valores no nulos con nullsFirst o nullsLast.
Un comparador de nombres que admite valores nulos maneja un estudiante existente cuyo nombre extraído es null.
Composición de funciones
La composición de funciones une transformaciones para que una reciba el resultado de otra; andThen y compose especifican órdenes de llamada opuestos.
La composición de funciones duplica y después incrementa en twice.andThen(inc), lo que da 11 a partir de 5.
Contexto estático
Un contexto estático pertenece a la propia clase, por lo que no puede utilizar un parámetro de tipo ligado a las instancias parametrizadas de esa clase.
En un contexto estático, una fábrica de cajas introduce su propio <E> en lugar de utilizar el T de la clase.
Cortocircuito
El cortocircuito permite que una operación termine en cuanto se conoce su resultado, sin procesar necesariamente todos los elementos de la fuente.
El cortocircuito permite que anyMatch devuelva true en cuanto un elemento satisface su predicado.
Cota superior
Una cota superior restringe un parámetro de tipo a un tipo nombrado y sus subtipos, y permite utilizar las operaciones de ese tipo en el código genérico.
La cota superior de <T extends Number> permite llamar a doubleValue() sobre un T.
Cotas múltiples
Las cotas múltiples combinan requisitos con &, de modo que un argumento de tipo debe cumplir cada cota; si hay una clase, se indica primero.
Las cotas múltiples de <T extends Number & Comparable<T>> requieren comportamiento numérico y de comparación.
Criterio de desempate
Un criterio de desempate es una regla de comparación adicional que se utiliza solo cuando todas las reglas anteriores sitúan los dos elementos como iguales.
Un criterio de desempate por nombre coloca a Ada antes que Chen después de que sus puntuaciones iguales hayan dejado empatada la primera comparación.
Cuerpo de bloque
Un cuerpo de bloque es un cuerpo de expresión lambda encerrado entre llaves; cuando se requiere un resultado, sus rutas deben devolver valores explícitamente.
Un cuerpo de bloque utiliza return a; y return b; para implementar una operación de máximo.
Cuerpo de expresión
Un cuerpo de expresión es un cuerpo de expresión lambda escrito como una sola expresión cuyo valor proporciona el resultado requerido sin un retorno explícito.
El cuerpo de expresión x + 1 permite que x -> x + 1 devuelva directamente el entero incrementado.

D

Desbordamiento de enteros
El desbordamiento de enteros ocurre cuando la aritmética entera supera el rango representable y el valor se desborda, lo que puede invertir un signo de comparación.
El desbordamiento de enteros hace que restar enteros grandes de signos opuestos sea inseguro para implementar un comparador.

E

Efectivamente final
Una variable local efectivamente final recibe una asignación y no se reasigna después, lo que permite que una expresión lambda la capture sin la palabra clave final.
Una referencia a una lista efectivamente final aún puede permitir que se añadan elementos a la lista a la que hace referencia.
Efecto secundario
Un efecto secundario es un cambio externo al valor devuelto por un cálculo, como modificar una lista capturada o un campo de un objeto circundante.
Un efecto secundario oculto en una secuencia de procesamiento puede hacer que el resultado dependa de actualizaciones externas a sus valores de entrada.
Elemento neutro
Un elemento neutro es un valor que deja otro valor sin cambios al aplicar la operación de una reducción, como cero para la suma o uno para la multiplicación.
El elemento neutro 1 permite que una reducción por producto conserve su primer factor en lugar de forzar el resultado a cero.
Empaquetado automático
El empaquetado automático es la conversión automática de Java de un valor primitivo en su objeto envoltorio correspondiente cuando se necesita un valor de referencia.
El empaquetado automático permite pasar el literal 5 a un constructor que espera un Integer.
Entrada de mapa
Una entrada de mapa es un par clave-valor visto como un solo objeto, lo que permite procesar las asociaciones de un mapa como flujo y compararlas por clave o valor.
Un comparador de entradas de mapa puede colocar primero la mayor cantidad de existencias sin reordenar el mapa de origen.
Especialización para tipos primitivos
Una especialización para tipos primitivos es una variante de interfaz funcional que acepta o devuelve directamente valores primitivos y evita la conversión a objetos envoltorio.
La especialización para tipos primitivos ToIntFunction<String> proporciona longitudes directamente a mapToInt.
Evaluación diferida
La evaluación diferida retrasa el procesamiento hasta que se necesita un resultado y permite que un flujo consuma solo los elementos necesarios.
La evaluación diferida permite que findFirst se detenga cuando se ha encontrado y transformado una palabra que cumple la condición.
Expresión lambda
Una expresión lambda proporciona parámetros y un cuerpo para el método abstracto de una interfaz funcional, utilizando la flecha -> entre ellos.
La expresión lambda (a, b) -> a * b proporciona la regla de multiplicación para una operación.
Extractor de clave
Un extractor de clave es una función que selecciona el valor utilizado para comparar un objeto, como su nombre, grupo o puntuación numérica.
El extractor de clave Student::getPoints construye un comparador numérico de estudiantes con comparingInt.

F

Fábrica de colecciones
Una fábrica de colecciones proporciona una implementación de colección concreta a un recolector para que el resultado tenga el comportamiento o el orden necesario.
La fábrica de colecciones ArrayList::new solicita explícitamente una lista que permite adiciones posteriores.
Firma tras el borrado de tipos
Una firma tras el borrado de tipos es una firma de método cuyos tipos de parámetro se consideran después del borrado, lo que ayuda a determinar si las sobrecargas entran en conflicto.
La firma tras el borrado show(Set) es común a los intentos de sobrecarga con Set<String> y Set<Long>.
Flujo
Un flujo es una secuencia procedente de una fuente que se procesa mediante operaciones; no almacena una colección separada de elementos en cada etapa.
Un flujo de la lista points puede contar los valores que cumplen una condición sin modificar la lista.
Función clasificadora
Una función clasificadora asigna a cada elemento de entrada una clave de agrupación que determina qué entrada del mapa lo recibe.
La función clasificadora String::length coloca todas las palabras de cuatro letras bajo la misma clave entera.
Función de combinación
Una función de combinación combina dos valores asociados a la misma clave en el único valor que un mapa debe conservar para esa clave.
La función de combinación Math::max conserva la mayor cantidad cuando una clave de existencias aparece dos veces.

I

Inferencia de tipos
La inferencia de tipos es el proceso por el que el compilador determina información de tipo omitida a partir de los argumentos y del contexto de destino circundante.
La inferencia de tipos permite que last(words) produzca un String cuando words es una lista de cadenas.
Interfaz BinaryOperator
La interfaz BinaryOperator representa una función que combina dos valores del mismo tipo en un resultado de ese mismo tipo.
Un BinaryOperator<Integer> como Integer::sum puede combinar recuentos para una clave de mapa repetida.
Interfaz Consumer
La interfaz Consumer es una interfaz funcional estándar para una acción sobre una entrada; su método accept no devuelve ningún valor.
Un Consumer<String> puede imprimir cada palabra cuando se pasa al forEach de una colección.
Interfaz funcional
Una interfaz funcional tiene un método abstracto que implementar, excluidas las declaraciones que coinciden con métodos públicos de instancia de Object.
Una interfaz funcional como Operation permite a quien la utiliza proporcionar su cálculo mediante una expresión lambda.
Interfaz Function
La interfaz Function es una interfaz funcional estándar que transforma una entrada de tipo T en un resultado de tipo R mediante apply.
Una Function<String, Integer> puede transformar cada palabra en su longitud antes de agrupar.
Interfaz genérica
Una interfaz genérica declara parámetros de tipo que relacionan los tipos utilizados en el contrato de sus métodos entre distintas implementaciones.
Una interfaz genérica Container<T> puede mantener relacionada la entrada de put con el resultado de take.
Interfaz IntStream
Un IntStream es un flujo de valores primitivos int con operaciones numéricas como suma y media, además de conversiones de vuelta a objetos.
Un IntStream obtenido de mapToInt puede sumar puntos sin recopilar primero valores envoltorio.
Interfaz Predicate
La interfaz Predicate es una interfaz funcional estándar para una condición: recibe un valor y devuelve boolean mediante test.
Un Predicate<String> pasado a removeIf identifica las palabras que cumplen la condición y deben eliminarse.
Interfaz Supplier
La interfaz Supplier es una interfaz funcional estándar cuyo método get sin argumentos proporciona un valor de su tipo de resultado.
Un Supplier<List<String>> que utiliza ArrayList::new puede proporcionar una lista nueva en cada llamada.
Interfaz UnaryOperator
La interfaz UnaryOperator representa una función cuya única entrada y cuyo resultado tienen el mismo tipo; suele utilizarse para sustituir elementos de una colección.
Un UnaryOperator<String> puede convertir cada palabra a mayúsculas mediante replaceAll de una lista modificable.
Invariancia
La invariancia significa que las parametrizaciones de un tipo genérico con argumentos distintos no adquieren una relación de subtipo solo porque sus tipos de argumento la tengan.
La invariancia impide utilizar List<Integer> como List<Number> y la protege de escrituras de valores de doble precisión.
Inversión de un comparador
La inversión de un comparador invierte el orden completo representado por el comparador anterior a la llamada a reversed(), incluidos los criterios de desempate existentes.
La inversión de un comparador al final de una cadena de grupo y puntuación invierte tanto el orden de grupos como el de puntuaciones.

L

Lista no modificable
Una lista no modificable rechaza las operaciones que cambian sus elementos o su estructura mediante la lista, sin hacer inmutables los objetos que contiene.
La lista no modificable de Stream.toList() lanza una excepción cuando el código intenta añadir otro elemento.

M

Método genérico
Un método genérico declara sus propios parámetros de tipo antes de su tipo de retorno, lo que permite que cada llamada elija tipos relacionados de entrada y resultado.
El método genérico last devuelve una cadena para una lista de cadenas y un entero para una lista de enteros.

N

No interferencia
La no interferencia significa que el comportamiento de una secuencia de procesamiento deja su fuente sin cambios mientras se procesa, preservando un recorrido válido.
Se incumple la no interferencia cuando una acción de flujo añade elementos nuevos a su ArrayList de origen.

O

Operación de comprobación de predicados
Una operación de comprobación de predicados devuelve si alguno, todos o ninguno de los elementos de un flujo satisface un predicado y puede detenerse cuando se conoce la respuesta.
Una operación de comprobación de predicados con una entrada vacía da false para anyMatch, pero true para allMatch.
Operación distinct
La operación distinct elimina elementos duplicados de un flujo mediante la igualdad de objetos y conserva el orden de encuentro en un flujo ordenado.
Aplicar distinct antes de transformar palabras en longitudes puede dejar longitudes repetidas en el resultado.
Operación filter
La operación filter conserva los elementos de un flujo cuyo predicado es verdadero, mantiene su tipo de elemento y descarta los demás valores.
Un filter por puntos debe ejecutarse antes de transformar estudiantes en nombres si necesita el método de acceso a los puntos.
Operación flatMap
La operación flatMap convierte cada entrada en un flujo y combina los elementos de esos flujos en una sola secuencia de salida.
Un flatMap que utiliza List::stream convierte listas anidadas de enteros en un solo flujo de enteros.
Operación intermedia
Una operación intermedia añade una etapa de procesamiento diferida y devuelve otro flujo, lo que permite seleccionar o transformar antes de obtener un resultado final.
La operación intermedia filter espera a una operación terminal antes de comprobar los elementos.
Operación limit
La operación limit conserva como máximo el número solicitado de elementos desde el inicio de un flujo ordenado y permite omitir la entrada posterior.
Un limit antes de ordenar selecciona desde el principio original, no los valores más altos del conjunto.
Operación map
La operación map sustituye cada elemento de entrada por un resultado calculado, por lo que puede cambiar el tipo de elemento del flujo.
Un map que utiliza String::length convierte palabras en valores enteros envoltorio, no en un flujo de tipos primitivos.
Operación peek
La operación peek deja pasar los elementos sin modificarlos mientras ejecuta una acción de inspección para los valores recorridos; sus llamadas pueden omitirse.
Un peek utilizado para imprimir valores no es fiable para trabajo necesario porque la ejecución puede omitir su etapa.
Operación skip
La operación skip descarta el número solicitado de elementos iniciales de un flujo ordenado y deja pasar los elementos restantes.
Después de skip(2) en el rango inclusivo de 1 a 10, el siguiente valor emitido es 3.
Operación terminal
Una operación terminal termina una secuencia de procesamiento de flujo produciendo un resultado o realizando una acción, y consume el flujo para ese cálculo.
La operación terminal count devuelve un long y deja el mismo flujo indisponible para reutilizarlo.
Operador diamante
El operador diamante es <> en una llamada a un constructor genérico; pide al compilador que infiera los argumentos de tipo y conserve las comprobaciones de tipo.
El operador diamante de new ArrayList<>() mantiene el tipo de la lista a partir de su contexto.
Orden de encuentro
Un orden de encuentro es la secuencia definida en la que una fuente presenta sus elementos, como el orden de una lista o el orden de claves de un mapa ordenado.
El orden de encuentro hace que findFirst elija el primer nombre que cumple la condición en una lista de origen.
Orden natural
Un orden natural es el orden que una clase define mediante Comparable.compareTo y que se utiliza cuando no se proporciona un comparador independiente.
El orden natural de las cadenas coloca la palabra con mayúscula "Cherry" antes que la palabra con minúscula "apple".
Ordenación estable
Una ordenación estable conserva el orden relativo original de los elementos que se comparan como iguales según la regla de ordenación utilizada.
Una ordenación estable mantiene a Ada antes que Chen cuando ambos tienen 85 puntos y Ada aparecía primero.

P

Parámetro de tipo
Un parámetro de tipo es un nombre introducido en una declaración genérica para representar un tipo proporcionado por el código que la utiliza.
El parámetro de tipo T da al campo y al método de acceso de una caja el mismo tipo elegido por quien la utiliza.
Productor extends, consumidor super
Productor extends, consumidor super es una regla para elegir comodines extends para fuentes de valores y comodines super para destinos de valores.
Productor extends, consumidor super orienta la elección de los parámetros de origen y destino en una transferencia entre listas.

R

Recolector
Un recolector describe cómo se reúnen o resumen los elementos de un flujo en un resultado, como una lista, cadena, mapa o cálculo agrupado.
Un recolector puede construir directamente la lista de resultados en lugar de modificar una lista externa en forEach.
Recolector groupingBy
El recolector groupingBy construye un mapa que asocia las claves encontradas de la función clasificadora con listas o resúmenes posteriores de los elementos correspondientes.
Un recolector groupingBy con averagingInt almacena una media de doble precisión por cada grupo de estudiantes.
Recolector joining
El recolector joining combina secuencias de caracteres en una sola cadena, con texto opcional entre los elementos y alrededor de todo el resultado.
Un recolector joining con una coma como separador no produce una coma final después del último nombre.
Recolector mapping
El recolector mapping transforma elementos antes de pasar sus resultados a otro recolector, a menudo dentro de un grupo o una partición.
Un recolector mapping puede extraer los nombres de los estudiantes dentro de cada grupo antes de recopilarlos en una lista.
Recolector partitioningBy
El recolector partitioningBy divide los elementos según un predicado y siempre devuelve ambas claves booleanas, incluso si uno de los lados está vacío.
Un recolector partitioningBy puede devolver una lista true vacía cuando ningún estudiante alcanza el umbral que comprueba su predicado.
Recolector posterior
Un recolector posterior reúne o resume los elementos dentro de cada grupo o partición y determina el tipo de valor del mapa.
El recolector posterior counting() convierte un mapa de listas por grupo en un mapa de recuentos Long.
Reducción
Una reducción combina los elementos de un flujo en un solo resultado mediante un acumulador, opcionalmente a partir de un elemento neutro.
Una reducción mediante suma y cero calcula un total sin actualizar un contador local externo.
Referencia a constructor
Una referencia a constructor utiliza ClassName::new para proporcionar comportamiento de creación de objetos, con argumentos determinados por la interfaz de destino.
La referencia a constructor Student::new puede convertir un nombre en un estudiante mediante un constructor compatible.
Referencia a método
Una referencia a método nombra el comportamiento existente de un método o constructor mediante :: y se ajusta a las entradas y al resultado de una interfaz funcional de destino.
La referencia a método Integer::parseInt proporciona el comportamiento de análisis a una función de cadena a entero.
Referencia a método sin receptor vinculado
Una referencia a método sin receptor vinculado nombra un método de instancia mediante su clase y toma el receptor del primer argumento funcional.
La referencia a método sin receptor vinculado String::length llama a length() sobre cada cadena proporcionada.
Referencia a método vinculada a un receptor
Una referencia a método vinculada a un receptor nombra un método de instancia sobre un objeto receptor concreto que se fija cuando se evalúa la referencia.
La referencia a método vinculada System.out::println imprime utilizando ese objeto de salida concreto.
Resultado intermedio
Un resultado intermedio es un valor con nombre entre etapas de procesamiento, como un mapa agrupado que consulta una secuencia posterior.
Un resultado intermedio que contiene las temperaturas medias por ciudad se filtra después de agrupar para seleccionar las ciudades cálidas.

S

Secuencia de procesamiento
Una secuencia de procesamiento enlaza una fuente, cero o más operaciones intermedias y una operación terminal que produce un resultado o realiza una acción.
La secuencia de procesamiento que va de stream a count mediante filter produce un número de coincidencias de tipo long.

T

Tipo de destino
Un tipo de destino es la interfaz funcional esperada que proporciona a una expresión lambda o referencia a método sus tipos de parámetro y resultado.
El tipo de destino Predicate<String> hace que s -> s.isEmpty() sea una comprobación booleana de una cadena.
Tipo sin parametrizar
Un tipo sin parametrizar es una clase o interfaz genérica utilizada sin argumentos de tipo, lo que elude algunas comprobaciones que se aplicarían en un uso parametrizado.
Un tipo sin parametrizar en List xs puede permitir una escritura incorrecta que una List<String> rechazaría.
Transitividad
La transitividad exige que un orden coloque a antes que c siempre que coloque a antes que b y b antes que c, manteniendo consistentes las comparaciones.
La transitividad impide que un comparador cree un ciclo entre tres objetos ordenados.

Sobre esta unidad

Por StudeliInformar de un error

Crea tus propios Materiales de Estudio

Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.

Comenzar