Esta es una traducción que desde EGA Futura ofrecemos como cortesía a toda la Ohana y comunidad de programadores , consultores , administradores y arquitectos de Salesforce para toda Iberoamérica .
El enlace a la publicación original, lo encontrarás al final de este artículo.
…
Este tema fue presentado originalmente por Philippe Ozil durante una sesión de Apex Hours el 4 de septiembre de 2021.
Trabajar con colecciones como List, Map y Set es parte de una rutina diaria para los desarrolladores de Apex. Si bien su uso básico es sencillo, existen algunas peculiaridades avanzadas que pueden llevarlo al siguiente nivel. En esta publicación, comenzaremos con los conceptos básicos sobre objetos y colecciones, luego profundizaremos en conceptos avanzados, como iteradores y clasificación.
Explorando objetos y colecciones
Acerca de los objetos y sus métodos
En Apex, todas las clases y primitivas heredan de la clase Object. Esta herencia está implícita en el sentido de que no es necesario especificarla al crear una clase personalizada, por ejemplo.
Gracias a la herencia de Object, todas las instancias de objetos pueden usar los equals
, hashCode
y toString
como se demuestra aquí:
Booleano myBoolean = falso; Objeto myObject = (Objeto) myBoolean; // Boolean es un hijo de Object System.debug (myObject.equals (true)); // falso System.debug (myObject.hashCode ()); // 1237 System.debug (myObject.toString ()); // falso
equals
permiten comparar una instancia de objeto en cuanto a igualdad con otra. Lo interesante de este método es que le permite comparar objetos de diferentes tipos. Por ejemplo, puede comparar una identificación con una cadena:
Id accountId = '0010o00002svksw'; // ID de 15 caracteres String accountIdString = '0010o00002svkswAAA'; // Misma ID en formato de 18 caracteres accountId.equals (accountIdString); // cierto
Si bien equals
es poderoso, tiene un costo de rendimiento cuando se ejecuta en objetos con una gran cantidad de propiedades. Aquí es donde entra en juego el método hashCode
hashCode
devuelve un número entero que identifica una instancia de objeto en función de algunos de sus valores de propiedad. Por ejemplo, el entero 123 tiene un código hash de 123 y una cadena "Hello World" tiene un código hash de -862545276.
Los valores del código hash son pseudo-únicos ya que algunas instancias de objetos con diferentes valores de propiedad pueden compartir el mismo código hash. Esto se denomina colisión de código hash y se considera aceptable. Cuando ocurre una colisión, equals
se usa como alternativa para comparar objetos. En cualquier caso, comparar los códigos hash de dos objetos es estadísticamente más rápido que llamar a los equals
.
Los equals
y hashCode
son esenciales cuando se trabaja con colecciones, y veremos por qué a medida que exploramos los diferentes tipos de colecciones.
Una descripción general de los tipos de colecciones
Las instancias de objetos se pueden almacenar en tres tipos de colecciones: Lista, Conjunto y Mapa. Estos tipos de colección tienen diferentes propósitos y propiedades:
- Una lista contiene una colección ordenada de elementos no únicos
- Un conjunto contiene una colección desordenada de elementos únicos
- Un mapa contiene un diccionario de valores clave en el que las claves son únicas pero los valores no
Dato curioso: Set y Map tienen nombres de conceptos matemáticos (consulte Definiciones de Set y Map ).
List y Set tienen cierto grado de interoperabilidad ya que comparten algunos constructores y métodos que funcionan con ambos tipos. Por ejemplo, puede crear una lista a partir de un conjunto y viceversa, y puede llamar a métodos como addAll
con ambos tipos. Esto permite casos de uso poderosos, como eliminar elementos duplicados de una lista al convertirlos en un conjunto:
// Al convertir una lista en un conjunto se eliminan los elementos duplicados // a expensas del orden de la lista Lista <Intero> duplicados = nueva Lista <Intero> { 5 , 1 , 3 , 1 , 5 , 3 }; List <Integer> uniqueItems = new List <Integer> (new Set <Integer> (duplicados)); System.debug (artículos únicos); // (5, 1, 3)
La unicidad de los valores establecidos y las claves del mapa se aplica gracias al hashCode
y una alternativa a los equals
en caso de colisión del código hash. Estos dos métodos también son esenciales para algunas operaciones Set y Map, como Set.contains
o Map.containsKey
. Curiosamente, las operaciones de lista como List.contains
basan únicamente en equals
y nunca en hashCode
.
Ahora que hemos visto el panorama general de los objetos y los tipos de colección, profundicemos en los detalles de la colección. Comenzaremos iterando en Listas y Conjuntos.
Iterando en listas y conjuntos
List y Set se pueden atravesar con for
, pero hay una alternativa poderosa: los iteradores . List y Set implementan la Iterable
, y esta interfaz proporciona acceso a un iterator
que expone un objeto que implementa la interfaz Iterator
Gracias a los hasNext
y next
del Iterator, puede atravesar una colección en una sola dirección:
List <Cuenta> myList = new List <Cuenta> {/ * algunas cuentas * /}; Iterador <Cuenta> it = myList.iterator (); while (it.hasNext ()) { Cuenta acc = it.next (); // Hacer algo }
Si bien el código anterior es más detallado que los for
, el uso de iteradores tiene tres ventajas clave:
- Los iteradores proporcionan acceso de solo lectura a las colecciones. Si pasa un iterador como parámetro de método, está seguro de que este método no puede modificar su colección.
- Los iteradores bloquean la colección en modo de solo lectura, evitando cualquier modificación a la colección mientras la estás iterando:
Set <Integer> mySet = new Set <Integer> {1, 2, 3}; Iterador <Intero> it = mySet.iterator (); it.next (); it.next (); mySet.add (4); // Falla con System.FinalException porque el iterador está en uso it.next ();
- Los iteradores proporcionan un mecanismo que le permite recuperar elementos dinámicamente sobre la marcha. Nuestro enfoque principal en este artículo son las colecciones, pero puede implementar sus propias clases iterables. Esto es extremadamente útil cuando se trabaja con datos paginados y cuando recupera elementos en lotes. Por ejemplo, podría crear un cliente REST que itera en un recurso y podría comenzar a iterar sin saber el número exacto de elementos que están disponibles.
Consulte las recetas de iteración en Apex Recipes para ver ejemplos de iteradores junto con su clase de prueba relacionada.
Listas de clasificación
La primera opción cuando se trata de ordenar registros es generalmente escribir una consulta SOQL con una cláusula ORDER BY . Sin embargo, a veces es necesario ordenar los registros sin obtenerlos de la base de datos o incluso ordenar los objetos que no son registros. En esos casos, puede usar el método List.sort como este:
List <String> colores = new List <String> {'Amarillo', 'Rojo', 'Verde'}; colors.sort (); // Ordena la lista de cadenas alfabéticamente System.debug (colores); // (verde, rojo, amarillo)
El List.sort
es fácil de usar, pero echemos un vistazo más de cerca a cómo funciona y cómo puede personalizar el orden.
Trabajar con la interfaz comparable
List.sort
trabaja con elementos List que implementan la interfaz Comparable . La interfaz especifica un método único: compareTo
que es llamado por el algoritmo de ordenación de listas para ordenar elementos.
compareTo
devuelve un número entero con los siguientes valores:
- 0 si
this
instancia yobjectToCompareTo
son iguales - > 0 si
this
instancia es mayor queobjectToCompareTo
- <0 si
this
instancia es menor queobjectToCompareTo
List.sort
puede ordenar cualquier combinación de objetos de varios tipos siempre que sean primitivos o implementen Comparable
. Por ejemplo, podrías escribir perfectamente algo como esto:
Lista <Objeto> myList = nueva Lista <Objeto> { 4, 3.14, verdadero, nulo, 'Mundo', 'Hola', 1L, falso, Fecha.today () }; myList.sort (); // nulo, falso, 4, 3.14, verdadero, 1, 2021-09-03 00:00:00, Hola, mundo
Como advertencia, es seguro colocar objetos que no implementan Comparable
en una Lista, pero si llama al sort
en esa Lista, obtendrá una excepción System.ListException
La documentación del método de sort
proporciona una implementación de ejemplo para clasificar una clase personalizada, por lo que no profundizaremos en los detalles de este tema. Pero, ¿qué pasa con la clasificación de SObject
(lista de registros)?
Ordenar listas de sObjects
SObject
implementa la Comparable
y las instancias de esa clase tienen un orden de clasificación predecible. Sin embargo, es posible que deba implementar un orden de clasificación personalizado en algunos casos, y aquí es donde las cosas se vuelven más complejas. La clase SObject es final, por lo que no puede sobrescribir sus métodos internos, como compareTo
.
Para compensar eso, debe trabajar con una clase contenedora alrededor del SObject
que desea ordenar. Imagine que está importando algunas cuentas de una integración de terceros y que desea ordenarlas según el campo del país de envío antes de guardarlas. No puede ordenar los registros con SOQL porque aún no están en la base de datos. Debes implementar la siguiente clase:
La clase pública SortableAccount implementa Comparable { cuenta de cuenta final privada; public SortableAccount (cuenta de cuenta) { this.account = cuenta; } public Integer compareTo (Object otherObject) { // Para mayor seguridad de tipos, verifique si otherObject es una SortableAccount // si no, lanza una SortException if (! (otherObject instanceof SortableAccount)) { lanzar una nueva SortException ('No se puede ordenar con un tipo incompatible'); } // Lanza otherObject a SortableAccount y compáralo SortableAccount otro = (SortableAccount) otroObjeto; if (this.account.ShippingCountry <other.account.ShippingCountry) { return -1; } if (this.account.ShippingCountry> other.account.ShippingCountry) { return 1; } return 0; } La clase pública SortException extiende la excepción {} }
Puede ordenar su Lista con su lógica de pedido personalizada en tres pasos:
- Convierta la
List<Account>
en unaList<SortableAccount>
- Llame al método de
sort
List<SortableAccount>
- Convierta la
List<SortableAccount>
lista de nuevo a laList<Account>
Sin embargo, implementar estos pasos no es práctico, ya que se necesitarían casi tantas líneas de código como la implementación de SortableAccount
y esas líneas adicionales no serían reutilizables. Afortunadamente, hay algo que puede hacer para reducir el código repetitivo. Simplemente agregue el siguiente método estático a SortableAccount
:
clasificación vacía estática pública (Lista <Cuenta> cuentas) { // Convertir Lista <Cuenta> en Lista <Cuenta Clasificable> List <SortableAccount> sortableAccounts = new List <SortableAccount> (); para (Cuenta acc: cuentas) { sortableAccounts.add (nueva SortableAccount (acc)); } // Ordenar cuentas usando SortableAccount.compareTo sortableAccounts.sort (); // Sobrescribe la lista de cuentas proporcionada en el parámetro de entrada // con la lista ordenada. Hacer esto evita una declaración de devolución // y es menos detallado para el usuario del método. para (Entero i = 0; i <cuentas.tamaño (); i ++) { cuentas [i] = sortableAccounts [i] .account; } }
Con este SortableAccount.sort
, todo lo que se necesita para ordenar una lista de registros de cuenta por país de envío es una sola línea:
SortableAccount.sort (cuentas);
Consulte Listar recetas en Apex Recipes para la implementación de SortableAccount
junto con la clase de prueba relacionada.
Clasificación de listas con comparadores reutilizables
Si bien el List.sort
predeterminado es conveniente, tiene dos limitaciones importantes:
- La lógica de ordenación está directamente relacionada con el
Comparable
que se está ordenando. - El método de clasificación carece de la capacidad de clasificar con diferentes estrategias y parámetros.
El lenguaje Java (que está cerca de Apex) va más allá del método básico de clasificación de Apex y expone un método conveniente Arrays.sort(T[], Comparator)
donde T
es el tipo que se está ordenando y Comparator
una interfaz que especifica un método de compare
como Comparable.compareTo
.
Este patrón se puede replicar fácilmente en Apex con una ListUtils
personalizada y una interfaz Comparator
Con este enfoque, puede ordenar listas con diferentes comparadores e incluso pasar parámetros a comparadores. Se beneficia del hecho de que la lógica de ordenación está desacoplada de los objetos que clasifica.
// Ordenar las cuentas alfabéticamente según el país de envío ListUtils.sort (cuentas, nuevo SObjectStringFieldComparator ('ShippingCountry')); // Ordene las cuentas alfabéticamente según la industria ListUtils.sort (cuentas, nuevo SObjectStringFieldComparator ('Industria')); // Ordene las cuentas según los valores de calificación // como se define en el orden de la lista de selección de clasificación (clasificación no alfabética) ListUtils.sort (cuentas, nuevo AccountRatingComparator ());
Consulte ListUtils
en Apex Recipes para la implementación de ListUtils, algunos comparadores y clases de prueba relacionadas.
Palabras de cierre
Eso es un envoltorio. Le dimos un repaso sobre la clase Object y los diferentes tipos de colección. Cubrimos conceptos de colección avanzados: ha aprendido a usar iteradores y a ordenar Listas con el método de ordenación predeterminado y comparadores personalizados. Esperamos que este artículo le haya ayudado a comprender mejor las colecciones. Ahora te toca a ti poner en práctica estos conocimientos en tus proyectos.
Recursos
Sobre el Autor
Philippe Ozil es un promotor principal de desarrolladores en Salesforce, donde se centra en la plataforma Salesforce. Escribe contenido técnico y habla con frecuencia en conferencias. Es un desarrollador de pila completa y disfruta trabajar en proyectos de DevOps, robótica y realidad virtual. Síguelo en Twitter @PhilippeOzil o revisa sus proyectos de GitHub @pozil .
…
Esta es una traducción realizada por EGA Futura, y este es el link a la publicación original: https://developer.salesforce.com/blogs/2021/10/mastering-apex-collections.html