Índex de continguts
Avui us porto a aquest blog una entrada sobre de les millores de Java 8. he de confessar, que si bé a el principi semblava un autèntic galimaties, amb el transcurs de les hores i sobretot, amb la pràctica de les noves estructures, m’he anat convencent que realment el codi és moltíssim més llegible, còmode d’implementar i el més important: eficient.
Bé és cert que em queda moltíssim per provar, aprendre i consolidar però hi ha estructures i maneres d’ocupació que m’han facilitat la vida.
Així que, potser el mateix, també us puguin servir a vosaltres.
Stream, amb tu va començar tot en Java agost!
A diferència del seu predecessor, Java 7, la seva versió més recent (Java 8) li ha afegit a la interfície Collection (de el paquet java.util) el mètode stream.
/** * Returns a sequential {@code Stream} with this collection as its source. * *This method should be overridden when the {@link #spliterator()} * method cannot return a spliterator that is {@code IMMUTABLE}, * {@code CONCURRENT}, or late-binding. (See {@link #spliterator()} * for details.) * * @implSpec * The default implementation creates a sequential {@code Stream} from the * collection's {@code Spliterator}. * * @return a sequential {@code Stream} over the elements in this collection * @since 1.8 */ default Stream stream() { return StreamSupport.stream(spliterator(), false); }
Què és Stream? Doncs ni més ni menys que una seqüència d’elements. Amb aquest mètode podrem transformar una col·lecció d’objectes (arrays, lists, …) en una successió d’objectes.
Deixarem constància de com es faria servir aquest mètode per a les classes ArrayList o per Arrays:
la interfície List hereta de Collection pel que qualsevol classe que la implementi també podrà fer ús del seu mètode stream pel que és fàcil deduir el següent:
List listaCadenas = new ArrayList(); //Notación diamanteList listaCadenas = new ArrayList(); listaCadenas.add("Juan"); listaCadenas.add("Antonio"); listaCadenas.add("Maria"); Stream streamCadenas = listaCadenas.stream();En el caso de Arrays también podemos usar el método: public static Stream stream(T array) { return stream(array, 0, array.length); }Integer enteros = {1,2,3,4,5,6}; Stream enterosStream = Arrays.stream(enteros);Además del método stream Java 8 incluye otro método muy en la línea: parallelStream. Para más información: https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#stream--
Perfecte, però i amb això què fem? Shhh, tranquil·litat … poc a poc, suau suau … ehem 😉 Seguim.
Map, el teu aliat
Encara que la interfície Stream ens proporciona diversos mètodes, començarem per aquest: map.
Si bé en les següents entrades no deixarem en l’oblit: filter, flatMap i redueix.
Quantes vegades hem recorregut una estructura on comprovant una condició realitzàvem una acció o una altra?
Quantes línies de codi? Quants for / if / else imbricats dels que es queixava Sónar (i nosaltres mateixos transcorregudes unes setmanes en què oblidàvem què feia aquest fragment de codi)?
Doncs bé en Java agost se’ns permet fer això mateix en una línia.
Com abans el primer és presentar-vos el mètode:
/** * Returns a stream consisting of the results of applying the given * function to the elements of this stream. * *This is an intermediate * operation. * * @param The element type of the new stream * @param mapper a non-interfering, * stateless * function to apply to each element * @return the new stream */ Stream map(Function mapper);
map aplicarà una funció que llameremos F sobre cadascun dels elements de la successió i tornarà una altra successió d’elements ja modificats.
i aquesta funció és …?
Qualsevol que necessitem aplicar, això sí complint unes restriccions.
Per exemple, podem dir que volem que faci un trim () a cada element de la successió de tipus String de la successió del primer element. Per això podem escriure-ho així en Java 8:
streamCadenas.map(s->s.trim());
És la forma en què ho he començat a fer servir i amb ella segueixo encara que no és l’única.
En aquesta opció fem ús d’una variable s, l’àmbit és el de l’mètode (fora d’aquest map no existirà i no serà necessari declarar-la prèviament).
Estem indicant que a cada element d’aquesta successió, ho anem a guardar en una variable s, de el mateix tipus de l’element, i anem a aplicar-li la funció trim ().
Per què no hem posat String s- > s .trim ()? Perquè Java reconeix perfectament el tipus de la variable s a el conèixer el tipus dels elements de la successió (Stream). Si haguéssim utilitzat el Stream enterosStream s seria de l’tipus Integer i tampoc caldria declarar-lo.
Una altra forma de fer el mateix seria la següent:
streamCadenas.map(String :: trim).
la diferència amb l’anterior és que ens estalviem la variable, aquesta nomenclatura no és vàlida sempre. Ens hem d’assegurar que el mètode no té paràmetres, si els tingués no podria usar-se d’aquesta manera encara que sí en la seva primera versió
Filter, o com estalviar-me uns quants bucles
/** * Returns a stream consisting of the elements of this stream that match * the given predicate. * *This is an intermediate * operation. * * @param predicate a non-interfering, * stateless * predicate to apply to each element to determine if it * should be included * @return the new stream */ Stream filter(Predicate predicate);
Com en la pròpia API s’indica, filter rep una successió d’elements i retorna aquells que compleixin amb el patró buscat (predicate).
Comencem mostrant un exemple bàsic.
Tenim una llista de cadenes en les que guardem tipus de vehicles i anem a obtenir d’ells els que no siguin “motorbike”
List vehicles = Arrays.asList("car", "motorbike", "bus");Antes haríamos algo de este tipo:List filteredVehicles = new ArrayList(); for(String vehicle: vehicles){ if(!"motorbike".equals(vehicle)){ filteredVehicles.add(vehicle); } }
Amb Java agost seria tal que així:
List filteredVehicles = vehicles.stream() .filter(v -> !"motorbike".equals(v)) .collect(Collectors.toList());
En el nostre cas, ja per a un projecte, teníem una llista de tipus Profile anomenada profiles on teníem instàncies de diversos tipus: INDIVIDUAL, CORPORATIU , … volíem d’aquest llistat obtenir 3. Un per cada tipus de perfil de què constava l’aplicació.
Al principi recorríem als for / if en el qual en el for iterábamos la llista profiles i en el if especificàvem la condició a complir. Com més eren diverses llistes les que volíem com a sortida era necessari usar if / else niats. Finalment vam aconseguir fer-ho molt més net amb aquest mètode de Java 8. A baix l’exemple d’una de les llistes obtingudes.
En aquest cas, l’important és veure un altre tipus d’estructura on el predicat de l’filter és un altre mètode: isType, un mètode propi.
List individuales = profiles.stream().filter(s -> isType(s, EnumTypeCertificate.INDIVIDUAL.name())).collect(Collectors.toList());private boolean isType(Profile profile,String typeProfile) { return profile.getType().equals(typeProfile); }flatMap, magia!/** * Returns a stream consisting of the results of replacing each element of * this stream with the contents of a mapped stream produced by applying * the provided mapping function to each element. Each mapped stream is * {@link java.util.stream.BaseStream#close() closed} after its contents * have been placed into this stream. (If a mapped stream is {@code null} * an empty stream is used, instead.) * *This is an intermediate * operation. * * @apiNote * The {@code flatMap()} operation has the effect of applying a one-to-many * transformation to the elements of the stream, and then flattening the * resulting elements into a new stream. * *Examples. * *If {@code orders} is a stream of purchase orders, and each purchase * order contains a collection of line items, then the following produces a * stream containing all the line items in all the orders: *{@code * orders.flatMap(order -> order.getLineItems().stream())... * }* *If {@code path} is the path to a file, then the following produces a * stream of the {@code words} contained in that file: *{@code * Stream lines = Files.lines(path, StandardCharsets.UTF_8); * Stream words = lines.flatMap(line -> Stream.of(line.split(" +"))); * }* The {@code mapper} function passed to {@code flatMap} splits a line, * using a simple regular expression, into an array of words, and then * creates a stream of words from that array. * * @param The element type of the new stream * @param mapper a non-interfering, * stateless * function to apply to each element which produces a stream * of new values * @return the new stream */ Stream flatMap(Function<? super T, ? extends Stream> mapper);
En l’exemple que veiem a sota teníem el següent problema. Cada element Profile tenia un camp token de tipus cadena que contenia valors separats per coma.
Volíem obtenir cada un d’aquests elements i afegir-los a una llista però sense elements repetits.
Per això amb map tranformamos la llista d’objectes Profile en una successió d’elements als quals s’aplicava per a la seva variable símbol el mètode split.
Imaginem que tenim una mena
public class Profile { String token; ... //Getters and Setters ... }
El camp token té una valor com aquest = > token = “BROWSER, PKCS12, JKS”; a Ara imaginem que tenim dos objectes Profile tindríem alguna cosa així:
Lista de Profile Profile = > token = "BROWSER,PKCS12,JKS,USER_GENERATED,JKS,SOFT" ...
El que pretenem obtenir és això:
Lista de String "BROWSER" "PKCS12" "JKS" "USER_GENERATED" "SOFT"
I més encara, no volíem en realitat la llista, volíem tornar fals o cert depenent de si el perfil contenia algun dels tokens per la qual cosa hauria de mostrar en pantalla.
profiles.stream() .map(s->StringUtils.split(s.getToken(), ",")) .flatMap(Arrays::stream) .distinct() .map(StringUtils::trim) .collect(Collectors.toList()) .stream() .anyMatch(EnumToken::isShowInMenu);
per acabar us deixem el capítol 1, de descàrrega gratuïta, de la doc umentación Java 8: (clic aquí)
Aquest post va ser modificat el 19 gener 2021 13:07