- Aprender
- Informática
- Programación
- Programación orientada a objetos con Java
- Genéricos y expresiones lambda en Java
Genéricos, expresiones lambda y flujos en Java
PrincipianteUnidad 11 de 11 del curso Curso de Programación orientada a objetos con Java
Lo que aprenderás
- 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éricasDeclara 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éricosDeclara 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 comodinesUtiliza parámetros de tipo con cotas para llamar a métodos sobre un tipo genérico, explica por qué una
List<Integer>no es unaList<Number>y elige entre? extendsy? super. - Trabaja dentro de las limitaciones del borrado de tiposExplica 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.
- Define y utiliza clases genéricas
- 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 funcionalesReconoce 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ándarElige 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étodosUtiliza 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.
- Escribe expresiones lambda para interfaces funcionales
- 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 ordenarImplementa correctamente el contrato de compare, ordena listas y arreglos con un comparador y decide entre Comparable y Comparator.
- Combina comparadores con comparing, thenComparing y reversedConstruye 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.
- Escribe un comparador y utilízalo para ordenar
- 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 ejecutaCrea 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 elementosUtiliza 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 resultadoCalcula recuentos, sumas, medias, valores extremos y respuestas verdaderas o falsas con operaciones terminales, y maneja los resultados ausentes con Optional.
- Construye una secuencia de procesamiento de flujo y comprende cómo se ejecuta
- 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 cadenasUtiliza 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 recolectoresDivide datos en grupos con groupingBy y partitioningBy y resume cada grupo con un recolector posterior.
- Elige entre flujos y bucles para resolver una tarea completaResuelve 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.
- Recopila resultados en listas, conjuntos, mapas y cadenas
Guía de estudio de Genéricos y expresiones lambda en Java
1Escribe 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
Integercomo 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ámetro | Operació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
Tlos 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 Tpara leer elementos comoT. - Utiliza
? super Tpara añadir valores de tipoT; las lecturas tienen tipoObject. - 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.copysustituye 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
Objectcuando 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.
2Pasa 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. @FunctionalInterfacecomprueba 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
| Interfaz | Método funcional | Funció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.
andThenejecuta primero el receptor;composeejecuta primero su argumento.removeIfcomprueba un predicado;replaceAllaplica un operador unario.mergecombina un valor existente de un mapa con un valor proporcionado.computeIfAbsentpuede 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.
| Tipo | Referencia | Expresión lambda equivalente |
|---|---|---|
| Método estático | Integer::parseInt | s -> Integer.parseInt(s) |
| Instancia vinculada | System.out::println | x -> System.out.println(x) |
| Instancia sin receptor vinculado | String::length | s -> s.length() |
| Constructor | ArrayList::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.
3Ordena 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.
Comparableproporciona 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
comparingextrae una clave; los métodos de construcción para tipos primitivos evitan empaquetar las claves numéricas.thenComparingsolo 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.
4Procesa 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.
sorteddebe 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
filterconserva el tipo de elemento;mappuede cambiarlo.- Los flujos de tipos primitivos proporcionan operaciones numéricas que no están en
Stream<Integer>. - El orden de
skip,limity la ordenación cambia los elementos seleccionados. distinctsigue la igualdad; la ordenación de un flujo ordenado conserva los empates.flatMapaplana los flujos producidos a partir de los elementos de entrada.peeksirve 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.
allMatchynoneMatchson verdaderos con una entrada vacía;anyMatches falso.- Una reducción necesita un elemento neutro y un acumulador asociativo.
- Amplía a
longantes de sumar valores cuyo total supere el rango deint.
5Recopila 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
toCollectioncuando se requiera un tipo de colección concreto. joiningreúne texto con un separador, un prefijo y un sufijo opcionales.toMapcon 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 posterior | Valor bajo A | Tipo de valor |
|---|---|---|
counting() | 3 | Long |
summingInt(Student::getPoints) | 234 | Integer |
averagingInt(Student::getPoints) | 78,0 | Double |
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.
countingproduceLong; los recolectores de medias producenDouble.- 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
1Construye una API reutilizable con seguridad de tipos
17 pasos
- 1Introducció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.
- 2ExplicaciónDeclara la caja
Tnombra 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. - 3EjemploUtiliza un tipo concreto
Crea una caja y lee su valor:
Box<String> b = new Box<>("tea"); String drink = b.get(); b.set("water");Stringproporciona 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. - 4DesafíoPredice la escritura rechazada
El
Box<T>genérico tienevoid set(T value). Después deBox<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 unString. No se necesita ninguna lectura posterior para descubrir el error. El literal 8 puede empaquetarse enInteger; los propios argumentos de tipo deben seguir siendo tipos de referencia, así queBox<int>no es válido. - 5Conexió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 siguienteTnombra el tipo de resultado. Llamarlo sobre[4, 8, 15]devuelve elInteger15; llamarlo sobre["x", "y"]devuelve la cadena"y". El método requiere una lista no vacía, independientemente de sus comprobaciones de tipo. - 6ConsejoEscribe 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). ElTde la clase pertenece a los usos parametrizados de instancia, mientras que el método estático pertenece a la clase compartida. - 7Desafí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 escribestatic 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. EscribirObjectcomo resultado perdería esa relación útil en lugar de corregirla. - 8ExplicaciónPermite la comparación
Un
Tsin restricciones no tiene ninguna operacióncompareToconocida. Una cota puede proporcionar esa garantía:<T extends Comparable<T>>Un método
largest(List<T>)puede entonces mantener unT best, comparar cadaxconbesty sustituirlo cuandox.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. - 9Idea claveProtege una lista de enteros
Un
Integeres unNumber, pero unaList<Integer>no es unaList<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 unInteger. La invariancia genérica impide la asignación para que tanto las lecturas como las escrituras mantengan sus garantías.ArrayList<Number>se acepta comoList<Number>porque su argumento de elemento sigue coincidiendo. - 10EjemploTransfiere 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 fuenteList<Integer>, Java puede elegirTcomoInteger. Cada elemento de la fuente es aceptable para el destino, sin afirmar que ambas listas tengan argumentos de elemento idénticos. - 11DesafíoElige la lectura segura
Un parámetro tiene el tipo
List<? super Integer>. ¿Puede asignarse su primer elemento directamente a una variableInteger? ¿Qué tipo está garantizado en su lugar?---reveal---
Solo está garantizado
Object. La lista real puede ser unaList<Number>o unaList<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. - 12Explicació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
Objectcuando 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 unBox<Integer>comparten la misma claseBoxen 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. - 13EjemploProporciona 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 comoArrayList<T>suele servir para almacenar los elementos. - 14ConsejoSigue la advertencia de tipo sin parametrizar
Una
Listsin 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. - 15DesafíoComprueba la sobrecarga tras el borrado
Una clase declara
show(Set<String> xs)yshow(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 deObjectfrente aList<String>tiene una limitación relacionada: compruebaList<?>sin afirmar cuál es su argumento de elemento. - 16DesafíoRevisa una API de transferencia reutilizable
Una API utiliza una lista de origen sin parametrizar, convierte explícitamente cada elemento a
Numbery 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 comoNumber, 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. - 17RepasoInterpreta 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.
2Convierte el comportamiento en un argumento
14 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónEmpieza por la forma del comportamiento
- 2ExplicaciónIdentifica el método que hay que implementar
- 3EjemploUtiliza un bloque cuando una decisión lo requiera
- 4DesafíoCorrige el resultado que falta
- 5ExplicaciónRelaciona cuatro tareas habituales
- 6EjemploObserva el orden de composición
- 7ConexiónDeja que las colecciones ejecuten la regla
- 8DesafíoDistingue la eliminación de la selección
- 9ExplicaciónCaptura un valor sin reasignarlo
- 10EjemploIdentifica el receptor de la referencia
- 11DesafíoComprueba qué protege final
- 12EjemploCuenta con una regla de actualización de mapa
- 13DesafíoCombina el tipo de destino y la captura
- 14RepasoSigue una llamada a través de la regla
3Construye un orden decisión a decisión
12 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónNombra el orden que necesita la tarea
- 2ExplicaciónInterpreta un resultado de comparación
- 3EjemploEvita la resta en comparaciones numéricas
- 4EjemploConserva el orden original de las puntuaciones iguales
- 5DesafíoSepara el orden natural del orden específico de la tarea
- 6ExplicaciónAñade una regla solo para empates
- 7Idea claveInvierte solo las decisiones deseadas
- 8ConsejoManeja los valores nulos en el nivel correspondiente
- 9DesafíoInterpreta la inversión final
- 10ConexiónOrdena las entradas, no el mapa
- 11DesafíoHaz predecibles las entradas de mapa empatadas
- 12RepasoComprueba el árbol de comparación
4Sigue los valores a través de un flujo
16 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónSigue los valores y los tipos a la vez
- 2ExplicaciónIdentifica el resultado terminal
- 3EjemploDetente después de la primera coincidencia
- 4DesafíoEjecuta otro cálculo
- 5EjemploSelecciona estudiantes antes de transformar en nombres
- 6ConexiónPasa a las operaciones numéricas
- 7EjemploAplana antes de eliminar duplicados
- 8DesafíoInterpreta el orden de las operaciones
- 9EjemploCalcula una suma y una media
- 10ExplicaciónMantén separada la ausencia de un valor
- 11DesafíoManeja un máximo ausente
- 12Idea claveComprueba las pruebas universales con una entrada vacía
- 13EjemploElige un valor inicial neutro
- 14ConsejoAmplía el tipo antes de que ocurra el desbordamiento
- 15DesafíoCombina aplanado, selección y resultado
- 16RepasoComprueba los casos extremos del resultado
5Construye un resumen útil de datos
15 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónParte del resultado deseado
- 2ExplicaciónEstablece los cambios posteriores como requisito explícito
- 3EjemploResuelve colisiones con una regla de combinación
- 4DesafíoElige un valor por cada clave repetida
- 5ConexiónUne valores como texto
- 6ExplicaciónElige una clave y después un resultado de grupo
- 7EjemploResume dentro de los grupos de estudiantes
- 8Idea claveConserva ambos lados de una partición
- 9DesafíoPredice un lado vacío
- 10EjemploCalcula primero las medias por ciudad
- 11EjemploConsulta el resumen que has construido
- 12ConsejoMuestra el flujo de control cuando ayude
- 13DesafíoProtege la fuente durante el procesamiento
- 14DesafíoEncuentra el grupo ganador de forma segura
- 15RepasoRevisa toda la tarea de datos
Ejercicios de Genéricos y expresiones lambda en Java
Set 1: Utiliza los fundamentos10 Preguntas de Práctica
Pregunta 1 de 10
compare(a, b)?Mostrar respuestaOcultar respuesta
Pregunta 2 de 10
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()?Mostrar respuestaOcultar respuesta
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
Mostrar respuestaOcultar respuesta
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
doubleValue() sobre cada elemento. Debe aceptar tanto List<Integer> como List<Double>. ¿Qué tipo de parámetro es adecuado?Mostrar respuestaOcultar respuesta
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
Function<Integer, Integer> debe duplicar su entrada mediante un cuerpo de bloque. ¿Qué expresión lambda es adecuada?Mostrar respuestaOcultar respuesta
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
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?Mostrar respuestaOcultar respuesta
Pregunta 7 de 10
names.stream().filter(s -> s.length() > 2) y no hace nada con el flujo devuelto. ¿Cuándo se realizan las llamadas al predicado?Mostrar respuestaOcultar respuesta
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
reduce(0, (a, b) -> a * b). ¿Qué causa el resultado incorrecto?Mostrar respuestaOcultar respuesta
Pregunta 9 de 10
add. ¿Qué expresión terminal proporciona explícitamente un ArrayList?Mostrar respuestaOcultar respuesta
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
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?Mostrar respuestaOcultar respuesta
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
Box<Integer> contiene 12. El programa asigna int next = box.get() + 3;. ¿Cómo puede compilar y dar 15?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
T durante el borrado de tipos?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
int run(int x), un método predeterminado reset() y un método estático help(). ¿Puede x -> x + 2 implementarla?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
String y decide si termina en ".csv". ¿Qué interfaz estándar es adecuada para la condición que espera filter?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
comparing(Item::getGroup).thenComparingInt(Item::getScore).reversed(). ¿Qué orden de nombres resulta?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
IntStream.rangeClosed(2, 9).skip(3).limit(2).boxed().toList()?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
toMap(Item::getCode, Item::getCount) con dos argumentos encuentra dos elementos con el código "R", ambos con recuento 3. ¿Qué sucede?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
partitioningBy(x -> x > 10, Collectors.counting()). ¿Qué resultado está garantizado?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
Pair<String, Integer> p = new Pair<>("zone", 4);, ¿cuáles son el tipo de p.getFirst() y el valor de p.getSecond() + 2?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
List<? super Integer> sink. ¿Cuál es el tipo más específico garantizado para sink.get(0)?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
show(Set<Long> xs) como show(Set<String> xs). ¿Por qué falla la compilación?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
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)?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
["z", null, "a"]. Se ordena con Comparator.nullsLast(Comparator.reverseOrder()). ¿Qué orden resulta?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Stream.of("mint", "ash", "mint", "elm").distinct().map(String::length).sorted().toList()?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
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
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
addAll. Debe copiar enteros en una lista de números o de objetos y conservar la seguridad de tipos. ¿Qué firma es adecuada?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
boolean check(String s) y boolean equals(Object other), sin otros métodos abstractos. ¿Puede implementarla una expresión lambda y por qué?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
String::compareToIgnoreCase cuando se utiliza como un Comparator<String>?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
(int) (a - b). ¿Qué sucede al comparar 3,6 con 3,1?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
List.of(List.of("red"), List.of("blue", "gold")).stream().map(List::stream)?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
"reed", "oak", "fern", "elm", "cedar". Después de agruparlas por longitud con counting() en un TreeMap, ¿qué mapa se imprime?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
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
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
Function<Integer, Integer> triple = x -> x * 3; y shift = x -> x + 2;, ¿qué devuelve triple.andThen(shift).apply(4)?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
HashMap<String, Integer> contiene oak=6, ash=6, elm=2. ¿Qué comparador de entradas garantiza valores descendentes y claves ascendentes en caso de empate?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
Stream.of("ash", "cedar", "elm", "pine").filter(w -> w.length() > 3).map(String::length).findFirst()Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
[2, 5], [] y [5, 8]. ¿Qué devuelve groups.stream().flatMap(List::stream).distinct().filter(n -> n > 3).mapToInt(n -> n).sum()?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
values.stream().filter(n -> n > -8).max(Integer::compare).map(n -> n * 2).orElse(0)?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
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.Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
"an", "oak", "to", "elm", ¿cuál es el valor del lado verdadero de partitioningBy(w -> w.length() == 2, mapping(String::toUpperCase, toList()))?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
¿Te atascas con una pregunta tuya? Ayuda con Tareas
Términos Clave
A
Advertencia de operación no comprobada
add de una lista sin parametrizar señala una posible lectura posterior de tipo incorrecto.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
Lists.<String>last(words) indica explícitamente la elección de tipo del método.Asociatividad
B
Borrado de tipos
Object, después de la comprobación; el compilador inserta las conversiones explícitas necesarias.Box en tiempo de ejecución.Bucle con índice
list.get(i) con list.get(i - 1) mientras actualiza dos contadores.C
Captura de variables
limit del código circundante.Clase envoltorio
Integer hace que List<Integer> sea válido, mientras que List<int> se rechaza.Clase genérica
Box<T> permite crear una caja de cadenas y otra de enteros con una sola declaración.Clase Optional
Optional obtenido de un máximo filtrado puede estar vacío, por lo que una alternativa maneja el caso ausente.Clase OptionalDouble
OptionalDouble obtenido de average() puede proporcionar un valor alternativo elegido de 0,0 mediante orElse.Clave duplicada
toMap con dos argumentos falle incluso cuando ambos valores asociados son 85.Comodín con cota inferior
? super Integer representa un tipo desconocido que es el tipo indicado o uno de sus supertipos.List<Number> modificable.Comodín con cota superior
? extends Number representa un tipo desconocido que es el tipo indicado o uno de sus subtipos.Comodín sin cota
? sin ninguna cota; representa un argumento desconocido y solo permite leer elementos como Object.List<?> permite que un método de presentación consulte el tamaño de una lista sin conocer su tipo de elemento.Comparador
compare devuelve un resultado negativo, cero o positivo para dos valores de entrada.Comparator<String> puede ordenar palabras por longitud sin cambiar su orden natural como cadenas.Comparador que admite valores nulos
nullsFirst o nullsLast.Composición de funciones
andThen y compose especifican órdenes de llamada opuestos.twice.andThen(inc), lo que da 11 a partir de 5.Contexto estático
<E> en lugar de utilizar el T de la clase.Cortocircuito
anyMatch devuelva true en cuanto un elemento satisface su predicado.Cota superior
<T extends Number> permite llamar a doubleValue() sobre un T.Cotas múltiples
&, de modo que un argumento de tipo debe cumplir cada cota; si hay una clase, se indica primero.<T extends Number & Comparable<T>> requieren comportamiento numérico y de comparación.Criterio de desempate
Cuerpo de bloque
return a; y return b; para implementar una operación de máximo.Cuerpo de expresión
x + 1 permite que x -> x + 1 devuelva directamente el entero incrementado.D
Desbordamiento de enteros
E
Efectivamente final
final.Efecto secundario
Elemento neutro
Empaquetado automático
5 a un constructor que espera un Integer.Entrada de mapa
Especialización para tipos primitivos
ToIntFunction<String> proporciona longitudes directamente a mapToInt.Evaluación diferida
findFirst se detenga cuando se ha encontrado y transformado una palabra que cumple la condición.Expresión lambda
-> entre ellos.(a, b) -> a * b proporciona la regla de multiplicación para una operación.Extractor de clave
Student::getPoints construye un comparador numérico de estudiantes con comparingInt.F
Fábrica de colecciones
ArrayList::new solicita explícitamente una lista que permite adiciones posteriores.Firma tras el borrado de tipos
show(Set) es común a los intentos de sobrecarga con Set<String> y Set<Long>.Flujo
points puede contar los valores que cumplen una condición sin modificar la lista.Función clasificadora
String::length coloca todas las palabras de cuatro letras bajo la misma clave entera.Función de combinación
Math::max conserva la mayor cantidad cuando una clave de existencias aparece dos veces.I
Inferencia de tipos
last(words) produzca un String cuando words es una lista de cadenas.Interfaz BinaryOperator
BinaryOperator<Integer> como Integer::sum puede combinar recuentos para una clave de mapa repetida.Interfaz Consumer
accept no devuelve ningún valor.Consumer<String> puede imprimir cada palabra cuando se pasa al forEach de una colección.Interfaz funcional
Object.Operation permite a quien la utiliza proporcionar su cálculo mediante una expresión lambda.Interfaz Function
T en un resultado de tipo R mediante apply.Function<String, Integer> puede transformar cada palabra en su longitud antes de agrupar.Interfaz genérica
Container<T> puede mantener relacionada la entrada de put con el resultado de take.Interfaz IntStream
int con operaciones numéricas como suma y media, además de conversiones de vuelta a objetos.IntStream obtenido de mapToInt puede sumar puntos sin recopilar primero valores envoltorio.Interfaz Predicate
boolean mediante test.Predicate<String> pasado a removeIf identifica las palabras que cumplen la condición y deben eliminarse.Interfaz Supplier
get sin argumentos proporciona un valor de su tipo de resultado.Supplier<List<String>> que utiliza ArrayList::new puede proporcionar una lista nueva en cada llamada.Interfaz UnaryOperator
UnaryOperator<String> puede convertir cada palabra a mayúsculas mediante replaceAll de una lista modificable.Invariancia
List<Integer> como List<Number> y la protege de escrituras de valores de doble precisión.Inversión de un comparador
reversed(), incluidos los criterios de desempate existentes.L
Lista no modificable
Stream.toList() lanza una excepción cuando el código intenta añadir otro elemento.M
Método genérico
last devuelve una cadena para una lista de cadenas y un entero para una lista de enteros.N
No interferencia
ArrayList de origen.O
Operación de comprobación de predicados
anyMatch, pero true para allMatch.Operación distinct
distinct elimina elementos duplicados de un flujo mediante la igualdad de objetos y conserva el orden de encuentro en un flujo ordenado.distinct antes de transformar palabras en longitudes puede dejar longitudes repetidas en el resultado.Operación filter
filter conserva los elementos de un flujo cuyo predicado es verdadero, mantiene su tipo de elemento y descarta los demás valores.filter por puntos debe ejecutarse antes de transformar estudiantes en nombres si necesita el método de acceso a los puntos.Operación flatMap
flatMap convierte cada entrada en un flujo y combina los elementos de esos flujos en una sola secuencia de salida.flatMap que utiliza List::stream convierte listas anidadas de enteros en un solo flujo de enteros.Operación intermedia
filter espera a una operación terminal antes de comprobar los elementos.Operación limit
limit conserva como máximo el número solicitado de elementos desde el inicio de un flujo ordenado y permite omitir la entrada posterior.limit antes de ordenar selecciona desde el principio original, no los valores más altos del conjunto.Operación map
map sustituye cada elemento de entrada por un resultado calculado, por lo que puede cambiar el tipo de elemento del flujo.map que utiliza String::length convierte palabras en valores enteros envoltorio, no en un flujo de tipos primitivos.Operación peek
peek deja pasar los elementos sin modificarlos mientras ejecuta una acción de inspección para los valores recorridos; sus llamadas pueden omitirse.peek utilizado para imprimir valores no es fiable para trabajo necesario porque la ejecución puede omitir su etapa.Operación skip
skip descarta el número solicitado de elementos iniciales de un flujo ordenado y deja pasar los elementos restantes.skip(2) en el rango inclusivo de 1 a 10, el siguiente valor emitido es 3.Operación terminal
count devuelve un long y deja el mismo flujo indisponible para reutilizarlo.Operador diamante
<> en una llamada a un constructor genérico; pide al compilador que infiera los argumentos de tipo y conserve las comprobaciones de tipo.new ArrayList<>() mantiene el tipo de la lista a partir de su contexto.Orden de encuentro
findFirst elija el primer nombre que cumple la condición en una lista de origen.Orden natural
Comparable.compareTo y que se utiliza cuando no se proporciona un comparador independiente."Cherry" antes que la palabra con minúscula "apple".Ordenación estable
P
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
R
Recolector
forEach.Recolector groupingBy
groupingBy construye un mapa que asocia las claves encontradas de la función clasificadora con listas o resúmenes posteriores de los elementos correspondientes.groupingBy con averagingInt almacena una media de doble precisión por cada grupo de estudiantes.Recolector joining
joining combina secuencias de caracteres en una sola cadena, con texto opcional entre los elementos y alrededor de todo el resultado.joining con una coma como separador no produce una coma final después del último nombre.Recolector mapping
mapping transforma elementos antes de pasar sus resultados a otro recolector, a menudo dentro de un grupo o una partición.mapping puede extraer los nombres de los estudiantes dentro de cada grupo antes de recopilarlos en una lista.Recolector partitioningBy
partitioningBy divide los elementos según un predicado y siempre devuelve ambas claves booleanas, incluso si uno de los lados está vacío.partitioningBy puede devolver una lista true vacía cuando ningún estudiante alcanza el umbral que comprueba su predicado.Recolector posterior
counting() convierte un mapa de listas por grupo en un mapa de recuentos Long.Reducción
Referencia a constructor
ClassName::new para proporcionar comportamiento de creación de objetos, con argumentos determinados por la interfaz de destino.Student::new puede convertir un nombre en un estudiante mediante un constructor compatible.Referencia a método
:: y se ajusta a las entradas y al resultado de una interfaz funcional de destino.Integer::parseInt proporciona el comportamiento de análisis a una función de cadena a entero.Referencia a método sin receptor vinculado
String::length llama a length() sobre cada cadena proporcionada.Referencia a método vinculada a un receptor
System.out::println imprime utilizando ese objeto de salida concreto.Resultado intermedio
S
Secuencia de procesamiento
stream a count mediante filter produce un número de coincidencias de tipo long.T
Tipo de destino
Predicate<String> hace que s -> s.isEmpty() sea una comprobación booleana de una cadena.Tipo sin parametrizar
List xs puede permitir una escritura incorrecta que una List<String> rechazaría.Transitividad
- 1Primeros programas en Java
- 2Condiciones y bucles en Java
- 3Métodos en Java
- 4Listas, arreglos y cadenas en Java
- 5Clases y objetos en Java
- 6Objetos y referencias en Java
- 7HashMap y colecciones en Java
- 8Herencia, interfaces y polimorfismo en Java
- 9Excepciones y archivos en Java
- 10Paquetes en Java
- 11Genéricos y expresiones lambda en Java
Sobre esta unidad
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.