Mostrando entradas con la etiqueta Interfaces. Mostrar todas las entradas
Mostrando entradas con la etiqueta Interfaces. Mostrar todas las entradas

jueves, 3 de febrero de 2011

Ordenamiento de colecciones con interface Comparable

Una de las interfaces más útiles en aspectos de ordenar colecciones es la interface Comparable. Al usar esta interface en una clase podemos disponer de la clase de utilidades "Collections" para ordenar de alguna forma deseada, cualquier colección que contenga objetos de ese tipo. En el siguiente ejemplo se utiliza una lista de personas que se ordenan por edad. Para ello solo se implementa el método "compareTo" (preferiblemente usando "generics") en el cual solo se resta la edad de los dos objetos comparados. Después es solo cuestión de invocar el método Collections.sort(Collection<object>).


import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class Person implements Comparable<Person>{

protected String name;
protected int age;

public Person(String name, int age) {
this.name=name;
this.age=age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

/**
* Implementing comparison by age.
*/
public int compareTo(Person o) {
return this.age - o.getAge();
}

@Override
public String toString() {
return this.name + ":" + this.age;
}

public static void main(String[] args) {
List<Person> myFamily = new LinkedList<Person>();
myFamily.add(new Person("Gabriel", 28));
myFamily.add(new Person("Yamai", 51));
myFamily.add(new Person("Blanca", 25));
myFamily.add(new Person("Daniel", 12));
myFamily.add(new Person("Daniela", 27));
myFamily.add(new Person("Lydia", 17));

/*Let's sort the collection.*/
Collections.sort(myFamily);

for(Person familiyMember : myFamily) {
System.out.println(familiyMember);
}
}
}


Salida:

Daniel:12
Lydia:17
Blanca:25
Daniela:27
Gabriel:28
Yamai:51

Así que olvidemonos de hacer ordenamientos de burbuja ;).


miércoles, 13 de octubre de 2010

Obtener Valor Serializado de un Objeto en un String

Hasta hace poco pensaba que existía una manera directa de obtener el valor serializado de un objeto como una cadena de caracteres (String). Necesitaba el valor String para guardarlo en una base de datos. Resulta que todos los ejemplos que encontraba grababan el valor serializado en bytes en un archivo.

Finalmente encontré una solución en un foro que quiero replicar en este blog. La solución require utilizar esta clase que tiene licencia GPL:Base64
package com.eol.common.util;
package com.eol.common.util;

//Copyright 2003-2010 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
//www.source-code.biz, www.inventec.ch/chdh
//
//This module is multi-licensed and may be used under the terms
//of any of the following licenses:
//
//EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal
//LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html
//GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html
//AL, Apache License, V2.0 or later, http://www.apache.org/licenses
//BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php
//
//Please contact the author if you need another license.
//This module is provided "as is", without warranties of any kind.


/**
* A Base64 encoder/decoder.
*
* <p>
* This class is used to encode and decode data in Base64 format as described in RFC 1521.
*
* <p>
* Project home page: <a href="http://www.source-code.biz/base64coder/java/">www.source-code.biz/base64coder/java</a><br>
* Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>
* Multi-licensed: EPL / LGPL / GPL / AL / BSD.
*/
public class Base64Coder {

//The line separator string of the operating system.
private static final String systemLineSeparator = System.getProperty("line.separator");

//Mapping table from 6-bit nibbles to Base64 characters.
private static char[] map1 = new char[64];
static {
int i=0;
for (char c='A'; c<='Z'; c++) map1[i++] = c;
for (char c='a'; c<='z'; c++) map1[i++] = c;
for (char c='0'; c<='9'; c++) map1[i++] = c;
map1[i++] = '+'; map1[i++] = '/'; }

//Mapping table from Base64 characters to 6-bit nibbles.
private static byte[] map2 = new byte[128];
static {
for (int i=0; i<map2.length; i++) map2[i] = -1;
for (int i=0; i<64; i++) map2[map1[i]] = (byte)i; }

/**
* Encodes a string into Base64 format.
* No blanks or line breaks are inserted.
* @param s A String to be encoded.
* @return A String containing the Base64 encoded data.
*/
public static String encodeString (String s) {
return new String(encode(s.getBytes())); }

/**
* Encodes a byte array into Base 64 format and breaks the output into lines of 76 characters.
* This method is compatible with <code>sun.misc.BASE64Encoder.encodeBuffer(byte[])</code>.
* @param in An array containing the data bytes to be encoded.
* @return A String containing the Base64 encoded data, broken into lines.
*/
public static String encodeLines (byte[] in) {
return encodeLines(in, 0, in.length, 76, systemLineSeparator); }

/**
* Encodes a byte array into Base 64 format and breaks the output into lines.
* @param in An array containing the data bytes to be encoded.
* @param iOff Offset of the first byte in <code>in</code> to be processed.
* @param iLen Number of bytes to be processed in <code>in</code>, starting at <code>iOff</code>.
* @param lineLen Line length for the output data. Should be a multiple of 4.
* @param lineSeparator The line separator to be used to separate the output lines.
* @return A String containing the Base64 encoded data, broken into lines.
*/
public static String encodeLines (byte[] in, int iOff, int iLen, int lineLen, String lineSeparator) {
int blockLen = (lineLen*3) / 4;
if (blockLen <= 0) throw new IllegalArgumentException();
int lines = (iLen+blockLen-1) / blockLen;
int bufLen = ((iLen+2)/3)*4 + lines*lineSeparator.length();
StringBuilder buf = new StringBuilder(bufLen);
int ip = 0;
while (ip < iLen) {
int l = Math.min(iLen-ip, blockLen);
buf.append (encode(in, iOff+ip, l));
buf.append (lineSeparator);
ip += l; }
return buf.toString(); }

/**
* Encodes a byte array into Base64 format.
* No blanks or line breaks are inserted in the output.
* @param in An array containing the data bytes to be encoded.
* @return A character array containing the Base64 encoded data.
*/
public static char[] encode (byte[] in) {
return encode(in, 0, in.length); }

/**
* Encodes a byte array into Base64 format.
* No blanks or line breaks are inserted in the output.
* @param in An array containing the data bytes to be encoded.
* @param iLen Number of bytes to process in <code>in</code>.
* @return A character array containing the Base64 encoded data.
*/
public static char[] encode (byte[] in, int iLen) {
return encode(in, 0, iLen); }

/**
* Encodes a byte array into Base64 format.
* No blanks or line breaks are inserted in the output.
* @param in An array containing the data bytes to be encoded.
* @param iOff Offset of the first byte in <code>in</code> to be processed.
* @param iLen Number of bytes to process in <code>in</code>, starting at <code>iOff</code>.
* @return A character array containing the Base64 encoded data.
*/
public static char[] encode (byte[] in, int iOff, int iLen) {
int oDataLen = (iLen*4+2)/3; // output length without padding
int oLen = ((iLen+2)/3)*4; // output length including padding
char[] out = new char[oLen];
int ip = iOff;
int iEnd = iOff + iLen;
int op = 0;
while (ip < iEnd) {
int i0 = in[ip++] & 0xff;
int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
int o0 = i0 >>> 2;
int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
int o3 = i2 & 0x3F;
out[op++] = map1[o0];
out[op++] = map1[o1];
out[op] = op < oDataLen ? map1[o2] : '='; op++;
out[op] = op < oDataLen ? map1[o3] : '='; op++; }
return out; }

/**
* Decodes a string from Base64 format.
* No blanks or line breaks are allowed within the Base64 encoded input data.
* @param s A Base64 String to be decoded.
* @return A String containing the decoded data.
* @throws IllegalArgumentException If the input is not valid Base64 encoded data.
*/
public static String decodeString (String s) {
return new String(decode(s)); }

/**
* Decodes a byte array from Base64 format and ignores line separators, tabs and blanks.
* CR, LF, Tab and Space characters are ignored in the input data.
* This method is compatible with <code>sun.misc.BASE64Decoder.decodeBuffer(String)</code>.
* @param s A Base64 String to be decoded.
* @return An array containing the decoded data bytes.
* @throws IllegalArgumentException If the input is not valid Base64 encoded data.
*/
public static byte[] decodeLines (String s) {
char[] buf = new char[s.length()];
int p = 0;
for (int ip = 0; ip < s.length(); ip++) {
char c = s.charAt(ip);
if (c != ' ' && c != '\r' && c != '\n' && c != '\t')
buf[p++] = c; }
return decode(buf, 0, p); }

/**
* Decodes a byte array from Base64 format.
* No blanks or line breaks are allowed within the Base64 encoded input data.
* @param s A Base64 String to be decoded.
* @return An array containing the decoded data bytes.
* @throws IllegalArgumentException If the input is not valid Base64 encoded data.
*/
public static byte[] decode (String s) {
return decode(s.toCharArray()); }

/**
* Decodes a byte array from Base64 format.
* No blanks or line breaks are allowed within the Base64 encoded input data.
* @param in A character array containing the Base64 encoded data.
* @return An array containing the decoded data bytes.
* @throws IllegalArgumentException If the input is not valid Base64 encoded data.
*/
public static byte[] decode (char[] in) {
return decode(in, 0, in.length); }

/**
* Decodes a byte array from Base64 format.
* No blanks or line breaks are allowed within the Base64 encoded input data.
* @param in A character array containing the Base64 encoded data.
* @param iOff Offset of the first character in <code>in</code> to be processed.
* @param iLen Number of characters to process in <code>in</code>, starting at <code>iOff</code>.
* @return An array containing the decoded data bytes.
* @throws IllegalArgumentException If the input is not valid Base64 encoded data.
*/
public static byte[] decode (char[] in, int iOff, int iLen) {
if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4.");
while (iLen > 0 && in[iOff+iLen-1] == '=') iLen--;
int oLen = (iLen*3) / 4;
byte[] out = new byte[oLen];
int ip = iOff;
int iEnd = iOff + iLen;
int op = 0;
while (ip < iEnd) {
int i0 = in[ip++];
int i1 = in[ip++];
int i2 = ip < iEnd ? in[ip++] : 'A';
int i3 = ip < iEnd ? in[ip++] : 'A';
if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
int b0 = map2[i0];
int b1 = map2[i1];
int b2 = map2[i2];
int b3 = map2[i3];
if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
int o0 = ( b0 <<2) | (b1>>>4);
int o1 = ((b1 & 0xf)<<4) | (b2>>>2);
int o2 = ((b2 & 3)<<6) | b3;
out[op++] = (byte)o0;
if (op<oLen) out[op++] = (byte)o1;
if (op<oLen) out[op++] = (byte)o2; }
return out; }

//Dummy constructor.
private Base64Coder() {}

}


La siguiente clase sería la clase utilitaria para generar el valor String del objeto y para volver a construir el objeto a partir del valor serializado.

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
* Utility class to serialize and unserialize objects.
* @author gsolano
*
*/
public class ObjectSerializationUtils {

/**
* Gets a String value of the object's serialize value.
* @param object
* @return
* @throws IOException
*/
public static String serializeObject(Serializable object) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream( baos );
oos.writeObject( object );
oos.close();
return new String( Base64Coder.encode( baos.toByteArray() ) );
}

/**
* Rebuilds an object (unserializes) from the serialize String value.
* @param serializeString
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static Object unserializeObject( String serializeString )
throws IOException , ClassNotFoundException {
byte [] data = Base64Coder.decode( serializeString );
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream( data ) );
Object o = ois.readObject();
ois.close();
return o;
}
}

Una sencilla prueba:

import java.io.Serializable;

public class MySerializeClass implements Serializable{

private static final long serialVersionUID = 5021383691938311325L;

protected String fooValue;

public String getFooValue() {
return fooValue;
}

public void setFooValue(String fooValue) {
this.fooValue = fooValue;
}
}


import java.io.IOException;

public class StringSerializationTest {

public static void main(String[] args) {
MySerializeClass mySerializeClass = new MySerializeClass();
mySerializeClass.setFooValue("I will be turned in a string soon...");

try {
String DNACode = ObjectSerializationUtils.serializeObject(mySerializeClass);
System.out.println(DNACode);
MySerializeClass clon = (MySerializeClass)
ObjectSerializationUtils.unserializeObject(DNACode);
System.out.println(clon.getFooValue());

} catch (IOException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}


Salida:


rO0ABXNyABBNeVNlcmlhbGl6ZUNsYXNzRa+J3QbsLJ0CAAFMAAhmb29WYWx1ZXQAEkxqYXZhL2xhbmcvU3RyaW5nO3hwdAAkSSB3aWxsIGJlIHR1cm5lZCBpbiBhIHN0cmluZyBzb29uLi4u
I will be turned in a string soon...

viernes, 11 de diciembre de 2009

Iterando sobre estructuras complejas de mapas

¿Te has encontrado alguna vez con estructura de datos anidadas y para colmo con un colocho de genéricos ("Generics")? Aún peor ¿has sido el autor intelectual de estas sentencias confusas? Dejame mostrar un ejemplo de lo que me refiero y que he encontrado al interactuar con las prácticas "inusuales" de algunos compañeros de proyecto.

En nuestra aplicación de ejemplo necesitamos guardar y mostrar la información de matrícula para un cuatrimestre de universidad. La información está estructurada de una manera jerárquica donde el primer nodo es el año en curso, luego le siguen los cuatrimestres, seguidamente los cursos y en el nivel más bajo los estudiantes enlistados en esos cursos.

Año (Year)
++ Cuatrimestre (Quatrimester)
+++ Curso (Course)
++++ Estudiante (Student)

Pareciera que podríamos sentarnos con una buena tacita de café, un hoja de papel y lapiz para sentarnos gustosamente a diseñar nuestras clases; bueno voy a mostrar lo que puede pasar
cuando alguien quiere brincarse este paso y "ahorrar" un poco de tiempo de diseño:


Map<Integer, Map<Integer, Map<String, List<Student>>>> studentsByQuarter = new HashMap<Integer, Map<Integer, Map<String, List<Student>>>>();


¿Para qué complicar más nuestra aplicación agregando más archivos para representar nuestras clases cuando todo se puede resumir en una sola linea? Ese es el acercamiento implicito al problema cuando alguien decide crear este nivel de complejidad que para él o ella puede ser comprendible pero pobre de la persona que tenga que mantener ese código y comenzar por comprender ese colocho.

Voy a desarrollar más esta manera de codificar para mostrar la complejidad intrínseca que se obtiene al no aislar los distintos componentes del problema que queremos resolver.

La clase "Student" as bastante simple con solamente un atributo "fullName":


public class Student {
private String fullName;

public Student(String fullName) {
this.fullName = fullName;
}

public String getFullName() {
return fullName;
}

public void setFullName(String fullName) {
this.fullName = fullName;
}
}


Ahora agregamos un clase "EnrollmentProcessVr1" con la ya introducidad variable "monstruo" y dos métodos adicionales:

+ public static void startEnrollment(int year, int quarter){...} // ¨Para guardar los datos de matrícula.
+ public static void showEnrollment() {...} // Muestra los datos contenidos en la estructura de datos.

Veamos lo complicado que se vuelve tanto agregar como mostrar datos desde esta estructura:


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class EnrollmentProcessVr1 {
private static Map<Integer, Map<Integer, Map<String, List<Student>>>> studentsByQuarter =
new HashMap<Integer, Map<Integer, Map<String, List<Student>>>>();

public static void startEnrollment(int year, int quarter) {
// Create a Year.
Integer currentYear = new Integer(year);

// Create a quatrimester.
Integer quatrimester = new Integer(quarter);

// Create courses.
String course1 = "Discrete Maths";
String course2 = "Programming I";

// Create List of Students.
List<Student> students = new ArrayList<Student>();
students.add(new Student("Cristobal Colón"));
students.add(new Student("Juan SantaMaría"));

// Create a Map of Courses/Students.
Map <String, List<Student>> studentsByCourses = new HashMap<String, List<Student>>();

// Add Students to courses.
studentsByCourses.put(course1, students);
studentsByCourses.put(course2, students);

// Create a Map of Quarter/Courses.
Map<Integer, Map <String, List<Student>>> coursesQuarterMap =
new HashMap<Integer, Map<String,List<Student>>>();

coursesQuarterMap.put(quatrimester, studentsByCourses);

// Add quarter to year.
studentsByQuarter.put(currentYear, coursesQuarterMap);
}

public static void showEnrollment() {
Iterator studentsByQuarterIterator = studentsByQuarter.entrySet().iterator();
while (studentsByQuarterIterator.hasNext()) {
Map.Entry studentsByQuarterPairs = (Map.Entry)studentsByQuarterIterator.next();
System.out.println("Year: " + String.valueOf(studentsByQuarterPairs.getKey()));

Map<Integer, Map<String, List<Student>>> quarterCoursesMap =
(Map<Integer, Map<String, List<Student>>>) studentsByQuarterPairs.getValue();

Iterator quarterCoursesIterator = quarterCoursesMap.entrySet().iterator();

while (quarterCoursesIterator.hasNext()) {
Map.Entry coursesByQuarterPairs = (Map.Entry)quarterCoursesIterator.next();
System.out.println("\tQuarter: " + String.valueOf(coursesByQuarterPairs.getKey()));

Map<String, List<Student>> coursesStudentsMap =
(Map<String, List<Student>>) coursesByQuarterPairs.getValue();

Iterator coursesStudentsIterator = coursesStudentsMap.entrySet().iterator();

while (coursesStudentsIterator.hasNext()) {
Map.Entry coursesStudentsPairs = (Map.Entry)coursesStudentsIterator.next();
System.out.println("\t\tCourse: " + String.valueOf(coursesStudentsPairs.getKey()));

List<Student> studentsByCourse = (List<Student>) coursesStudentsPairs.getValue();

for(Student student : studentsByCourse) {
System.out.println("\t\t\tStudent: " + student.getFullName());
}
}
}
}
}

public static void main(String[] args) {
EnrollmentProcessVr1.startEnrollment(2009, 3);
EnrollmentProcessVr1.showEnrollment();
}
}


Salida del método main:



Mi foco en este post será mostrar una manera de resolver la complejidad intrisica de manejar estructuras de mapa, no necesariamente como hacer "refactoring" de un mal diseño de estructura de datos, pero el desarrollo de este ejemplo ayuda a probar un punto de que incluso con un buen diseño de clases podemos tener mucho código para hacer un recorrido de las estructuras internas de mapa de nuestras clases.

Volvamos entonces al modo diseño de clases:




EnrollmentTrackVr1:


import java.util.LinkedHashMap;
import java.util.Map;

public class EnrollmentTrackVr1 {
private Map<Integer, QuatrimestersVr1> quatrimesters;

public EnrollmentTrackVr1() {
quatrimesters = new LinkedHashMap<Integer, QuatrimestersVr1>();
}

public Map<Integer, QuatrimestersVr1> getQuatrimesters() {
return quatrimesters;
}


public void addStudent(Student student, int year, int quatrimesterNumber, String courseName) {
if (!quatrimesters.containsKey(year)) {
quatrimesters.put(year, new QuatrimestersVr1());
}
quatrimesters.get(year).addCourse(quatrimesterNumber, courseName, student);
}
}



QuatrimestersVr1:


import java.util.HashMap;
import java.util.Map;

public class QuatrimestersVr1 {

private Map<Integer, CoursesVr1> courses;

public QuatrimestersVr1() {
courses = new HashMap<Integer, CoursesVr1>();
}

public void addCourse(int quatrimesterNumber, String courseName, Student student) {
if (!courses.containsKey(quatrimesterNumber)) {
courses.put(quatrimesterNumber, new CoursesVr1());
}
courses.get(quatrimesterNumber).addStudent(courseName, student);
}

public Map<Integer, CoursesVr1> getCourses() {
return courses;
}
}


CoursesVr1:


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class CoursesVr1 {
private Map<String, List<Student>> students;

public CoursesVr1() {
students = new HashMap<String, List<Student>>();
}

public void addStudent(String courseName, Student student) {
if (!students.containsKey(courseName)) {
students.put(courseName, new ArrayList<Student>());
}
students.get(courseName).add(student);
}

public Map<String, List<Student>> getStudents() {
return students;
}

}


En la nueva versión de la clase EnrollmentProcess vemos como el código para agregar nueva información se simplifica significativamente pero la iteración de la estructura
sigue siendo un código a mi gusto complejo porque se necesita accesar los iteradores de cada una de las estructuras internas de cada clase de nuestro diseño:


import java.util.Iterator;
import java.util.List;
import java.util.Map;


public class EnrollmentProcessVr2 {
private static EnrollmentTrackVr1 enrollmentTrackVr1;

public static void startEnrollment(int year, int quarter) {

String course1 = "Discrete Maths";
String course2 = "Programming I";
Student student1 = new Student("Cristobal Colón");
Student student2 = new Student("Juan Santamaría");

enrollmentTrackVr1 = new EnrollmentTrackVr1();
enrollmentTrackVr1.addStudent(student1, year, quarter, course1);
enrollmentTrackVr1.addStudent(student2, year, quarter, course1);
enrollmentTrackVr1.addStudent(student1, year, quarter, course2);
enrollmentTrackVr1.addStudent(student2, year, quarter, course2);

}

public static void showEnrollment() {
Iterator enrollmentIterator =
enrollmentTrackVr1.getQuatrimesters().entrySet().iterator();

while (enrollmentIterator.hasNext()) {
Map.Entry enrollmentPairs = (Map.Entry)enrollmentIterator.next();
System.out.println("Year: " + String.valueOf(enrollmentPairs.getKey()));

QuatrimestersVr1 quatrimesters =
(QuatrimestersVr1) enrollmentPairs.getValue();

Iterator quatrimestersIterator =
quatrimesters.getCourses().entrySet().iterator();

while (quatrimestersIterator.hasNext()) {
Map.Entry quatrimestersPairs = (Map.Entry)quatrimestersIterator.next();
System.out.println("\tQuarter: " + String.valueOf(quatrimestersPairs.getKey()));

CoursesVr1 courses = (CoursesVr1) quatrimestersPairs.getValue();

Iterator coursesIterator =
courses.getStudents().entrySet().iterator();

while (coursesIterator.hasNext()) {
Map.Entry coursesPairs = (Map.Entry)coursesIterator.next();
System.out.println("\t\tCourse: " + String.valueOf(coursesPairs.getKey()));

List<Student> studentsByCourse = (List<Student>) coursesPairs.getValue();

for(Student student : studentsByCourse) {
System.out.println("\t\t\tStudent: " + student.getFullName());
}
}
}
}
}

public static void main(String[] args) {
EnrollmentProcessVr2.startEnrollment(2009, 3);
EnrollmentProcessVr2.showEnrollment();
}
}



¿No sería más pura vida que nuestras clases pudieran ser iteradas en la misma manera que hacemos con una lista de Java Collections?


List<String> fooList = new ArrayList<String>();
...
for (String fooObject : fooList) {
System.out.println(fooObject);
}


Es posible hacer esto si aprendemos a usar las interfaces "Iterable" y "Iterator". En el diseño que voy a mostrar a continuación uso mi propia interface la cual extiende de estas dos y además agregamos un nuevo método que nos será útil para retornar la llave del objeto en curso que estamos iterando (explicaré en detalle más adelante).

Así es como luce el nuevo diseño:



La nueva interface recibe un nuevo elemento genérico "K" que será el tipo/clase de la llave del mapa.


import java.util.Iterator;

public abstract interface MapIterable<K, V> extends Iterable<V>, Iterator<V>{
public K currentKey();
}


Explicamos a continuación en detalle cuáles métodos necesitamos implementar para que nuestras clases sean iterables:

+ public boolean hasNext() : este método indica si la clase puede avanzar en la estructura que está iterando. En mi implementación agrego una variable privada (currentKeyYearIndex)
para mantener el estado del índice actual del arreglo. Esta variable es incrementada con el método next().

+ public V next(): retorna el siguiente objeto de la iteración. En esta implementación obtengo el keyset del mapa interno de la clase y lo paso a un arreglo para obtener el elemento
de la locación indicada por el índice actual.

+ public void remove(): remueve el objeto actual. No necesitaba esta funcionalidad así que solamente arrojo una excepción de tipo UnsupportedOperationException;

+ public Iterator iterator(): Obtiene el iterador. Simplemente retorna la clase.

+ public K currentKey(): el nuevo método que agregué extra para retornar la llave actual de la iteración.

EnrollmentTrackVr2:


import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

public class EnrollmentTrackVr2 implements MapIterable<Integer, QuatrimestersVr2> {

private Map<Integer, QuatrimestersVr2> quatrimesters;
private int currentKeyYearIndex;

public EnrollmentTrackVr2() {
quatrimesters = new LinkedHashMap<Integer, QuatrimestersVr2>();
currentKeyYearIndex = 0;
}

public Map<Integer, QuatrimestersVr2> getQuatrimesters() {
return quatrimesters;
}


public void addStudent(Student student, int year, int quatrimesterNumber, String courseName) {
if (!quatrimesters.containsKey(year)) {
quatrimesters.put(year, new QuatrimestersVr2());
}
quatrimesters.get(year).addCourse(quatrimesterNumber, courseName, student);
}

public boolean hasNext() {
if (quatrimesters.keySet().toArray().length > currentKeyYearIndex) {
return true;
}
return false;
}

public QuatrimestersVr2 next() {
int currentKeyYear = (Integer)
quatrimesters.keySet().toArray()[currentKeyYearIndex++];
return quatrimesters.get(currentKeyYear);
}

public void remove() {
throw new UnsupportedOperationException();
}

public Iterator<QuatrimestersVr2> iterator() {
return this;
}
public Integer currentKey() {
return (Integer)
quatrimesters.keySet().toArray()[currentKeyYearIndex-1];
}
}


QuatrimestersVr2:


import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class QuatrimestersVr2 implements MapIterable<Integer, CoursesVr2> {

private Map<Integer, CoursesVr2> courses;
private int currentQuatriIndex;
private int year;

public QuatrimestersVr2() {
courses = new HashMap<Integer, CoursesVr2>();
currentQuatriIndex = 0;
}

public QuatrimestersVr2(int year) {
this();
this.year = year;
}

public void addCourse(int quatrimesterNumber, String courseName, Student student) {
if (!courses.containsKey(quatrimesterNumber)) {
courses.put(quatrimesterNumber, new CoursesVr2());
}
courses.get(quatrimesterNumber).addStudent(courseName, student);
}

public Map<Integer, CoursesVr2> getCourses() {
return courses;
}

public boolean hasNext() {
if (courses.keySet().toArray().length > currentQuatriIndex) {
return true;
}
return false;
}

public CoursesVr2 next() {
int currentQuatri = (Integer)
courses.keySet().toArray()[currentQuatriIndex++];
return courses.get(currentQuatri);
}

public void remove() {
throw new UnsupportedOperationException();
}

public Iterator<CoursesVr2> iterator() {
return this;
}

public int getYear() {
return year;
}

public void setYear(int year) {
this.year = year;
}

public Integer currentKey() {
return (Integer)
courses.keySet().toArray()[currentQuatriIndex-1];
}
}



CoursesVr2:


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class CoursesVr2 implements MapIterable<String, List<Student>> {

private Map<String, List<Student>> students;
private int currentCourseIndex;
private int quatrimester;

public CoursesVr2() {
students = new HashMap<String, List<Student>>();
currentCourseIndex = 0;
}

public CoursesVr2(int quatrimester) {
this();
this.quatrimester = quatrimester;
}

public void addStudent(String courseName, Student student) {
if (!students.containsKey(courseName)) {
students.put(courseName, new ArrayList<Student>());
}
students.get(courseName).add(student);
}

public Map<String, List<Student>> getStudents() {
return students;
}

public boolean hasNext() {
if (students.keySet().toArray().length > currentCourseIndex) {
return true;
}
return false;
}

public List<Student> next() {
String currentCourse =
students.keySet().toArray()[currentCourseIndex++].toString();
return students.get(currentCourse);
}

public void remove() {
throw new UnsupportedOperationException();
}

public Iterator<List<Student>> iterator() {
return this;
}

public int getQuatrimester() {
return quatrimester;
}

public void setQuatrimester(int quatrimester) {
this.quatrimester = quatrimester;
}

public String currentKey() {
return students.
keySet().toArray()[currentCourseIndex-1].toString();
}
}



Finalmente veamos como queda nuestro proceso de iteración:

EnrollmentProcessVr3:


import java.util.List;

public class EnrollmentProcessVr3 {
private static EnrollmentTrackVr2 enrollmentTrackVr2;

public static void startEnrollment(int year, int quarter) {

String course1 = "Discrete Maths";
String course2 = "Programming I";
Student student1 = new Student("Cristobal Colón");
Student student2 = new Student("Juan Santamaría");

enrollmentTrackVr2 = new EnrollmentTrackVr2();

enrollmentTrackVr2.addStudent(student1, year, quarter, course1);
enrollmentTrackVr2.addStudent(student2, year, quarter, course1);

enrollmentTrackVr2.addStudent(student1, year, quarter, course2);
enrollmentTrackVr2.addStudent(student2, year, quarter, course2);

}

public static void showEnrollment() {
for(QuatrimestersVr2 quatrimesters : enrollmentTrackVr2) {
System.out.println("Year: " + enrollmentTrackVr2.currentKey());
for (CoursesVr2 courses : quatrimesters) {
System.out.println("\tQuatrimester: " + quatrimesters.currentKey());
for (List<Student> students : courses) {
System.out.println("\t\tCourses: " + courses.currentKey());
for (Student student : students) {
System.out.println("\t\t\tStudent: " + student.getFullName());
}
}
}
}
}

public static void main(String[] args) {
EnrollmentProcessVr3.startEnrollment(2009, 3);
EnrollmentProcessVr3.showEnrollment();
}
}



El código puede que tenga algunas pulgas, creo que olvidé reinicializar la llave actual en algu punto, pero la idea no era tener un código a prueba de balas sino demostrar el poder de usar la interface "Iterable". Espero haya sido de interés este tema y ojalá puedan poner en práctica el uso de Iterable.