0% encontró este documento útil (0 votos)
281 vistas91 páginas

Trabajo Final

El documento habla sobre la herencia en programación orientada a objetos. Explica que la herencia permite crear nuevas clases derivadas a partir de clases base existentes, heredando sus atributos y comportamientos. Define las clases base como aquellas de las que derivan otras clases, y las clases derivadas como aquellas que dependen de las clases base y heredan de ellas. Incluye varios ejemplos que ilustran el uso de herencia para crear ventanas con diferentes funcionalidades.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
281 vistas91 páginas

Trabajo Final

El documento habla sobre la herencia en programación orientada a objetos. Explica que la herencia permite crear nuevas clases derivadas a partir de clases base existentes, heredando sus atributos y comportamientos. Define las clases base como aquellas de las que derivan otras clases, y las clases derivadas como aquellas que dependen de las clases base y heredan de ellas. Incluye varios ejemplos que ilustran el uso de herencia para crear ventanas con diferentes funcionalidades.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 91

HERENCIA

Definición: clase base, clase derivada


La herencia es una propiedad esencial de la Programación Orientada a Objetos
que consiste en la creación de nuevas clases a partir de otras ya existentes.
Este término ha sido prestado de la Biología donde afirmamos que un niño
tiene la cara de su padre, que ha heredado ciertas facetas físicas o del
comportamiento de sus progenitores.
La herencia es la característica fundamental que distingue un lenguaje
orientado a objetos, como el C++ o Java, de otro convencional como C, BASIC,
etc. La herencia ofrece una ventaja importante, permite la reutilización del
código. Una vez que una clase ha sido depurada y probada, el código fuente de
dicha clase no necesita modificarse. Su funcionalidad se puede cambiar
derivando una nueva clase que herede la funcionalidad de la clase base y le
añada otros comportamientos. Reutilizando el código existente, el programador
ahorra tiempo y dinero, ya que solamente tiene que verificar la nueva conducta
que proporciona la clase derivada.
Clase Base
Una clase base es aquella que no dependen ninguno de sus atributos u objetos
de la clase de alguna otra clase, se podría decir que, en términos de herencia,
sería la clase padre, la clase que se mantiene fija, en el aspecto de herencia.
Es también por así llamarlo la clase principal de un programa, sería la clase
primaria sin incluir la clase main en donde se corre todo el programa en sí.
Clase Derivada
son clases que dependen de las clases bases, ya que algunos de sus métodos
son también heredados, y muchas veces, el compilador arrojara malos
resultados, ya que al ser dependientes estas clases, a veces podrán generar
errores lógicos.
Ejemplo
Vamos a poner un ejemplo del segundo tipo, que simule la utilización de
librerías de clases para crear un interfaz gráfico de usuario como Windows 3.1
o Windows 95.
Supongamos que tenemos una clase que describe la conducta de una ventana
muy simple, aquella que no dispone de título en la parte superior, por tanto, no
puede desplazarse, pero si cambiar de tamaño actuando con el ratón en los
bordes derecho e inferior.
La clase Ventana tendrá los siguientes miembros dato: la posición x e y de la
ventana, de su esquina superior izquierda y las dimensiones de la ventana:
ancho y alto.
public class Ventana {
protected int x;
protected int y;
protected int ancho;
protected int alto;
public Ventana(int x, int y, int ancho, int alto) {
this.x=x;
this.y=y;
this.ancho=ancho;
this.alto=alto;
}
}
Las funciones miembros, además del constructor serán las siguientes: la función
mostrar que simula una ventana en un entorno gráfico, aquí solamente nos muestra la
posición y las dimensiones de la ventana.
public void mostrar(){
System.out.println("posición : x="+x+", y="+y);
System.out.println("dimensiones : w="+ancho+", h="+alto);
}
La función cambiarDimensiones que simula el cambio en la anchura y altura de la
ventana.
public void cambiarDimensiones(int dw, int dh){
ancho+=dw;
alto+=dh;
}
Como vemos en el código, el constructor de la clase base inicializa los cuatro
miembros dato. Llamamos al constructor creando un objeto de la claseVentana
Ventana ventana=new Ventana(0, 0, 20, 30);
Desde el objeto ventana podemos llamar a las funciones miembro públicas
ventana.mostrar();
ventana.cambiarDimensiones(10, 10);
ventana.mostrar();
Incrementamos la funcionalidad de la clase Ventana definiendo una clase derivada
denominada VentanaTitulo. Los objetos de dicha clase tendrán todas las
características de los objetos de la clase base, pero además tendrán un título, y se
podrán desplazar (se simula el desplazamiento de una ventana con el ratón).
La clase derivada heredará los miembros dato de la clase base y las funciones
miembros, y tendrá un miembro dato más, el título de la ventana.
public class VentanaTitulo extends Ventana{
protected String titulo;
public VentanaTitulo(int x, int y, int w, int h, String nombre) {
super(x, y, w, h);
titulo=nombre;
}
extends es la palabra reservada que indica que la clase VentanaTituloderiva, o es una
subclase, de la clase Ventana.
La primera sentencia del constructor de la clase derivada es una llamada al
constructor de la clase base mediante la palabra reservada super. La llamada
super(x, y, w, h);
inicializa los cuatro miembros dato de la clase base Ventana: x, y, ancho,alto. A
continuación, se inicializa los miembros dato de la clase derivada, y se realizan las
tareas de inicialización que sean necesarias. Si no se llama explícitamente al
constructor de la clase base Java lo realiza por nosotros, llamando al constructor por
defecto si existe.
La función miembro denominada desplazar cambia la posición de la ventana,
añadiéndoles el desplazamiento.
public void desplazar(int dx, int dy){
x+=dx;
y+=dy;
}
Redefine la función miembro mostrar para mostrar una ventana con un título.
public void mostrar(){
super.mostrar();
System.out.println("título : "+titulo);
}
En la clase derivada se define una función que tiene el mismo nombre y los mismos
parámetros que la de la clase base. Se dice que redefinimos la función mostrar en la
clase derivada. La función miembro mostrar de la clase derivada VentanaTitulo hace
una llamada a la función mostrar de la clase base Ventana, mediante: super.mostrar();
De este modo aprovechamos el código ya escrito, y le añadimos el código que
describe la nueva funcionalidad de la ventana, por ejemplo, que muestre el título.
Si nos olvidamos de poner la palabra reservada super llamando a la función mostrar,
tendríamos una función recursiva. La función mostrarllamaría a mostrar
indefinidamente.
public void mostrar(){ //¡ojo!, función recursiva
System.out.println("título : "+titulo);
mostrar();
}
Creamos un objeto ventana de la clase derivada VentanaTitulo
VentanaTitulo ventana=new VentanaTitulo(0, 0, 20, 30, "Principal");
Mostramos la ventana con su título, llamando a la función mostrar, redefinida en la
clase derivada
ventana.mostrar();
Desde el objeto ventana de la clase derivada llamamos a las funciones miembro
definidas en dicha clase
ventana.desplazar(4, 3);
Desde el objeto ventana de la clase derivada podemos llamar a las funciones miembro
definidas en la clase base.
ventana.cambiarDimensiones(10, -5);
Para mostrar la nueva ventana desplazada y cambiada de tamaño escribimos
ventana.mostrar();
Ejemplo
He aquí un ejemplo mucho más corto que el anterior de lo que serían las clases bases
y clases derivadas, y de cómo se demuestra la dependencia de la derivada con la
clase base:
public abstract class Figura {
protected int x;
protected int y;
public Figura(int x, int y) {
this.x=x;
this.y=y;
}
public abstract double area();
}
class Rectangulo extends Figura{
protected double ancho, alto;
public Rectangulo(int x, int y, double ancho, double alto){
super(x,y);
this.ancho=ancho;
this.alto=alto;
}
public double area(){
return ancho*alto;
}
}
Ejemplo
eh aquí un ejemplo representado por medio de una imagen
Clasificación: Herencia simple, Herencia múltiple

Hay dos tipos de herencia: Herencia Simple y Herencia Múltiple. La primera


indica que se pueden definir nuevas clases solamente a partir de una clase
inicial mientras que la segunda indica que se pueden definir nuevas clases a
partir de dos o más clases iniciales. Java sólo permite herencia simple.

1. Herencia múltiple:
Consiste en la utilización de las propiedades de una clase a varias clases
más, lo que significa que en esta propiedad una sola clase padre puede
heredarle atributos, u objetos de esta a varias clases hijo sin ninguna limitación
entre ellas.

Nota: Java no soporta la herencia múltiple.

2. Herencia simple:
La herencia simple consiste en cuando una clase, hereda a una clase hijo, y a
solo una le hereda sus atributos, es igual al concepto general de herencia, con
la limitante de solo poder heredar de una clase padre a una clase hijo, y solo a
una clase hijo
Ejemplo
Public class animal{
Public String ojos;
Public String color;
Public int patas;
Public void patas(){
...
}
}
Public class perro extends animal{ /*la clase perro hereda atributos y métodos
de la clase animal*/
System.out.println(“Dame el color del animal”+ color);//Hereda la variable color
de la clase animal
}
Ejemplo
La clase de una Operación Básica para un silabario quedará así:

public class operacionBase {


protected int operador;
public operacionBase(){
this.operador=0;
}

public void setOperador(int o){


this.operador=o;
}

public int getOperador(){


return this.operador;
}
}

Declaramos la propiedad con visibilidad protected por motivos de seguridad,


para que solamente los
miembros de esa clase y los que heredan de ella, es decir las subclases,
puedan accesar a estos atributos.
Como hemos definido ya una clase base, ahora podremos definir el resto de
clases que podrán heredar
de la clase base. Cabe destacar que no siempre se podrá hacer una clase base
y con respecto a esta
empezar a heredarles a otras. Esto depende de la naturaleza de cada
aplicación, y de la manera de cómo
el programador diseñe su capa de negocios.
Para realizar la clase que nos devuelva el factorial de un número, el
programador puede escoger la
instrucción While. Por lo tanto, el programador debe desarrollar el método que
reciba como parámetro
el número del que se desee la factorial y que devuelva el resultado esperado.
Heredando de la clase
base, tenemos ya garantizados los atributos y métodos propios de la misma,
por lo tanto, la clase para
realizar la factorial del número podría quedar así:

public class operacionFactorial extends operacionBase{


public operacionFactorial(){
super();
}

public int devolverFactorial(){


int factorial=1;
int cont=1;
while (cont <= this.operador) { factorial *= cont; cont++; } return factorial;
}
}
Ejemplo 3
Reutilización de miembros heredados
La reutilización de código se refiere al comportamiento y a las técnicas que
garantizan que una parte o la totalidad de un programa informático existente se
pueda emplear en la construcción de otro programa. De esta forma se
aprovecha el trabajo anterior, se economiza tiempo, y se reduce la
redundancia.

La manera más fácil de reutilizar código es copiarlo total o parcialmente desde


el programa antiguo al programa en desarrollo. Pero es trabajoso mantener
múltiples copias del mismo código, por lo que en general se elimina la
redundancia dejando el código reusable en un único lugar, y llamándolo desde
los diferentes programas. Este proceso se conoce como abstracción. La
abstracción puede verse claramente en las bibliotecas de software, en las que
se agrupan varias operaciones comunes a cierto dominio para facilitar el
desarrollo de programas nuevos. Hay bibliotecas para convertir información
entre diferentes formatos conocidos, acceder a dispositivos de almacenamiento
externos, proporcionar una interfaz con otros programas, manipular información
de manera conocida.
Para que el código existente se pueda reutilizar, debe definir alguna forma de
comunicación o interfaz. Esto se puede dar por llamadas a una subrutina, a un
objeto, o a una clase.

Ejemplo

Construyamos la clase Taxista.java con el siguiente código:

public class Taxista extends Persona {


private int nLicencia;
public void setNLicencia(int num){
nLicencia = num;
}
public int getLicencia(){
return nLicencia;
}
}

Y construyamos ArranqueTaxista.java:

public class ArranqueTaxista {


public static void main (String arg[]){
Taxista tax1 = new Taxista();
tax1.setNombre("Luis");
tax1.setEdad(50);
System.out.println( tax1.getNombre());
System.out.println(tax1.getEdad());
}
}
Ahora intentemos usar el constructor que existía en la clase Persona que
recibía el nombre de la persona y vamos a usarlo para la clase Taxista. Para
ello construyamos la clase ArranqueTaxista2.java:

public class ArranqueTaxista2 {


public static void main (String arg[]){
Taxista tax1 = new Taxista("Jose");
tax1.setEdad(50);
System.out.println( tax1.getNombre());
System.out.println(tax1.getEdad());
System.out.println(tax1.getNLicencia());
}
}

Se genera un error de compilación, debido a que los constructores no se


heredan, sino que hay que definir nuestros propios constructores. Agreguemos
en la clase Taxista los siguientes constructores:

public Taxista(int licencia){


super();
nLicencia = licencia;
}
public Taxista(String nombre,int licencia){
super(nombre);
nLicencia = licencia;
}

Ahora si podremos compilar y ejecutar la clase ArranqueTaxista2. La llamada al


método super indica que estamos llamando a un constructor de la clase base,
pensemos que un Taxista antes que Taxista es Persona y por tanto tiene
sentido llamar al constructor de Persona antes que al de Taxista.

Además, gracias al número de parámetros de la llamada a super podemos


especificar cuál de los constructores de la clase base queremos llamar.
En java se pueden emplear dos palabras clave: this y super. this hace alusión a
todo el objeto y super hace alusión a la parte heredada, por ello empleamos
super para referenciar al constructor de la clase base.
Ahora vamos a agregar la función getNombre dentro de la clase Taxista, es
decir, tenemos la misma función en Persona y en Taxista:

public String getNombre() {


return "Soy un taxista y me llamo: " + super.getNombre();
}

Compilamos Taxista y ejecutamos ArranqueTaxista2. Veremos que el mensaje


que aparece en pantalla demuestra que la función getNombre llamada es la de
del tipo real del objeto construido, en este caso la de la clase derivada que es
Taxista.
También apreciamos que para acceder al atributo nombre es necesario
acceder al método getNombre de la clase base (y por ello emplear super).

En java los atributos y métodos de la clase base pueden cambiar su


modificador de visibilidad dentro de la clase derivada, la siguiente tabla recoge
dichos cambios:
Modificadores en la clase base:
public
private
protected
paquete

En la clase derivada se transforman en:


public
inaccesible
protected
paquete

Inaccesible significa que, a pesar de haber sido heredado, no hay permisos en


la clase derivada para poder acceder a dicho elemento inaccesible, pero, aun
así, se pueden llamar a métodos de la clase base que si pueden acceder y
modificar al elemento.
Recordemos que protected significa que es private, pero que al heredar no se
hace inaccesible, es decir que desde la clase derivada se puede acceder.

Referencia al objeto de la clase base

La funcionalidad de una clase existente se puede extender al crear una nueva


clase que se deriva de ella. La clase derivada hereda las propiedades de la
clase base y es posible agregar o reemplazar métodos y propiedades según
sea necesario.

Como Java, C# no admite herencia múltiple, lo que significa que las clases no
pueden heredar más de una clase. Sin embargo, se pueden utilizar interfaces
para ese propósito, de la misma manera que en Java.

Un ejemplo
El código siguiente define una clase denominada CoOrds con dos variables
miembro privadas x e y que representan la posición del punto. Se tiene acceso
a estas variables mediante propiedades denominadas X e Y, respectivamente:
public class CoOrds
{
private int x, y;

public CoOrds() // constructor


{
x = 0;
y = 0;
}

public int X
{
get { return x; }
set { x = value; }
}

public int Y
{
get { return y; }
set { y = value; }
}
}

Una nueva clase, denominada ColorCoOrds, hereda todos los campos y


métodos de la clase base, a la cual se pueden agregar nuevos campos y
métodos para proporcionar características adicionales en la clase derivada,
según sea necesario. En este ejemplo, se agrega un miembro privado y
descriptores de acceso para agregar color a la clase:

public class ColorCoOrds : CoOrds


{
private System.Drawing.Color screenColor;
public ColorCoOrds() // constructor
{
screenColor = System.Drawing.Color.Red;
}

public System.Drawing.Color ScreenColor


{
get { return screenColor; }
set { screenColor = value; }
}
}

El constructor de la clase derivada llama implícitamente al constructor de la


clase base o la superclase, en terminología de Java. En caso de herencia, se
llama a todos los constructores de clase base antes que a los constructores de
la clase derivada en el orden en que las clases aparecen en la jerarquía de
clases.

Otro ejemplo
En el juego del ajedrez podemos definir una clase base denominada Pieza, con
las características comunes a todas las piezas, como es su posición en el
tablero, y derivar de ella las características específicas de cada pieza particular.
Así pues, la clase Pieza será una clase abstracta con una función abstract
denominada mover, y cada tipo de pieza definirá dicha función de acuerdo a las
reglas de su movimiento sobre el tablero.
 La clase figura

La definición de la clase abstracta Figura, contiene la posición x e y de la figura


particular, de su centro, y la función área, que se va a definir en las clases
derivadas para calcular el área de cada figura en particular.

public abstract class Figura {


protected int x;
protected int y;
public Figura(int x, int y) {
this.x=x;
this.y=y;
}
public abstract double area();
}
 La clase Rectangulo

Las clases derivadas heredan los miembros dato x e y de la clase base, y


definen la función área, declarada abstract en la clase base Figura, ya que
cada figura particular tiene una fórmula distinta para calcular su área. Por
ejemplo, la clase derivada Rectangulo, tiene como datos, aparte de su posición
(x, y) en el plano, sus dimensiones, es decir, su anchura ancho y altura alto.
class Rectangulo extends Figura{
protected double ancho, alto;
public Rectangulo(int x, int y, double ancho, double alto){
super(x,y);
this.ancho=ancho;
this.alto=alto;
}
public double area(){
return ancho*alto;
}
}
La primera sentencia en el constructor de la clase derivada es una llamada al
constructor de la clase base, para ello se emplea la palabra reservada super. El
constructor de la clase derivada llama al constructor de la clase base y le pasa
las coordenadas del punto x e y. Después inicializa sus miembros dato ancho y
alto.
En la definición de la función area, se calcula el área del rectángulo como
producto de la anchura por la altura, y se devuelve el resultado
 La clase Circulo
class Circulo extends Figura{
protected double radio;
public Circulo(int x, int y, double radio){
super(x,y);
this.radio=radio;
}
public double area(){
return Math.PI*radio*radio;
}
}
Como vemos, la primera sentencia en el constructor de la clase derivada es
una llamada al constructor de la clase base empleando la palabra reservada
super. Posteriormente, se inicializa el miembro dato radio, de la clase derivada
Circulo.
En la definición de la función area, se calcula el área del círculo mediante la
conocida fórmula r2, o bien *r*r. La constante Math.PI es una
aproximación decimal del número irracional .
Uso de la jerarquía de clases
Creamos un objeto c de la clase Circulo situado en el punto (0, 0) y de 5.5
unidades de radio. Calculamos y mostramos el valor de su área.
Circulo c=new Circulo(0, 0, 5.5);
System.out.println("Area del círculo "+c.area());
Creamos un objeto r de la clase Rectangulo situado en el punto (0, 0) y de
dimensiones 5.5 de anchura y 2 unidades de largo. Calculamos y mostramos el
valor de su área.
Rectangulo r=new Rectangulo(0, 0, 5.5, 2.0);
System.out.println("Area del rectángulo "+r.area());
Veamos ahora, una forma alternativa, guardamos el valor devuelto por new al
crear objetos de las clases derivadas en una variable f del tipo Figura (clase
base).
Figura f=new Circulo(0, 0, 5.5);
System.out.println("Area del círculo "+f.area());
f=new Rectangulo(0, 0, 5.5, 2.0);
System.out.println("Area del rectángulo "+f.area());
Constructores y destructores en clases derivadas

Constructores en clases derivadas

Al instanciar objetos de clases derivadas se inicia una cadena de invocaciones


a constructores en las cuales el constructor de la clase derivada, antes de
realizar sus propias tareas, invoca al constructor de su clase base.
Similarmente, si la clase base fue derivada de otra clase, el constructor de la
clase base debe invocar al constructor de la clase ubicada en el siguiente nivel
superior de la jerarquía, y así sucesivamente. El último constructor invocado en
la cadena es el constructor de la clase Object, cuyo cuerpo se ejecuta primero.
El cuerpo del constructor de la clase derivada se ejecuta al final. El constructor
de cada clase base inicializa las variables de instancia que el objeto de la clase
derivada hereda.

Destructores en clases derivadas

Cuando remueve de la memoria un objeto de una clase derivada, el recolector


de basura invoca al destructor del objeto. Esto inicia una cadena de
invocaciones a destructores, en donde el destructor de la clase derivada y los
destructores de las clases bases directas e indirectas se ejecutan en orden
inverso al que se ejecutaron los constructores, esto es, primero se ejecuta el
destructor de la clase derivada y al final se ejecuta el destructor de la clase
base ubicada en el nivel superior de la jerarquía. La ejecución de los
destructores debe liberar todos los recursos que el objeto adquirió, antes de
que el recolector de basura reclame la memoria de ese objeto. Cuando el
recolector de basura invoca al destructor de un objeto de una clase derivada,
ese destructor realiza su tarea y después invoca al destructor de la clase base.
El proceso se repite hasta que se invoca al destructor de la clase Object.

Ejemplo:

Clase base

package ejemploconstructor;

public class Main {

public static void main(String[] args) {

persona Persona=new persona ("ejemplo \n","de\n","constructor");


Persona.muestraNombre();
}
}

Clase escuela
package ejemploconstructor;
public class escuela {
public String nombre;
public String direccion;
public String cedula;

//contructor
public escuela(String nombre, String direccion, String cedula){

this.nombre=nombre;
this.direccion=direccion;
this.cedula=cedula;
}

public void muestraNombre(){


System.out.printf("Nombre es : %s",this.nombre);
System.out.printf("apellido es : %s",this.direccion);
System.out.printf("correo es : %s",this.cedula);
System.out.println();
}
protected void finalize(){
System.out.print("Datos destruidos");
}
}

Clase persona

package ejemploconstructor;

public class persona {


private String nombre;
private String apellido;
private String correo;
//contructor
public persona(String nombre, String apellidos, String correo){

this.nombre=nombre;
this.apellido =apellidos;
this.correo=correo;
}
public void muestraNombre(){
System.out.printf("Nombre es : %s",this.nombre);
System.out.printf("apellido es : %s",this.apellido);
System.out.printf("correo es : %s",this.correo);
System.out.println();
}
protected void finalize(){
System.out.print("Datos destruidos");
}
}
Ejemplo:

// Destruct Derivadas?.cs : Destructores en clases derivadas. using


C = System.Console;
class Animal {
Animal( ) {
C.Write Line?(“Muere mi parte Animal …”);
}}
class Mamífero : Animal {
Mamífero( ){
C.Write Line(“Muere mi parte Mamífero …”);
}}
class Perro : Mamífero {
Perro( ) {
C.Write Line(“Muere mi parte Perro …”);
}}
public class Principal {
static void Main( ) {
Perro Fido = new Perro ( );
}}

Redefinición de métodos en clases derivadas

El lenguaje permite redefinir miembros de la clase base en las clases


derivadas, pero el compilador emite una advertencia cuando detecta una
redefinición. Una advertencia (warning) es un mensaje del compilador acerca
de un posible problema. Sin embargo, en este caso sí se genera código
ejecutable (a diferencia del mensaje de error). Redefinición de campos. El
siguiente ejemplo muestra cómo reutilizar los identificadores de los campos de
la clase base en una clase derivada.

Ejemplo:

//Ejemplifica la redefinición de campos en clases derivadas.


class Punto
{
public int x;
public int y;
}
class Punto3D : Punto
{
public int x ;
public int y ;
public int z ;
}
class Principal
{
public static void Main( )
{
Punto a = new Punto( );
Punto3D b = new Punto3D( );
a.x = 100 ;
a.y = 200 ;
b.x = 300 ;
b.y = 400 ;
b.z = 500 ;
}
}
POLIMORFISMO
Definición.
El concepto de Polimorfismo es uno de los fundamentos para cualquier
lenguaje orientado a Objetos, las mismas raíces de la palabra pueden ser una
fuerte pista de su significado: Poli = Múltiple, morfismo= Formas , esto implica
que un mismo Objeto puede tomar diversas formas.
A través del concepto de Herencias ("Inheritance") es posible ilustrar este
comportamiento:

En general nos sirve para programar objetos con características comunes y


que todos estos compartan la misma superclase en una jerarquía de clases,
como si todas fueran objetos de la superclase. Esto nos simplifica la
programación.

Para ponerlo en práctica se hará un ejemplo bastante sencillo. Se hará una


librería de clases que represente figuras tridimensionales y bidimensionales, y
su respectiva jerarquía de clases. Las clases deben ser capaces de tener
funcionamiento bastante básico, como obtener áreas, volúmenes y perímetros
de la figura correspondiente.

La superclase de dicha jerarquía podría ser muy parecida a ésta:


public abstract class figura {
protected String nombre;
protected int color;
protected int grosorBorde;

public String getNombre(){


return this.nombre;
}

public void setNombre(String n){


this.nombre=n;
}
public int getColor(){
return this.color;
}

public void setColor(int c){


this.color=c;
}

public int getGrosorBorde(){


return this.grosorBorde;
}

public void setGrosorBorde(int g){


this.grosorBorde=g;
}

public abstract void dibujar();


}

Las siguientes clases en el nivel de la jerarquía podrían quedar muy parecidas


a éstas:

public abstract class figura2D extends figura {


public abstract int calcularArea();
public abstract int calcularPerimetro();
}
public abstract class figura3D extends figura {

public abstract int calcularVolumen();


}
Supongamos que deseamos saber la figura que tiene mayor área
independientemente de su forma. Primero, programamos una función que halle
el mayor de varios números reales positivos.
double valorMayor(double[] x){
double mayor=0.0;
for (int i=0; i<x.length; i++)
if(x[i]>mayor){
mayor=x[i];
}
return mayor;
}
Ahora, la llamada a la función valorMayor
double numeros[]={3.2, 3.0, 5.4, 1.2};
System.out.println("El valor mayor es "+valorMayor(numeros));
La función figuraMayor que compara el área de figuras planas es semejante a
la función valorMayor anteriormente definida, se le pasa el array de objetos de
la clase base Figura. La función devuelve una referencia al objeto cuya área es
la mayor.
static Figura figuraMayor(Figura[] figuras){
Figura mFigura=null;
double areaMayor=0.0;
for(int i=0; i<figuras.length; i++){
if(figuras[i].area()>areaMayor){
areaMayor=figuras[i].area();
mFigura=figuras[i];
}
}
return mFigura;
}
La clave de la definición de la función está en las líneas
if(figuras[i].area()>areaMayor){
areaMayor=figuras[i].area();
mFigura=figuras[i];
}

En la primera línea, se llama a la versión correcta de la función


areadependiendo de la referencia al tipo de objeto que guarda el elemento
figuras[i]del array. En areaMayor se guarda el valor mayor de las áreas
calculadas, y en mFigura, la figura cuya área es la mayor.
La principal ventaja de la definición de esta función estriba en que la función
figuraMayor está definida en términos de variable figuras de la clase
baseFigura, por tanto, trabaja no solamente para una colección de círculos y
rectángulos, sino también para cualquier otra figura derivada de la clase
baseFigura. Así si se deriva Triangulo de Figura, y se añade a la jerarquía de
clases, la función figuraMayor podrá manejar objetos de dicha clase, sin
modificar para nada el código de la misma.
Veamos ahora la llamada a la función figuraMayor
Figura[] fig=new Figura[4];
fig[0]=new Rectangulo(0,0, 5.0, 7.0);
fig[1]=new Circulo(0,0, 5.0);
fig[2]=new Circulo(0, 0, 7.0);
fig[3]=new Rectangulo(0,0, 4.0, 6.0);
Figura fMayor=figuraMayor(fig);
System.out.println("El área mayor es "+fMayor.area());

Pasamos el array fig a la función figuraMayor, el valor que retorna lo


guardamos en fMayor. Para conocer el valor del área, desde fMayor se llamará
a la función miembro area. Se llamará a la versión correcta dependiendo de la
referencia al tipo de objeto que guarde por fMayor. Si fMayor guarda una
referencia a un objeto de la clase Circulo, llamará a la función area definida en
dicha clase. SifMayor guarda una referencia a un objeto de la clase
Rectangulo, llamará a la función area definida en dicha clase, y así
sucesivamente.
La combinación de herencia y enlace dinámico se denomina polimorfismo. El
polimorfismo es, por tanto, la técnica que permite pasar un objeto de una clase
derivada a funciones que conocen el objeto solamente por su clase base

El poder manipular un Objeto como si éste fuera de un tipo genérico otorga


mayor flexibilidad al momento de programar con Objetos, el término
Polimorfismo también es asociado con un concepto llamado Late-Binding
(Ligamiento Tardío), observe el siguiente fragmento de código:
Figura a = new Circulo();
Figura b = new Triangulo();
Inicialmente se puede pensar que este código generaría un error debido a que
el tipo de referencia es distinta a la instancia del objeto, sin embargo, el
fragmento anterior es correcto y demuestra el concepto de Polimorfismo.
Con el polimorfismo se consigue que las instancias de una clase padre puedan
hacer uso de las funcionalidades de las clases hijas: misma instancia que se
comporta de varias maneras.
Ejemplo:
Public class Animal(){
public void habla(){
System.out.println("No se que soy");
}
}
Public class Perro() extends Animal{
public void() habla(){
System.out.println("Guau");
}
}
Public class Gato() extends Animal{
public void() habla(){
System.out.println("Miau");
}
}
Public class Zoo(){
public static void main(String[] args) {
Animal = new Gato(); animal. Habla(); animal=new Perro(); animal. Habla();
}
}
El resultado por consola será:
"Miau"
"Guau"

clases abstractas: definición, métodos abstractos,


implementación de clases abstractas, modelo de clase
abstracta

Clases abstractas
Concepto
Hay ocasiones, cuando se desarrolla una jerarquía de clases en que algún
comportamiento está presente en todas ellas pero se materializa de forma
distinta para cada una. Por ejemplo, pensemos en una estructura de clases
para manipular figuras geométricas. Podríamos pensar en tener una clase
genérica, que podría llamarse FiguraGeometrica y una serie de clases que
extienden a la anterior que podrían ser Circulo, Polígono, etc. Podría haber un
método dibujar dado que sobre todas las figuras puede llevarse a cabo esta
acción, pero las operaciones concretas para llevarla a cabo dependen del tipo
de figura en concreto . Por otra parte la acción dibujar no tiene sentido para la
clase genérica FiguraGeometrica, porque esta clase representa una
abstracción del conjunto de figuras posibles.
Para resolver esta problemática Java proporciona las clases y métodos
abstractos. Un método abstracto es un método declarado en una clase para el
cual esa clase no proporciona la implementación (el código). Una clase
abstracta es una clase que tiene al menos un método abstracto. Una clase que
extiende a una clase abstracta debe implementar los métodos abstractos
(escribir el código) o bien volverlos a declarar como abstractos, con lo que ella
misma se convierte también en clase abstracta.
Ejemplo:
Declaración e implementación de métodos abstractos
Siguiendo con el ejemplo del apartado anterior, se puede escribir:
abstract class FiguraGeometrica {
abstract void dibujar();
}
class Circulo extends FiguraGeometrica {
void dibujar() {
// codigo para dibujar Circulo
}
}
La clase abstracta se declara simplemente con el modificador abstract en su
declaración. Los métodos abstractos se declaran también con el mismo
modificador, declarando el método pero sin implementarlo (sin el bloque de
código encerrado entre {}). La clase derivada se declara e implementa de forma
normal, como cualquier otra. Sin embargo si no declara e implementa los
métodos abstractos de la clase base (en el ejemplo el método dibujar) el
compilador genera un error indicando que no se han implementado todos los
métodos abstractos y que, o bien, se implementan, o bien se declara la clase
abstracta.

Referencias y objetos abstractos


Se pueden crear referencias a clases abstractas como cualquier otra. No hay
ningún problema en poner:
FiguraGeometrica figura;
Sin embargo una clase abstracta no se puede instanciar, es decir, no se
pueden crear objetos de una clase abstracta. El compilador producirá un error
si se intenta:
FiguraGeometrica figura = new FiguraGeometrica();
Esto es coherente dado que una clase abstracta no tiene completa su
implementación y encaja bien con la idea de que algo abstracto no puede
materializarse.
Sin embargo utilizando el up-casting visto en el capítulo dedicado a la Herencia
si se puede escribir:
FiguraGeometrica figura = new Circulo(. . .);
figura.dibujar();
La invocación al método dibujarse resolverá en tiempo de ejecución y la JVM
llamará al método de la clase adecuada. En nuestro ejemplo se llamará al
método dibujarde la clase Circulo
Código Fuente Musica2.java

import java.util.*;

abstract class Instrumento {


public abstract void tocar();
public String tipo() {
return "Instrumento";
}
public abstract void afinar();
}

class Guitarra extends Instrumento {


public void tocar() {
System.out.println("Guitarra.tocar()");
}
public String tipo() { return "Guitarra"; }
public void afinar() {}
}

class Piano extends Instrumento {


public void tocar() {
System.out.println("Piano.tocar()");
}
public String tipo() { return "Piano"; }
public void afinar() {}
}

class Saxofon extends Instrumento {


public void tocar() {
System.out.println("Saxofon.tocar()");
}
public String tipo() { return "Saxofon"; }
public void afinar() {}
}
// Un tipo de Guitarra
class Guzla extends Guitarra {
public void tocar() {
System.out.println("Guzla.tocar()");
}
public void afinar() {
System.out.println("Guzla.afinar()");
}
}

// Un tipo de Guitarra
class Ukelele extends Guitarra {
public void tocar() {
System.out.println("Ukelele.tocar()");
}
public String tipo() { return "Ukelele"; }
}

public class Musica2 {

// No importa el tipo de Instrumento,


// seguirá funcionando debido a Polimorfismo:
static void afinar(Instrumento i) {
// ...
i.tocar();
}

static void afinarTodo(Instrumento[] e) {

for(int i = 0; i < e.length; i++)


afinar(e[i]);
}

public static void main(String[] args) {


// Declarar un Arreglo SIN INSTANCIAS es válido en Clases Abstractas
Instrumento[] orquesta = new Instrumento[5];
// Generar una INSTANCIA de una la Clase Abstracta no es valido
// Instrumento nuevo = new Instrumento();
int i = 0;
// Up-casting al asignarse el Arreglo
orquesta[i++] = new Guitarra();
orquesta[i++] = new Piano();
orquesta[i++] = new Saxofon();
orquesta[i++] = new Guzla();
orquesta[i++] = new Ukelele();
afinarTodo(orquesta);
}
}

La Clase anterior es idéntica aquella definida en el ejemplo de Polimorfismo,


sin embargo, la Clase Base de Instrumento fue definida como abstract, algunos
detalles de esta definición :
 Nótese que los métodos definidos como abstract no contienen ningún
tipo de código dentro de ellos, inclusive no declaran ni llaves ({ }).
 Cuando es definido más de un método como abstract, es necesario que
la Clase como tal sea definida también como abstract.
La característica de hacer una Clase/Método abstract reside en que no puede
ser generada una instancia de la misma, este comportamiento se demuestra en
el método principal (main)
 Aunque dentro del método sea generado un Arreglo de esta Clase
abstracta, recuerde que un arreglo es únicamente un contenedor de
Objetos, esto permite que sea generado sin ningún error.
 Dentro de comentarios se encuentra la generación de una instancia del
tipo Instrumento la cual generaría un error al ser compilada la Clase.
Interfaces: definición, implementación de interfaces,
herencias de interfaces

INTERFACES
Las interfaces Java son expresiones puras de diseño. Se trata de auténticas
conceptualizaciones no implementadas que sirven de guía para definir un
determinado concepto (clase) y lo que debe hacer, pero sin desarrollar un
mecanismo de solución.
Se trata de declarar métodos abstractos y constantes que posteriormente
puedan ser implementados de diferentes maneras según las necesidades de
un programa.
Por ejemplo una misma interfaz podría ser implementada en una versión de
prueba de manera poco óptima, y ser acelerada convenientemente en la
versión definitiva tras conocer más a fondo el problema.

Declaración
Para declarar una interfaz se utiliza la sentencia interface, de la misma manera
que se usa la sentencia class:
interface MiInterfaz {
int CONSTANTE = 100;
int metodoAbstracto( int parametro );
}
Se observa en la declaración que las variables adoptan la declaración en
mayúsculas, pues en realidad actuarán como constantes finales. En ningún
caso estas variables actuarán como variables de instancia.
Por su parte, los métodos tras su declaración presentan un punto y coma, en
lugar de su cuerpo entre llaves. Son métodos abstractos, por tanto, métodos
sin implementación

Implementación de una interfaz


Como ya se ha visto, las interfaces carecen de funcionalidad por no estar
implementados sus métodos, por lo que se necesita algún mecanismo para dar
cuerpo a sus métodos.
La palabra reservada implements utilizada en la declaración de una clase
indica que la clase implementa la interfaz, es decir, que asume las constantes
de la interfaz, y codifica sus métodos:

class ImplementaInterfaz implements MiInterfaz{


int multiplicando=CONSTANTE;
int metodoAbstracto( int parametro ){
return ( parametro * multiplicando );
}
}
En este ejemplo se observa que han de codificarse todos los métodos que
determina la interfaz (metodoAbstracto()), y la validez de las constantes
(CONSTANTE) que define la interfaz durante toda la declaración de la clase.
Una interfaz no puede implementar otra interfaz, aunque sí extenderla
(extends) ampliándola.
Dando otro ejemplo diferente seria:
Una clase puede implementar más de una interface. Declaración y uso Una
interface se declara:

interface nombre_interface {
tipo_retorno nombre_metodo ( lista_argumentos ) ;
...
}

Por ejemplo:

interface InstrumentoMusical {
void tocar();
void afinar();
String tipoInstrumento();
}
Y una clase que implementa la interface:

class InstrumentoViento extends Object implements


InstrumentoMusical {
void tocar() { . . . };
void afinar() { . . .};
String tipoInstrumento() {}
}

class Guitarra extends InstrumentoViento {


String tipoInstrumento() {
return "Guitarra";
}
}

Referencias a Interfaces

Es posible crear referencias a interfaces, pero las interfaces no pueden ser


instanciadas. Una referencia a una interface puede ser asignada a cualquier
objeto que implemente la interface.

Extensión de interfaces
Las interfaces pueden extender otras interfaces y, a diferencia de las clases,
una interface puede extender más de una interface. La sintaxis es:

interface nombre_interface extends nombre_interface , . . . {


tipo_retorno nombre_metodo ( lista_argumentos ) ;
...
}
Agrupaciones de constantes

Dado que, por definición, todos los datos miembros que se definen en una
interface son static y final, y dado que las interfaces no pueden instanciarse
resultan una buena herramienta para implantar grupos de constantes.

public interface Meses {


int ENERO = 1 , FEBRERO = 2 . . . ;
String [] NOMBRES_MESES = { " " , "Enero" , "Febrero" , . . . };
}

Esto puede usarse simplemente:

System.out.println(Meses.NOMBRES_MESES[ENERO]);

Variables polimórficas (plantillas): definición, uso y


aplicaciones

En Java, las variables que contienen objetos son variables polimórficas. El


término «polimórfico» (literalmente: muchas formas) se refiere al hecho de que
una misma variable puede contener objetos de diferentes tipos (del tipo
declarado o de cualquier subtipo del tipo declarado). El polimorfismo aparece
en los lenguajes orientados a objetos en numerosos contextos, las variables
polimórficas constituyen justamente un primer ejemplo.

Observemos la manera en que el uso de una variable polimórfica nos ayuda a


simplificar nuestro método listar. El cuerpo de este método es:

for (Elemento : elementos)


elemento.imprimir();
En este método recorremos la lista de elementos (contenida en un ArrayList
mediante la variable elementos), tomamos cada elemento de la lista y luego
invocamos su método imprimir.El uso de herencia en este ejemplo ha eliminado
la necesidad de escribir dos ciclos en el método listar. La herencia evita la
duplicación de código no sólo en las clases servidoras sino también en las
clases clientes de aquellas.

public class CD
{
private String title;
private String artist;
private String comment;
CD(String theTitle, String theArtist)
{
title = theTitle;
artist = theArtist;
comment = " ";
}
void setComment(String newComment)
{ ... }
String getComment()
{ ... }
void print()
{ ... }
...
}

public class DVD


{
private String title;
private String director;
private String comment;
DVD(String theTitle, String theDirector)
{
title = theTitle;
director = theDirector;
comment = " ";
}
void setComment(String newComment)
{ ... }
String getComment()
{ ... }
void print()
{ ... }
...
}

class Database {
private ArrayList<CD> cds;
private ArrayList<DVD> dvds;
...
public void list()
{
for(CD cd : cds) {
cd.print();
System.out.println(); }
for(DVD dvd : dvds) {
dvd.print();
System.out.println(); }
}
}
en este ejemplo para hacer más completo el demo de polimorfismo, vamos a
incorporar un elemento más:
Libro, que extiende directamente Elemento, sin incorporarle ningún atributo
adicional.

import java.util.ArrayList;
public class BaseDeDatos{
private ArrayList<Elemento> elementos;
protected String auxStr;
public BaseDeDatos(){ // constructor
elementos = new ArrayList<Elemento>();
}
public void agregarElemento (Elemento elElemento){
elementos.add(elElemento);
}
public String toString(){ // Cadena con todos los elementos contenidos
auxStr = "Contenidos BaseDeDatos\n";
auxStr+=elementos.toString();
return auxStr;
}
}
package dome;
public class Elemento{
private String titulo;
private int duracion;
private boolean loTengo;
private String comentario;
public Elemento(String elTitulo, int tiempo){
titulo = elTitulo;
duracion = tiempo;
loTengo = false;
comentario = "";
}
public String toString(){
String aux = titulo + " (" + duracion + " minutos) ";
if (loTengo)aux += "*";
aux += " " + comentario+"\n";
return aux;
}
}
package dome;
public class CD extends Elemento{
private String interprete;
private int numeroDeTemas;
public CD(String elTitulo, String elInterprete, int temas, int tiempo){
super(elTitulo, tiempo);
interprete = elInterprete;
numeroDeTemas = temas;
}
public String toString(){
String aux = super.toString();
aux+= " interprete (CD): " + interprete+"\n";
aux+= " temas: " + numeroDeTemas+"\n";
return aux;
}
}
package dome;
public class DVD extends Elemento{
private String director;
public DVD(String elTitulo, String elDirector, int time){
super(elTitulo, time);
director = elDirector;
}
public String toString(){
String aux = super.toString();
aux+= " director (DVD): " + director+"\n";
return aux;
}
}
package dome;
public class Libro extends Elemento{
public Libro(String elTitulo, int time){
super(elTitulo, time);
}
}
package dome;
// @author
public class Main {
private BaseDeDatos db;
public void DemoBaseDedatos(){
System.out.println("Demo inicia");
db = new BaseDeDatos();
Elemento elem;
// Incluyo 2 CDs
elem = new CD("Pajaros en la Cabeza","Amaral",14,35);
db.agregarElemento(elem);
elem = new CD("One chance","Paul Pots",10,30);
db.agregarElemento(elem);
// Incluyo 2 DVDs
elem = new DVD("Soy Leyenda","Francis Lawrence",120);
db.agregarElemento(elem);
elem = new DVD("Nada es Para Siempre","Robert Redford",105);
db.agregarElemento(elem);
// Incluyo dos libros
elem = new Libro("El Señor de los Anillos",5000);
db.agregarElemento(elem);
elem = new Libro("El Don Apacible",10000);
db.agregarElemento(elem);
// veamos que hemos hecho
System.out.println(db.toString());
System.out.println("Demo terminado");
}
public static void main(String[] args) {
Main demo = new Main();
demo.DemoBaseDedatos();
}
}
La sentencia System.out.println(db.toString()), método public
voidDemoBaseDedatos() es la que se ejecuta inicialmente. Esta sentencia:
- Incorpora en la cadena el resultado de elementos.toString. Como elementos
es una instancia de ArrayList, usa el toString() de esta clase (De ahí los
corchetes de cierre y las comas separadoras).
- elementos contiene 6 instancias de la variable polimórfica Elemento:
- las dos primeras tienen tipo dinámico CD. Entonces, en la ejecución del
toString() propio invocan super.toString() (el de Elemento) y luego completan
con los datos específicos de CD.
- Las dos siguientes tienen tipo dinámico DVD. Proceden exactamente lo
mismo que CD.
- Las dos últimas instancias tienen tipo dinámico Libro. Como no tienen
toString() propio, el despacho dinámico encuentra el de Elemnto y este es el
que se ejecuta.
Complicado o facil? En todo caso, la programación es muy sintética, nada de
sobreescritura, cada parte del armado de la cadena que
imprimeSystem.out.println(db.toString()) lo hace el método del objeto
responsable de ello, como manda la POO.

Reutilización de código

Lo primero que se le viene a la cabeza a los estudiantes (y a muchos


profesionales) cuando se les menciona la reutilización del código es el famoso
copiar y pegar al que se han acostumbrado en la programación estructurada, y
de hecho muchos lo hacen en Poo, lo cual es una de las practicas que más
encarece el desarrollo de software. Como todo en Java, el problema se
resuelve con las clases. Para reutilizar el código creamos nuevas clases, pero,
en lugar de partir de cero, partimos de clases, relacionadas con nuestra clase,
que han sido ya creadas y depuradas. El truco está en usar las clases sin
ensuciar el código existente.

Una forma de hacer esto es crear objetos de nuestras clases existentes dentro
de la nueva clase. Esto se conoce como composición porque la nueva clase
está compuesta de objetos de clases existentes. Estamos reutilizando la
funcionalidad del código, y no la forma.

Otra forma es crear una nueva clase como un tipo de una clase ya existente.
Tomamos la forma de la clase existente y añadimos código a la nueva, sin
modificar la clase existente. Esta forma de crear nuevos objetos se llamada
herencia, y lo que hacemos es extender la clase en la que nos basamos para
crear la nueva.

Composición:
Hasta ahora hemos usado la composición de cierta manera, ej. cuando
hacemos una interfaz gráfica de usuario, nuestra clase de interfaz gráfica está
compuesta por un frame, unos panel, botones, etc. todos estos objetos
componen el objeto de interfaz gráfica. Es decir que la composición consiste en
poner manejadores de objetos dentro de nuestra clase, estos manejadores de
objetos no serán otra cosa que instancias de las clases en las que nos estamos
basando para crear la nueva clase.
Recordemos que la forma para determinar cuándo usar composición es cuando
podemos decir que nuestra nueva clase “tiene un” elemento de otro tipo de
objetos, por ejemplo, un cronómetro tiene: horas, minutos y segundos, es decir
que una clase Cronometro está compuesta por otras clases llamadas: Horas,
Minutos y Segundos.
Veamos como seria esta clase:
public class Cronometro {
Horas h;
Minutos m;
Segundos s;
String cadena;
int seg,min,hor;
public Cronometro() {
seg=0;
min=0;
hor=0;
h = new Horas();
m = new Minutos();
s = new Segundos();
cadena = new String("0 : 0 : 0");
}

public String avanzar(){


seg = s.forward();
if(seg==0){
min=m.forward();
if(min==0){
hor=h.forward();
}
}
cadena = hor + " : " + min + " : " + seg;
return cadena;
}
public String reset(){
seg = s.reset();
min = m.reset();
hor = h.reset();
cadena = hor + " : " + min + " : " + seg;
return cadena;
}
}
A menudo hay que realizar una misma operación en varios
programas o en distintas partes del mismo programa
- Podemos copiar el código varias veces y manipular las entradas
para que funcione en otro programa
- No obstante, ¿qué pasa si hay que modificar ese código?
- Habrá que cambiarlo en todos los lugares donde se encuentra
-Por esto es mejor tener una única vez el código y poder llamarlo
desde donde haga falta
ejemplo:
public class SinMetodo2 {
public static void main(String[] args) {
int maximo;
int suma;
// Calcula la suma de los 5 primeros enteros
maximo = 5;
suma = 0;
for(int i=1; i<=maximo; i++){
suma += i;
}
System.out.println("La suma es: " + suma);
// Calcula la suma de los 7 primeros enteros
maximo = 7;
suma = 0;
for(int i=1; i<=maximo; i++){
suma += i;
}
System.out.println("La suma es: " + suma);

EXCEPCIONES
1. Definición.

Una excepción es un objeto que se genera automáticamente cuando se


produce un acontecimiento circunstancial que impide el normal funcionamiento
del programa:

–          Dividir por cero

–          No encontrar un determinado fichero

–          Utilizar un puntero nulo en lugar de una referencia a un objeto

El objeto generado “excepción” contiene información sobre el acontecimiento


ocurrido y transmite esta información al método desde el que se ha generado la
excepción.

La ocurrencia de estas situaciones excepcionales provocará la terminación no


controlada del programa o aplicación.

Las excepciones estándar

En Java las situaciones que pueden provocar un fallo en el programa se


denominan excepciones.

Las excepciones pueden originarse de dos modos:

 El programa hace algo ilegal (caso normal)

El siguiente código de ejemplo origina una excepción de división por cero:

public class PruebaExcepcion {

    public static void main( String[] a ) {


        int i=0, j=0, k;

        k = i/j;    // Origina un error de division-by-zero

    }

Si compilamos y ejecutamos esta aplicación Java, obtendremos la siguiente


salida por pantalla:

> javac PruebaExcepcion.java

> java PruebaExcepcion

     java.lang.ArithmeticException: / by zero

           at PruebaExcepcion.main(melon.java:5)

Las excepciones predefinidas, como, por ejemplo. ArithmeticException, se


conocen como excepciones runtime. Las excepciones en tiempo de ejecución
ocurren cuando el programador no ha tenido cuidado al escribir su código. Por
ejemplo:  cuando se sobrepasa la dimensión de un array,  se lanza una
excepción ArrayIndexOutOfBounds. Cuando se hace uso de una referencia a
un objeto que no ha sido creado se lanza la excepción NullPointerException.
Estas excepciones le indican al programador que tipos de fallos tiene el
programa y que debe arreglarlo antes de proseguir. Actualmente, como todas
las excepciones son eventos runtime, sería mejor llamarlas excepciones
irrecuperables. Esto contrasta con las excepciones que generamos
explícitamente, que suelen ser mucho menos severas y en la mayoría de los
casos podemos recuperarnos de ellas. Por ejemplo, si un fichero no puede
abrirse, preguntamos al usuario que nos indique otro fichero; o si una
estructura de datos se encuentra completa, podremos sobreescribir algún
elemento que ya no se necesite.

 El programa explícitamente genera una excepción .

 
Para generar explícitamente una excepción se ejecutará la sentencia throw. La
sentencia throw tiene la siguiente forma: Exception objetoException= new
Exception();

throw objetoExcepction;

El objeto ObjetoException es un objeto de una clase Excepcion o que hereda


(ver siguiente unidad)  de la clase Exception. Para que un método en Java,
pueda lanzar excepciones será necesario indicarlo expresamente.

void MetodoQueLanzaExcepcion() throws             Exception1, Exception2,….[ }

Se pueden definir excepciones propias, no hay por qué limitarse a las


predefinidas. Para definir una excepción será necesario con extender la clase
Exception (herencia) y proporcionar la funcionalidad extra que requiera el
tratamiento de esa excepción.

public       void  metodoQueLanzaDivisionPorCero  ( )  throws  DivisioCero{

if (…)     throw  new DivisionCero( );

..metodoQueUtilizaMetodoQueLanza(…){

try{
obj.metodoQueLanzaDivisionCero();

}catch( DivisionCero    objExcepcion){

….

…. Habría que definir DivisionCero

public class DivisionCero extends Exception{

Existe toda una jerarquía de clases derivada de la clase base Exception Las


excepciones en Java son siempre objetos de alguna clase derivada de la clase
base Exception. Existen también los errores internos que son objetos de la
clase Error que no estudiaremos. Ambas clases Error y Exception son clases
derivadas de la clase base abstracta Throwable.

Ejemplos de excepciones.

Por formato

               String str="  12 ";

               int numero=Integer.parseInt(str);

Si se introducen caracteres no numéricos, o no se quitan los espacios en


blanco al principio y al final del string, se lanza una
excepción NumberFormatException.

El mensaje que aparece en la ventana nos indica:

El tipo de excepción: NumberFormatException,


La función que la ha lanzado: Integer.parseInt() que se llama dentro
de main (por ejemplo)

Por objeto no inicializado

Habitualmente, en un mensaje a un objeto no inicializado

    public static void main(String[] args) {

               String str;

        str.length();

               //...

   }

El compilador se queja con el siguiente mensaje “variable str might not have
been initilized”. En otras ocasiones, se lanza una excepción del
tipo NulPointerException.

class MiCanvas....{

               Gráfico grafico;

    public void paint(...){

               grafico.dibuja();

               //...

    }

//...

Si al llamarse a la función paint, el objeto grafico no ha sido inicializado con el


valor devuelto por new al crear un objeto de la clase Grafico o de alguna de
sus clases derivadas, se lanza la excepción NullPointerException apareciendo
en la consola el siguiente texto.
Exception occurred during event dispatching:
java.lang.NullPointerException

Entrada/salida

En otras situaciones el mensaje de error aparece en el momento en el que se


compila el programa. Así, cuando intentamos leer un carácter del teclado,
llamamos a la función

        System.in.read();

Cuando compilamos el programa, nos aparece un mensaje de error que no nos


deja proseguir.

Sentencia finally

Se puede usar esta sentencia en un bloque try-catch para ejecutar un bloque


de código después de ejecutar las sentencias del try y del catch.

Se ejecutará tanto si se lanza, como si no una excepción, y aunque no haya


ningún catch que capture esa excepción.

Podrá usarse por ejemplo para cerrar los archivos abiertos.

Ejemplo

public class FinallyDemo {

public static void procA() {// Lanza una excepción fuera del método

try {

System.out.println("Dentro de procA");

throw new RuntimeException("demo");

} finally {

System.out.println("Sentencia finally de procA");

}
}

public static void procB() {// Ejecuta la sentencia return

// dentro del try

try {

System.out.println("Dentro de procB");

return;

} finally {

System.out.println("Sentencia finally de procB");

public static void procC() {// Ejecuta un bloque try normalmente

try {

System.out.println("Dentro de procC");

} finally {

System.out.println("Sentencia finally de procC");

public static void main(String args[]) {

try {procA();

} catch (Exception e) {

System.out.println("Excepción capturada");

}
procB(); procC();

Salida del anterior programa

Dentro de procA

Sentencia finally de procA

Excepción capturada

Dentro de procB

Sentencia finally de procB

Dentro de procC

Sentencia finally de procC

Tipos de excepciones

¿Qué es una excepción?

Una excepción es un evento que ocurre durante la ejecución del programa que
interrumpe el flujo normal de las sentencias.

Ósea, algo que altera la ejecución normal.

Muchas clases de errores pueden generar excepciones desde problemas de


hardware, como la avería de un disco duro, a los simples errores de
programación, como tratar de acceder a un elemento de un array fuera de sus
límites.
Existen varios tipos fundamentales de excepciones:

Error: Excepciones que indican problemas muy graves, que suelen ser no
recuperables y no deben casi nunca ser capturadas.

Exception: Excepciones no definitivas, pero que se detectan fuera del tiempo


de ejecución.

RuntimeException: Excepciones que se dan durante la ejecución del programa.

Todas las excepciones tienen como clase base la clase Throwable, que está
incluida en el paquete java.lang, y sus métodos son:

Trowable( String mensaje ); Constructor. La cadena es opcional

Throwable fillInStackTrace(); Llena la pila de traza de ejecución.

String getLocalizedMessage(); Crea una descripción local de este objeto.

String getMessage(); Devuelve la cadena de error del objeto.

void printStackTrace( PrintStream_o_PrintWriter s ); Imprime este objeto y su


traza en el flujo del parámetro s, o en la salida estándar (por defecto).

String toString; Devuelve una breve descripción del objeto.


La superclase de todas las excepciones es la clase Throwable. Sólo las
instancias de esta clase o alguna de sus subclases pueden ser utilizadas como
excepciones. La clase Trowable tiene dos clases derivadas: Error y Exception.
La clase Exception sirve como superclase para crear excepciones de propósito
específico (adaptadas a nuestras necesidades).

La clase Error sirve de superclase para una serie de clases derivadas ya


definidas que nos informan de situaciones anormales relacionadas con errores
de muy difícil recuperación producidos en el sistema.

La clase Exception tiene un amplio número de clases derivadas proporcionadas


por el SDK, por ejemplo, existen excepciones predefinidas para el uso de
ficheros, de SQL, etc. De todas estas subclases, RuntimeExeption tiene una
característica propia: no es necesario realizar un tratamiento explícito de estas
excepciones (de todas las demás clases derivadas de Exception si es
necesario). Esto es debido a que, al igual que con las excepciones derivadas
de Error, existen pocas posibilidades de recuperar situaciones .

Otro ejemplo seria

try
{
using (StreamReader lector = File.OpenText(DIR_ARCHIVO)){
String linea;
while ((linea = lector.ReadLine()) != null)
{
Console.WriteLine(linea);
}
lector.Close();
Console.WriteLine("------------------------");

}
}
catch(Exception ex){Console.WriteLine("Error al leer el archivo:
{0}",ex.Message);}
finally{Console.ReadLine();}return;
Propagación de excepciones

La propagación de excepciones es el mecanismo recomendado para


interceptar errores que se produzcan durante la ejecución de las aplicaciones
(divisiones por cero, lectura de archivos no disponibles, etc.) Básicamente, son
objetos derivados de la clase System.Exception que se generan cuando en
tiempo de ejecución se produce algún error y que contienen información sobre
el mismo. Esto es una diferencia respecto a su implementación en el C++
tradicional que les proporciona una cierta homogeneidad, consistencia y
sencillez, pues en éste podían ser valores de cualquier tipo.

Tradicionalmente, el sistema que en otros lenguajes y plataformas se ha venido


usando para informar estos errores consistía simplemente en hacer que los
métodos en cuya ejecución pudiesen producirse devolvieran códigos que
informasen sobre si se han ejecutado correctamente o, en caso contrario, sobre
cuál fue el error producido. Sin embargo, las excepciones proporcionan las
siguientes ventajas frente a dicho sistema:

Claridad: El uso de códigos especiales para informar de error suele dificultar la


legibilidad de la fuente en tanto que se mezclan las instrucciones propias de la
lógica del mismo con las instrucciones propias del tratamiento de los errores
que pudiesen producirse durante su ejecución.
ejemplo:

int resultado = obj.Método();


if (resultado == 0) // Sin errores al ejecutar obj.Método();
{...}
else if (resultado == 1) // Tratamiento de error de código 1
{...}
else if (resultado == 2) // Tratamiento de error de código 2
...
Como se verá, utilizando excepciones es posible escribir el código como si
nunca se fuesen a producir errores y dejar en una zona aparte todo el código
de tratamiento de errores, lo que contribuye a facilitar la legibilidad de las
fuentes.
Ejemplo:
Para propagar de forma explícita una excepción se emplea la palabra
reservada throw seguida del objeto excepción. Ejemplo:

try {

// codigo

Origen.CopiaFichero(Destino);

// codigo

catch (IOException e) {

System.out.println (“Error de lectura ¿Desea intentarlo de Nuevo?†);

..........

public void CopiaFichero (TipoFichero Destino) {

try {

// codigo

}
catch (IOException e) {

// cerrar ficheros, etc.

throw e;

En el ejemplo anterior, el método CopiaFichero, después de tratar la excepción,


la propaga (throw) al método llamante, que a su vez hace otro tratamiento de la
excepción.

Estado de una excepción

Como hemos visto, todas las excepciones derivan de la clase Throwable y


tienen acceso a sus dos constructores y sus 7 métodos. El estado de las
instancias de esta clase se compone de un String que sirve de mensaje
indicando las características de la excepción y una pila de ejecución que
contiene la relación de métodos que se encuentran en ejecución en el
momento en el que se produce la excepción. Los métodos más significativos de
la clase Throwable son:

Para ilustrar la forma de uso de estos métodos, en la clase Excepcion2


modificamos el contenido de los bloques catch para imprimir la pila de
ejecución:

catch (ArithmeticException e) {

System.out.println("Division por cero");

e.printStackTrace();

}
catch(IndexOutOfBoundsException e) {

System.out.println("Índice fuera del array");

e.printStackTrace();

Gestión de excepciones: manejo de excepciones,


lanzamiento de excepciones

Para informar de un error no basta con crear un objeto del tipo de excepción
apropiado, sino que también hay pasárselo al mecanismo de propagación de
excepciones del CLR. A esto se le llama lanzar la excepción, y para hacerlo se
usa la siguiente instrucción:

throw < objetoExcepciónALanzar>;

Por ejemplo, para lanzar una excepción de tipo DivideByZeroException se


podría hacer:

throw new DivideByZeroException();

Si el objeto a lanzar vale null, entonces se producirá una


NullReferenceException que será lanzada en vez de la excepción indicada en
la instrucción throw.

CAPTURA DE EXCEPCIONES. INSTRUCCIÓN TRY


Una vez lanzada una excepción es posible escribir código que se encargue de
tratarla. Por defecto, si este código no se escribe la excepción provoca que la
aplicación aborte mostrando un mensaje de error en el que se describe la
excepción producida y dónde se ha producida. Así, dado el siguiente código
fuente de ejemplo:

using System;

class PruebaExcepciones
{
static void Main()
{
A obj1 = new A();
obj1.F();
}
}

class A
{
public void F()
{
G();
}

static public void G()


{
int c = 0;
int d = 2/c;
}
}
Al compilarlo no se detectará ningún error ya que al compilador no le merece la
pena calcular el valor de c en tanto que es una variable, por lo que no detectará
que dividir 2/c no es válido. Sin embargo, al ejecutarlo se intentará dividir por
cero en esa instrucción y ello provocará que aborte la aplicación mostrando el
siguiente mensaje:
Unhandled Exception: System.DivideByZeroException: Attempted to divide by
zero at PruebaExcepciones.Main()

Como se ve, en este mensaje se indica que no se ha tratado una excepción de


división por cero dentro del código del método Main() del tipo
PruebaExcepciones. Si al compilar la fuente hubiésemos utilizado la opción
/debug, el compilador habría creado un fichero .pdb con información extra
sobre las instrucciones del ejecutable generado que permitiría que al ejecutarlo
se mostrase un mensaje mucho más detallado con información sobre la
instrucción exacta que provocó la excepción, la cadena de llamadas a métodos
que llevaron a su ejecución y el número de línea que cada una ocupa en la
fuente:

Unhandled Exception: System.DivideByZeroException: Attempted to divide by


zero.
at A.G() in E:\c#\Ej\ej.cs:line 22
at A.F() in E:\c#\Ej\ej.cs:line 16
at PruebaExcepciones.Main() in E:\c#\Ej\ej.cs:line 8

Si se desea tratar la excepción hay que encerrar la división dentro de una


instrucción try con la siguiente sintaxis:

try
< instrucciones>
catch (< excepción1>)
< tratamiento1>
catch (< excepción2>)
< tratamiento2>
...
finally
< instruccionesFinally>
El significado de try es el siguiente: si durante la ejecución de las <
instrucciones> se lanza una excepción de tipo < excepción1> se ejecutan las
instrucciones < tratamiento1>, si fuese de tipo < excepción2> se ejecutaría <
tratamiento2>, y así hasta que se encuentre una cláusula catch que pueda
tratar la excepción producida. Si no se encontrase ninguna y la instrucción try
estuviese anidada dentro de otra, se miraría en los catch de su try padre y se
repetiría el proceso. Si al final se recorren todos los try padres y no se
encuentra ningún catch compatible, entonces se buscaría en el código desde el
que se llamó al método que produjo la excepción. Si así se termina llegando al
método que inició el hilo donde se produjo la excepción y tampoco allí se
encuentra un tratamiento apropiado se aborta dicho hilo; y si ese hilo es el
principal se aborta el programa y se muestra el mensaje de error con
información sobre la excepción lanzada ya visto.

Así, para tratar la excepción del ejemplo anterior de modo que una división por
cero provoque que a d se le asigne el valor 0, se podría rescribir G() de esta
otra forma:

static public void G()


{
try
{
int c = 0;
int d = 2/c;
}
catch (DivideByZeroException)
{ d=0; }
}
Para simplificar tanto el compilador como el código generado y favorecer la
legibilidad del fuente, en los catchs se busca siempre orden de aparición
textual, por lo que para evitar catchs absurdos no se permite definir catchs que
puedan capturar excepciones capturables por catchs posteriores a ellos en su
misma instrucción try.

También hay que señalar que cuando en < instrucciones> se lance una
excepción que sea tratada por un catch de algún try -ya sea de la que contiene
las < instrucciones>, de algún try padre suyo o de alguno de los métodos que
provocaron la llamada al que produjo la excepción- se seguirá ejecutando a
partir de las instrucciones siguientes a ese try.
El bloque finally es opcional, y si se incluye ha de hacerlo tras todas los
bloques catch. Las <instrucciones Finally> de este bloque se ejecutarán tanto si
se producen excepciones en <instrucciones> como si no. En el segundo caso
sus instrucciones se ejecutarán tras las <instrucciones>, mientras que en el
primero lo harán después de tratar la excepción pero antes de seguirse
ejecutando por la instrucción siguiente al try que la trató. Si en un try no se
encuentra un catch compatible, antes de pasar a buscar en su try padre o en su
método llamante padre se ejecutarán las <instruccionesFinally>.

Sólo si dentro de un bloque finally se lanzase una excepción se aborta la


ejecución del mismo. Dicha excepción sería propagada al try padre o al método
llamante padre del try que contuviese el finally.

Aunque los bloques catch y finally son opcionales, toda instrucción try ha de
incluir al menos un bloque catch o un bloque finally.

El siguiente ejemplo resume cómo funciona la propagación de excepciones:

using System;

class MiException:Exception {}

class Excepciones
{
public static void Main()
{
try
{
Console.WriteLine("En el try de Main()");
Método();
Console.WriteLine("Al final del try de Main()");
}
catch (MiException)
{
Console.WriteLine("En el catch de Main()");
}
finally
{
Console.WriteLine("finally de Main()");
}
}

public static void Método()


{
try
{
Console.WriteLine("En el try de Método()");
Método2();
Console.WriteLine("Al final del try de Método()");
}
catch (OverflowException)
{
Console.WriteLine("En el catch de Método()");
}
finally
{
Console.WriteLine("finally de Método()");
}
}

public static void Método2()


{
try
{
Console.WriteLine("En el try de Método2()");
throw new MiException();
Console.WriteLine("Al final del try de Método2()");
}
catch (DivideByZeroException)
{
Console.WriteLine("En el catch de Método2()");
}
finally
{
Console.WriteLine("finally de Método2()");
}
}
}
Nótese que en este código lo único que se hace es definir un tipo nuevo de
excepción llamado MiException y llamarse en el Main() a un método llamado
Método() que llama a otro de nombre Método2() que lanza una excepción de
ese tipo. Viendo la salida de este código es fácil ver el recorrido seguido
durante la propagación de la excepción:

En try de Main()
En try de Método()
En try de Método2()
finally de Método2
finally de Método
En catch de Main()
finally de Main()

Como se puede observar, hay muchos WRITELINE() que nunca se ejecutan ya


que en cuanto se lanza una excepción se sigue ejecutando tras la instrucción
siguiente al try que la trató (aunque ejecutando antes los finally pendientes,
como se deduce de la salida del ejemplo) De hecho, el compilador se dará
cuenta que la instrucción siguiente al throw nunca se ejecutará e informará de
ello con un mensaje de aviso.
La idea tras este mecanismo de excepciones es evitar mezclar código normal
con código de tratamiento de errores. En < instrucciones> se escribiría el
código como si no pudiesen producirse errores, en las cláusulas catch se
tratarían los posibles errores, y en el FINALLY se incluiría el código a ejecutar
tanto si producen errores como si no (suele usarse para liberar recursos
ocupados, como ficheros o conexiones de red abiertas).
En realidad, también es posible escribir cada cláusula CATCH definiendo una
variable que se podrá usar dentro del código de tratamiento de la misma para
hacer referencia a la excepción capturada. Esto se hace con la sintaxis:

catch (< tipoExcepción> < nombreVariable>)


{
< tratamiento>
}
Nótese que en tanto que todas las excepciones derivan de System.Exception,
para definir una cláusulacatch que pueda capturar cualquier tipo de excepción
basta usar:

catch(System.Exception < nombreObjeto>)


{
< tratamiento>
}

En realidad la sintaxis anterior sólo permite capturar las excepciones propias de


la plataforma .NET, que derivan de SYSTEM.EXCEPTION. Sin embargo,
lenguajes como C++ permiten lanzar excepciones no derivadas de dicha clase,
y para esos casos se ha incluido en C# una variante de catch que sí que
realmente puede capturar excepciones de cualquier tipo, tanto si derivan de
System.Exception como si no. Su sintaxis es:

catch
{
< tratamiento>
}
Como puede deducirse de su sintaxis, el problema que presenta esta última
variante de catch es que no proporciona información sobre cuál es la excepción
capturada, por lo que a veces puede resultar poco útil y si sólo se desea
capturar cualquier excepción derivada de SYSTEM.EXCEPTION es mejor usar
la sintaxis previamente explicada.

En cualquier caso, ambos tipos de cláusulas catch sólo pueden ser escritas
como la última cláusula catch del try, ya que si no las cláusulas catch que le
siguiesen nunca llegarían a ejecutarse debido a que las primeras capturarían
antes cualquier excepción derivada de System.Exception.

Respecto al uso de THROW, hay que señalar que hay una forma extra de
usarlo que sólo es válida dentro de códigos de tratamiento de excepciones
(códigos< tratamientoi> de las cláusulas catch) Esta forma de uso consiste en
seguir simplemente esta sintaxis:

throw

En este caso lo que se hace es relanzar la misma excepción que se capturó en


el bloque catch dentro de cuyo de código de tratamiento se usa el throw; Hay
que precisar que la excepción relanzada es precisamente la capturada, y
aunque en el bloque catch se la modifique a través de la variable que la
representa, la versión relanzada será la versión original de la misma y no la
modificada.

Además, cuando se relance una excepción en un try con cláusula finally, antes
de pasar a reprocesar la excepción en el try padre del que la relanzó se
ejecutará dicha cláusula.

Instruccion Throw
La instrucción throw ya se ha visto que se usa para lanzar excepciones de este
modo:

throw < objetoExcepciónALanzar>;

En caso de que no se indique ningún se relanzará el que se estuviese tratando


en ese momento, aunque esto sólo es posible si el throw se ha escrito dentro
del código de tratamiento asociado a alguna cláusula catch.
Como esta instrucción ya ha sido explicada a fondo en este mismo tema, para
más información sobre ella puede consultarse el epígrafe Excepciones del
mismo.

Creación y manejo de excepciones definidas por el usuario

Las excepciones predefinidas cubren las situaciones de error más habituales


con las que nos podemos encontrar, relacionadas con el propio lenguaje y el
hardware. Cuando se desarrollan aplicaciones existen otras situaciones de
error de más ‘alto nivel’ relacionadas con la funcionalidad de nuestros
programas. Imaginemos una aplicación informática que controla la utilización
de los remontes de
una estación de esquí: los pases de acceso a los remontes son personales e
intransferibles y dispondrán de un código de barras que los identifica. Cada vez
que un usuario va a hacer uso de un remonte debe introducir su pase de
acceso en una máquina de validación, que acciona un torno y devuelve el
pase.

El sistema puede constar de un ordenador central al que les llegan


telemáticamente los datos correspondientes a los códigos de barras de los
pases que en cada momento se están introduciendo en cada máquina de
validación de cada remonte; si un código de barras está en regla, el ordenador
envía una orden de liberar el torno para permitir al usuario acceder al remonte.
El ordenador central habitualmente recibirá códigos correctos utilizados en
momentos adecuados, sin embargo, en ciertas ocasiones nos encontraremos
con situaciones anómalas:

1 código de barras ilegible

2 código de barras no válido (por ejemplo correspondiente a un pase


caducado)

3 código de barras utilizado en otro remonte en un periodo de tiempo


demasiado breve

4 etc.
Una vez que disponemos de una excepción propia, podremos programar la
funcionalidad de nuestras aplicaciones provocando (lanzando) la excepción
cuando detectemos alguna de las situaciones anómalas asociadas.

Ejemplo:

//Demostración del mecanismo de manejo de excepciones


//try...catch...finally

public class UsoDeExcepciones {


public static void main(String args[]){
try{
lanzaExcepcion();//llama al metodo lanzaExcepcion
}//fin de try
catch(Exception excepcion){ //excepcion lanzada por lanzaExcepcion
System.err.println("La excepcion se manejo en main ");
}//fin de catch
noLanzaExcepcion();
}//fin de main
//demuestra los bloques try...catch...finally

public static void lanzaExcepcion() throws Exception{


try{//lanza una excepción y la atrapa de inmediato
System.out.println("Metodo lanzaExcepcion");
throw new Exception();//genera la excepcion
}//fin de try

catch (Exception excepcion){ //atrapa la excepcion lanzada en el bloque try


System.err.println("La excepcion se manejo en el metodo lanzaExcepcion ");
throw excepcion;//vuelve a lanzar para procesarla más adelante
//no se llegaría al codigo que se coloque aquí, la excepcion se vuelve a lanzar
en el bloque catch
}//fin catch
finally{//se ejecuta sin importar lo que ocurra en los bloques try..catch
System.err.println("Se ejecuto finally en lanzaExcepcion ");
}//din de finally
//no se llega al codigo que se coloque aquí, la excepcion se vuelve a lanzar en
el bloque catch
}//fin del metodo lanzaException

//demuestra el uso de finally cuando no ocurre una excepcion


public static void noLanzaExcepcion(){
try{//el bloque try no lanza una excepcion
System.out.println("Metodo noLanzaExcepcion");
}//fin de try

catch(Exception excepcion){//no se ejecuta


System.err.println(excepcion);
}//fin de catch

finally{//se ejecuta sin importar lo que ocurra en los bloques try..catch


System.err.println("Se ejecuto Finally en noLanzaExcepcion");
}//fin de bloque finally

System.out.println("Fin del metodo noLanzaExcepcion");


}//fin del metodo noLanzaExcepcion

}//fin de la clase UsoDeExcepciones

NOTA
En los casos en los que se use la interacción remota, se debe garantizar que
los metadatos de cualquier excepción definida por el usuario están disponibles
en el servidor (destinatario de la llamada) y en el cliente (el objeto proxy o
llamador). Por ejemplo, el código que llama a un método de otro dominio de
aplicación debe poder encontrar el ensamblado que contiene una excepción
producida por una llamada remota.

En el ejemplo siguiente, se deriva una nueva clase de excepción,


EmployeeListNotFoundException, de Exception. Se definen tres constructores
en la clase, cada uno con parámetros diferentes.

using System;
public classEmployeeListNotFoundException: Exception
{
public EmployeeListNotFoundException()
{
}
public EmployeeListNotFoundException(string message)
: base(message)
{
}
public EmployeeListNotFoundException(string message, Exception inner)
: base(message, inner)
{
}
}
FLUJOS Y ARCHIVOS
Definición
Archivo
Los archivos tienen como finalidad guardar datos de forma permanente. Una
vez que acaba la aplicación los datos almacenados están disponibles para que
otra aplicación pueda recuperarlos para su consulta o modificación.

La organización de un archivo define la forma en la en que se estructuran u


organizan los datos. Formas de organización fundamentales:
Secuenciales: los registros se insertan en el archivo en orden de llagada. Las
operaciones básicas permitidas son: escribir, añadir al final del archivo y
consultar .
Directa o aleatoria: cuando un registro es directamente accesible mediante la
especificación de un índice.
Flujo
La información que necesita un programa para su función se obtiene mediante
una entrada de datos de una fuente que puede ser de tipos muy variados:
desde el teclado,  un archivo, una comunicación de red, un objeto en internet,
etc. Cuando el programa genera los resultados como salida de la ejecución
puede hacerlo de muy diversas maneras: en un archivo, en la pantalla, en una
impresora, etc. 
En java la entrada de los datos se realiza mediante un flujo de entrada. La
salida de datos realiza mediante un flujo de salida.

Esquema para trabajar con los flujos de datos Entrada/Salida.

Entrada de datos (leer datos) Salida de datos (escribir datos)


  1. Se crea un objeto flujo de datos de  1.Se crea un objeto flujo de datos de
lectura. escritura.
  2. Se leen datos de él  con los métodos  2.  Se escriben datos utilizando los
apropiados. métodos apropiados del objeto flujo
  3. Se cierra el flujo de datos.   3. Se cierra el flujo de datos.
TIPOS DE FLUJOS

Existen dos tipos de Flujos:

            -Los que trabajan con Bytes


            -Los  que trabajan con Caracteres

Las clases más importantes a tener en cuenta son las siguientes, donde el
sangrado de las líneas indica la herencia, es decir, DataInputStream hereda de
FilterInputStream que, a su vez, hereda de InputStream.

Flujos con Bytes Flujos con caracteres


InputStream Reader
E      ByteArrayInputStream      BufferedReader
N      FileInputStream           LineNumberReader
T      FilterInputStream      CharArrayReader
R   D           BufferedInputStream      FilterReader
A   A           DataInputStream           PushbackReader
D   T           PushbackInputStream      InputStreamReader
A   O      ObjectInputStream           FileReader
      S      PidedInputStream      PidedReader
D      SequenceInputStream     StringReader
E      StringBufferInputStream

OutputStream Writer
S      ByteArrayOutputStream       BufferedWriter
A      FileOutputStream       CharArrayWriter
L      FilterOutputStream      FilterWriter
I   D           BufferedOutputStream      OutputStreamWriter
D  A           DataOutputStream           FileWriter
A  T           PrintStream      PidedWriter
    O      ObjectOutputStream      PrintWriter
D  S      PipedOutputStream      StringWriter
E

Clasificación: Archivos de texto y binarios


Definición de archivos de texto y binarios.

Los archivos de texto plano son aquellos que están compuestos únicamente
por texto sin formato, solo caracteres. estos caracteres se pueden codificar de
distintos modos dependiendo de la lengua usada. Se les conoce también como
archivos de texto llano o texto simple por carecer de información destinada a
generar formatos y tipos de letra.
Un archivo binario es una archivo informático que contiene información de
cualquier tipo, codificada en forma binaria para el propósito de almacenamiento
y procesamiento de ordenadores.
Muchos formatos binarios contienen partes que pueden ser interpretados como
texto. Un archivo binario que solo contiene información de tipo textual sin
información sobre el formato del mismo, se dice que es un archivo de texto
plano. Habitualmente se contraponen los términos archivo binario y archivo de
texto de forma que los primeros no contienen solamente texto.

Ejemplo:

ejemplo:
clase 1.
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;
private JScrollPane panelDespl;
//constructor de la interfaz
public Demostracionfile(){
super("Ejemplo de archivos");
areaSalida= new JTextArea();
panelDespl=new JScrollPane(areaSalida);
add(panelDespl,BorderLayout.CENTER);
setSize(400,400); //establece el tamaño de la interfaz
setVisible(true);//muestra la interfaz GUI8
analizarRuta(); //crear y analizar un objeto File
} // fin del contructor
private File obtenerArchivo(){
JFileChooser selectorArchivos=new JFileChooser();
selectorArchivos.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORI
ES
);
int resultado = selectorArchivos.showOpenDialog(this);
if(resultado == JFileChooser.CANCEL_OPTION)
System.exit(1);
File nombreArchivo = selectorArchivos.getSelectedFile(); //obtiene el nombre
del
archivo
if ((nombreArchivo==null)||(nombreArchivo.getName().equals(""))) {
JOptionPane.showMessageDialog(this, "nombre del archivo
invalido","nombre del archivo invalido", JOptionPane.ERROR_MESSAGE );
System.exit(1);
}//fin del IF
return nombreArchivo;
}
//yet..another class
public void analizarRuta(){
File nombre = obtenerArchivo();
if (nombre.exists())
{
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() ? "no es directorio":"no es directorio"),
(nombre.isAbsolute() ? "es una ruta absoluta":"no es un a ruta
absoluta"),
"ultima modifecacion: ",nombre.lastModified(), "tamaño:
",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");
}
}
else
{
JOptionPane.showMessageDialog(this, nombre + "no existe",
"ERROR",JOptionPane.ERROR_MESSAGE);
}
}
}
clase 2.
import javax.swing.JFrame;
public class pruebademostracionfile {
public static void main(String args[]){
Demostracionfile aplicacion = new Demostracionfile();
aplicacion.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}

Operaciones Básicas y Tipos de Acceso

Las operaciones básicas en archivos son:


1. Creación
2. Apertura
3. Lectura
4. Escritura
5. Recorrido
6. Cierre
Archivos De Texto
El manejo de archivos de texto se puede llevar a cabo por medio de dos tipos
de flujos: de bytes y de caracteres.
Archivos De Texto Con Flujos De Bytes.
Para escribir o leer datos de tipo byte en un archivo se declara un flujo de la
clase FileStream, cuyos constructores son:
FileStream (string nombre , FileMode modo )
FileStream (string nombre , FileMode modo , FileAccess acceso )
DONDE:
Nombre es el nombre del archivo en disco, incluyendo la trayectoria.
Ejemplo
"C: \\ POOISC \\ ARCHIVOS \\ archivo.txt"
O Su Forma Equivalente
@ "C: \ POOISC \ ARCHIVOS \ archivo.txt"
Modo es un valor del tipo enumerado FileMode; puede tomar uno de los
siguientes valores:
1. CreateNew
Crea un nuevo archivo. Si el archivo existe, lanzará una excepción del
tipo IOException.
2. Create
Crea un nuevo archivo. Si el archivo existe, será sobreescrito.
3. Open
Abre un archivo existente.
4. OpenOrCreate
Abre un archivo, si existe;en caso contrario, se crea un nuevo archivo.
5. Truncate
Abre un archivo existente y lo trunca a cero bytes de longitud.
6. Append
Abre un archivo para agregarle datos al final. Si el archivo no existe, lo
crea.
ACCESO es un valor del tipo enumerado FileAccess ; puede tomar uno de los
siguientes valores:
1. Read
Permite leer un archivo.
2. ReadWrite
Permite leer o escribir en el archivo.
3. Write
Permite escribir en el archivo.
Ejemplo 1
using System;
using System.IO;

public class CEscribirBytes


{

public static void Main ( )


{
FileStream fs = null;
byte[] buffer = new byte[81];
int nbytes = 0, car;

try
{
// Crea un flujo hacia el archivo texto.txt

fs = new FileStream("texto.txt",FileMode.Create, FileAccess.Write);

Console.WriteLine("Escriba el texto que desea almacenar en el archivo:");

while ((car = Console.Read()) != '\r' && (nbytes < buffer.Length))


{
buffer[nbytes] = (byte)car;
nbytes++;
}

// Escribe la línea de texto en el archivo.

fs.Write(buffer, 0, nbytes);
}
catch(IOException e)
{

Console.WriteLine("Error: " + e.Message);


}
finally
{
if (fs != null) fs.Close();
}
}
}

---

Ejemplo 2
using System;
using System.IO;

public class CLeerBytes


{
public static void Main( )
{
FileStream fe = null;
char[] cBuffer = new char[81];
byte[] bBuffer = new byte[81];
int nbytes;

try
{
// Crea un flujo desde el archivo texto.txt
fe = new FileStream("texto.txt", FileMode.Open, FileAccess.Read);

// Lee del archivo una línea de texto


nbytes = fe.Read(bBuffer, 0, 81);

// Crea un objeto string con el texto leído


Array.Copy(bBuffer, cBuffer, bBuffer.Length);

String str = new String(cBuffer, 0, nbytes);


// Muestra el texto leído
Console.WriteLine(str);
}
catch(IOException e)
{
Console.WriteLine("Error: " + e.Message);
}
finally
{
// Cierra el archivo.
if (fe != null) fe.Close();
}
}
}

Archivos De Texto Con Flujos De Caracteres


En este caso los datos se manejan por medio de flujos de caracteres, utilizando
las clases StreamWriter y StreamReader.

Escritura
StreamWriter es una clase derivada de TextWriter.
Sus constructores son:
StreamWriter (string nombre ) // Abre un nuevo flujo para escribir en un archivo
especificado por nombre .
StreamWriter (Stream flujo ) // Utiliza un flujo existente para escribir.
Antes de ser escritos, los datos son convertidos automáticamente a un formato
portable de 8 bits (UTF-8, UCS Transformation Format 8).
Los objetos de la clase StreamWriter poseen varios métodos, entre los que
destacan:
1. Write()
2. WriteLine()
3. Flush()
Además, poseen la propiedad BaseStream .
EJEMPLO
using System;
using System.IO;

public class CEscribirCars


{
public static void Main( )
{
StreamWriter sw = null;
String str;

try
{

// Crea un flujo hacia el archivo doc.txt


// Si el archivo existe se destruye su contenido.
sw = new StreamWriter("doc.txt");

Console.WriteLine("Escriba las líneas de texto a


almacenar en el archivo.\n" + "Finalice cada línea
pulsando la tecla .\n" +
"Para finalizar pulse sólo la tecla .\n");

// Lee una línea de la entrada estándar


str = Console.ReadLine();

// Mientras la cadena str no esté vacía


while (str.Length != 0)
{
// Escribe la línea leída en el archivo
sw.WriteLine(str);

// Lee la línea siguiente


str = Console.ReadLine();
}
}
catch(IOException e)
{
Console.WriteLine("Error: " + e.Message);
}
finally
{
if (sw != null) sw.Close();
}
}
}

LECTURA
StreamReader es una clase derivada de TextReader y cuenta con los
siguientes constructores:
StreamReader (string nombre ) abre un nuevo flujo para leer de un archivo
especificado por nombre.
StreamReader (Stream flujo ) utiliza un flujo existente para leer.
Algunos de los métodos más importantes de la clase StreamReader son:
1. Read( )
2. ReadLine( )
3. Peek( )
4. DiscardBufferData( )
EJEMPLO
using System;
using System.IO;

public class CLeerCars


{
public static void Main (string[] args)
{

StreamReader sr = null;
String str;

try
{
// Crea un flujo desde el archivo doc.txt
sr = new StreamReader("doc.txt");

// Lee del archivo una línea de texto


str = sr.ReadLine();

// Mientras la cadena str no esté vacía


while (str != null)
{
// Muestra la línea leída
Console.WriteLine(str);

// Lee la línea siguiente


str = sr.ReadLine();
}
}
catch(IOException e)
{
Console.WriteLine("Error: " + e.Message);
}
finally
{
// Cierra el archivo
if (sr != null) sr.Close();
}
}
}
El siguiente programa pregunta al usuario si desea sobrescribir los datos
existentes en el archivo.
using System;
using System.IO;

public class CEscribirCars


{
public static void Main( )
{
StreamWriter sw = null;
String str;

try
{
// Obtiene el nombre del archivo desde la entrada estándar
Console.Write("Nombre del archivo: ");
str = Console.ReadLine();

char resp = 's';


if ( File.Exists(str) )
{
Console.Write("El archivo existe ¿desea sobreescribirlo? (s/n) ");
resp = (char)Console.Read();
// Salta los bytes no leídos del flujo de entrada estándar
Console.ReadLine();
}

if (resp != 's') return;

// Crea un flujo hacia el archivo seleccionado por el usuario.


sw = new StreamWriter(str);
Console.WriteLine("Escriba las líneas de texto a almacenar
en el archivo.\n" + "Finalice cada línea pulsando
la tecla .\n" + "Para finalizar pulse sólo la tecla .\n");

// Lee una línea de la entrada estándar


str = Console.ReadLine();

// Mientras la cadena str no esté vacía


while (str.Length != 0)
{
// Escribe la línea leída en el archivo
sw.WriteLine(str);

// Lee la línea siguiente


str = Console.ReadLine();
}
}
catch(UnauthorizedAccessException e)
{
Console.WriteLine("Error: " + e.Message);
}
catch(IOException e)
{
Console.WriteLine("Error: " + e.Message);
}
finally
{
if (sw != null) sw.Close();
}
}
}
El siguiente programa lee caracteres desde un archivo seleccionado por el
usuario.
using System;
using System.IO;

public class CLeerCars


{
public static void Main( )
{
StreamReader sr = null;
String str;

try
{
// Obtiene el nombre del archivo desde la entrada estándar
do{
Console.Write("Nombre del archivo: ");
str = Console.ReadLine();
} while (!File.Exists(str));

// Crea un flujo desde el archivo str


sr = new StreamReader(str);
// Lee del archivo una línea de texto
str = sr.ReadLine();

// Mientras la cadena str no esté vacía


while (str != null)
{
// Muestra la línea leída
Console.WriteLine(str);

// Lee la línea siguiente


str = sr.ReadLine();
}
}
catch(IOException e)
{
Console.WriteLine("Error: " + e.Message);
}
finally
{
// Cierra el archivo
if (sr != null) sr.Close( );
}
}
}

Archivos Binarios
Cuando se requiere efectuar operaciones con datos de alguno de los tipos
primitivos, tales datos deberán escribirse y leerse en formato binario.
El espacio de nombres System.IO proporciona las clases:
1. • BinaryWriter
2. • BinaryReader
Con estas clases se podrán manipular datos de los tipos primitivos y cadenas
de caracteres en formato UTF-8.
Los archivos escritos en formato binario no se pueden desplegar directamente
en los dispositivos de salida estándar, como el monitor, sino que deben leerse
a través de flujos de la clase BinaryReader.
BINARYWRITER crea flujos para escribir archivos con datos de los tipos
primitivos en formato binario.
Su constructor es:
BinaryWriter ( Stream flujo )
Y requiere, como parámetro, un flujo de la clase Stream o sus derivadas.
Ejemplo
FileStream fs =new FileStream("datos.dat",FileMode. Create ,FileAccess.
Write );
BinaryWriter bw=new BinaryWriter( fs );
Un objeto de la clase BinaryWriter actúa como filtro entre el programa y un flujo
de la clase FileStream.
En la siguiente tabla se describen algunos de los principales métodos y
propiedades de la clase BinaryWriter:
 Write(byte)
Escribe un valor de tipo byte.
 Write(byte[])
Escribe una cadena como una secuencia de bytes.
 Write(char)
Escribe un valor de tipo char.
 Write(char[])
Escribe una cadena como una secuencia de caracteres.
 Write(short)
Escribe un valor de tipo short.
 Write(int)
Escribe un valor de tipo int.
 Write(long)
Escribe un valor de tipo long.
 Write(Decimal)
Escribe un valor de tipo Decimal.
 Write(float)
Escribe un valor de tipo float.
 Write(double)
Escribe un valor de tipo double.
 Write(string)
Escribe una cadena de caracteres en formato UTF-8.El primero o los
dos primeros bytes especifican el número de bytes de datos escritos en
la cadena.
 BaseStream
Obtiene el flujo base ( fs en el ejemplo mostrado).
 Close
Cierra el flujo y libera los recursos adquiridos.
 Flush
Limpia el buffer asociado con el flujo.
 Seek
Establece el apuntador de Lectura/Escritura en el flujo.

BINARYREADER crea flujos para leer archivos con datos de los tipos


primitivos en formato binario, escritos por un flujo de la clase Binaryreader.
Su constructor es:
BinaryReader ( Stream flujo )
Y requiere, como parámetro, un flujo de la clase Stream o sus derivadas.
Ejemplo
FileStream fs =new FileStream("datos.dat",FileMode. Open ,FileAccess.
Read );
BinaryReader br=new BinaryReader( fs );
Un objeto de la clase BinaryReader actúa como filtro entre un flujo de la clase
FileStream y el programa.
En la siguiente tabla se describen algunos de los principales métodos y
propiedades de la clase BinaryReader:
o Read(byte)
Devuelve un valor de tipo byte.
o Read(byte[])
Devuelve una cadena como una secuencia de bytes.
o Read(char)
Devuelve un valor de tipo char.
o Read(char[])
Devuelve una cadena como una secuencia de caracteres.
o Read(short)
Devuelve un valor de tipo short.
o •Read(int)
Devuelve un valor de tipo int.
o •Read (long)
Devuelve un valor de tipo long.
o •Read (Decimal)
Devuelve un valor de tipo Decimal.
o •Read (float)
Devuelve un valor de tipo float.
o •Read (double)
Devuelve un valor de tipo double.
o •Read (string)
Devuelve una cadena de caracteres en formato UTF-8. El primero o los
dos primeros bytes especifican el número de bytes de datos que serán
leídos.
o •BaseStream
Obtiene el flujo base ( fs en el ejemplo mostrado).
o •Close
Cierra el flujo y libera los recursos adquiridos.
o •Flush
Limpia el buffer asociado con el flujo.
o •PeekChar
Obtiene el siguiente carácter, sin extraerlo.

ACCESO ALEATORIO
Cuando se abre un archivo, se puede acceder a sus registros de manera
secuencial o de manera aleatoria.
El acceso aleatorio consiste en posicionar el apuntador de archivo en una
localidad específica del archivo. Este tipo de acceso es necesario cuando se
requiere modificar alguno de los campos de un registro. En lugar de leer desde
el primero hasta el registro elegido, el apuntador se posiciona, en una especie
de salto, hasta dicho registro.
El salto se hace con base al conteo de los bytes existentes entre una posición
inicial y el primer byte del registro elegido.
Para el acceso aleatorio a los registros de un archivo, la clase FileStream
implementa las propiedades Position y Length , así como el método Seek( )
Position devuelve la posición actual, en bytes, del apuntador de archivo.
El apuntador de archivo marca el byte donde se hará la siguiente operación de
lectura o de escritura.
La declaración de la propiedad Position es:
public long Position ;
La propiedad Length devuelve la longitud del archivo en bytes. Está declarada
así:
public long Length ;
El método Seek( ) mueve el apuntador de archivo a una ubicación localizada
desp bytes, a partir de la posición pos del archivo.
La sintaxis para Seek( ) es:
public long Seek( long desp , SeekOrigin pos )
El desplazamiento desp puede ser positivo o negativo.
La posición pos puede tomar uno de los siguientes valores del tipo enumerado
SetOrigin .
1. Begin ........ El inicio del archivo.
2. Current ..... Posición actual del apuntador de archivo.
3. End ........... El final del archivo.
EJEMPLOS
1 .- Suponiendo un flujo fs de la clase FileStream .
fs.Seek(desp,SeekOrigin.Begin);
fs.Seek(desp,SeekOrigin.Current);
fs.Seek( -desp , SeekOrigin. End );
El valor almacenado en desp debe calcularse en base al tamaño de registro del
archivo.
2 .- Suponiendo un flujo br de la clase BinaryReader .
FileStream fs = new FileStream("datos", FileMode.Open,
FileAccess.Read);
BinaryReader br = new BinaryReader( fs );
Debido a que el flujo br no soporta directamente el método Seek( ) , tiene que
acceder a él a través de su propiedad BaseStream , como se muestra a
continuación:
br. BaseStream.Seek (desp, SeekOrigin.Current);
Para facilitar el cálculo del desplazamiento desp , es conveniente que todos los
registros tengan la misma longitud.
En la figura anterior se muestra un archivo con 6 registros (numerados de 0 a
5) con 100 bytes de longitud cada uno.
El desplazamiento para acceder al tercer registro se calcula de la siguiente
manera:
desp = Numero de registro * tamaño de registro
desp = 2*100
Es difícil recordar el número de registro al que se necesita acceder, por lo que
se hace necesario establecer una relación entre el número de registro y el valor
almacenado en alguno de los campos del registro.
Por ejemplo, si cada registro tiene un campo denominado clave, de tipo entero
en el rango de 1 a N (que corresponde al número de control de un empleado),
la relación entre la clave y el número de registro es:
número de registro = clave -1
Así, el cálculo del desplazamiento para el empleado con la clave 4 se efectúa
de la siguiente manera :
desp = (4-1) * 100 = 300
Esto obliga a que, cuando se introduzcan los datos de un nuevo empleado, el
número de su clave deba ser igual al número de registro +1.
El número de registro que sigue es igual al número de registros existentes.

También podría gustarte