About this entry




Trabajando con Múltiples Objetos: Ciclos en Java (Repetitions in Java, Spanish only)

Programas que trabajan con un solo objeto son bastantes limitados. Lo que podemos hacer con un objeto lo podemos hacer para muchos; simplemente necesitamos determinar que acción llevar a cabo para un objeto, y repetimos dicho calculo tantas veces como queramos. En este articulo consideraremos los procesos repetitivos en el contexto de múltiples instancias de una clase.

Liked it? !

Antes de leer este artículo se recomienda haber leído Ejecución Condicional.

Índice

  1. Introducción
  2. Procesando Múltiples Objetos
    1. Ciclo while
    2. Ejemplo
    3. Patrón de repetición
  3. Ejemplo completo
  4. Mantenimiento de Múltiples Objetos
    1. Introducción
    2. Colección de Clases
    3. Moviéndose a través del Vector
    4. Datos Primitivos y Colecciones
    5. Ejemplo completo
    6. Ultimo ejemplo
  5. Los Metodos de la clase Object
    1. El metodo toString
    2. El metodo equals

 

  1. Introducción

    La habilidad de repetir la ejecución de una acción es la tercera estructura de control, siendo la primera la secuencia y la segunda las decisiones. Las estructuras de control son las herramientas con que contamos para alterar el flujo de ejecución de un programa. En este articulo cubriremos los siguientes temas:

    • Procesamiento de múltiples objetos creados por una entrada.
    • Mantenimiento de múltiples instancias.
    • Procesamiento de múltiples objetos almacenados de alguna manera.

     

  2. Procesando Múltiples Objetos

    Comenzamos con el caso mas simple del trabajo con múltiples objetos. Asumimos que los objetos:

    • Pueden ser procesados independientemente uno del otro.
    • Una vez procesados ya no se necesitaran.

    Por ejemplo, consideremos el sistema simplificado de planilla con muchos empleados. En el dia de pago, el sueldo de cada empleado puede ser calculado independientemente. De hecho, una vez procesado el sueldo de cada empleado el objeto Empleado puede ser desechado. Tenemos el código que procesa a un empleado:

    int horas; // podriamos tener el flujo de entrada de cualquier lado BufferedReader br = new BufferedReader(...); // el codigo que repetiriamos tantas veces como sea necesario seria: Empleado e = Empleado.read(br); horas = Integer.parseInt(br.readLine()); System.out.println("El empleado " + e.getName() + " obtiene " + e.calcPay(horas));

     

    1. Ciclo while

      El primer constructo que veremos para implementar los ciclos será el enunciado while. Su forma general es:

      while (condicion) cuerpo

      Al igual que con el enunciado if la condición representa una expresión booleana (llamada condición del ciclo) y el cuerpo puede ser un enunciado simple o compuesto.

      Los pasos de la ejecución del while son:

      • Se verifica la condición
      • Si la condición se evalúa falsa el ciclo termina y la ejecución continua con el siguiente enunciado después del while.
      • Si la condición se evalúa verdadera, el cuerpo del ciclo es ejecutado y se repiten los pasos.

       

    2. Ejemplo de la planilla

      Utilizaremos el enunciado while para implementar el calculo del sueldo para un conjunto de empleados. Recordemos que el método read de la clase Empleado lo implementamos de tal forma que devolviera null cuando llegara al fin del archivo (recordemos que cuando se hacia un readLine de un BufferedReader construido sobre un archivo, este devolvía null cuando se llegaba al fin del archivo, condición que verificábamos en la implementación de read).

      Vamos a repetir el bloque de instrucciones explicado al principio del tema hasta que no hayan mas empleados en el archivo (cuando read devuelva null). La implementación quedaría así:

      int horas; BufferedReader br = new BufferedReader(...); Empleado e = Empleado.read(br); while (e != null) { horas = Integer.parseInt(br.readLine()); System.out.println("El empleado " + e.getName() + " obtiene " + e.calcPay(horas)); e = Empleado.read(br); }

      Hacemos la lectura del primer empleado fuera del ciclo para que la condición se verifique verdadero la primera vez. Además nos interesa que la condición del ciclo se verifique inmediatamente después de la lectura del objeto Empleado antes del calculo del sueldo (para evitar el calculo de un empleado que no existe). Nuestra primera intuición pudo haber sido así:

      // este codigo es INCORRECTO! int horas; BufferedReader br = new BufferedReader(...); Empleado e; while (e != null) { e = Empleado.read(br);   horas = Integer.parseInt(br.readLine()); System.out.println("El empleado " + e.getName() + " obtiene " + e.calcPay(horas)); }

      Este código falla por dos razones:

      • La primera vez no entraría al ciclo. Como no hemos leído ningún objeto Empleado, la referencia e vale null, por lo que la condición se verificaría falsa desde el principio.
      • Aunque entrara al ciclo, de todos modos fallaría en la ultima iteración: Cuando se acabe la información del archivo y read devuelva null trataría de invocar a calcPay. Dado que es imposible exigirle un comportamiento a un objeto inexistente, esto levantaría un error de corrida.

      Una versión abreviada del ciclo seria:

      int horas; BufferedReader br = new BufferedReader(...); Empleado e; /* aprovechamos a asignar y comparar de una sola vez. Perfectamente valido. */ while ((e = Empleado.read(br)) != null) { horas = Integer.parseInt(br.readLine()); System.out.println("El empleado " + e.getName() + " obtiene " + e.calcPay(horas)); }

       

    3. Patrón de repetición

      Esta misma estructura tendría la lectura del cualquier objeto. Por ejemplo si quisiéramos calcularle el peaje a un conjunto de camiones tendríamos:

      Cabina cabina = new Cabina(); Camion camion; while ((camion = Camion.read(br)) != null) cabina.calculateToll(camion);

      Supongamos que para un conjunto de hileras queramos leerlas y desplegarlas en mayúsculas:

      String s; while ((s = br.readLine()) != null) System.out.println (s.toUpperCase());

      Supongamos que para un conjunto de números queramos leerlos, calcular y desplegar el cuadrado:

      int i; String temp; while ((temp = br.readLine()) != null) { i = Integer.parseInt(temp); System.out.println(i*i); }

      Vemos que todos los ejemplos han tenido la estructura del ciclo similar. A este patrón de repetición se le llama patron de ciclo de lectura/procesamiento.

       

  3. Ejemplo completo: Librería de Canciones para una Estación de Radio

    1. Planteo del Problema

      La estación de radio local nos pide que computaricemos la librería de canciones. Un archivo a sido creado con entradas consistiendo de el titulo de la canción y su compositor. Queremos proveer al disc jockey de la habilidad de buscar en la librería todas las canciones sobre un artista particular.

       

    2. Muestra de Escenario

      Esta podría ser una forma que el disc jockey podría interactuar con el sistema:

      Ingrese el nombre del archivo de libreria de canciones: rock.lib Archivo rock.lib cargado. Ingrese el nombre del artista a buscar: Alux Nahual Canciones de Alux Nahual encontradas: Libre Sentimiento Alto al Fuego Hombres de Maiz Ingrese nombre del artista a buscar: Maddona Ninguna cancion de Maddona encontrada.

       

    3. Determinando los Objetos Primarios

      Los sustantivos relevantes del problema son: librería de canciones, canción, archivo, entrada, titulo y artista. Artista y titulo son subsidiarios a canción y canción es subsidiario a librería de canciones. Entrada y archivo son solo datos en el disco, que representan a la canción a a la librería de canciones. Por lo que nuestro objeto primario será: Librería de Canciones.

       

    4. Comportamiento

      Nos gustaría poder

      1. Cargar un archivo de canciones
      2. Buscar los títulos de las canciones para un artista

       

    5. Interfase

      En código típico primeramente necesito crear el objeto LibreriaDeCanciones, cargando el archivo de canciones:

      LibreriaDeCanciones pop = new LibreriaDeCanciones ("pop.lib"); LibreriaDeCanciones instr = new LibreriaDeCanciones ("instrumental.lib");

      Después me gustaría poder buscar a los artistas:

      pop.lookUp("Laura Pausini"); pop.lookUp("Miguel Bose"); instr.lookUp("Nacho Cano");

       

    6. Esqueleto

      Vemos que necesitamos un constructor que reciba una hilera (el nombre del archivo de canciones) y un método lookUp que también reciba una hilera (el artista a buscar) el cual desplegaría los títulos al System.out.

      class LibreriaDeCanciones { // atributos public LibreriaDeCanciones (String archivoCanciones) { // enunciado } public void lookUp (String artista) { // enunciado } }

       

    7. Implementación

      Cada vez que sea invocado el método lookUp, vamos a recorrer el archivo de canciones comparando el artista de cada canción con el artista a buscar. Necesitaremos por lo tanto que almacenar el nombre del archivo como atributo (ya que necesita ser accedido desde el constructor y desde el método lookUp).

      class LibreriaDeCanciones { private String archivoCanciones; public LibreriaDeCanciones (String archivoCanciones) { this.archivoCanciones = archivoCanciones; } public void lookUp (String artista) { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(archivoCanciones))); Cancion cancion; boolean noHayCanciones=true; while ((cancion=Cancion.read(br)) != null) { if (artista.equals(cancion.getArtist())) { if (noHayCanciones) { noHayCanciones=false; System.out.println("Canciones de ".concat(artista).concat(" encontradas:")); } System.out.println(" ".concat(cancion.getTitle())); } } if (noHayCanciones) System.out.println(" Ninguna cancion de ".concat(artist).concat(" encontrada.")); } }

      Las entradas que vamos a leer desde el archivo serán canciones, por lo que podríamos trabajar con una clase Cancion para modelar este comportamiento. Nos gustaría que la clase Cancion proveyera los métodos:

      1. Read: lee información desde un BufferedReader y en base ha esta crea y devuelve un objeto Cancion.
      2. getArtist: devuelve el artista de la canción
      3. getTitle: devuelve el titulo de la canción

    Ejercicios de clase

    1. Implemente la clase Cancion que incluya una interfase como la usada en el método lookUp.
    2. Escriba un programa que utilice la clase LibreriaDeCanciones para proveer una interacción con el disc jockey como la descrita en el escenario de prueba.

     

  4. Mantenimiento de Múltiples Objetos

    1. Introducción

      En los ejemplos anteriores cada objeto era creado y procesado independientemente. Media vez los objetos eran utilizados, después eran descartados. Sin embargo, hay veces que el calculo de los objetos creados van ha depender de otros objetos por lo que estos no pueden ser desechados inmediatamente. Algunos ejemplos en donde esto es necesario pueden ser:

      1. Dado una lista de nombres, desplegarlos en un orden inverso al de aparición. Aquí es necesario almacenar todos los nombres hasta el ultimo (ya que este es el que se imprime primero).
      2. Dado una lista de nombres, desplegar los nombres en orden alfabético.
      3. Dadas las notas de un conjunto de estudiantes, determinar cuales estudiantes están abajo y cuales estudiantes están arriba de promedio.
      4. Dado un archivo de texto, desplegar una lista de todas las palabras del documento junto con su frecuencia de ocurrencia.

       

    2. Colección de Clases

      Podemos modelar los conjuntos de clases a través de dos tipos de estructuras:

      1. Vectores
      2. Arreglos

      Antes nos enfocaremos a vectores y en el siguiente tema hablaremos de arreglos. Al trabajar con colecciones de objetos nos gustaría poder:

      1. Crear una colección.
      2. Agregar objetos a la colección.
      3. Procesar los objetos de la colección. Una vez agregado un objeto a la colección quisiéramos poder regresar y procesarlo tantas veces como sea necesario.

      Java nos provee de muchas clases para modelar las colecciones en la librería java.util. La mas simple de estas es la clase Vector. A través de una instancia de la clase Vector podemos de una manera muy sencilla modelar el comportamiento antes descrito. El constructor de la clase Vector no recibe argumentos. Para agregar elementos al Vector se utiliza el método addElement. Por ejemplo, para leer un conjunto de hileras e irlas almacenando:

      Vector v = new Vector(); String s; while ((s=br.readLine())!= null)   v.addElement(s);

      Aquí de nuevo estamos trabajando el patrón de ciclo de lectura/procesamiento, en donde el procesamiento es agregar el objeto al vector.

       

    3. Moviéndose a través del Vector

      Al proceso de moverse a través del vector se le llama recorriendo el vector. El proceso de recorrer el vector es uno cíclico, en donde siempre que tengamos mas elementos en el vector, procesamos el siguiente. Para recorrer los elementos de un vector quisiéramos la habilidad de:

      1. De alguna forma obtener los elementos.
      2. Obtener el siguiente elemento
      3. Verificar si quedan mas elementos a visitar.

      Podríamos esperar que la clase Vector nos provea de este comportamiento. Sin embargo la clase Vector no es la única clase que incluye Java para modelar las colecciones y el recorrido es común a todas las colección. Por lo que Java provee el comportamiento de recorrer la colección a través la clase Enumeration.

      En particular, la clase Vector provee un método elements que me devuelve una referencia a un Enumeration. La clase Enumeration me provee de dos métodos para recorrer la colección:

      1. hasMoreElements: predicado que devuelve verdadero si aun quedan elementos que procesar.
      2. nextElement: devuelve el siguiente elemento del vector.

      Supongamos que queramos desplegar el conjunto de hileras que hemos almacenado en el Vector:

      String s; Enumeration enum = v.elements(); while (enum.hasMoreElements()) { s = (String)enum.nextElement(); System.out.println(s); }

      La clase Vector puede trabajar con referencias a objetos de cualquier clase, lo cual provee una flexibilidad maravillosa. Puedo utilizar la clase Vector para almacenar una colección de Strings, asi como para almacenar una colección de Nombres. Una pregunta que surge casi inmediatamente es: entonces una referencia a que clase recibe addElement y devuelve nextElement?

      Para modelar la capacidad de poder trabajar con cualquier tipo de objeto, Java nos provee de la clase Object. La clase Object se utiliza para modelar cualquier objeto, por lo que una referencia a un String, Nombre o Empleado es también una referencia a un Object. Por lo tanto lo que recibe addElement y devuelve nextElement es una referencia a un Object. Esto le da la flexibilidad a la clase Vector de almacenar referencias a cualquier clase.

      Hay que pagar un pequeño precio por esta flexibilidad: si queremos utilizar el objeto que devuelve nextElement con el comportamiento de la clase a la cual pertenece, necesitamos especificarle la clase a la que pertenece. Esto lo hacemos a través del operador cast, como fue ilustrado en el enunciado:

      s = (String)enum.nextElement();

      Ejercicios de clase

      1. Escribir el código para leer un conjunto de objetos Nombre desde el teclado para ser almacenados en un vector. A continuación se deben de desplegar las iniciales de todos los nombres.
      2. Agréguele a la clase Tiempo un método read. Utilice este método para leer un conjunto de tiempos y almacenarlos en un vector. A continuación despliegue estos tiempos leídos.
      3. La clase LibreriaDeCanciones funciona muy ineficientemente. Cada vez que se invoca el método lookUp para buscar un artista tiene que recorrer todo el archivo, lo cual es sumamente lento. Modifique la clase, de tal forma que el constructor cargue todos las canciones del archivo a un Vector y que el método lookUp recorra dicho vector buscando al artista (en vez de recorrer el archivo). Esta mejora hará la clase sustancialmente mas eficiente ya que se necesitara recorrer el archivo una sola vez. Asegúrese de no modificar la interfase!

       

    4. Datos Primitivos y Colecciones

      Para mantener una colección de int, quizás lo primero que nos venga a la mente es a través del Vector. Necesitamos trabajar con un pequeño detalle: el vector acepta objetos como elementos y un int es un dato intrínseco y no un objeto. Para resolver este problema, Java incluye las clases de envoltura. Hay una clase de envoltura casi por cada dato intrínseco: la clase Integer para el tipo int, la clase Float para el tipo float y así sucesivamente.

      Las clases de envoltura tienen dos funciones básicas:

      1. Proveen un lugar lógico en donde agrupar todos los métodos estáticos asociados con el tipo de dato. De hecho, ya hemos utilizado los algunos métodos estáticos de estas clases para la conversión de datos en la lectura desde un BufferedReader (Integer.parseInt por ejemplo).
      2. Proveen la capacidad de poder envolver los datos intrínsecos en objetos. Eso es lo que nos da la capacidad de mantener una colección de datos primitivos. Por ejemplo para mantener un Vector de datos enteros, creamos instancias de la clase Integer que contienen a cada dato int. Dado que estas instancias son objetos, pueden ser agregados al Vector.

      Por ejemplo, supongamos que deseamos leer un conjunto de enteros y almacenarlos en un vector:

      int num; String temp; Vector v = new Vector(); while ((temp=br.readLine())!=null) { num = Integer.parseInt(temp); v.addElement(new Integer(num)); }

      Supongamos que ahora quisiéramos promediar los elementos del Vector:

      Integer i; int suma=0; float prom; int num, n=0; Enumeration enum = v.elements(); while (enum.hasMoreElements()) { i = (Integer) enum.nextElement(); num = i.intValue(); suma+=num; n++; } prom = (float)suma/(float)n; System.out.println(prom);

       

    5. Ejemplo completo: Determinando el rendimiento relativo de un alumno

      Esto ejemplo servirá para ilustrar el uso de vectores, vectores con datos intrínsecos y el procesamiento de elementos almacenados.

       

      1. Planteo del problema

        Supóngase que contamos con un registro de alumnos almacenado en un archivo que almacena para cada alumno:

        1. Su nombre
        2. Su promedio

        Quisiéramos para cada alumno indicar si esta arriba o abajo del promedio de la clase.

         

      2. Determinando el Objeto Primario

        Los sustantivos del problema son: registro de alumnos, alumno, nombre, promedio de alumno y promedio de clase.. El nombre y promedio son subsidiarios al alumno, el cual a su vez es subsidiario al registro de alumnos. El promedio de la clase es una característica del registro de alumnos. Por lo que escogemos trabajar con la clase RegistroDeAlumnos.

         

      3. Comportamiento

        Quisiéramos estar en la capacidad de:

        1. Cargar el archivo de alumnos
        2. Analizar las notas de los alumnos, determinando quienes están abajo y arriba de promedio.

        Nótese que aun no hemos determinado cuando se leerán la información de los alumnos o cuando se calculara el promedio de la clase. Eso no es relevante al comportamiento.

         

      4. Interfase

        Supongamos que tenemos la información de las notas en un archivo llamado fin_progra.lib y deseo analizar dichas notas. Necesito crearía crear un objeto RegistroDeAlumnos, mandándole el nombre del archivo en donde la información de estos alumnos esta almacenada. A continuación le pediría al objeto que analice los datos. El código podría ser así:

        RegistroDeAlumnos reg; reg = new RegistroDeAlumnos("fin_progra.lib"); reg.evaluate();

        Vemos que necesitamos un constructor que reciba como parámetro una hilera que representaría el nombre del archivo de notas. Además necesitamos un método evaluate que no recibe ni devuelve nada.

         

      5. Esqueleto

        Con la información recopilada en la interfase construimos el esqueleto:

        class RegistroDeEstudiantes { // atributos public RegistroDeEstudiantes(String archivoEst) { // enunciados } public void evaluate() { // enunciados } }

         

      6. Implementación

        Vamos a necesitar recorrer la colección de estudiantes dos veces: una para el calculo del promedio de la clase y otra para evaluar cada estudiante verificando si esta arriba o abajo de promedio. Podríamos recorrer el archivo dos veces, pero esto seria sumamente ineficiente. Una mejor implementación podría ser en el constructor ir almacenando en un Vector la información de todos los estudiantes a las veces que se van acumulando los promedios individuales. Media vez calculado el promedio se podría simplemente recorrer el vector desde el método lookUp evaluando quienes están arriba y abajo de promedio. Dado que el promedio y el vector de alumnos van a ser accedidos desde ambos métodos necesitan ser atributos.

        Para modelar el comportamiento del estudiante podríamos diseñar una clase Estudiante que incluya los métodos:

        1. read: lee la información sobre un estudiante desde un BufferedReader y en base a esta crea y devuelve un objeto Estudiante
        2. getAverage: devuelve el promedio
        3. getName: devuelve el nombre

        La implementación quedaría así:

        class RegistroDeEstudiantes { private Vector colEstudiantes; private float promedioClase; public RegistroDeEstudiantes(String archivoEst) throws Exception { colEstudiantes = new Vector; BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(archivoEst))); Estudiante est; int total=0; float suma=0f; while ((est=Estudiante.read(br))!=null) { suma+=e.getAverage(); total++; colEstudiantes.addElement(est); } promedioClase=suma/(float)total; } public void evaluate() { Enumeration enum = colEstudiantes.elements(); Estudiante est; while (enum.hasMoreElements()) { est = enum.nextElement(); System.out.print("El estudiante " + est.getName() + " esta "); if (est.getAverage()>=promedioClase) System.out.print("arriba"); else System.out.print("abajo"); System.out.println(" de promedio"); } } }

      Ejercicio de clase

      complete la clase Estudiante según la interfase utilizada en RegistroDeEstudiantes.

       

    6. Ultimo ejemplo: la clase Conjunto

      Ahora que hemos trabajado con una de las colecciones predefinidas de Java, vamos a crear una nuestra

       

      1. Planteo del problema

        Modele la noción matemática de un conjunto.

         

      2. Determinando el Comportamiento

        Las operaciones que nos gustaría realizar sobre el conjunto son las siguientes:

        1. Verificar pertenencia: contains
        2. Verificar si esta vacío: isEmpty
        3. Agregar elementos: addElement
        4. Duplicar el conjunto: copy
        5. Determinar tamaño: size
        6. Obtener los elementos (en un Enumeration): elements
        7. Unión con otro conjunto: union
        8. Intersección con otro conjunto: intersection
        9. Desplegar el conjunto: print

         

      3. Interfase

        Podríamos realizar las siguientes operaciones sobre el conjunto:

        Conjunto s1, s2, s3, s4; s1 = new Conjunto(); s1.addElement("A"); s1.addElement("B"); s1.addElement("C"); s1.addElement("A"); // a este punto s1 = {A, B, C} s1.print(System.out); s2 = s1.copy(); if (!s2.contains("D")) s2.addElement("D"); s3 = s1.union(s2); s4 = s1.intersect(s2); if (s3.size()<5 && !s3.isEmpty()) Enumeration enum = s3.elements();

        Desearíamos mantener la generalidad lo máximo posible, por lo que los argumentos de addElement y contains serán una referencia a la clase Object.

         

      4. Esqueleto

        Recopilando la información anterior tenemos:

        class Conjunto { // atributos public Conjunto() { // enunciados } public boolean contains(Object elemento) { // enunciados } public boolean isEmpty() { // enunciados } public void addElement(Object elemento) { // enunciados } public Conjunto copy() { // enunciados } public int size() { // enunciados } public Enumeration elements() { // enunciados } public Conjunto union(Conjunto c2) { // enunciados } public Conjunto intersection(Conjunto c2) { // enunciados } public void print(PrintStream ps) { // enunciados } }

         

      5. Implementación

        Necesitaremos un atributo Vector para almacenar a los elementos del conjunto. La implementación de isEmpty, elements y size es bastante directa ya que la clase Vector ya me provee dicho comportamiento, por lo que simplemente es invocado. La explicación del resto de los métodos quedan en sus comentarios.

        class Conjunto { private Vector losElementos; /**Simplemente creamos la coleccion*/ public Conjunto() { losElementos = new Vector(); } /**Comparamos el elemento con cada uno de los elementos del vector. Si lo encontramos devolvemos false; si al terminar la comparacion no lo hemos encontrado devolvemos true.*/ public boolean contains(Object elemento) { Enumeration enum = losElementos.elements(); Object o; while (enum.hasMoreElements()) { o = enum.nextElement(); if (o.equals(elemento)) return true; } return false; } /**Utilizamos el isEmpty que ya nos provee Vector*/ public boolean isEmpty() { return losElementos.isEmpty(); } /**Agregamos un elemento al conjunto solo si no esta ya*/ public void addElement(Object elemento) { if (!contains(elemento)) losElementos.addElement(elemento); } /**Creamos un conjunto nuevo y agregamos todos los elementos del conjunto actual.*/ public Conjunto copy() { Conjunto conjDest = new Conjunto(); Enumeration enum = losElementos.elements(); while (enum.hasMoreElements()) conjDest.addElement(enum.nextElement()); return conjDest; } /**Utilizamos el metodo size que ya provee la clase Vector*/ public int size() { return losElementos.size(); } /**Utilizamos el metodo element que ya provee la clase Vector*/ public Enumeration elements() { return losElementos. elements(); } /**Comenzamos con una replica del conjunto 2 y agregamos todos los elementos del conjunto actual que hagan falta. Recordemos que addElement ya valida para elementos repetidos.*/ public Conjunto union(Conjunto c2) { Conjunto conjUnion = c2.copy(); Enumeration enum = losElementos.elements(); while (enum.hasMoreElements()) conjUnion.addElement(enum.nextElement); return conjUnion; } /**Creamos un conjunto nuevo. Despues, para cada uno de los elementos del conjunto actual verificamos si esta en el conjunto 2. Si esta lo agregamos al conjunto nuevo*/ public Conjunto intersection(Conjunto c2) { Conjunto conjInter = new Conjunto(); Enumeration enum = losElementos.elements(); Object obj; while (enum.hasMoreElements()) { obj = enum.nextElement(); if (c2.contains(obj)) conjInter.addElement(obj); } return conjInter; } /**Desplegamos cada uno de los elementos del vector*/ public void print(PrintStream ps) { Enumeration enum = losElementos.elements(); ps.print("{"); if (!isEmpty()) ps.print(enum.nextElement().toString()); while (enum.hasMoreElements()) { ps.print(", "); ps.print(enum.nextElement().toString()); } ps.print("}"); } }

        Se incluyo un método copy para duplicar un Conjunto. Recordemos que si llevamos a cabo una asignación de la forma:

        Conjunto s1, s2; s1 = new Conjunto(); ... s2 = s1;

        No estamos copiando el conjunto s1 en s2, sino que meramente estamos haciendo referencia al mismo Conjunto con dos variables. Si quisiéramos crear un nuevo Conjunto con los mismos elementos de s1 y referirlo a través de s2, lo haríamos así:

        s2 = s1.copy();

        De esta forma ya tendremos dos conjunto distintos, con los mismos elementos.

      Ejercicios de clase

      1. Agregue el método difference (Conjunto c) a la clase conjunto. La diferencia de dos conjuntos consiste en el nuevo conjunto consistiendo de todos los elementos del primer conjunto que no están en el segundo conjunto.
      2. Agregue el método symetricDifference (Conjunto c) a la clase conjunto. La diferencia simétrica de dos conjuntos consiste en el nuevo conjunto consistiendo de todos los elementos que están en un conjunto, pero no en ambos.

       

  5. Los Metodos de la clase Object

    Como todos los objetos son considerados instancias de la clase Object, cualquier método de esta clase debe de ser aplicable universalmente. En esta discusión nos vamos a enfocar a dos métodos específicos (utilizados en la implementación de la clase Conjunto): equals y toString.

     

    1. El metodo toString

      El propósito del método toString es crear un String que represente al objeto al que se le invoco este método. Ya que el método toString que provee la clase Object no sabe nada sobre la clase a la que en realidad pertenece el objeto, este produce una hilera con un mínimo de información: el nombre de la clase a la que en realidad pertenece y un numero que identifica de manera única al objeto. Por ejemplo si tuviera la siguiente clase:

      class Test { public Test() { } }

      El resultado de ejecutar el toString en una instancia de la clase Test:

      Test t = new Test(); String s = t.toString(); System.out.println(s);

      hace que se invoque el método toString de la clase Object que produce el siguiente resultado:

      Test@15368

      Si quisiéramos que el método toString produzca un String adaptado a la clase Test, necesitamos implementar este método en la clase misma. Para hacer esto necesitamos que la firma del método toString de la clase Test tenga exactamente la misma firma que la implementada en la clase Object. La firma de este método se puede ver en el prototipo:

      public String toString()

      Al agregar este método a nuestra clase, estamos indicando que una invocación al método toString sobre un objeto de la clase Test debe de invocar nuestra versión de la clase Test y no la que esta en la clase Object. Ha esto se le llama sobremontar.

      class Test { public Test() { } public String toString() { return "Soy un objeto de la clase test."; } }

      La invocación a toString para un objeto de Test ahora produce la hilera "Soy un objeto de la clase test.". A pesar de que esta mucho mejor, la clase sigue siendo trivial. Vamos a escribir el método toString ahora en una clase mas elaborada: en la clase Nombre. De una manera arbitraria podemos escoger que el método toString devuelva el titulo, seguido del primer nombre y el apellido (que ya es devuelto por el método getFirstLast que diseñamos en el tema de Diseñando Clases).

      class Nombre { ... public String toString() { return this.getFirstLast(); } }

      De esta forma, cualquier usuario que desee una hilera que represente a una instancia de la clase Nombre puede invocar toString.

      Podemos también crear un método toString a nuestra clase conjunto. La método podría devolver una hilera con todos los elementos encerrados entre llaves, como es la simbología convencional para un conjunto. Este quedaría asi:

      public String toString() { Enumeration enum = losElementos.elements(); String s = "{"; if (!isEmpty()) s += enum.nextElement().toString(); while (enum.hasMoreElements()) { s += ", " + enum.nextElement().toString(); } s += "}"; return s; }

      De hecho, con esta representación para evitar repetición de código modificaríamos el método print de la siguiente manera:

      public void print(PrintStream ps) { ps.print(toString()); }

      aunque ya no seria necesaria, ya que la forma en que esta implementado el método print de la clase PrintStream es que recibe un Object y despliega el toString del Object, por lo que escribir:

      Conjunto c= new Conjunto(); ... c.print(System.out);

      daría exactamente lo mismo que escribir:

      Sytem.out.print(c);

      si es que ya esta implementado el método toString para la clase Conjunto.

       

    2. El metodo equals

      Este método es utilizado para comparar la igualdad entre dos objetos. La firma de este método se puede ver en el prototipo:

      public boolean equals(Object o)

      Recuerde que los métodos de la clase Object no saben nada sobre la clase a la que en realidad son los objetos y lo que esta comparando son dos instancias de la clase Object: el receptor y el argumento. Lo que devuelve el método toEquals de la clase Object es si el receptor el argumento hacen referencia exactamente al mismo objeto. Por ejemplo si tenemos:

      Nombre n1 = new Nombre ("Juan", "Perez"); Nombre n2 = n1;

      La invocación a

      n1.equals(n2)

      devuelve verdadero ya que n1 y n2 hacen referencia exactamente al mismo objeto. Sin embargo, si tuviéramos

      Nombre n1 = new Nombre ("Juan", "Perez"); Nombre n2 = new Nombre ("Juan", "Perez");

      La invocación a

      n1.equals(n2)

      devuelve falso ya que n1 y n2 hacen referencia a objetos distintos. El hecho de que contienen la misma información, por lo que intuitivamente son iguales no importa.

      De la misma forma que hicimos con toString si queremos una versión de equals adaptada a la clase Nombre (esto es una que funcione con nuestro entendimiento de igualdad) entonces debemos de incluirla en la clase Nombre misma. El único problema es que la firma tiene que ser exactamente igual que la que esta dentro de la clase Object y esta recibe un argumento Object. Resolvemos este problema haciendo un cast del argumento hacia un objeto Nombre. Podemos estar seguros de que el argumento será un objeto Nombre ya que el receptor es un objeto Nombre. La implementación quedaría así:

      class Nombre { ... public boolean equals (Object o) { Nombre n = (Nombre) o; return this.primer.equals(n.primer) && this.apellido.equals(n.apellido) && this.titulo.equals(n.titulo); } }

    Ejercicio de clase

    Implemente el método equals para la clase Conjunto. Este deberá devolver verdadero si el Conjunto receptor y el Conjunto recibido de parámetro contienen los mismos elementos (aunque en distinto orden).

Siguiente articulo que se recomienda leer: Iteración y Conjuntos de Objetos: Arreglos y Vectores

Etiquetas de technorati:

Liked it? !

Posted on December 24th | 0 comments | Filed Under: Tutorial de Java