Manual 2.pdf
Short Description
Download Manual 2.pdf...
Description
Unidad 10. Polimorfismo 10.1 Introducción
El polimorfismo nos permite escribir programas para procesar objetos que compartan la misma superclase en una jerarquía de clases, como si todos fueran objetos de la superclase; esto puede simplificar la programación. Con el polimorfismo, podemos diseñar e implementar sistemas que puedan extenderse con facilidad; pueden agregarse nuevas clases con sólo modificar un poco (o nada) las porciones generales del programa, siempre y cuando las nuevas clases sean parte de la jerarquía de herencia que el programa procesa en forma genérica. Las únicas partes de un programa que deben alterarse para dar cabida a las nuevas clases son las que requieren un conocimiento directo de las nuevas clases que el programador agregará a la jerarquía.
10.3 Demostración del comportamiento polimórfico
Cuando el compilador encuentra una llamada a un método que se realiza a través de una variable, determina si el método puede llamarse verificando el tipo de clase de la variable. Si esa clase contiene la declaración del método apropiada (o hereda una), se compila la llamada. En tiempo de ejecución, el tipo del objeto al cual se refiere la variable es el que determina el método que se utilizará.
// Fig. 10.1: PruebaPolimorfismo.java // Asignación de referencias a la superclase y la subclase, a variables de la superclase y la subclase. public class PruebaPolimorfismo { public static void main( String args[] ) { // asigna la referencia a la superclase a una variable de la superclase EmpleadoPorComision3 empleadoPorComision = new EmpleadoPorComision3( "Sue", "Jones", "222‐22‐2222", 10000, .06 ); // asigna la referncia a la subclase a una variable de la subclase EmpleadoBaseMasComision4 empleadoBaseMasComision = new EmpleadoBaseMasComision4( "Bob", "Lewis", "333‐33‐3333", 5000, .04, 300 ); // invoca a toString en un objeto de la superclase, usando una variable de la superclase System.out.printf( "%s %s:\n\n%s\n\n", "Llamada a toString de EmpleadoPorComision3 con referencia de superclase ", "a un objeto de la superclase", empleadoPorComision.toString() ); // invoca a toString en un objeto de la subclase, usando una variable de la subclase System.out.printf( "%s %s:\n\n%s\n\n", "Llamada a toString de EmpleadoBaseMasComision4 con referencia", "de subclase a un objeto de la subclase", empleadoBaseMasComision.toString() ); // invoca a toString en un objeto de la subclase, usando una variable de la superclase EmpleadoPorComision3 empleadoPorComision2 = empleadoBaseMasComision; System.out.printf( "%s %s:\n\n%s\n", "Llamada a toString de EmpleadoBaseMasComision4 con referencia de superclase", "a un objeto de la subclase", empleadoPorComision2.toString() ); } // fin de main } // fin de la clase PruebaPolimorfismo
Llamada a toString de EmpleadoPorComision3 con referencia de superclase a un objeto de la superclase: empleado por comision: Sue Jones numero de seguro social: 222-22-2222 ventas brutas: 10000.00 tarifa de comision: 0.06 Llamada a toString de EmpleadoBaseMasComision4 con referencia de subclase a un objeto de la subclase: sueldo base empleado por comision: Bob Lewis con numero de seguro social: 333-33-3333 ventas brutas: 5000.00 tarifa de comision: 0.04 sueldo base: 300.00 Llamada a toString de EmpleadoBaseMasComision4 con referencia de superclase a un objeto de la subclase: con sueldo base empleado por comision: Bob Lewis numero de seguro social: 333-33-3333 ventas brutas: 5000.00 tarifa de comision: 0.04 sueldo base: 300.00 10.4 Clases y métodos abstractos
En algunos casos es conveniente declarar clases para las cuales no tenemos la intención de crear instancias de objetos. A dichas clases se les conoce como clases abstractas. Como se utilizan sólo como superclases en jerarquías de herencia, nos referimos a ellas como superclases abstractas. Estas clases no pueden utilizarse para instanciar objetos, ya que están incompletas. El propósito principal de una clase abstracta es proporcionar una superclase apropiada, a partir de la cual puedan heredar otras clases y, por ende, compartir un diseño común. Las clases que pueden utilizarse para instanciar objetos se llaman clases concretas. Dichas clases proporcionan implementaciones de cada método que declaran (algunas de las implementaciones pueden heredarse). No todas las jerarquías de herencia contienen clases abstractas. Sin embargo, a menudo los programadores escriben código cliente que utiliza sólo tipos de superclases abstractas para reducir las dependencias del código cliente en un rango de tipos de subclases específicas. Algunas veces las clases abstractas constituyen varios niveles de la jerarquía. Para hacer una clase abstracta, ésta se declara con la palabra clave abstract. Por lo general, una clase abstracta contiene uno o más métodos abstractos. Los métodos abstractos no proporcionan implementaciones. Una clase que contiene métodos abstractos debe declararse como clase abstracta, aun si esa clase contiene métodos concretos (no abstractos). Cada subclase concreta de una superclase abstracta también debe proporcionar implementaciones concretas de los métodos abstractos de la superclase. Los constructores y los métodos static no pueden declararse como abstract. Aunque no podemos instanciar objetos de superclases abstractas, sí podemos usar superclases abstractas para declarar variables que puedan guardar referencias a objetos de cualquier clase concreta que se derive de esas superclases abstractas. Por lo general, los programas utilizan dichas variables para manipular los objetos de las subclases mediante el polimorfismo. En especial, el polimorfismo es efectivo para implementar los sistemas de software en capas.
10.5 Ejemplo práctico: sistema de nómina utilizando polimorfismo
Al incluir un método abstracto en una superclase, obligamos a cada subclase directa de la superclase a sobrescribir ese método abstracto para que pueda convertirse en una clase concreta. Esto permite al diseñador de la jerarquía de clases demandar que cada subclase concreta proporcione una implementación apropiada del método. La mayoría de las llamadas a los métodos se resuelven en tiempo de ejecución, con base en el tipo del objeto que se está manipulando. Este proceso se conoce como vinculación dinámica o vinculación postergada. Una referencia a la superclase puede utilizarse para invocar sólo a métodos de la superclase (y la superclase puede invocar versiones sobrescritas de éstos en la subclase).
El operador instanceof se puede utilizar para determinar si el tipo de un objeto específico tiene la relación “es un” con un tipo específico. Todos los objetos en Java conocen su propia clase y pueden acceder a esta información a través del método
getClass, que todas las clases heredan de la clase Object. El método getClass devuelve un objeto de tipo Class (del paquete java.lang), el cual contiene información acerca del tipo del objeto, incluyendo el nombre de su clase. La relación “es un” se aplica sólo entre la subclase y sus superclases, no viceversa. No está permitido tratar de invocar a los métodos que sólo pertenecen a la subclase, en una referencia a la superclase. Aunque el método que se llame en realidad depende del tipo del objeto en tiempo de ejecución, podemos usar una variable para invocar sólo a los métodos que sean miembros del tipo de esa variable, que el compilador verifica.
// Fig. 10.4: Empleado.java La superclase abstracta Empleado.
public abstract class Empleado { private String primerNombre; private String apellidoPaterno; private String numeroSeguroSocial;
// constructor con tres argumentos public Empleado( String nombre, String apellido, String nss ) { primerNombre = nombre; apellidoPaterno = apellido; numeroSeguroSocial = nss; } // fin del constructor de Empleado con tres argumentos
// establece el primer nombre public void establecerPrimerNombre( String nombre ) { primerNombre = nombre; } // fin del método establecerPrimerNombre
// devuelve el primer nombre public String obtenerPrimerNombre() { return primerNombre; } // fin del método obtenerPrimerNombre
// establece el apellido paterno public void establecerApellidoPaterno( String apellido ) { apellidoPaterno = apellido; } // fin del método establecerApellidoPaterno
// devuelve el apellido paterno public String obtenerApellidoPaterno() { return apellidoPaterno; } // fin del método obtenerApellidoPaterno
// establece el número de seguro social public void establecerNumeroSeguroSocial( String nss ) { numeroSeguroSocial = nss; // debe validar } // fin del método establecerNumeroSeguroSocial
// devuelve el número de seguro social public String obtenerNumeroSeguroSocial() { return numeroSeguroSocial; } // fin del método obtenerNumeroSeguroSocial
// devuelve representación String de un objeto Empleado public String toString() { return String.format( "%s %s\nnumero de seguro social: %s", obtenerPrimerNombre(), obtenerApellidoPaterno(), obtenerNumeroSeguroSocial() ); } // fin del método toString
// método abstracto sobrescrito por las subclases public abstract double ingresos(); // aquí no hay implementación
} // fin de la clase abstracta Empleado
// Fig. 10.5: EmpleadoAsalariado.java La clase EmpleadoAsalariado extiende a Empleado.
public class EmpleadoAsalariado extends Empleado { private double salarioSemanal;
// constructor de cuatro argumentos public EmpleadoAsalariado( String nombre, String apellido, String nss, double salario ) { super( nombre, apellido, nss ); // los pasa al constructor de Empleado establecerSalarioSemanal( salario ); // valida y almacena el salario } // fin del constructor de EmpleadoAsalariado con cuatro argumentos
// establece el salario public void establecerSalarioSemanal( double salario ) { salarioSemanal = salario 0), primer nombre, apellido paterno y saldo. ? 100 Bob Jones 24.98 Escriba el numero de cuenta (> 0), primer nombre, apellido paterno y saldo. ? 200 Steve Doe -345.67 Escriba el numero de cuenta (> 0), primer nombre, apellido paterno y saldo. ? 300 Pam White 0.00 Escriba el numero de cuenta (> 0), primer nombre, apellido paterno y saldo. ? 400 Sam Stone -42.16 Escriba el numero de cuenta (> 0), primer nombre, apellido paterno y saldo. ? 500 Sue Rich 224.62 Escriba el numero de cuenta (> 0), primer nombre, apellido paterno y saldo. ? ^Z
14.5.2 Cómo leer datos de un archivo de texto de acceso secuencial // Fig. 14.11: LeerArchivoTexto.java Este programa lee un archivo de texto y muestra cada registro. import java.io.File; import java.io.FileNotFoundException; import java.lang.IllegalStateException; import java.util.NoSuchElementException; import java.util.Scanner; import com.deitel.jhtp7.cap14.RegistroCuenta; public class LeerArchivoTexto { private Scanner entrada; // permite al usuario abrir el archivo public void abrirArchivo() { try { entrada = new Scanner( new File( "clientes.txt" ) ); } // fin de try catch ( FileNotFoundException fileNotFoundException ) { System.err.println( "Error al abrir el archivo." ); System.exit( 1 ); } // fin de catch } // fin del método abrirArchivo // lee registro del archivo public void leerRegistros() { // objeto que se va a escribir en la pantalla RegistroCuenta registro = new RegistroCuenta(); System.out.printf( "%‐9s%‐15s%‐18s%10s\n", "Cuenta", "Primer nombre", "Apellido paterno", "Saldo" ); try // lee registros del archivo, usando el objeto Scanner { while ( entrada.hasNext() ) { registro.establecerCuenta( entrada.nextInt() ); // lee el número de cuenta registro.establecerPrimerNombre( entrada.next() ); // lee el primer nombre registro.establecerApellidoPaterno( entrada.next() ); // lee el apellido paterno registro.establecerSaldo( entrada.nextDouble() ); // lee el saldo // muestra el contenido del registro System.out.printf( "%‐9d%‐15s%‐18s%10.2f\n",
registro.obtenerCuenta(), registro.obtenerPrimerNombre(), registro.obtenerApellidoPaterno(), registro.obtenerSaldo() ); } // fin de while } // fin de try catch ( NoSuchElementException elementException ) { System.err.println( "El archivo no esta bien formado." ); entrada.close(); System.exit( 1 ); } // fin de catch catch ( IllegalStateException stateException ) { System.err.println( "Error al leer del archivo." ); System.exit( 1 ); } // fin de catch } // fin del método leerRegistros
public void cerrarArchivo() { // cierra el archivo y termina la aplicación if ( entrada != null ) entrada.close(); // cierra el archivo
} // fin del método cerrarArchivo } // fin de la clase LeerArchivoTexto
// Fig. 14.12: PruebaLeerArchivoTexto.java Este programa prueba la clase LeerArchivoTexto. public class PruebaLeerArchivoTexto { public static void main( String args[] ) { LeerArchivoTexto aplicacion = new LeerArchivoTexto(); aplicacion.abrirArchivo(); aplicacion.leerRegistros(); aplicacion.cerrarArchivo(); } // fin de main } // fin de la clase PruebaLeerArchivoTexto
Cuenta Primer nombre 100 Bob 200 Steve 300 Pam 400 Sam 500 Sue
Apellido paterno Jones Doe White Stone Rich
Saldo 24.98 ‐345.67 0.00 ‐42.16 224.62
14.5.3 Ejemplo práctico: un programa de solicitud de crédito
// Fig. 14.13: OpcionMenu.java Define un tipo enum para las opciones del programa de consulta de crédito. public enum OpcionMenu { // declara el contenido del tipo enum SALDO_CERO( 1 ), SALDO_CREDITO( 2 ), SALDO_DEBITO( 3 ), FIN( 4 );
private final int valor; // opción actual del menú
OpcionMenu( int valorOpcion ) { valor = valorOpcion; } // fin del constructor del tipo enum OpcionMenu public int obtenerValor() { return valor; } // fin del método obtenerValor } // fin del tipo enum OpcionMenu // Fig. 14.14: ConsultaCredito.java Este programa lee un archivo secuencialmente y muestra su contenido con base en el tipo //de cuenta que solicita el usuario (saldo con crédito, saldo con débito o saldo de cero). import java.io.File; import java.io.FileNotFoundException; import java.lang.IllegalStateException; import java.util.NoSuchElementException; import java.util.Scanner;
import com.deitel.jhtp7.cap14.RegistroCuenta;
public class ConsultaCredito {
private OpcionMenu tipoCuenta; private Scanner entrada; private OpcionMenu opciones[] = { OpcionMenu.SALDO_CERO, OpcionMenu.SALDO_CREDITO, OpcionMenu.SALDO_DEBITO, OpcionMenu.FIN }; Figura 14.14 | Programa de consulta de crédito. (Parte 1 de 3).
// lee los registros del archivo y muestra sólo los registros del tipo apropiado private void leerRegistros() { // objeto que se va a escribir en el archivo RegistroCuenta registro = new RegistroCuenta(); try { // lee registros // abre el archivo para leer desde el principio entrada = new Scanner( new File( "clientes.txt" ) ); while ( entrada.hasNext() ) { // recibe los valores del archivo registro.establecerCuenta( entrada.nextInt() ); // lee número de cuenta registro.establecerPrimerNombre( entrada.next() ); // lee primer nombre registro.establecerApellidoPaterno( entrada.next() ); // lee apellido paterno registro.establecerSaldo( entrada.nextDouble() ); // lee saldo // si el tipo de cuenta es apropiado, muestra el registro if ( debeMostrar( registro.obtenerSaldo() ) ) System.out.printf( "%‐10d%‐12s%‐12s%10.2f\n", registro.obtenerCuenta(), registro.obtenerPrimerNombre(), registro.obtenerApellidoPaterno(), registro.obtenerSaldo() ); } // fin de while } // fin de try catch ( NoSuchElementException elementException ) { System.err.println( "El archivo no esta bien formado." ); entrada.close(); System.exit( 1 ); } // fin de catch catch ( IllegalStateException stateException ) { System.err.println( "Error al leer del archivo." ); System.exit( 1 ); } // fin de catch catch ( FileNotFoundException fileNotFoundException ) { System.err.println( "No se puede encontrar el archivo." ); System.exit( 1 ); } // fin de catch finally { if ( entrada != null ) entrada.close(); // cierra el objeto Scanner y el archivo } // fin de finally } // fin del método leerRegistros // usa el tipo de registro para determinar si el registro debe mostrarse private boolean debeMostrar( double saldo ) { if ( ( tipoCuenta == OpcionMenu.SALDO_CREDITO ) && ( saldo 0 ) ) return true; else if ( ( tipoCuenta == OpcionMenu.SALDO_CERO ) && ( saldo == 0 ) ) return true; return false; } // fin del método debeMostrar // obtiene solicitud del usuario private OpcionMenu obtenerSolicitud() { Scanner textoEnt = new Scanner( System.in ); int solicitud = 1; Figura 14.14 | Programa de consulta de crédito. (Parte 2 de 3).
// muestra opciones de solicitud System.out.printf( "\n%s\n%s\n%s\n%s\n%s\n", "Escriba solicitud", " 1 ‐ Lista de cuentas con saldos de cero", " 2 ‐ Lista de cuentas con saldos con credito", " 3 ‐ Lista de cuentas con saldos con debito", " 4 ‐ Finalizar ejecucion" ); try // trata de recibir la opción del menú { do // recibe solicitud del usuario { System.out.print( "\n? " ); solicitud = textoEnt.nextInt(); } while ( ( solicitud 4 ) ); } // fin de try catch ( NoSuchElementException elementException ) { System.err.println( "Entrada invalida." ); System.exit( 1 ); } // fin de catch return opciones[ solicitud ‐ 1 ]; // devuelve valor de enum para la opción } // fin del método obtenerSolicitud public void procesarSolicitudes() { // obtiene la solicitud del usuario (saldo de cero, con crédito o con débito) tipoCuenta = obtenerSolicitud(); while ( tipoCuenta != OpcionMenu.FIN ) { switch ( tipoCuenta ) { case SALDO_CERO: System.out.println( "\nCuentas con saldos de cero:\n" ); break; case SALDO_CREDITO: System.out.println( "\nCuentas con saldos con credito:\n" ); break; case SALDO_DEBITO: System.out.println( "\nCuentas con saldos con debito:\n" ); break; } // fin de switch leerRegistros(); tipoCuenta = obtenerSolicitud(); } // fin de while } // fin del método procesarSolicitudes } // fin de la clase ConsultaCredito // Fig. 14.15: PruebaConsultaCredito.java // Este programa prueba la clase ConsultaCredito. public class PruebaConsultaCredito { public static void main( String args[] ) { ConsultaCredito aplicacion = new ConsultaCredito(); aplicacion.procesarSolicitudes(); } } // fin de main } // fin de la clase PruebaConsultaCredito
Escriba solicitud 1 - Lista de cuentas con saldos de cero 2 - Lista de cuentas con saldos con credito 3 - Lista de cuentas con saldos con debito 4 - Finalizar ejecución ?1 Cuentas con saldos de cero: 300 Pam White 0.00 Escriba solicitud 1 - Lista de cuentas con saldos de cero 2 - Lista de cuentas con saldos con credito 3 - Lista de cuentas con saldos con debito 4 - Finalizar ejecución ?2 con saldos con credito: Cuentas 200 Steve Doe -345.67 400 Sam Stone -42.16 Escriba solicitud 1 - Lista de cuentas con saldos de cero de cuentas con saldos con credito 23 -- Lista Lista de cuentas con saldos con debito 4 - Finalizar ejecución ?3 Cuentas con saldos con debito: 100 Bob Jones 24.98 Sue Rich 224.62 500 ? 4 14.6 Serialización de objetos
Java cuenta con un mecanismo llamado Serialización de objetos, el cual permite escribir o leer objetos completos mediante un flujo. Un objeto serializado es un objeto que se representa como una secuencia de bytes, e incluye los datos del objeto, así como información acerca del tipo del objeto y los tipos de datos almacenados en el mismo. Una vez que se escribe un objeto serializado en un archivo, se puede leer del archivo y deserializarse; es decir, se puede utilizar la información de tipo y los bytes que representan al objeto para recrearlo en la memoria. Las clases ObjectInputStream y ObjectOutputStream, que implementan en forma respectiva a las interfaces ObjectInput y ObjectOutput, permiten leer o escribir objetos completos de/a un flujo (posiblemente un archivo). Sólo las clases que implementan a la interfaz Serializable pueden serializarse y deserializarse con objetos ObjectOutputStream y ObjectInputStream.
14.6.1 Creación de un archivo de acceso secuencial mediante el uso de la Serialización de objetos // Fig. 14.17: RegistroCuentaSerializable.java Una clase que representa un registro de información. package com.deitel.jhtp7.cap14; // empaquetada para reutilizarla
import java.io.Serializable;
public class RegistroCuentaSerializable implements Serializable { private int cuenta; private String primerNombre; private String apellidoPaterno; private double saldo;
// el constructor sin argumentos llama al otro constructor con valores predeterminados public RegistroCuentaSerializable() { this( 0, "", "", 0.0 ); } // fin del constructor de RegistroCuentaSerializable sin argumentos
Figura 14.17 | La clase RegistroCuentaSerializable para los objetos serializables. (Parte 1 de 2).
// el constructor con cuatro argumentos inicializa un registro public RegistroCuentaSerializable( int cta, String nombre, String apellido, double sal ) { establecerCuenta( cta ); establecerPrimerNombre( nombre ); establecerApellidoPaterno( apellido ); establecerSaldo( sal ); } // fin del constructor de RegistroCuentaSerializable con cuatro argumentos
public void establecerCuenta( int cta ) { // establece el número de cuenta cuenta = cta; } // fin del método establecerCuenta
public int obtenerCuenta() { // obtiene el número de cuenta return cuenta; } // fin del método obtenerCuenta public void establecerPrimerNombre( String nombre ) { // establece el primer nombre primerNombre = nombre; } // fin del método establecerPrimerNombre
public String obtenerPrimerNombre() { // obtiene el primer nombre return primerNombre; } // fin del método obtenerPrimerNombre
public void establecerApellidoPaterno( String apellido ) { // establece el apellido paterno apellidoPaterno = apellido; } // fin del método establecerApellidoPaterno
public String obtenerApellidoPaterno() { // obtiene el apellido paterno return apellidoPaterno; } // fin del método obtenerApellidoPaterno
public void establecerSaldo( double sal ) { // establece el saldo saldo = sal; } // fin del método establecerSaldo
public double obtenerSaldo() { // obtiene el saldo return saldo; } // fin del método obtenerSaldo } // fin de la clase RegistroCuentaSerializable
Figura 14.17 | La clase RegistroCuentaSerializable para los objetos serializables. (Parte 2 de 2). ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ // Fig. 14.18: CrearArchivoSecuencial.java Escritura de objetos en forma secuencial a un archivo, con la clase ObjectOutputStream. import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.NoSuchElementException; import java.util.Scanner; import com.deitel.jhtp7.cap14.RegistroCuentaSerializable; public class CrearArchivoSecuencial { private ObjectOutputStream salida; // envía los datos a un archivo Figura 14.18 | Archivo secuencial creado mediante ObjectOutputStream. (Parte 1 de 2).
public void abrirArchivo() { // permite al usuario especificar el nombre del archivo try { // abre el archivo salida = new ObjectOutputStream( new FileOutputStream( "clientes.ser" ) ); } // fin de try catch ( IOException ioException ) { System.err.println( "Error al abrir el archivo." ); } // fin de catch } // fin del método abrirArchivo
public void agregarRegistros() { // agrega registros al archivo RegistroCuentaSerializable registro; // objeto que se va a escribir al archivo int numeroCuenta = 0; // número de cuenta para el objeto registro String primerNombre; // primer nombre para el objeto registro String apellidoPaterno; // apellido paterno para el objeto registro double saldo; // saldo para el objeto registro
Scanner entrada = new Scanner( System.in );
System.out.printf( "%s\n%s\n%s\n%s\n\n", "Para terminar de introducir datos, escriba el indicador de fin de archivo ", "Cuando se le pida que introduzca los datos.", "En UNIX/Linux/Mac OS X escriba d y oprima Intro", "En Windows escriba z y oprima Intro" );
System.out.printf( "%s\n%s", "Escriba el numero de cuenta (> 0), primer nombre, apellido y saldo.", "? " );
while ( entrada.hasNext() ) { // itera hasta el indicador de fin de archivo try { // envía los valores al archivo numeroCuenta = entrada.nextInt(); // lee el número de cuenta primerNombre = entrada.next(); // lee el primer nombre apellidoPaterno = entrada.next(); // lee el apellido paterno saldo = entrada.nextDouble(); // lee el saldo if ( numeroCuenta > 0 ) { // crea un registro nuevo registro = new RegistroCuentaSerializable( numeroCuenta, primerNombre, apellidoPaterno, saldo ); salida.writeObject( registro ); // envía el registro como salida } // fin de if else { System.out.println( "El numero de cuenta debe ser mayor de 0." ); } // fin de else } // fin de try catch ( IOException ioException ) { System.err.println( "Error al escribir en el archivo." ); return; } // fin de catch catch ( NoSuchElementException elementException ) { System.err.println( "Entrada invalida. Intente de nuevo." ); entrada.nextLine(); // descarta la entrada para que el usuario intente de nuevo } // fin de catch System.out.printf( "%s %s\n%s", "Escriba el numero de cuenta (>0),", "primer nombre, apellido y saldo.", "? " ); } // fin de while } // fin del método agregarRegistros public void cerrarArchivo() { // cierra el archivo y termina la aplicación try { // cierra el archivo if ( salida != null ) salida.close(); } // fin de try catch ( IOException ioException ) { System.err.println( "Error al cerrar el archivo." ); System.exit( 1 ); } // fin de catch } // fin del método cerrarArchivo } // fin de la clase CrearArchivoSecuencial
// Fig. 14.19: PruebaCrearArchivoSecuencial.java // Prueba de la clase CrearArchivoSecuencial.
public class PruebaCrearArchivoSecuencial { public static void main( String args[] ) { CrearArchivoSecuencial aplicacion = new CrearArchivoSecuencial();
aplicacion.abrirArchivo(); aplicacion.agregarRegistros(); aplicacion.cerrarArchivo();
} // fin de main } // fin de la clase PruebaCrearArchivoSecuencial Para terminar de introducir datos, escriba el indicador de fin de archivo cuando se le pida que introduzca los datos. En UNIX/Linux/Mac OS X escriba d y oprima Intro En Windows escriba z y oprima Intro Escriba el numero de cuenta (> 0), primer nombre, apellido y saldo. ? 100 Bob Jones 24.98 Escriba el numero de cuenta (> 0), primer nombre, apellido y saldo. ? 200 Steve Doe -345.67 Escriba el numero de cuenta (> 0), primer nombre, apellido y saldo. ? 300 Pam White 0.00 Escriba el numero de cuenta (> 0), primer nombre, apellido y saldo. ? 400 Sam Stone -42.16 Escriba el numero de cuenta (> 0), primer nombre, apellido y saldo. ? 500 Sue Rich 224.62 Escriba el numero de cuenta (> 0), primer nombre, apellido y saldo. ? ^Z
14.6.2 Lectura y deserialización de datos de un archivo de acceso Secuencial // Fig. 14.20: LeerArchivoSecuencial.java // Este programa lee un archivo de objetos en forma secuencial y muestra cada registro. import java.io.EOFException; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream;
import com.deitel.jhtp7.cap14.RegistroCuentaSerializable;
public class LeerArchivoSecuencial { private ObjectInputStream entrada; // permite al usuario seleccionar el archivo a abrir public void abrirArchivo() { try { // abre el archivo entrada = new ObjectInputStream( new FileInputStream( "clientes.ser" ) ); } // fin de try catch ( IOException ioException ) { System.err.println( "Error al abrir el archivo." ); } // fin de catch } // fin del método abrirArchivo // lee el registro del archivo public void leerRegistros() { RegistroCuentaSerializable registro; System.out.printf( "%‐10s%‐15s%‐15s%10s\n", "Cuenta", "Primer nombre", "Apellido paterno", "Saldo" );
Figura 14.20 | Lectura de un archivo secuencial, usando un objeto ObjectInputStream. (Parte 1 de 2).
try { // recibe los valores del archivo while ( true ) { registro = ( RegistroCuentaSerializable ) entrada.readObject(); // muestra el contenido del registro System.out.printf( "%‐10d%‐15s%‐15s%11.2f\n", registro.obtenerCuenta(), registro.obtenerPrimerNombre(), registro.obtenerApellidoPaterno(), registro.obtenerSaldo() ); } // fin de while } // fin de try catch ( EOFException endOfFileException ) { return; // se llegó al fin del archivo } // fin de catch catch ( ClassNotFoundException classNotFoundException ) { System.err.println( "No se pudo crear el objeto." ); } // fin de catch catch ( IOException ioException ) { System.err.println( "Error al leer del archivo." ); } // fin de catch } // fin del método leerRegistros // cierra el archivo y termina la aplicación public void cerrarArchivo() { try { // cierra el archivo y sale if ( entrada != null ) entrada.close(); System.exit( 0 ); } // fin de try catch ( IOException ioException ) { System.err.println( "Error al cerrar el archivo." ); System.exit( 1 ); } // fin de catch } // fin del método cerrarArchivo } // fin de la clase LeerArchivoSecuencial
Figura 14.20 | Lectura de un archivo secuencial, usando un objeto ObjectInputStream. (Parte 2 de 2). // Fig. 14.21: PruebaLeerArchivoSecuencial.java // Este programa prueba la clase ReadSequentialFile. public class PruebaLeerArchivoSecuencial { public static void main( String args[] ) { LeerArchivoSecuencial aplicacion = new LeerArchivoSecuencial(); aplicacion.abrirArchivo(); aplicacion.leerRegistros(); aplicacion.cerrarArchivo(); } // fin de main } // fin de la clase PruebaLeerArchivoSecuencial
14.7 Clases adicionales de java.io
La interfaz ObjectOutput contiene el método writeObject, el cual recibe un objeto Object que implementa a la interfaz Serializable como argumento y escribe su información en un objeto OutputStream. La interfaz ObjectInput contiene el método readObject, que lee y devuelve una referencia a un objeto Object de un objeto InputStream. Una vez que se ha leído un objeto, su referencia puede convertirse al tipo actual del objeto. El uso de búfer es una técnica para mejorar el rendimiento de E/S. Con un objeto BufferedOutputStream, cada instrucción de salida no necesariamente produce una transferencia física real de datos al dispositivo de salida. En vez de ello, cada operación de salida se dirige hacia una región en memoria llamada búfer, la cual es lo bastante grande como para contener los datos de muchas operaciones de salida. La transferencia actual al dispositivo de salida se realiza entonces en una sola operación de salida física extensa cada vez que se llena el búfer. Con un objeto BufferedInputStream, muchos trozos “lógicos” de datos de un archivo se leen como una sola operación de entrada física extensa y se colocan en un búfer de memoria. A medida que un programa solicita cada nuevo trozo de datos, se obtiene del búfer. Cuando el búfer está vacío, se lleva a cabo la siguiente operación de entrada física real desde el dispositivo de entrada, para leer el nuevo grupo de trozos “lógicos” de datos.
14.8 Abrir archivos con JFileChooser
La clase JFileChooser se utiliza para mostrar un cuadro de diálogo, que permite a los usuarios de un programa seleccionar archivos con facilidad, mediante una GUI.
// Fig. 14.22: DemostracionFile.java Demostración de la clase File. import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; public class DemostracionFile extends JFrame { private JTextArea areaSalida; // se utiliza para salida private JScrollPane panelDespl; // se utiliza para que la salida pueda desplazarse // establece la GUI public DemostracionFile() { super( "Prueba de la clase File" ); areaSalida = new JTextArea(); // agrega areaSalida a panelDespl panelDespl = new JScrollPane( areaSalida ); add( panelDespl, BorderLayout.CENTER ); // agrega panelDespl a la GUI setSize( 400, 400 ); // establece el tamaño de la GUI setVisible( true ); // muestra la GUI analizarRuta(); // crea y analiza un objeto File } // fin del constructor de DemostracionFile // permite al usuario especificar el nombre del archivo private File obtenerArchivo() { // muestra el cuadro de diálogo de archivos, para que el usuario pueda elegir el archivo a abrir JFileChooser selectorArchivos = new JFileChooser(); selectorArchivos.setFileSelectionMode( JFileChooser.FILES_AND_DIRECTORIES ); Figura 14.22 | Demostración de JFileChooser. (Parte 1 de 2).
int resultado = selectorArchivos.showOpenDialog( this ); // si el usuario hizo clic en el botón Cancelar en el cuadro de diálogo, regresa if ( resultado == JFileChooser.CANCEL_OPTION ) System.exit( 1 ); File nombreArchivo = selectorArchivos.getSelectedFile(); // obtiene el archivo seleccionado // muestra error si es inválido if ( ( nombreArchivo == null ) || ( nombreArchivo.getName().equals( "" ) ) ) { JOptionPane.showMessageDialog( this, "Nombre de archivo inválido", "Nombre de archivo inválido", JOptionPane.ERROR_MESSAGE ); System.exit( 1 ); } // fin de if return nombreArchivo; } // fin del método obtenerArchivo // muestra información acerca del archivo que especifica el usuario public void analizarRuta() { // crea un objeto File basado en la entrada del usuario File nombre = obtenerArchivo(); if ( nombre.exists() ) { // si el nombre existe, muestra información sobre él // muestra la información sobre el archivo (o directorio) areaSalida.setText( String.format( "%s%s\n%s\n%s\n%s\n%s%s\n%s%s\n%s%s\n%s%s\n%s%s", nombre.getName(), " existe", ( nombre.isFile() ? "es un archivo" : "no es un archivo" ), ( nombre.isDirectory() ? "es un directorio" : "no es un directorio" ), ( nombre.isAbsolute() ? "es una ruta absoluta" : "no es una ruta absoluta" ), "Ultima modificacion: ", nombre.lastModified(), "Tamanio: ", nombre.length(), "Ruta: ", nombre.getPath(), "Ruta absoluta: ", nombre.getAbsolutePath(), "Padre: ", nombre.getParent() ) ); if ( nombre.isDirectory() ) { // imprime el listado del directorio String directorio[] = nombre.list(); areaSalida.append( "\n\nContenido del directorio:\n" ); for ( String nombreDirectorio : directorio ) areaSalida.append( nombreDirectorio + "\n" ); } // fin de else } // fin de if exterior else // no es archivo ni directorio, imprime mensaje de error { JOptionPane.showMessageDialog( this, nombre + " no existe.", "ERROR", JOptionPane.ERROR_MESSAGE ); } // fin de else } // fin del método analizarRuta } // fin de la clase DemostracionFile
// Fig. 14.23: PruebaDemostracionFile.java Prueba de la clase DemostracionFile. import javax.swing.JFrame;
public class PruebaDemostracionFile { public static void main( String args[] ) { DemostracionFile aplicacion = new DemostracionFile(); aplicacion.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
} // fin de main } // fin de la clase PruebaDemostracionFile
Unidad 15. Recursividad 15.1 Introducción
Un método recursivo se llama a sí mismo en forma directa o indirecta a través de otro método. Cuando se llama a un método recursivo para resolver un problema, en realidad el método es capaz de resolver sólo el (los) caso(s) más simple(s), o caso(s) base. Si se llama con un caso base, el método devuelve un resultado.
15.2 Conceptos de recursividad
Si se llama a un método recursivo con un problema más complejo que el caso base, por lo general, divide el problema en dos piezas conceptuales: una pieza que el método sabe cómo resolver y otra pieza que no sabe cómo resolver. Para que la recursividad sea factible, la pieza que el método no sabe cómo resolver debe asemejarse al problema original, pero debe ser una versión ligeramente más simple o pequeña del mismo. Como este nuevo problema se parece al problema original, el método llama a una nueva copia de sí mismo para trabajar en el problema más pequeño; a esto se le conoce como paso recursivo. Para que la recursividad termine en un momento dado, cada vez que un método se llame a sí mismo con una versión más simple del problema original, la secuencia de problemas cada vez más pequeños debe converger en un caso base. Cuando el método reconoce el caso base, devuelve un resultado a la copia anterior del método. Una llamada recursiva puede ser una llamada a otro método, que a su vez realiza una llamada de vuelta al método original. Dicho proceso sigue provocando una llamada recursiva al método original. A esto se le conoce como llamada recursiva indirecta, o recursividad indirecta.
15.3 Ejemplo de uso de recursividad: factoriales
La acción de omitir el caso base, o escribir el paso recursivo de manera incorrecta para que no converja en el caso base, puede ocasionar una recursividad infinita, con lo cual se agota la memoria en cierto punto. Este error es análogo al problema de un ciclo infinito en una solución iterativa (no recursiva).
15.4 Ejemplo de uso de recursividad: serie de Fibonacci
La serie de Fibonacci empieza con 0 y 1, y tiene la propiedad de que cada número subsiguiente de Fibonacci es la suma de los dos anteriores. La proporción de números de Fibonacci sucesivos converge en un valor constante de 1.618…, un número al que se le denomina la proporción dorada, o media dorada. Algunas soluciones recursivas, como la de Fibonacci (que realiza dos llamadas por cada paso recursivo), producen una “explosión” de llamadas a métodos.
// Fig. 15.3: CalculoFactorial.java Método factorial recursivo. public class CalculoFactorial { // declaración recursiva del método factorial public long factorial( long numero ) { if ( numero 3 1 --> 2 3 --> 2 1 --> 3 2 --> 1 2 --> 3 1 --> 3
15.8 Fractales o Un fractal es una figura geométrica que se genera a partir de un patrón que se repite en forma recursiva, un número infinito de veces. o Los fractales tienen una propiedad de auto-similitud: las subpartes son copias de tamaño reducido de toda la pieza.
Pág. 66
15.9 “Vuelta atrás” recursiva (backtracking)
Al uso de la recursividad para regresar a un punto de decisión anterior se le conoce como “vuelta atrás” recursiva. Si un conjunto de llamadas recursivas no produce como resultado una solución al problema, el programa retrocede hasta el punto de decisión anterior y toma una decisión distinta, lo cual a menudo produce otro conjunto de llamadas recursivas.
View more...
Comments