Sie sind auf Seite 1von 60

Programación Orientada a Objetos

Características del lenguaje de programación.

1 Fundamentos del Paradigma de la Programación Orientada a Objetos


1.1 Introducción a la Programación Orientada a Objetos
1.1.1 Conceptos Básicos de la POO.
1.2 Clases y Objetos

2 Métodos y mensajes.
2.1 Atributos static.
2.2 Concepto de método.
2.3 Declaración de métodos.
2.4 Llamadas a métodos (mensajes).
2.5 Tipos de métodos.
2.5.1 Métodos y atributos static.
2.5.2 Métodos normales y volátiles.
2.6 Referencia this.
2.7 Forma de pasar argumentos.
2.8 Constructor, destructor

3 Arreglos (Arrays)
3.1 Arreglos de una dimensión.
3.2 Arreglos multidimensionales.
3.3 Arreglos de Objetos.

4 Sobrecarga.
4.1 Conversión de tipos.
4.2 Sobrecarga de métodos.
4.3 Sobrecarga de operadores.

5 Herencia.
5.1 Introducción a la herencia.
5.2 Herencia simple.
5.3 Herencia múltiple.
5.4 Parte protegida.
5.5 Redefinición de los miembros de las clases derivadas.

6 Polimorfismo y reutilización
6.1 Concepto del polimorfismo.
6.2 Clases abstractas.
6.3 Definición de una interfaz.
6.4 Definición y creación de paquetes/librería.

7 Excepciones.
7.1 Definición.
7.2 Excepciones definidas por el usuarios.

Página: 1
Programación Orientada a Objetos
8 Flujos y archivos.
8.1 Clasificación de acuerdo al tipo de acceso
8.2 Definición de Archivos de texto y archivos binarios.
8.3 Operaciones básicas en archivos texto y binario.
8.4 Ficheros Secuenciales de Texto
8.5 Ficheros de Acceso Directo
8.5.1 Creación de un fichero de acceso directo
8.5.2 Escritura en un fichero de acceso directo
8.5.3 Lectura de un fichero de acceso directo

Bibliografía:

CAMELIA MUÑOZ CARO/ALFONSO NIÑO RAMOS/AURORA VIZCAÍNO


BARCELÓ
INTRODUCCIÓN A LA PROGRAMACIÓN CON ORIENTACIÓN A OBJETOS
PRENTICE HALL

Programación en C, C++, Java y UML


Luis Joyanes Aguilar
Mc Graw Hill

Página: 2
Programación Orientada a Objetos
Características del lenguaje de programación.

Lenguaje simple

Java posee una curva de aprendizaje muy rápida. Resulta relativamente sencillo escribir applets
interesantes desde el principio. Todos aquellos familiarizados con C++ encontrarán que Java es
más sencillo, ya que se han eliminado ciertas características, como los punteros. Debido a su
semejanza con C y C++, y dado que la mayoría de la gente los conoce aunque sea de forma
elemental, resulta muy fácil aprender Java. Los programadores experimentados en C++ pueden
migrar muy rápidamente a Java y ser productivos en poco tiempo.

Orientado a objetos

Java fue diseñado como un lenguaje orientado a objetos desde el principio. Los objetos agrupan en
estructuras encapsuladas tanto sus datos como los métodos (o funciones) que manipulan esos
datos. La tendencia del futuro, a la que Java se suma, apunta hacia la programación orientada a
objetos, especialmente en entornos cada vez más complejos y basados en red.

Distribuido

Java proporciona una colección de clases para su uso en aplicaciones de red, que permiten abrir
sockets y establecer y aceptar conexiones con servidores o clientes remotos, facilitando así la
creación de aplicaciones distribuidas.

Interpretado y compilado a la vez

Java es compilado, en la medida en que su código fuente se transforma en una especie de código
máquina, los bytecodes, semejantes a las instrucciones de ensamblador.

Por otra parte, es interpretado, ya que los bytecodes se pueden ejecutar directamente sobre
cualquier máquina a la cual se hayan portado el intérprete y el sistema de ejecución en tiempo real
(run-time).

Robusto

Java fue diseñado para crear software altamente fiable. Para ello proporciona numerosas
comprobaciones en compilación y en tiempo de ejecución. Sus características de memoria liberan
a los programadores de una familia entera de errores (la aritmética de punteros), ya que se ha
prescindido por completo los punteros, y la recolección de basura elimina la necesidad de
liberación explícita de memoria.

Seguro

Dada la naturaleza distribuida de Java, donde las applets se bajan desde cualquier punto de la
Red, la seguridad se impuso como una necesidad de vital importancia. A nadie le gustaría ejecutar
en su ordenador programas con acceso total a su sistema, procedentes de fuentes desconocidas.
Así que se implementaron barreras de seguridad en el lenguaje y en el sistema de ejecución en
tiempo real.

Indiferente a la arquitectura

Java está diseñado para soportar aplicaciones que serán ejecutadas en los más variados entornos
de red, desde Unix a Windows Nt, pasando por Mac y estaciones de trabajo, sobre arquitecturas
distintas y con sistemas operativos diversos. Para acomodar requisitos de ejecución tan variados,
el compilador de Java genera bytecodes: un formato intermedio indiferente a la arquitectura,

Página: 3
Programación Orientada a Objetos
diseñado para transportar el código eficientemente a múltiples plataformas hardware y software. El
resto de problemas los soluciona el intérprete de Java.

Portable

La indiferencia a la arquitectura representa sólo una parte de su portabilidad. Además, Java


especifica los tamaños de sus tipos de datos básicos y el comportamiento de sus operadores
aritméticos, de manera que los programas son iguales en todas las plataformas.

Estas dos últimas características se conocen como la Máquina Virtual Java (JVM).

Alto rendimiento

 Multihebra

Hoy en día ya se ven como terriblemente limitadas las aplicaciones que sólo pueden ejecutar
una acción a la vez. Java soporta sincronización de múltiples hilos de ejecución
(multithreading) a nivel de lenguaje, especialmente útiles en la creación de aplicaciones de red
distribuidas. Así, mientras un hilo se encarga de la comunicación, otro puede interactuar con el
usuario mientras otro presenta una animación en pantalla y otro realiza cálculos.

 Dinámico

El lenguaje Java y su sistema de ejecución en tiempo real son dinámicos en la fase de


enlazado. Las clases sólo se enlazan a medida que son necesitadas. Se pueden enlazar
nuevos módulos de código bajo demanda, procedente de fuentes muy variadas, incluso desde
la Red.

 Produce applets

Java puede ser usado para crear dos tipos de programas: aplicaciones independientes y
applets.

Las aplicaciones independientes se comportan como cualquier otro programa escrito en


cualquier lenguaje, como por ejemplo el navegador de Web HotJava, escrito íntegramente en
Java.

Por su parte, las applets son pequeños programas que aparecen embebidos en las páginas
Web, como aparecen los gráficos o el texto, pero con la capacidad de ejecutar acciones muy
complejas, como animar imágenes, establecer conexiones de red, presentar menús y cuadros
de diálogo para luego emprender acciones, etc.

Página: 4
Programación Orientada a Objetos
Unidad 1 Fundamentos del Paradigma de la Programación
Orientada a Objetos
1.1 Introducción a la Programación Orientada a Objetos

El principio de la Programación Orientadas a Objetos es ''divide y vencerás''. Este principio es


usado en otras técnicas de programación y la diferencia fundamental entre cada una de ellas es la
elección de los bloques con los que se construye un software. En las técnicas estructuradas las
unidades fundamentales son los procedimientos (subrutinas o subprogramas), mientras que en la
programación modular se usan interfaces y módulos. En el modelo de objetos las unidades básicas
se conocen como objetos los cuales contienen tanto datos (es decir, variables de estado que
describen el objeto en un cierto instante) como métodos (es decir reglas dinámicas, reglas que
explican la interacción del objeto con el mundo exterior). En la terminología OO, lo anterior quiere
decir que el objeto encapsula sus datos y su comportamiento. Los objetos interactúan
intercambiando mensajes para producir un comportamiento colectivo, que en principio debe
resolver un problema.

El modelo de objetos provee fundamentos teóricos a partir de los cuales se construye un programa
orientado a objetos. Este modelo se basa en los principios de abstracción, encapsulación,
modularidad, jerarquía, polimorfismo, entre otros. En las siguientes secciones se tratará de definir
formalmente los conceptos de la POO para posteriormente llevarlos a la práctica con el lenguaje
Java.

El término de Programación Orientada a Objetos indica más una forma de diseño y una
metodología de desarrollo de software que un lenguaje de programación, ya que en realidad se
puede aplicar el Diseño Orientado a Objetos (DOO), a cualquier tipo de lenguaje de programación.

El desarrollo de la POO empieza a destacar durante la década de lo 80 tomando en cuenta la


programación estructurada, a la que engloba y dotando al programador de nuevos elementos para
el análisis y desarrollo de software.

Básicamente la POO permite a los programadores escribir software, de forma que esté organizado
en la misma manera que el problema que trata de modelizar. Los lenguajes de programación
convencionales son poco más que una lista de acciones a realizar sobre un conjunto de datos en
una determinada secuencia. Si en algún punto del programa modificamos la estructura de los datos
o la acción realizada sobre ellos, el programa cambia.

1.1.1 Conceptos Básicos de la POO.

Abstracción: Es la caracterización de un objeto de acuerdo a las propiedades que nos interesen


en un instante de tiempo.

Las características escogidas son relativas a la perspectiva del observador.

Página: 5
Programación Orientada a Objetos

Figura 1.1: Abstracción

Tipos de Datos Abstractos

Algunos autores describen a la POO como programación con Tipos de Datos Abstractos (TDA) y
sus relaciones. Aunque el concepto de Abstracción es muy importante dentro de la POO, esta
última consiste de más herramientas.

El primer problema que enfrenta un programador cuando inicia un proyecto grande de


programación, es el problema mismo que se quiere resolver y como hacer abstracciones de este
último. Típicamente, se enfrenta con problemas de la ''vida real'' y se quiere hacer la vida más fácil
construyendo un programa que resuelva el problema de manera automática. Sin embargo, los
problemas de la vida real son muchas veces obscuros y difíciles de entender. Entonces, es
necesario dejar de lado los detalles de la implementación y tratar de obtener una visión abstracta, o
un modelo, del problema. Este proceso de modelado es conocido como abstracción y se ilustra en
la figura 1.2.

Figura 1.2 Modelo

El modelo define una visión abstracta del problema. Esto implica que el modelo se enfoca en la
definición de las propiedades del problema. Estas propiedades incluyen:

Las estructuras de datos que serán afectadas y modificadas


Las operaciones identificadas por el problema.

Página: 6
Programación Orientada a Objetos

Una abstracción es entonces, la estructuración de un problema de la vida real en entidades bien


definidas por sus datos y sus operaciones. Por lo tanto, estas entidades combinan datos y
operaciones y no están desacopladas.

Una entidad con las propiedades descritas antes se conoce como un tipo de datos abstracto o
TDA. La figura 1.3 muestra un TDA. Sólo algunas operaciones son visibles desde el exterior y
definen la interfaz de el TDA.

Figura 1.3: Tipo de Datos Abstracto.

El ocultamiento de los datos de el TDA y sólo proveer de una interfaz bien definida se conoce
como encapsulación.

Encapsulación: Es la manera de ocultar los detalles de la representación interna de un objeto


presentando solo la interface para el usuario (ver figura 1.4).

Figura 1.4: Encapsulación.


Resumiendo, la separación de los datos de las operaciones, y la restricción de que sólo es posible
tener acceso a los datos a través de las operaciones definidas en la interfaz, permite seleccionar
los TDA apropiados para resolver un problema determinado.

Los TDA proveen una visión abstracta para describir propiedades de un conjunto de entidades y su
uso es independiente del lenguaje de programación. Por esta razón, se introduce una notación que
consiste de dos partes:

Datos o Atributos: En esta parte se describen las estructuras de los datos usados dentro del TDA
de manera informal.

Página: 7
Programación Orientada a Objetos
Operaciones o Métodos: En esta parte se describen las operaciones que serán válidas para el
TDA. Un conjunto de estas operaciones serán la interfaz del TDA. Para cada operación se deben
describir los argumentos, así como sus posibles precondiciones y postcondiciones.

Los TDA permiten la creación de instancias con propiedades y comportamientos bien definidos. En
la POO los TDA's se conocen como clases. Una clase define las propiedades de los objetos los
cuales son entidades tangibles en el ambiente de orientación a objetos.

Los TDA's definen su funcionalidad poniendo énfasis en los datos, la estructura, las operaciones y
en los axiomas y precondiciones. Consecuentemente, POO es ''programar con TDA's'' (y algo
más). Instancias (objetos) de TDA's (clases) se crean dinámicamente, se usan y se destruyen con
el objetivo de resolver un problema real mediante la POO.

1.2 Clases y Objetos

Una clase es una representación de un TDA. Esta provee los detalles de la implementación de los
datos y de las operaciones. De manera formal se puede decir que: Una clase es la implementación
de un tipo de datos abstracto (TDA) y define los atributos y los métodos de la TDA.

Las instancias de clases se conocen como objetos. Entonces las clases definen las propiedades de
un conjunto de objetos.

CLASE: Agrupación de un conjunto de objetos de similares características.

Una clase define las funciones y atributos de un tipo de objetos.

Un objeto se convierte en una variable o instancia de la clase.

Ejemplo:

CLASE: Coche

Atributos: Color, Velocidad, Carburante, etc.


Métodos: Andar, Parar, Girar, etc.

Un sistema real está formado por varios tipos de objetos (clases), que interactúan entre sí,
colaborando y pasándose información de unos a otros.

Objetos: Como se mencionó anteriormente, los objetos son instancias de clases y cada objeto se
diferencía de otros similares por su nombre particular (identidad). Es posible entonces, tener dos
objetos con los mismos datos pero distinto nombre. Esto es similar a lo que sucede con los
lenguajes tradicionales de programación donde se puede tener, por ejemplo, dos enteros “i” y “j” ,
ambos conteniendo el valor de ''2''. En este caso ''i'' y ''j'' se usan para identificar a los dos enteros.

El conjunto de valores asignados a los datos de un objeto, en un instante determinado, se conoce


como el estado del objeto.

El estado del objeto cambia de acuerdo a los métodos que se aplican sobre él. La posible
secuencia de cambios de estado se conoce como el comportamiento del objeto:

Comportamiento: El comportamiento de un objeto está definido por el conjunto de métodos que


se pueden aplicar sobre él.

Un objeto puede ser cualquier cosa, real o abstracta, acerca de la cual se almacenan datos y las
funciones que manejan y controlan estos datos.

Página: 8
Programación Orientada a Objetos
Ejemplo de Objetos: coche del profesor, bicicleta del cartero, ...

Ejemplo de No Objetos: un coche, una bicicleta, color, tamaño, edad. Los primeros 2 ejempos
serían clases porque son conceptos muy generales, los restantes 3 ejemplos corresponderían a
atributos de alguna clase

Se tienen hasta ahora dos conceptos importantes de la orientación a objetos, clases y objetos.
Podría decirse que la POO es la acción de implementar clases y crear instancias de ellas. En la
ejecución de un programa OO, las instancias de clases, los objetos, realizan ciertas acciones para
obtener la solución de un problema cambiando. Consecuentemente, se puede pensar al programa
en ejecución como una colección de objetos en interacción. La pregunta inmediata es: ¿Cómo es
que estos objetos interactúan?. La respuesta es: por medio de intercambio de mensajes.

Ejemplo:

CLASE: Coche

Atributos: Color, Velocidad, Carburante.


Métodos: Andar, Parar, Girar.

Nombre de objeto 1: Coche Fernando


Funciones permitidas objeto 1: Andar, Parar, Girar
Datos objeto 1: gris, 140 Km/h, súper

Nombre de objeto 2: Coche Nuria


Funciones permitidas objeto 2: Andar, Parar, Girar
Datos objeto 2: rojo, 170 Km/h, sin plomo

Unidad 2: Métodos y mensajes.


2.1 Atributos static.

Los atributos describen un objeto que ha sido seleccionado para ser incluido en el modelo de
análisis. En esencia, son los atributos los que definen al objeto, los que clarifican lo que representa
el objeto en el contexto del espacio del problema. Para obtener un conjunto significativo de
atributos para un objeto, el analista debe estudiar la narrativa del proceso (o descripción del ámbito
del alcance) para el problema y seleccionar aquellos elementos que razonablemente “pertenecen”
al objeto. Además, para cada objeto debe responderse el siguiente interrogante: “¿Qué elementos
(compuestos y/o simples) definen completamente al objeto en el contexto del problema actual?” .

La manera de acceder (si es posible) a un atributo (una "variable") es por medio del objeto de la
siguiente manera:

Objeto.Atributo

La accesibilidad de los atributos de una clase depende de los modificadores que se incluyan al
definirlos. Las posibilidades son:

Modificador Significado
private Sólo puede accederse al atributo desde la clase
protected Atributo accesible por las subclases y por todas las clases del mismo paquete
public Accesible por todas las clases
por defecto Accesible por todas las clases del mismo paquete

Página: 9
Programación Orientada a Objetos

Además, tras estos modificadores pueden aparecer otros modificadores: static, final, transient y
volatile. Con static indicamos que la variable es global. Sólo hay una parte de memoria reservada
para el atributo correspondiente por muchos objetos que definamos correspondientes a esa clase.
Los modificadores final y volatile tienen que ver respectivamente con la herencia.
static: los atributos miembros de una clase pueden ser atributos de clase o atributos de instancia;
se dice que son atributos de clase si se usa la palabra clave static: en ese caso la variable es única
para todas las instancias (objetos) de la clase (ocupa un único lugar en memoria). A veces a las
variables de clase se les llama variables estáticas. Si no se usa static, el sistema crea un lugar
nuevo para esa variable con cada instancia (la variable es diferente para cada objeto). En el caso
de una constante no tiene sentido crear un nuevo lugar de memoria por cada objeto de una clase
que se cree. Por ello es adecuado el uso de la palabra clave static. Cuando usamos “static final” se
dice que creamos una constante de clase, un atributo común a todos los objetos de esa clase.
final: en este contexto indica que una variable es de tipo constante: no admitirá cambios después
de su declaración y asignación de valor. final determina que un atributo no puede ser sobreescrito
o redefinido. O sea: no funcionará como una variable “tradicional”, sino como una constante. Toda
constante declarada con final ha de ser inicializada en el mismo momento de declararla. final
también se usa como palabra clave en otro contexto: una clase final (final) es aquella que no puede
tener clases que la hereden. Lo veremos más adelante cuando hablemos sobre herencia.

2.2 Concepto de método.

Las clases contienen normalmente métodos, esto es, funciones y se puede decir que son las
operaciones o acciones que puede ejecutar un objeto.

Se puede decir que el estado del objeto cambia de acuerdo a los métodos que se aplican sobre él.
La posible secuencia de cambios de estado se conoce como el comportamiento del objeto:

Comportamiento: El comportamiento de un objeto está definido por el conjunto de métodos que


se pueden aplicar sobre él.

La forma de acceder a un método de una clase es similar a la de los atributos. Basta escribir el
nombre del objeto de la clase, un punto y después el nombre del método:

Objeto.Método()

Los métodos también admiten modificadores igual que los atributos. Las posibilidades de
accesibilidad son como antes private, protected, public; seguidos de static, abstract, final, native,
syncronized.

2.3 Declaración de métodos.

Como ya se mencionó anteriormente, los métodos son las operaciones o acciones que puede
realizar una clase y son los que, prácticamente, definen el comportamiento y el estado de un
objeto.

Todo método debe estar contenido dentro de una clase.

La sintaxis para declara un método es la siguiente:

[modificador de acceso][static] tipo Nombre([parámetros formales])


{
[declaración de variables locales]
sentencias
}

Página: 10
Programación Orientada a Objetos
Donde:
modificador de acceso: puede ser public, private, protected o sin modificador (amigo).

Tipo: es cualquier tipo de dato. Si es un tipo de dato diferente de “void”, debe existir dentro del
cuerpo del método al menos una linea con la sentencia return(valor); donde “valor” es una
constante o variable que debe corresponder al tipo de dato con el que se declaró el método.

Nombre: Es el identificador del método.

Parámetros formales: es la lista de parámetros que incluye, tipo de dato y nombre del parámetro, si
son varios se separan por comas.

Variables locales: Son las variables que usará el método y son creadas cuando se ejecuta el
método y dejarán de existir cuando éste concluya.

2.4 Llamadas a métodos (mensajes).

La llamada a un método se puede realizar de dos formas:

Llamada al método desde otro método perteneciente a la misma clase. En este caso la llamada al
método se realiza de la siguiente manera:

Si el tipo de dato del método es “void” la llamada se efectúa sólo nombrando al método, esto es:

nombreMetodo(parámetros Actuales);

Si el tipo de dato del método es diferente de “void”, generalmente, va precedido de una variable o
atributo mas el operador de asignación.

atributo=nombreMetodo(parámetros Actuales);

Llamada al método por medio de un objeto se realiza de manera similar al anterior, la diferencia es
que se antepone el nombre del objeto y un punto:

objeto.nombreMetodo(parámetros Actuales);

Los parámetros actuales, no incluyen tipos de datos, pero se debe tomar en cuenta que los
parámetros actuales y formales se corresponden por posición, no por nombre, por lo tanto, los tipos
de datos deben ser iguales.

Ademas de lo expuesto anteriormente, el número de parámetros formales y actuales debe ser el


mismo.

2.5 Tipos de métodos.

2.5.1 Métodos y atributos static.

Se puede decir que cuando se crean objetos de alguna clase y que los atributos y métodos no son
declarados como “static”, cada uno de los objetos creados ocupará localidades de memoria para
cada uno de ellos. Por ejemplo, si una clase tiene definidos 3 atributos que no son “static” y se
crean 4 objetos, se ocuparán 4x3=12 localidades de memoria.

Cuando se declaran los atributos o métodos como estáticos (static), todos los objetos creados
comparten la misma localidad de memoria y cualquier cambio de valor en alguno de los objetos,
afecta a todos los objetos. Por ejemplo, si una clase tiene definidos 3 atributos que son “static” y se
crean 4 objetos, se ocuparán sólo 3 localidades de memoria y tendrán acceso los 4 objetos.

Página: 11
Programación Orientada a Objetos

El siguiente ejemplo complementa lo anterior, añadiendo métodos para asignar y recuperar las
coordenadas de un objeto de la clase Punto.

class Punto public class Proto


{ {
int x,y; public static void main(String[] args)
public void ponx(int primeracoord) {
{ Punto P=new Punto();
x=primeracoord; Punto Q=new Punto();
} P.ponx(3); P.pony(5);
public void pony(int segundacoord) Q.ponx(7); Q.pony(15);
{ System.out.println("("+P.dimex()+","+P.dimey()+")");
y=segundacoord; System.out.println("("+Q.dimex()+","+Q.dimey()+")");
} }
public int dimex() }
{
return x;
}
public int dimey()
{
return y;
}
}

el resultado será: (3,5), (7,15). Pero si cambiamos int x,y; por static int x,y en la clase “Punto”; el
resultado será (7,15), (7,15). Las variables del objeto Q interfieren con las de P, porque no hay
memoria separada para ambos juegos de atributos.

Los atributos y métodos estáticos tienen la propiedad de que se puede acceder a ellos sin que se
declare ningún objeto correspondiente a la clase.

Por ejemplo, tiene sentido el programa:


class Punto
{
static int x,y;
}
public class Proto
{
public static void main(String[] args)
{
Punto.x=13;
Punto.y=34;
System.out.println("("+Punto.x+","+Punto.y+")");
}
}

Sin embargo, si los atributos no fueran estáticos se obtendría un error del tipo:
non-static variable cannot be referenced from a static context.

2.5.2 Métodos normales y volátiles.

El modificador volatile indica que un campo puede ser modificado en el programa por algo como el
sistema operativo, el hardware o un hilo de ejecución concurrente (tareas que se ejecutan en
paralelo).

Página: 12
Programación Orientada a Objetos
El calificador de tipo volatile se aplica a la definición de una variable que puede alterarse desde el
exterior del programa.

Se utiliza este modificador sobre los atributos de los objetos para indicar al compilador que es
posible que dicho atributo vaya a ser modificado por varios threads de forma simultanea y
asíncrona, y que no queremos guardar una copia local del valor para cada thread a modo de
caché, sino que queremos que los valores de todos los threads estén sincronizados en todo
momento, asegurando así la visibilidad del valor actualizado a costa de un pequeño impacto en el
rendimiento.

2.6 Referencia this.

Como ya sabemos, es posible declarar una variable dentro de un bloque con el mismo nombre que
una variable miembro, pero no con el nombre de otra variable local que ya existiera. La variable
declarada dentro del bloque oculta a la variable miembro en ese bloque. Para acceder a la variable
miembro oculta será preciso utilizar el operador this, en la forma this.varname.

Ejemplo:

public class Ejemplo {


int a=5,b=10;
void metodoA() {
String a=”LOCAL”;
System.out.println(“variable a= “+a); // imprime: variable a= LOCAL
System.out.println(“atributo a= “+this.a); // imprime: atributo a= 5
}
void metodoB(String b) // un parámetro con nombre de un atributo de la clase
{
b=”PARAMETRO”;
System.out.println(“parametro b= “+b); // imprime: variable b= PARAMETRO
System.out.println(“atributo b= “+this.b); // imprime: atributo b= 10
}
}

2.7 Forma de pasar argumentos.

En el paso de parámetros a funciones hay dos aproximaciones clásicas: el paso por valor y paso
por referencia.

En el paso por valor se realiza una copia de los valores que se pasan, trabajando dentro de la
función con la copia. Es por ello que cualquier cambio que sufran dentro, no repercute fuera de la
función.

En el paso por referencia no se realiza dicha copia, por lo que las modificaciones de dentro de las
funciones afectan a los parámetros y esos cambios permanecen al final de la función.

En Java el paso por parámetro es por valor, aunque los efectos son de paso por referencia cuando
los argumentos son objetos. ¿cómo sucede eso? Pues es muy fácil, si una función tiene como
argumento un tipo primitivo (int, float, etc...), en Java se realiza una copia para la función y
cualquier cambio a dicho argumento no afecta a la variable original. Este paso de parámetros en
Java está orientado a utilizar el valor de la variable para otros cálculos.

En el caso de los objetos es distinto. En realidad lo que sucede es que en Java siempre tenemos
referencias a los objetos. Por eso al pasar a una función como argumento un objeto, pasamos la
referencia al mismo, es decir, aunque se hace una copia para el paso por valor, como lo que se

Página: 13
Programación Orientada a Objetos
copia es una referencia, los cambios al objeto referenciado sí son visibles y afectan fuera de la
función.

2.8 Constructor, destructor.

Constructores

Al definir un objeto podemos querer especificar el valor de algunos atributos. La manera de hacerlo
es dotando a la clase correspondiente de un método constructor. Éste es un método que tiene el
mismo nombre que la clase, que se suele colocar en primer lugar y que se ejecuta
automáticamente.

Si un constructor tiene ciertos parámetros como argumentos, se pueden especificar al declarar el


objeto con:

Clase Objeto = new Clase(parámetros);

Por ejemplo, con la siguiente variante de la clase Punto, se puede escribir Punto P= new
Punto(31,22) para especificar que P es el punto (31,22).

class Punto
{
int x,y;
Punto(int abcisa, int ordenada)
{
x=abcisa;
y=ordenada;
}
}
public class Proto
{
public static void main(String[] args)
{
Punto P=new Punto(31,22);
System.out.println("("+P.x+","+P.y+")");
}
}

‘La destrucción del objeto’

Todo objeto creado ocupa espacio en memoria y en algunas ocasiones ya no se ocupa ese objeto,
por lo tanto ya no nos conviene que esté ocupando esa memoria y es necesario liberarla, lo cual se
realiza con el destructor.

Existen dos formas de liberar memoria ocupada por los objetos:

a.) La destrucción de los objetos en forma automática.

Cuando un objeto no va a ser utilizado, el espacio de memoria de dinámica que utiliza ha de


ser explícitamente.

El recolector de basura será el encargado de liberar una zona de memoria dinámica que había
sido reservada mediante el operador new, cuando el objeto ya no va a ser utilizado más
durante el programa (por ejemplo, sale del ámbito de utilización, o no es referenciado
nuevamente).

Página: 14
Programación Orientada a Objetos
El sistema de recogida de basura se ejecuta periódicamente, buscando objetos que ya no
estén referenciados.

b.) La destrucción personalizada: finalize

A veces una clase mantiene un recurso que no es de Java como un descriptor de archivo o un
tipo de letra del sistema de ventanas. En este caso sería acertado el utilizar la finalización
explícita, para asegurar que dicho recurso se libera. Esto se hace mediante la destrucción
personalizada.

Para especificar una destrucción personalizada se añade un método a la clase con el nombre
finalize:

class Clase Finalizada


{
void Finalizada() // Constructor
{
// Reserva del recurso no Java o recurso compartido
}
protected void finalize() // destructor
{
// Liberación del recurso no Java o recurso compartido
}
}

El intérprete de Java llama al método finalize(), si existe cuando vaya a reclamar el espacio de ese
objeto, mediante la recogida de basura.

Debe observarse que el método finalize() es de tipo protected void y por lo tanto deberá de
sobreescribirse con este mismo tipo.

Unidad 3 Arreglos (Arrays)

Cuando declaramos una variable en java, al ejecutar el programa se busca en la memoria de la


computadora una localidad de memoria que esté disponible para almacenar valores del tipo de
dato que se definió y la identificará con el nombre que se le asignó. Por ejemplo, en la declaración:

double d;

Se buscará una localidad de memoria que se identificará con el nombre “d” y almacenara datos de
tipo “double” (reales). Gráficamente se tendría:

Si tenemos las siguientes sentencias

d=3.1416;

El valor se almacena en la localidad de memoria, quedando como:

Página: 15
Programación Orientada a Objetos
d

3.1416

Si en líneas posteriores del programa tenemos las siguiente sentencia:

d=2943.857;

El valor en la localidad de memoria se actualiza, lo que quiere decir que el valor anterior se pierde,
quedando como:

2943.857

En resumen, hasta este momento, una variable solo puede almacenar un solo valor en un
determinado tiempo.

Si queremos hacer referencia a múltiples valores en un tiempo determinado y hacerlo por medio de
un mismo nombre de variable se deberá usar un arreglo (array).

Un array es un identificador que referencia un conjunto de datos del mismo tipo. Imagina un tipo de
dato int; podremos crear un conjunto de datos de ese tipo y utilizar uno u otro con sólo cambiar el
índice que lo referencia. El índice será un valor entero y positivo.

3.1.- Vectores.

Un vector es un array unidimensional, es decir, sólo utiliza un índice para referenciar a cada uno de
los elementos. Su declaración será:

tipo [ ] nombre = new tipo[tamaño];

El tipo puede ser cualquiera de los ya conocidos y el tamaño indica el número de elementos del
vector (se debe indicar entre corchetes [ ] ).

EJEMPLO:
/* Declaración de un array. */
public static void main(String [ ] args) /* Rellenamos del 0 - 9 */
{
int [ ] vector= new int [5];
int i;
for (i=0;i<5;i++)
vector[i]=i;
for (i=0;i<5;i++)
System.out.println(vector[i]);
}
En el ejemplo se puede observar que la variable “i” es utilizada como índice, el primer for sirve
para rellenar el vector y el segundo para visualizarlo. Como se puede ver, las posiciones van de 0
a 4 (total 5 elementos).

Al ejecutar el programa anterior, gráficamente las localidades de memoria se verían, de acuerdo a


las instrucciones como:

int [ ] vector= new int [5]; // declaración del arreglo

Página: 16
Programación Orientada a Objetos
vector
0
1 Observe que en las localidades de memoria no se tiene ningún valor
2 almacenado, pero ya están disponibles para su uso.
3
4
El código:

for (i=0;i<5;i++)
vector[i]=800;

Es un ciclo que hace variar el valor de “i” de cero a 4 (al llegar a 5 sale del ciclo). Y la instrucción
que se incluye en el ciclo es: Al elemento “i” del arreglo “vector” se le asigna el valor 800. Esto es,
al elemento cero se le asigna el valor de 800, al elemento 1 se le asigna 800, etc.

Gráficamente se vería:

vector
i=0  vector[0]= 800 0 800
i=1  vector[1]= 800 1 800
i=2  vector[2]= 800
2 800
i=3  vector[3]= 800
i=4  vector[4]= 800 3 800
4 800

Las instrucciones siguientes actualizan los valores de los elementos indicados entre corchetes
como se observa en la figura:

vector
vector[0]=25; 0 25
1 800
vector[2]=50;
2 50
3 800
vector[4]=vector[0]+vector[2]; 4 75

vector[5]= 123; // Esta línea daría un error pues el elemento “5” no existe en el arreglo.

En las siguientes instrucciones

for (i=0;i<5;i++)
System.out.println(vector[i]);

Se recorre el arreglo y se imprime cada uno de los valores almacenados en sus localidades.

Podemos inicializar (asignarle valores) a un vector en el momento de declararlo. Si lo hacemos así


no es necesario indicar el tamaño. Su sintaxis es:

tipo [ ] nombre ={ valor 1, valor 2...}

Ejemplo:

int [ ] vector={1,2,3,4,5,6,7,8};

Página: 17
Programación Orientada a Objetos
char [ ] vector={'p','r','o','g','r','a','m','a','d','o','r'};

Llamadas a métodos con arrays

public class arrayApp


{
public static void main(String[ ] args)
{
int num[ ]=rellenarArrayDesde(5);
imprimirArray(num);
}

public static void imprimirArray (int lista[ ])


{
for(int i=0;i<lista.length;i++)
{
System.out.println(lista[i]);
}
}
public static int[ ] rellenarArrayDesde(int a)
{
int num[]=new int [10];
for(int i=0;i<num.length;i++)
{
num[i]=a;
a++;
}
return num;
}

3.2.- Arreglos multidimensionales

Se definen igual que los vectores excepto que se requiere un índice por cada dimensión.

Su sintaxis es la siguiente:

tipo [ ][ ]…[ ] nombre = new tipo [tamaño 1][tamaño 2]….[tamaño n];

Una matriz es un arreglo bidimensional y se podría representar gráficamente como una tabla con
filas y columnas.

La matriz tridimensional se utiliza, por ejemplo, para trabajos gráficos con objetos 3D.

En el ejemplo se puede ver cómo se rellena y visualiza una matriz bidimensional. Se necesitan dos
bucles para cada una de las operaciones. Un bucle controla las filas y otro las columnas.

EJEMPLO:
/* Matriz bidimensional. */
public static void main( String [ ] args) /* Rellenamos una matriz */
{
int x,I,y;
int [ ] [ ] numeros= new int [3][4];
/* rellenamos la matriz */
y=1;
for (x=0;x<3;x++)
for (i=0;i<4;i++)

Página: 18
Programación Orientada a Objetos
{
numeros[x][i]=y;
y++;
}
/* visualizamos la matriz */
for (x=0;x<3;x++)
{
for (i=0;i<4;i++)
System.out.print(numeros[x][i]+ “ “);
System.out.println(“”);
}
}

Al hacer la declaración del arreglo (int [ ] [ ] numeros= new int [3][4];), se indica que se usarán 12
localidades de memoria en total (3x4), donde, generalmente, el primer índice hace referencia al
número de renglones y el segundo índice al número de columnas. En este caso, el índice de los
renglones va del 0 al 2 y el de las columnas va del 0 al 3. Se muestra gráficamente cómo se
organizan las localidades de memoria:

numeros
índices 0 1 2 3
0
1
2

El código:

for (x=0;x<3;x++)
for (i=0;i<4;i++)
{
numeros[x][i]=y;
y++;
}

Como se observa, por cada vez que se ejecuta el ciclo de la “x”, el ciclo de la “i” se ejecuta en su
totalidad por lo que el llenado del arreglo quedaría de la siguiente manera.

numeros
índices 0 1 2 3
0 1 2 3 4
1 5 6 7 8
2 9 10 11 12

Es importante observar los límites de los ciclos que son los que determinan cuál hace referencia al
renglón y cuál a la columna.

Por ejemplo, el código:

for (x=0;x<3;x++)
{
for (i=0;i<4;i++)
{
System.out.print(numeros[x][i]+”\t“); 77Imprime en el mismo renglón
}
System.out.println(“”); // Salto de renglón
}

Página: 19
Programación Orientada a Objetos
La impresión de los elementos del arreglo sería por renglón, de la siguiente manera:

1 2 3 4
5 6 7 8
9 10 11 12

Cambiando los límites de los ciclos y la posición de los índices de la siguiente manera:

for (x=0;x<4;x++)
{
for (i=0;i<3;i++)
{
System.out.print(numeros[i][x]+”\t“); 77Imprime en el mismo renglón
}
System.out.println(“”); // Salto de renglón
}

La impresión de los elementos del arreglo sería por columna (primero la columna 0, después la
columna 1 y por último la columna 2), de la siguiente manera:

1 5 9
2 6 10
3 7 11
4 8 12

Si al declarar una matriz también queremos inicializarla, habrá que tener en cuenta el orden en
el que los valores son asignados a los elementos de la matriz. Veamos algunos ejemplos:

int[ ][ ] matriz = {{1,2},{3,4},{5,6}};

Quedarían asignados de la siguiente manera:

numeros[0][0]=1 numeros[0][1]=2
numeros[1][0]=3 numeros[1][1]=4
numeros[2][0]=5 numeros[2][1]=6

3.3.- Arreglos de Objetos.

Si tenemos la clase Alumno definida de la siguiente manera:

class Alumno
{
String nombre,nroCtrl;
int semester,edad;
void metodo1()
{
…..
}
void metodo2()
{
…..
}
}

Página: 20
Programación Orientada a Objetos
Si queremos crear el objeto “x” de la clase Alumno, lo hacemos de la siguiente manera:
Alumno x=new Alumno();

La anterior es la forma comprimida de hacerlo, pero tamien se puede hacer dela siguiente manera:

Alumno x;
x=new Alumno();

Se muestra gráficamente lo que se hace internamente la computadora:

Alumno x;

Con la instrucción anterior se busca una localidad de memoria con la que se va a hacer referencia
a un objeto.

Observe que es solo una casilla. Para crear el objeto se manda llamar al constructor con la
siguiente instrucción:

x=new Alumno();

Hasta ese momento se genera el objeto, como se observa en la figura:

x nombre nroCtrl semestre edad

Para hacer referencia a algún atributo o método se realiza como x. seguido de lo correspondiente
(nombre del atributo o método)

Para crear un arreglo de una dimensión de 5 objetos, se realiza de la siguiente manera:

Alumno [] vec= new Alumno[5];

Gráficamente tenemos:

vec
0
1
2
3
4

Si observa solo esta creado el arreglo, aun no existe ningún objeto, una forma de hacerlo es con el
siguiente código:

for(int i=0; i<5; i++)


{
vec[i]=new Alumno();
}

Página: 21
Programación Orientada a Objetos

Gráficamente tendríamos:

vec
nombre nroCtrl semestre edad
0

1 nombre nroCtrl semestre edad

2
nombre nroCtrl semestre edad

3 nombre nroCtrl semestre edad

4 nombre nroCtrl semestre edad

Si quisiéramos asignarle el valor de “Juan Pérez” al atributo nombre del objeto del elemento 3, y
18 al atributo edad del elemento 1 tendríamos:

vec[3].nombre=”Juan Pérez”;
vec[1].edad=18;

Gráficamente tendríamos:

vec
nombre nroCtrl semestre edad
0

1 nombre nroCtrl semestre edad


18

2
nombre nroCtrl semestre edad

3 nombre nroCtrl semestre edad


“Juan Pérez”
4 nombre nroCtrl semestre edad

Unidad 4: Sobrecarga.
4.1 Conversión de tipos.

Conversión de tipos en Java.

Como en muchos otros lenguajes de programación, en Java también es posible asignar un valor de
un tipo a una variable de otro tipo. Es muy probable que muchos de nosotros no le veamos a esto
mucha ciencia o mucha dificultad pero también es verdad que muchos hacemos la conversión

Página: 22
Programación Orientada a Objetos
automáticamente sin un cierto rigor que nos respalde y cuando el compilador de Java nos da error
seguimos intentándolo por el método del "ensayo y error".

Veamos a continuación, cómo hace la conversión de tipos Java:


Conversión de tipos automática.

Si al hacer la conversión de un tipo a otro se dan las 2 siguientes premisas:

 Los dos tipos son compatibles.


 El tipo de la variable destino es de un rango mayor al tipo de la variable que se va a
convertir.

Entonces, la conversión entre tipos es automática, esto es, la máquina virtual de Java tiene toda la
información necesaria para realizar la conversión sin necesidad de "ayuda" externa. A esto se le
conoce como conversión implícita y cuando hablamos de los tipos numéricos de Java se le da el
nombre de widening conversion que traducido vendría a ser algo así como: "conversión por
ampliación.

Conversión de tipos incompatibles.

Cuando alguna de las 2 premisas anteriores no se cumple, entonces la conversión automática es


imposible (salvo que se esté evaluando una expresión como se ve en el siguiente apartado), el
compilador nos da un error diciéndonos algo así como "la variable tal necesita un cast explícito".
Esto no significa que no podamos realizar tal conversión, sino que la máquina virtual de Java
(JVM) necesita que le demos una información adicional: el tipo al que vamos a convertir la variable
fuente. Pues bien, a esta conversión explícita es a lo que se le llama hacer un casting de tipos.
Cuando se trata de tipos numéricos a este tipo de conversión se le llama narrowing compresion
que se podría traducir como "conversión por estrechamiento" dado que se está "recortando" la
variable para poderla meter en el tipo destino. La expresión general del casting de un tipo es:

(tipo_destino) valor

donde tipo_destino indica el tipo al que queremos hacer la conversión explícita del valor asociado.

En el caso de los tipos numéricos, al "recortar" el valor de la variable de rango mayor para que
quepa en el tipo de rango menor resulta que perdemos información. En el ejemplo se verá en qué
se traduce esa pérdida de información según sean los tipos.

Promoción de tipos automáticamente al evaluar una expresión.

Aquí radica uno de los puntos donde se puede generar conflictos si no lo sabemos pero, que una
vez sabido, es una de las muchas ventajas que Java tiene frente a otros lenguajes. El caso es que
cuando la máquina virtual de Java tiene que analizar una expresión existe la posibilidad real de que
el resultado intermedio de una operación exceda el rango de los operandos. Para solventar este
problema lo que Java hace es una promoción automática de todos los operandos de tipo byte o
short a int. Mejor que explicarlo con letra lo vemos en el siguiente ejemplo y donde se verá qué
sencillo es recordarlo para no volver a cometer errores.

Caso 1:
byte a = 40;
byte b = 50;
int c = a * b;//el compilador no dará ningún error.

Caso 2:
byte a = 25;
a = a*2; //ERROR: Explicit cast needed to convert int to byte.

Página: 23
Programación Orientada a Objetos

Ahora te preguntarás, ¿cómo puede dar este error si el resultado no excede del rango de un tipo
byte (-128,127).

Pues bien, lo que ocurre es que al hacer la multiplicación, Java ha promocionado automáticamente
este valor a int y por eso, para volver a meterlo en un tipo byte tendremos que hacer una
conversión explícita. En el caso 1 este error no se produce porque el resultado de la operación se
guarda en un int.

a = (byte)a*2;//ahora el compilador no dará ningún error.

Para concluir con las reglas de promoción de tipos cuando evaluamos expresiones aquí van otros
detalles más que hay que tener en cuenta:

 Si alguno de los operandos es long, entonces la expresión entera promociona a long.


 Si alguno de los operandos es float, entonces la expresión entera promociona a float.
 Si alguno de los operandos es double, entonces el resultado será double.

Ejemplo.

class ConversionTipos
{
public static void main(String args[])
{
byte b;
int i = 257;
double d = 323.142;
double d2;
float f = 5.65f;
char c = 'c';
short s = 1024;
b = (byte) i;//efecto: se devuelve (i modulo 256), rango de byte.
System.out.println(b);
i = (int) d;//efecto: truncamiento de la parte decimal.
System.out.println(i);
b = (byte)d;//efecto: truncamiento y luego (result_truncado modulo 256)
System.out.println(b);
i = (int)f;//efecto: truncamiento de la parte decimal
System.out.println(i);
i = c;//correcto por ser int, pero un byte o short necesitaría un cast explícito.
System.out.println(i);
f = c; d = c;//correcto.
i = b * c;//efecto: la operación promociona a int.
System.out.println(i);
d2 = (f*b) + (i/c) - (d*s);
/*
efecto: el resultado de la promoción de todas las expresiones es un double:
(f*b) promociona a float, (i/c) promociona a int y (d*s) promociona a double. Luego,
float + int - double promociona a double.
*/
System.out.println(d2);
}
}

Página: 24
Programación Orientada a Objetos
4.2 Sobrecarga de métodos.

Un método sobrecargado se utiliza para reutilizar el nombre de un método pero con diferentes
argumentos (opcionalmente un tipo diferente de retorno). Las reglas para sobrecargar un método
son las siguientes:

 Los métodos sobrecargados deben de cambiar la lista de argumentos.


 Pueden cambiar el tipo de retorno.
 Pueden cambiar el modificador de acceso.
 Pueden declarar nuevas o ampliar excepciones.
 Un método puede ser sobrecargado en la misma clase o en una subclase.

Ejemplo de un método que se desea sobrecargar:

public void cambiarTamano(int tamano, String nombre, float patron)


{
}

Los siguientes métodos son sobrecargas legales del método cambiarTamano():

public void cambiarTamano(int tamano, String nombre)


{
}

public int cambiarTamano(int tamano, float patron)


{
}

public void cambiarTamano(float patron, String nombre) throws IOException


{
}

Cómo invocar un método sobrecargado:

Lo que define qué método es el que se va a llamar son los argumentos que se envían al mismo
durante la llamada. Si se invoca a un método con un String como argumento, se ejecutará el
método que tome un String como argumento, si se manda a llamar al mismo método pero con un
float como argumento, se ejecutará el método que tome un float como argumento y así
sucesivamente. Si se invoca a un método con un argumento que no es definido en ninguna de las
versiones sobrecargadas entonces el compilador arrojará un mensaje de error.

Ejemplo de una clase con un método sobrecargado:

public class Sobrecarga


{
public void Numeros(int x, int y)
{
System.out.println("Método que recibe enteros.");
}
public void Numeros(double x, double y)
{
System.out.println("Método que recibe flotantes.");
}
public void Numeros(String cadena)
{
System.out.println("Método que recibe una cadena: "+ cadena);

Página: 25
Programación Orientada a Objetos
}
public static void main (String [ ] args)
{
Sobrecarga s = new Sobrecarga();
int a = 1;
int b = 2;
s.Numeros(a,b);
s.Numeros(3.2,5.7);
s.Numeros("Programación O.O.");
}
}

Al ejecutar el código anterior obtendremos lo siguiente:

Método que recibe enteros.


Método que recibe flotantes.
Método que recibe una cadena: Programación O.O.

Al utilizar objetos en lugar de tipos primitivos o cadenas se vuelve más interesante. Veamos lo que
sucede cuando se tiene un método sobrecargado que en una de sus versiones toma un Animal
como parámetro y en otra un Caballo.

class Animal
{

class Caballo extends Animal


{
}

class Animales
{
public void MetodoSobrecargado(Animal a)
{
System.out.println("Método de parámetro Animal...");
}
public void MetodoSobrecargado(Caballo c)
{
System.out.println("Método de parámetro Caballo...");
}
public static void main(String [ ] args)
{
Animales as = new Animales();
Animal objetoAnimal = new Animal();
Caballo objetoCaballo = new Caballo();
as.MetodoSobrecargado(objetoAnimal);
as.MetodoSobrecargado(objetoCaballo);
}
}

Al ejecutar el código anterior obtenemos:

Método de parámetro Animal...


Método de parámetro Caballo...

Página: 26
Programación Orientada a Objetos
Como era de esperarse, cada objeto manda llamar al método que le corresponde de acuerdo a su
tipo, pero qué pasa si creamos una referencia Animal sobre un objeto Caballo...

Animal objetoAnimalRefCaballo = new Caballo();


as.MetodoSobrecargado(objetoAnimalRefCaballo);

El resultado es:

Método de parámetro Animal...

Aunque en tiempo de ejecución el objeto es un Caballo y no un Animal, la elección de qué método


sobrecargado invocar no se realiza dinámicamente en tiempo de ejecución, el tipo de referencia,
no el objeto actual, es el que determina qué método es el que se ejecutará.

Sobreescritura de Métodos

La sobreescritura de métodos es una característica más de la herencia en Java. Es decir, en Java


las nuevas clases se pueden definir extendiendo clases ya existentes. Aquí surgen los conceptos
de subclase que sería la clase obtenida, y superclase, que sería la clase que está siendo
extendida, tal como también ya se ha explicado.

Cuando una nueva clase se extiende desde otra que ya existía, todas las variables y métodos que
son miembros de la superclase (y todos aquellos miembros de los antecesores de la superclase)
serán también miembros de la subclase.

En el supuesto de que en el entorno en que se va a mover la nueva subclase, alguno de los


métodos de la superclase (o alguno de sus antecesores) no sea adecuado para los objetos
originados de la subclase, es posible reescribir el método en la subclase para que se adapte en su
funcionamiento a los objetos del tipo de la subclase.

La reescritura del método dentro de la subclase es lo que se conoce por sobreescritura de


métodos (overriding methods). Todo lo que se requiere en Java para poder sobreescribir un
método es utilizar el mismo nombre del método en la subclase, el mismo tipo de retorno y la misma
lista de argumentos de llamada, y en el cuerpo de ese método en la subclase proporcionar un
código diferente y específico para las acciones que vaya a realizar sobre objetos del tipo que
origina la nueva subclase.

Reglas de la sobrecarga y sobreescritura de métodos:

Ahora que hemos visto ambas formas de reescribir métodos, revisemos las reglas y diferencias
entre ambos tipos de reescritura:

Argumentos: En un método sobrecargado los argumentos deben de cambiar mientras que en un


método sobreescrito NO deben cambiar.

El tipo de retorno: En un método sobrecargado el tipo de retorno puede cambiar, en un método


sobreescrito NO puede cambiar, excepto por subtipos del tipo declarado originalmente.

Excepciones: En un método sobrecargado las excepciones pueden cambiar, en un método


sobreescrito pueden reducirse o eliminarse pero NO deben de arrojarse excepciones nuevas o
más amplias.

Acceso: En un método sobrecargado puede cambiar, en un método sobreescrito el acceso NO


debe de hacerse más restrictivo (puede ser menos restrictivo).

Página: 27
Programación Orientada a Objetos
Al invocar: En un método sobrecargado los argumentos son los que determinan qué método es el
que se invocará, en un método sobreescrito el tipo de objeto determina qué método es elegido.

4.3 Sobrecarga de operadores.

En algunos lenguajes de programación se permite sobrecargar operadores para utilizarlos en


clases propias. Esto hace posible que utilizar un tipo de datos definido por el usuario parezca tan
natural y lógico como utilizar un tipo de datos fundamental. Por ejemplo, podría crear un nuevo tipo
de datos denominado “numeroComplejo” para representar un número complejo y proporcionar
métodos que realicen operaciones matemáticas en esos números mediante operadores aritméticos
estándar, como utilizar el operador + para sumar dos números complejos.

En Java no se puede realizar la sobrecarga de operadores, lo único que maneja de sobrecarga es


la definición del operador + , que ademas de realizar la operación de suma entre números, Java lo
usa para la concatenación de cadenas.
El siguiente ejemplo utiliza + para concatenar la cadena "Contados ", con el valor de la variable
contador y la cadena " caracteres.":

Unidad 5: Herencia.
5.1 Introducción a la herencia.

La herencia es otra de las propiedades relativas a la POO, consiste en la propagación de los


atributos y las operaciones a través de distintas subclases definidas a partir de una clase común.

Introduce, por tanto, una posibilidad de refinamiento sucesivo del concepto de clase. Nos permite
definir una clase principal y, a través de sucesivas aproximaciones, cualquier característica de los
objetos. A partir de ahora definiremos como subclases todas aquellas clases obtenidas mediante
refinamiento de una (o varias) clases principales.
La herencia nos permite crear estructuras jerárquicas de clases donde es posible la creación de
subclases que incluyan nuevas propiedades y atributos. Estas subclases admiten la definición de
nuevos atributos, así como crear, modificar o inhabilitar propiedades.

Para pensarlo de manera más fácil podemos abstraernos al siguiente ejemplo.

Pensemos en los distintos submodelos asociados a un modelo básico de automóvil. A partir de


este modelo básico, los fabricantes introducen distintas características (aire acondicionado, ABS,
distintas tapicerías , acabados, etc.) que crean subclases. Todas estas subclases tienen en común
la estructura básica (chasis , dirección , etc.) o varían sólo en algunos de sus componentes.

Asociemos a este tipo básico una clase cuyos atributos representen las piezas que componen el
coche. Las subclases aportarán sus propios atributos (en el caso de vehículos con aire
acondicionado, todos aquellas piezas que lo componen), permitiendo la definición de todos los
posibles modelos.

Página: 28
Programación Orientada a Objetos

Además, es posible que una subclase herede atributos y propiedades de más de una clase. Este
proceso se denomina herencia.

La herencia permite que los objetos pueden compartir datos y comportamientos a través de las
diferentes subclases, sin incurrir en redundancia. Más importante que el ahorro de código, es la
claridad que aporta al identificar que las distintas operaciones sobre los objetos son en realidad
una misma cosa.

Como notación, se puede indicar que la clase original que se usa para heredar a una clase nueva
se denomina clase padre, clase base, ascendiente o superclase. La clase que recibe la herencia se
llama clase derivada, hija, subclase o descendiente.

5.2 Herencia simple.

Una clase puede heredar de otra atributos o métodos. Se dice que la primera es subclase de la
segunda, o que la segunda es la superclase. Para definir una subclase se escribe:

class Nombre_de_la_subclase extends Nombre_de_la_superclase

Evidentemente sólo se heredan los atributos y métodos cuyos modificadores lo permitan (no los
private ).

Ejemplo:

class Segmento
{
public double x1,y1,x2,y2;
double longitud()
{
return Math.sqrt((x1-x2)*(x1-x2) +(y1-y2)*(y1-y2));
}
}
class SegmentoOrientado extends Segmento
{
public int orient;
public void ImprimeOrientacion()
{
switch(orient){

Página: 29
Programación Orientada a Objetos
case 1:
System.out.println("Está orientado de ("+x1+","+y1+") a "+x2+","+y2+")");
return;
case -1:
System.out.println("Está orientado de ("+x2+","+y2+") a("+x1+","+y1+")");
return;
default:
System.out.println("Todavía no tiene una orientación válida");
return;
}
}
}

Al ejecutar:

public class Proto


{
public static void main(String[] args)
{
SegmentoOrientado s=new SegmentoOrientado();
s.x1=1.0; s.y1=5.0; s.x2=4.0; s.y2=9.0;
s.orient=-1;
System.out.println("El segmento conecta ("+s.x1+","+s.y1+") con
("+s.x2+","+s.y2+")");
System.out.println("Su longitud es s "+s.longitud());
s.ImprimeOrientacion();
}
}

se obtiene, como cabría esperar:

El segmento conecta (1.0,5.0) con (4.0,9.0)


Su longitud es s 5.0
Está orientado de (4.0,9.0) a (1.0,5.0)"

Lo que muestra que efectivamente los métodos y atributos de Segmento se han añadido a los de la
subclase SegmentoOrientado sin necesidad de definirlos de nuevo.

Constructores en clases derivadas.

Una subclase puede tener su propio constructor que incluso puede sobrecargar al de su
superclase (incluso con el mismo tipo y número de parámetros). Normalmente un constructor de
una subclase empleará el de la superclase y para invocarlo se emplea la sentencia super(). Debe
aparecer necesariamente como primera instrucción en el constructor. Por ejemplo, incluyendo en
Segmento y en SegmentoOrientado los constructores:

Segmento(double a,double b,double c,double d) SegmentoOrientado(double a,double b,double c,double


{ d, int e)
x1=a; y1=b; x2=c; y2=d; {
} super(a,b,c,d);
orient=e;
}

entonces se podría haber hecho en main la declaración.

Página: 30
Programación Orientada a Objetos
SegmentoOrientado s=new SegmentoOrientado(11.0,2.0,3.0,7.0,-1);

Es importante tener en cuenta que si la sentencia super no aparece explícitamente en el


constructor de una subclase o si no hay constructor, entonces el compilador inserta super() . Por
ejemplo, no serían válidas la siguiente definición y el siguiente constructor de Segmento si no hay
constructor en SegmentoOrientado, porque al ejecutarse super() en la clase SegmentoOrientado
se llamaría a un constructor sin parámetros que no existe.
Como ya se ha mencionado, se puede redefinir (sobrecargar) un constructor de una clase en
algunas de sus subclases, incluso permitiendo el mismo tipo y número de parámetros. Así sería un
constructor válido en SegmentoOrientado :

SegmentoOrientado(double a,double b,double c,double d)


{
super(a,b,c,d);
x1=c; y1=d; x2=a; y2=b;
}

y también suprimiendo super(a,b,c,d) si se añade a Segmento el constructor vacío Segmento(){}.

Este tipo de sobrecargas no es particular de los constructores sino que se extiende al resto de los
métodos. Por ejemplo, se podría incluir en SegmentoOrientado una de las siguientes
declaraciones:

Double longitud() double longitud()


{ {
return orient*Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)); return orient*super.longitud();
} }

En la segunda se emplea super de una manera distinta que en los constructores y no


necesariamente como primera sentencia, pero de nuevo se refiere al método homónimo en la
superclase.

Si un método se va a redefinir en todas las subclases entonces no merece la pena que tenga
cuerpo (instrucciones). En ese caso se le declara como abstracto por medio del modificador
abstract . Esto es como los prototipos de funciones en C, una señal que indica que se va a definir
una función de cierto tipo pero sin darla explícitamente. Si una clase tiene uno o más métodos
abstractos, debe también llevar el modificador abstract. Por ejemplo, antes, si no se usase
super.longitud(), se podría haber sustituido la definición del método longitud() en Segmento por

abstract double longitud();

en cuyo caso la clase Segmento se debería declara como abstract class Segmento .

El modificador final aplicado a clases, métodos o atributos, impide en cierto modo la herencia.
Concretamente,
final class Nombre_de_clase{....}

prohíbe que se puedan crear subclases de Nombre_de_clase , mientras que

final Metodo(...){...};

impide que en alguna subclase se pueda redefinir (sobrecargar) este método. En un atributo, el

Página: 31
Programación Orientada a Objetos
modificador final impide que se altere su valor en una subclase, por ejemplo, class Madre{ final int
n;} class Hija extends Madre{ int m=++n;} causaría un error.

En java todavía no existe el tipo de datos constante, pero se puede emular empleando los
modificadores static final. Con static se asegura que no habrá copias diferentes suyas en diferentes
objetos (sólo una copia en la memoria), y con final se impide que se pueda modificar en ninguna
subclase.

5.3 Herencia múltiple.

A diferencia de lo que ocurre en C++ no hay herencia múltiple, es decir, cada clase tiene a lo más
una superclase.

5.4 Parte protegida.

Las variables y los métodos que se heredan vienen controlados por los modificadores de
visibilidad. Los miembros con visibilidad “public” se heredan y los que tienen visibilidad “private” no
se heredan. Si éstas fueran las únicas posibilidades, la herencia serviría de poco, pues todos los
miembros (datos y métodos) heredables tendrían que ser públicos. En estas condiciones, al tener
variables públicas perderíamos las ventajas de la encapsulación. Para conseguir que el
comportamiento para clases foráneas sea privado pero para las clases derivadas sea público se
usa el modificador “protected”. El modificador “protected” establece un nivel intermedio de
protección entre un acceso “public” y uno “private”. Los miembros de una superclase etiquetados
como “protected” son accesibles para las subclases y las otras clases del mismo paquete (ver la
siguiente tabla).

Modificador Clases e Interfaces Métodos y variables


default (no Visible en su paquete. Accesible desde cualquier clase en el mismo
modificador) paquete que su clase, pero no sus sublases
que no estén en el mismo paquete.
public Visible en cualquier lugar. Accesible desde cualquier lugar.
protected No se aplica. Accesible desde cualquier clase en el mismo
paquete que su clase o desde cualquier
subclase aún en paquetes distintos
private No se aplica. Accesible sólo desde la propia clase.

5.5 Redefinición de los miembros de las clases derivadas.

Suponiendo que en una clase hija declaramos una variable o se define un método con el mismo
nombre que una variable o método heredadas, ¿qué ocurre en este caso?. Se explica en primera
instancia para las variables y posteriormente para los métodos.

Variables: Cuando una variable se hereda y en la clase hija se declara una nueva variable con el
mismo identificador, la nueva variable es la que se usa. Lo que quiere decir que se ha creado una
nueva variable. Lógicamente, cuando desde la clase hija se use el identificador de la variable se
usa la definida en la clase hija. La variable de clase padre sigue existiendo, y se podría acceder a
ella pero indicando explícitamente que nos referimos a la clase padre con el prefijo “super”.

Ejemplo:

class Padre
{
protected int dato=10;
} // fin de la clase Padre

Página: 32
Programación Orientada a Objetos
class Hija extends Padre {
private int dato;
public Hija(int dato)
{
this.dato=dato;
}
public void imprimeHija()
{
System.out.println(“Valor de dato en Hija: “+dato);
}
public void imprimePadre()
{
System.out.println(“Valor de dato en Padre: “+super.dato);
}
}
class Herencia
{
public static void main(String [ ] args)
{
Hija ejemplo=new Hija(5);
Ejemplo.imprimeHija();
Ejemplo.imprimePadre();
}
}

El resultado del programa será:

Valor de dato en Hija: 5


Valor de dato en Padre: 10

Métodos: Un comportamiento similar ocurre cuando se define un método con el mismo nombre y
parámetros que otro que se hereda. En este caso, el método al que se accede a través de los
objetos de la clase hija es el definido en la clase hija. Se dice que el nuevo método sobrescribe el
método heredado.

Es importante distinguir la sobrescritura de la sobrecarga:

 Sobrecarga: El mismo identificador con diferentes parámetros.


 Sobrescritura: Mismo identificador y parámetros (número de parámetros y tipo de datos de
éstos).

Al identificador del método y lista de parámetros con sus tipos, se le conoce como firma del
método.

La sobrescritura está asociada con la herencia. Si se pretende sobrescribir un método heredado


pero se usa una firma distinta (por ejemplo, añadiendo un parámetro más) no se tendrá
sobrescritura y los dos métodos sobrecargados son accesibles desde los objetos creados de la
clase hija. La finalidad de la sobrescritura es que se pueda usar sólo un identificador y un único
conjunto de parámetros para diferentes clase relacionadas por herencia. El código de cada método
sobrescrito es diferente en la clase hija que en la clase padre.

Ejemplo:

class Padre {
protected int n=10;
public void imprimir()

Página: 33
Programación Orientada a Objetos
{
System.out.println(“En clase Padre, n= “+n);
}
} // fin de la clase Padre
class Hija extends Padre {
protected int m=20;
public void imprimir() // método sobrescrito
{
System.out.println(“En clase Hija, m= “+m);
}
}
class Herencia
{
public static void main(String [ ] args)
{
Padre x=new Padre();
Hija y=new Hija();
x.imprimir();
y.imprimir();
}
}

En la clase Hija se sobrescribe el método imprimir(), por lo que el código de los objetos de esta
clase usan el código del método escrito para está y el resultado será:

En clase Padre, n= 10
En clase Hija, m= 20

Unidad 6: Polimorfismo y reutilización


6.1 Concepto del polimorfismo.

El polimorfismo es la capacidad de una entidad determinada de conectarse a objetos de varias


clases relacionadas por herencia. Esta capacidad tiene sentido y sirve para algo si esos distintos
objetos de diferentes clases tienen métodos sobresctitos, que son los que se van a invocar a través
de la referencia polimórfica. El polimorfismo implica la posibilidad de usar una sola referencia para
referirse a varios objetos relacionados jerárquicamente. Generalmente, en una clase padre se
declara un método que es el que se va a usar polimórficamente. Entonces, ese mismo método se
redefine en clases que son derivadas de la clase padre, es decir, el método se sobrescribe. Así,
existirán métodos en las clases descendientes con la misma firma que el de la clase padre. Si un
objeto de la clase padre se declara en un programa, la definición del método original que se
encuentra en la clase padre será la que se invoque cuando se llame al método. Sin embargo, si un
objeto de una clase hija se asigna posteriormente a la referencia de la clase padre, entonces se
invoca la definición de método para la clase hija.

En términos prácticos, el polimorfismo implica que una referencia puede referir a cualquier objeto
de su clase o a un objeto de una clase descendiente de la primera.

Ejemplo:

class Padre {
void onToy() {
System.out.println("Estoy en el método de Padre");
}
} // fin clase padre

Página: 34
Programación Orientada a Objetos
class HijaPrimera extends Padre {
void onToy()
{
System.out.println("Estoy en el método de HijaPrimera");
}
} // fin clase HijaPrimera

class HijaSegunda extends Padre {


void onToy()
{
System.out.println("Estoy en el método de HijaSegunda");
}
} // fin clase HijaSegunda

public class Herencia {


public static void main(String[] args)
{ Padre padre = new Padre();
HijaPrimera hprimera=new HijaPrimera();
HijaSegunda hsegunda=new HijaSegunda();

Padre polimorfico;
polimorfico=padre; // hace referencia a un objeto padre
polimorfico.onToy();

polimorfico=hprimera; // hace referencia a un objeto hprimera


polimorfico.onToy();

polimorfico=hsegunda; // hace referencia a un objeto hsegunda


polimorfico.onToy();
}
}

Al ejecutar el programa se tendría la siguiente salida:

Estoy en el método de Padre


Estoy en el método de HijaPrimera
Estoy en el método de HijaSegunda

Otro Ejemplo:

class Elipse {
double ejeX, ejeY;
Elipse(double a,double b)
{ ejeX=a;
ejeY=b;
}
double Area()
{ return Math.PI*ejeX*ejeY;
}
String prueba()
{
return "Método de class Elipse";
}
}

Página: 35
Programación Orientada a Objetos
class Circulo extends Elipse {
Circulo(double r)
{
super(r,r);
}
double Longitud()
{
return 2*Math.PI*ejeX;
}
String prueba()
{
return "Método de class Circulo";
}
}

public class Herencia {


public static void main(String[] args) {
Elipse obj1=new Circulo(10);
Circulo obj2=new Circulo(10);
Elipse obj3=new Elipse(10,1);

// Sentencias (ver tabla)


}
}

En la tabla siguiente se incluye algunas sentencias y sus resultados:

Sentencias Resultado y Explicación


System.out.println(obj1.Longitud()); Error al compilar. Desde Elipse no se ve Longitud().
System.out.println(obj1.prueba()); Método de class Circulo. Al estar sobrecargado, busca el método
de la subclase.
System.out.println(obj1.Area()); 10*10*PI
System.out.println(obj2.Longitud()); 20*PI
System.out.println(obj2.prueba()); Método de class Circulo
System.out.println(obj2.Area()); 100*PI La subclase hereda los métodos de la superclase.
System.out.println(obj3.Area()); 10*PI
System.out.println(((Circulo)obj1).Longitud()); 20*PI Para aplicar el método Longitud a Elipse hay que
transformarlo en Circulo.

6.2 Clases abstractas.

6.2.1 Definición.

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, Poligono, 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 (de su clase). 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.

Página: 36
Programación Orientada a Objetos

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.

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();

Página: 37
Programación Orientada a Objetos
La invocación al método dibujar se 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 dibujar de la clase Circulo.

Ejemplo 2:

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("Guitarra Guzla.tocar()");
}
public void afinar()
{
System.out.println("Guzla.afinar()");
}
}

Página: 38
Programación Orientada a Objetos
// Un tipo de Guitarra
class Ukelele extends Guitarra {
public void tocar()
{
System.out.println("Guitarra Ukelele.tocar()");
}
public String tipo()
{
return "Ukelele";
}
}
public class Musica
{ // 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++)
{
System.out.println("Es un instrumento de tipo: "+e[i].tipo());
afinar(e[i]);
}
}
public static void main(String[] args)
{ // Declarar un Arreglo SIN INSTANCIAS es valido 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);
orquesta[3].afinar();
}
}

La salida sería:

Es un instrumento de tipo: Guitarra


Guitarra.tocar()
Es un instrumento de tipo: Piano
Piano.tocar()
Es un instrumento de tipo: Saxofon
Saxofon.tocar()
Es un instrumento de tipo: Guitarra
Guitarra Guzla.tocar()
Es un instrumento de tipo: Ukelele
Guitarra Ukelele.tocar()
Guzla.afinar()

Página: 39
Programación Orientada a Objetos
6.3 Definición de una interfaz.

El concepto de Interface lleva un paso más adelante la idea de las clases abstractas. En Java una
interface es una clase abstracta pura, es decir una clase donde todos los métodos son abstractos
(no se implementa ninguno). Permite al diseñador de clases establecer la forma de una clase
(nombres de métodos, listas de argumentos y tipos de retorno, pero no bloques de código). Una
interface puede también contener datos miembro, pero estos son siempre static y final. Una
interface sirve para establecer un 'protocolo' entre clases.

Para crear una interface, se utiliza la palabra clave interface en lugar de class. La interface puede
definirse public o sin modificador de acceso, y tiene el mismo significado que para las clases.
Todos los métodos que declara una interface son siempre public.

Para indicar que una clase implementa los métodos de una interface se utiliza la palabra clave
implements. El compilador se encargará de verificar que la clase efectivamente declare e
implemente todos los métodos de la interface. 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 Figura
{
int area();
}

y una clase que lo implementa:

public class Cuadrado implements Figura


{

int lado;
public Cuadrado (int ladoParametro)
{
lado = ladoParametro;
}
public int area()
{
return lado*lado;
}
public int perimetro() // Método no implementado en la interface
{
return lado*4;
}
}

Página: 40
Programación Orientada a Objetos
Uso de la clase:

public class PruebaInterfaz {


public static void main(String args[])
{
Figura figura=new Cuadrado (5); // Haciendo referencia desde Figura
System.out.println(figura.area());
System.out.println("Perimetro= "+figura.perimetro()); // Error Método no

Cuadrado c=new Cuadrado (4); // Haciendo referencia desde Cuadrado


System.out.println("Area= "+c.area());
System.out.println("Perimetro= "+c.perimetro()); // Si acetado
}
}

Al utilizar implements para la interface es como si se hiciese una acción de copiar-y-pegar del
código de la interface, con lo cual no se hereda nada, solamente se pueden usar los métodos.

La ventaja principal del uso de interfaces es que una clase interface puede ser implementada por
cualquier número de clases, permitiendo a cada clase compartir la interfaz de programación sin
tener que ser consciente de la implementación que hagan las otras clases que implementen el
interface.

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. Por
ejemplo:

InstrumentoMusical instrumento = new Guitarra();


instrumento.play();
System.out.prinln(instrumento.tipoInstrumento());

InstrumentoMusical i2 = new InstrumentoMusical(); //error.No se puede instanciar

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. Por ejemplo:

public interface Meses {


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

Página: 41
Programación Orientada a Objetos
Esto puede usarse simplemente:

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

Diferencias entre un interface y una clase abstracta

Una interface es simplemente una lista de métodos no implementados, además puede incluir la
declaración de constantes. Una clase abstracta puede incluir métodos implementados y no
implementados o abstractos, miembros dato constantes y otros no constantes.

6.4 Definición y creación de paquetes/librería.

Los paquetes son el mecanismo por el que Java permite agrupar clases, interfaces, excepciones y
constantes. De esta forma, se agrupan conjuntos de estructuras de datos y de clases con algún
tipo de relación en común.

Con la idea de mantener la reutilización y facilidad de uso de los paquetes desarrollados es


conveniente que las clases e interfaces contenidas en los mismos tengan cierta relación funcional.
De esta manera los desarrolladores ya tendrán una idea de lo que están buscando y fácilmente
sabrán qué pueden encontrar dentro de un paquete.

Creación de un paquete

a.) Declaración: Para declarar un paquete se utiliza la sentencia package seguida del nombre del
paquete que estemos creando:

package NombrePaquete;

La estructura que ha de seguir un fichero fuente en Java es:

 Una única sentencia de paquete (opcional).


 Las sentencias de importación deseadas (opcional).
 La declaración de una (y sólo una) clase pública (public).
 Las clases privadas del paquete (opcional).

Por lo tanto la sentencia de declaración de paquete ha de ser la primera en un archivo fuente Java.

b.) Subpaquetes: Cada paquete puede tener a su vez paquetes con contenidos parecidos, de
forma que un programador probablemente estará interesado en organizar sus paquetes de forma
jerárquica. Para eso se definen los subpaquetes.

Para crear un subpaquete bastará con almacenar el paquete hijo en un directorio


Paquete/Subpaquete.

Así una clase dentro de un subpaquete como Paquete.Subpaquete.clase estará codificada en el


fichero Paquete/Subpaquete.java.

c.) Uso de un paquete: Con el fin de importar paquetes ya desarrollados se utiliza la sentencia
import seguida del nombre de paquete o paquetes a importar.

Se pueden importar todos los elementos de un paquete o sólo algunos.

Para importar todas las clases e interfaces de un paquete se utiliza el metacaracter *:

import PaquetePrueba.*;

Página: 42
Programación Orientada a Objetos
También existe la posibilidad de que se deseen importar sólo algunas de las clases de un cierto
paquete o subpaquete:

import Paquete.Subpaquete1.Subpaquete2.Clase1;

Para acceder a los elementos de un paquete, no es necesario importar explícitamente el paquete


en que aparecen, sino que basta con referenciar el elemento tras una especificación completa de
la ruta de paquetes y subpaquetes en que se encuentra.

Paquete.Subpaquetes1.Subpaquete2.Clase_o_Interfaz.elemento

En la API de Java se incluyen un conjunto de paquetes ya desarrollados que se pueden incluir en


cualquier aplicación Java que se desarrolle.

d.) Ámbito de los elementos de un paquete: Al introducir el concepto de paquete, surge la duda
de cómo proteger los elementos de una clase, qué visibilidad presentan respecto al resto de
elementos del paquete, respecto a los de otros paquetes...

Ya en la herencia se vieron los identificadores de visibilidad public (visible a todas las clases),
private (no visible más que para la propia clase), y protected (visible a clases hijas).

Por defecto se considera los elementos (clases, variables y métodos) de un mismo paquete como
visibles entre ellos.
Situación del elemento private sin modificador protected public
En la misma clase Sí Sí Sí Sí
En una clase en el mismo paquete No Sí Sí Sí
En una clase hija en otro paquete No No Sí Sí
En una clase no hija en otro paquete No No No Sí
Tabla: Visibilidad dentro de un paquete
Unidad 7 Excepciones.
7.1 Definición.

El término excepción se utiliza cuando algo salió mal, es decir, cuando ocurrió un error. Como
seguramente usted sabe, existe un gran número de situaciones por la que el software puede fallar,
pero el software de buena calidad debe de enfrentar esos problemas de manera satisfactoria.

En Java una excepción es un objeto que define una situación inusual o un error.

Cuando se crean sistemas de software y de hardware, la mayoría se encuentra en forma de


elementos empacados, por ejemplo, tarjetas de circuitos, clases y métodos de Java. Para
simplificar el proceso de diseño, es esencial tratar estos elementos como encapsulados, de tal
forma que no es necesario preocuparse de cómo funcionan internamente, pero si es necesario que
informen a nuestro software sobre las situaciones de error.

Los sistemas complejos constan de una jerarquía de métodos y algunas excepciones pueden
manejarse de forma local en el método que ocurren, y en algunos otros casos pueden pasarse a
métodos de nivel más alto, por supuesto todo depende de la naturaleza del error. Así, existen
varias categorías de errores, las cuales pueden ser tratadas en distintos lugares.

Así, un programa puede estar diseñado para procesar las excepciones en tres formas distintas:

Página: 43
Programación Orientada a Objetos
 No manejarla
 Manejar la excepción cuando ocurre
 Manejar la excepción en algún otro punto del programa

Un ejemplo de un programa que no maneja excepciones es cuando al ejecutarse el programa se


detiene y marca un error, de tal forma que la última sentencia nunca será ejecutada.

En general el código para manejar errores podría ser:

si algoSaleMal
encargarse del problema
otro
situación normal

Sin embargo cuando se realizan más acciones el código:

hacerA ( )
hacerB ( )
hacerC ( )

se convierte en:

hacerA ( )
si A salió mal
encargarse del problema cuando A salió mal
otro
comienza
hacerB ( )
si B salió mal
encargarse del problema cuando B salió mal
otro
comienza
hacerC ( )
si C salió mal
encargarse del problema cuando C salió mal
termina
termina

Ya que no se espera que los casos de error ocurran muy a menudo, Java ofrece apegarnos a la
codificación para el caso normal, y manejar las excepciones en un área separada del programa.

Las excepciones en Java son objetos, por lo que es necesario utilizar el operador new para crear
una instancia de una clase apropiada de excepciones.

Para indicar que ha ocurrido una excepción, se dice que se ha lanzado y cuando se detectan en
cualquier otra parte, se dice que son atrapadas. Java tiene las palabras clave:

 throws
 throw
 try
 catch

Las excepciones predefinidas y su jerarquía de clases es la que se muestra en la figura:

Página: 44
Programación Orientada a Objetos

Los nombres de las excepciones indican la condición de error que representan.

Las siguientes son las excepciones predefinidas más frecuentes que se pueden encontrar:

ArithmeticException: Las excepciones aritméticas son típicamente el resultado de una división


por 0:

int i = 12 / 0;

NullPointerException: Se produce cuando se intenta acceder a una variable o método antes de


ser definido.

IncompatibleClassChangeException: El intento de cambiar una clase afectada por referencias


en otros objetos, específicamente cuando esos objetos todavía no han sido recompilados.

ClassCastException: El intento de convertir un objeto a otra clase que no es válida.

Página: 45
Programación Orientada a Objetos
NegativeArraySizeException: Puede ocurrir si hay un error aritmético al intentar cambiar el
tamaño de un array.

OutOfMemoryException: ¡No debería producirse nunca! El intento de crear un objeto con el


operador new ha fallado por falta de memoria. Y siempre tendría que haber memoria suficiente
porque el garbage collector se encarga de proporcionarla al ir liberando objetos que no se usan y
devolviendo memoria al sistema.

NoClassDefFoundException: Se referenció una clase que el sistema es incapaz de encontrar.

ArrayIndexOutOfBoundsException: Es la excepción que más frecuentemente se produce. Se


genera al intentar acceder a un elemento de un array más allá de los límites definidos inicialmente
para ese array.

UnsatisfiedLinkException: Se hizo el intento de acceder a un método nativo que no existe.

InternalException: Este error se reserva para eventos que no deberían ocurrir. Por definición, el
usuario nunca debería ver este error y esta excepción no debería lanzarse.

También podemos lanzar nuestras propias excepciones, extendiendo la clase System.exception.

Por ejemplo, consideremos un programa cliente/servidor. El código cliente se intenta conectar al


servidor, y durante 5 segundos se espera a que conteste el servidor. Si el servidor no responde, el
servidor lanzaría la excepción de time-out:

class ServerTimeOutException extends Exception


{
}

public void conectame( String nombreServidor ) throws Exception


{
int exito;
int puerto = 80;
exito = open( nombreServidor,puerto );
if( exito == -1 )
throw ServerTimeOutException;
}

Si se quieren capturar las propias excepciones, se deberá utilizar la sentencia //try//:

public void encuentraServidor()


{
...
try
{
conectame( servidorDefecto );
}
catch( ServerTimeOutException e )
{
g.drawString("Time-out del Servidor, intentando alternativa",5,5 );
conectame( servidorAlterno );

}
...
}

Página: 46
Programación Orientada a Objetos
Cualquier método que lance una excepción también debe capturarla, o declararla como parte de la
interface del método. Cabe preguntarse entonces, el porqué de lanzar una excepción si hay que
capturarla en el mismo método. La respuesta es que las excepciones no simplifican el trabajo del
control de errores. Tienen la ventaja de que se puede tener muy localizado el control de errores y
no tenemos que controlar millones de valores de retorno, pero no van más allá.
A continuación se presenta un ejemplo muy simple:

import java.util.*;
public class Main {
public static void main(String[] args)
{
Scanner t=new Scanner(System.in);
int a,s=0;
double b;
boolean x=true;
do
{
try
{
x=false;
System.out.println("Teclea un entero");
a=t.nextInt();
System.out.println("Teclea un double");
b=t.nextDouble();
if(s==0)
{
s++;
a=a/0;
}
}
catch (InputMismatchException e)
{
System.out.println("Ya valio....no es de su tipo-->>"+e.toString());
x=true;
t.nextLine();
}
catch (ArithmeticException e)
{
System.out.println("Ya valió....capturó div. Entre cero-->>"+e.toString());
x=true;
t.nextLine();
}
finally
{
System.out.println("Hace el finally ");
}
}while(x);
}
}

Otro ejemplo:

/**
* Aplicación para duplicar un entero dado por el usuario Sin manejo de excepciones
*/

Página: 47
Programación Orientada a Objetos
public class ApliD1
{
public static void main()
{
// crear objetos
System.out.print ("Dame un entero: ");
int entrada = Teclado.readInt ();
System.out.print("El número duplicado es: ");
int salida = 2*entrada;
}
}

En caso de no introducir un entero se produce el siguiente error:

Exception in thread "main" java.util.InputMismatchException

La estructura de control try-catch tiene la siguiente forma:


try
{
enunciados
}
catch (AlgunaExcepciónA e)
{
enunciados
}
catch (AlgunaExcepciónB e)
{
enunciados
}
.....
finally
{
enunciados
}

y Java tratará de ejecutar el bloque de enunciados dentro del try, si no ocurre ninguna excepción,
se ignorará el bloque de enunciados dentro del catch y si se produce una excepción del bloque try,
entonces se puede ejecutar el bloque catch especificándo el tipo de excepción que se quiere tratar.
Si ocurre un tipo que no está especifícado entonces no se ejecutará el bloque catch.

Independientemente de si ocurre una excepción o no, se ejecutarán los enunciados que se


encuentran en el bloque finally, el cual es oopcional incluirlo.
Recuerde que las variables declaradas dentro de un bloque existen dentro de él y no fuera de él,
de tal forma que si declara una variable dentro del bloque try, no podrá utilizarla dentro del bloque
catch. Por supuesto que una solución a este problema es declarar dicha variable fuera y antes de
estos bloques.
Como ya se mencionó, las excepciones son el mecanismo por el cual pueden controlarse en un
programa Java las condiciones de error que se producen. Estas condiciones de error pueden ser
errores en la lógica del programa como un índice de un array fuera de su rango, una división por
cero o errores disparados por los propios objetos que denuncian algún tipo de estado no previsto, o
condición que no pueden manejar.

La idea general es que cuando un objeto encuentra una condición que no sabe manejar crea y
dispara una excepción que deberá ser capturada por el que le llamó o por alguien más arriba en la
pila de llamadas. Las excepciones son objetos que contienen información del error que se ha
producido y que heredan de la clase Throwable o de la clase Exception. Si nadie captura la

Página: 48
Programación Orientada a Objetos
excepción interviene un manejador por defecto que normalmente imprime información que ayuda a
encontrar quién produjo la excepción.

Existen dos categorías de excepciones:

Excepciones verificadas: El compilador obliga a verificarlas. Son todas las que son lanzadas
explicitamente por objetos de usuario.

Excepciones no verificadas: El compilador no obliga a su verificación. Son excepciones como


divisiones por cero, excepciones de puntero nulo, o índices fuera de rango.

Generación de excepciones

Supongamos que tenemos una clase Empresa que tiene un array de objetos Empleado. En esta
clase podríamos tener métodos para contratar un Empleado (añadir un nuevo objeto al array),
despedirlo (quitarlo del array) u obtener el nombre a partir del número de empleado. La clase
podría ser algo así como lo siguiente:

public class Empresa


{
String nombre;
Empleado [] listaEmpleados;
int totalEmpleados = 0;
...
Empresa(String n, int maxEmp)
{
nombre = n;
listaEmpleados = new Empleado [maxEmp];
}
...
void nuevoEmpleado(String nombre, int sueldo)
{
if (totalEmpleados < listaEmpleados.length )
{
listaEmpleados[totalEmpleados++] = new Empleado(nombre,sueldo);
}
}
}

Obsérvese en el método nuevoEmpleado que se comprueba que hay sitio en el array para
almacenar la referencia al nuevo empleado. Si lo hay se crea el objeto. Pero si no lo hay el método
no hace nada más. No da ninguna indicación de si la operación ha tenido éxito o no. Se podría
hacer una modificación para que, por ejemplo el método devolviera un valor booleano true si la
operación se ha completado con éxito y false si ha habido algún problema.

Otra posibilidad es generar una excepción verificada (una excepción no verificada se produciría si
no se comprobara si el nuevo empleado va a caber o no en el array). Vamos a ver como se haría
esto.

Las excepciones son clases, que heredan de la clase genérica Exception. Es necesario por tanto
asignar un nombre a nuestra excepción. Se suelen asignar nombres que den alguna idea del tipo
de error que controlan. En nuestro ejemplo le vamos a llamar CapacidadEmpresaExcedida.

Para que un método lance una excepción:


Debe declarar el tipo de excepción que lanza con la cláusula throws, en su declaración.

Página: 49
Programación Orientada a Objetos
Debe lanzar la excepción, en el punto del código adecuado con la sentencia throw.

En nuestro ejemplo:

void nuevoEmpleado(String nombre, int sueldo) throws CapacidadEmpresaExcedida


{
if (totalEmpleados < listaEmpleados.length)
{
listaEmpleados[totalEmpleados++] = new Empleado(nombre,sueldo);
}
else
throw new CapacidadEmpresaExcedida(nombre);
}

Además, necesitamos escribir la clase CapacidadEmpresaExcedida. Sería algo como:

public class CapacidadEmpresaExcedida extends Exception {


CapacidadEmpresaExcedida(String nombre)
{
super("No es posible añadir el empleado " + nombre);
}
...
}

La sentencia throw crea un objeto de la clase CapacidadEmpresaExcedida. El constructor tiene un


argumento (el nombre del empleado). El constructor simplemente llama al constructor de la
superclase pasándole como argumento un texto explicativo del error ( y el nombre del empleado
que no se ha podido añadir).

La clase de la excepción puede declarar otros métodos o guardar datos de depuración que se
consideren oportunos. El único requisito es que extienda la clase Exception. Consultar la
documentación del API para ver una descripción completa de la clase Exception.

De esta forma se pueden construir métodos que generen excepciones.

Captura de excepciones

Con la primera versión del método nuevoEmpleado (sin excepción) se invocaría este método de la
siguiente forma:

Empresa em = new Empresa("La Mundial");


em.nuevoEmpleado("Adán Primero",500);

Si se utilizara este formato en el segundo caso (con excepción) el compilador produciría un error
indicando que no se ha capturado la excepción verificada lanzada por el método nuevoEmpleado.
Para capturar la excepción es utiliza la construcción try / catch, de la siguiente forma:

Empresa em = new Empresa("La Mundial");


try
{
em.nuevoEmpleado("Adán Primero",500);
}
catch (CapacidadEmpresaExcedida exc)
{
System.out.println(exc.toString());
System.exit(1);
}

Página: 50
Programación Orientada a Objetos
Se encierra el código que puede lanzar la excepción en un bloque try / catch.

A continuación del catch se indica que tipo de excepción se va a capturar.

Después del catch se escribe el código que se ejecutará si se lanza la excepción.

Si no se lanza la excepción el bloque catch no se ejecuta.

El formato general del bloque try / catch es:

try
{
...
}
catch (Clase_Excepcion nombre) { . . .}
catch (Clase_Excepcion nombre) { . . .}
...

Observese que se puede capturar más de un tipo de excepción declarando más de una sentencia
catch. También se puede capturar una excepción genérica (clase Exception) que engloba a todas
las demás.

En ocasiones el código que llama a un método que dispara una excepción tampoco puede (o sabe)
manejar esa excepción. Si no sabe que hacer con ella puede de nuevo lanzarla hacia arriba en la
pila de llamada para que la gestione quien le llamo (que a su vez puede capturarla o reenviarla).

7.1.1 Propagación.

Propagar los Errores sobre la Pila de Llamadas

Una segunda ventaja de las exepciones es la posibilidad del propagar el error encontrado sobre la
pila de llamadas a métodos. Supongamos que el método leerFichero es el cuarto método en una
serie de llamadas a métodos anidadas realizadas por un programa principal: metodo1 llama a
metodo2, que llama a metodo3, que finalmente llama a leerFichero.

metodo1
{
call metodo2;
}
metodo2
{
call metodo3;
}
metodo3
{
call leerFichero;
}

Supongamos también que metodo1 es el único método interesado en el error que ocurre dentro de
leerFichero. Tradicionalmente las técnicas de notificación del error forzarían a metodo2 y
metodo3 a propagar el código de error devuelto por leerFichero sobre la pila de llamadas hasta
que el código de error llegue finalmente a metodo1 -- el único método que está interesado en él.

Como se vió anteriormente, el sistema de ejecución Java busca hacia atrás en la pila de llamadas
para encontrar cualquier método que esté interesado en manejar una excepción particular. Un
método Java puede "esquivar" cualquier excepción lanzada dentro de él, por lo tanto permite a los

Página: 51
Programación Orientada a Objetos
métodos que están por encima de él en la pila de llamadas poder capturarlo. Sólo los métodos
interesados en el error deben preocuparse de detectarlo.

metodo1
{
try
{
call metodo2;
}
catch (excepcion)
{
procesodelError;
}
}
metodo2 throws excepcion
{
call metodo3;
}
metodo3 throws excepcion {
call leerFichero;
}

Sin embargo, como se puede ver desde este pseudo-código, requiere cierto esfuerzo por parte de
los métodos centrales. Cualquier excepción chequeada que pueda ser lanzada dentro de un
método forma parte de la interface de programación público del método y debe ser especificado en
la clausula throws del método. Así el método informa a su llamador sobre las excepciones que
puede lanzar, para que el llamador pueda decidir concienzuda e inteligentemente qué hacer con
esa excepción.

7.2 Excepciones definidas por el usuarios.

En algunas circunstancias el programador querrá definir sus propias excepciones


El programador debe controlar cuándo deben activarse dichas excepciones
Se tratan igual que las predefinidas

Ejemplo:

class DivisionPorPar extends Exception


{
public DivisionPorPar()
{
super();
}
public DivisionPorPar(Strings)
{
super(s);
}
}

public double dividir(double x, double y) throws DivisionPorPar


{
if (y % 2 == 0)
throw new DivisionPorPar(“División entre un numero par”);
}

Página: 52
Programación Orientada a Objetos
public static void main(String[] args)
{
double resul;
System.out.println(“Tecleados numeros: “);

/* Recoger los números tecleados y guardarlos en las variables x, y */


try
resul=dividir(x, y);
catch (DivisionPorPar e)
{
y = y + 1;
resul= dividir(x,y);
}
...
}

Ejemplo

// “n” recibe cierto valor numérico


try
{
if (n % 2 == 1)
{ // n es impar
throw new ArithmeticException();
}
else// Código normal para valores pares
}
catch (ArithmeticException e)
{
// Tratar el caso especial de valores impares
}

Unidad 8: Flujos y archivos.


Un fichero es una colección de datos estructurados que se manejan como un todo. Los ficheros
están organizados en unidades elementales, todas con la misma estructura, que se denominan
registros y que a su vez constan de unidades denominadas campos.

8.1 Clasificación de acuerdo al tipo de acceso

El tipo de acceso a un fichero se refiere a la forma en que se puede acceder a un registro concreto,
es decir, la manera en la que se localiza un registro. Se consideran dos tipos de acceso a los
registros de un fichero:

Acceso Secuencial: Es aquel en el que se van recorriendo los registros de forma consecutiva. Los
registros se recorren en orden, y no se puede saltar de uno a otro que esté más de una posición
por encima o por debajo.

Acceso Directo: Es aquel en el que se puede acceder a cualquier registro desde cualquier otro, es
decir, saltando. No es forzoso recorrer el fichero secuencialmente.

8.2 Definición de Archivos de texto y archivos binarios.

Los ficheros se clasifican de acuerdo al formato usado para almacenar la información en ellos.

Página: 53
Programación Orientada a Objetos
Ficheros Binarios: Son aquellos cuyo contenido son secuencias de dígitos binarios. Los ficheros
binarios se diseñan para que se lean desde un programa, pero no pueden leerse directamente con
un editor de texto.

Ficheros de Texto: Son aquellos cuyos contenidos son una secuencia de caracteres y pueden,
por lo tanto, ser leídos con un editor de texto.

8.3 Operaciones básicas en archivos texto y binario.

Desde el punto de vista semántico las operaciones más comunes que se pueden realizar sobre un
fichero pueden clasificarse y considerarse independientemente de cualquier lenguaje. Una posible
clasificación de dichas operaciones es la siguiente:

Crear: Es la primera operación sobre un fichero, para ello es necesario saber el tipo del fichero y
qué organización necesitamos para el mismo. Todo fichero debe estar creado para antes de
empezar a operar en él por primera vez.

Abrir: La apertura es la primera operación sobre el fichero que ya existe. Esta operación consiste
en la conexión desde el programa al fichero para permitir su uso.

Consulta: Es el acceso a los registros de un fichero para recuperar y utilizar la información.

Modificación: Consiste en la alteración (actualización) de la información de algún o algunos


registros del fichero.

Inserción: Es la inclusión de un nuevo registro en el fichero.

Eliminación: Es la supresión de uno o mas registros del fichero.

Clausura (cerrar ficheros): Es la operación que corta el acceso (desconecta) el fichero del
programa.. Conviene cerrar los ficheros, principalmente porque si el programa acaba
anormalmente, el sistema no cerrará el fichero automáticamente. El fichero quedará abierto sin
tener ningún programa conectado a él y esto puede dañarlo o bloquearlo.

En Java todos los tipos de entradas y salidas se realizan por “stream” (corriente) y se habla de
corrientes de entrada o de salida. Un “stream” es un flujo de datos. No hay sentencias de entrada y
salida en Java. La entrada y salida se realiza usando bibliotecas de clases predefinidas. La
mayoría de las operaciones de entrada-salida están definidas en el paquete java.io de la API de
Java: Por estas razones Java considera los ficheros simplemente como flujos secuenciales de
bytes. Cada fichero termina con un “marcador de fichero” o bien en un número de byte específico
registrado en una estructura de datos administrativa mantenida por el sistema. Cuando se abre un
fichero se crea un objeto y se asocia un flujo a dicho objeto. A partir se ese momento, ese objeto
“es” el fichero para nuestro programa.

En el manejo de ficheros es especialmente útil capturar la excepciones que se puedan producir.


Esto es, el programa podrá detectar que se ha producido un problema y actuar en consecuencia
para resolverlo.

8.4 Ficheros Secuenciales de Texto

Para trabajar con ficheros secuenciales de texto se pueden usar las clases “BufferedReader” y
“PrintWriter” para lectura y escritura, respectivamente.
Escritura

Para escribir en un fichero de texto se puede usar la clase “PrintWriter”, que contiene los métodos
print y println. Estos métodos se comportan igual que los métodos System.out.println() o

Página: 54
Programación Orientada a Objetos
System.out.print() para la pantalla. Para abrir un fichero para escritura se debe crear un stream de
la clase “PrintWriter”, conectándolo al fichero de texto con el siguiente constructor:

PrintWriter nombreFlujoSalida;
nombreFlujoSalida=new PrintWriter(new FileWriter(nombre));

Donde “nombre” identifica el fichero a usar. También es válida la forma del constructor de la clase
FileWriter donde se acepta un objeto de la clase File.

Cabe destacar que el archivo “nombre”, si no existe lo crea, y si ya existe, lo borra y lo vuelve a
crear.

Lectura

Se debe usar la clase “BufferedReader” para leer un fichero de texto con el método readLine() y
read(). La sintaxis es la siguiente:

BufferedReader nombreFlujoEntrada;
nombreFlujoEntrada = new BufferedReader(new FileReader(nombre));

Donde “nombre”, de clase String, contiene el nombre físico del fichero.

El método readLinen() devuelve el valor “null” cuando llega al final del fichero, y el método read()
devuelve –1. El programa puede entonces comprobar el fin del fichero comprobando si readLine()
devuelve “null” o si read() lee un –1. estos métodos no lanzan una EOFException.

Ejemplo:

import java.io.*; // Importar clases para manejo de archivos


import java.util.Scanner;
public class Main
{
public static void main(String[] args)
{
Scanner t=new Scanner(System.in); // Objeto para leer desde teclado
String fichero,aux;
PrintWriter salida=null; // Inicializando referencia
try
{
System.out.println("Teclea nombre del fichero");
fichero=t.nextLine();
salida=new PrintWriter(new FileWriter(fichero)); // Se abre el Fichero
System.out.println("Teclea 3 lineas:");
for(int i=1;i<=3;i++)
{
aux=t.nextLine();
salida.println(i+" "+aux);
}
salida.close(); // Se cierra el fichero
System.out.println("Se escribieron las lineas en "+fichero);
}
catch (IOException e)
{
System.out.println(" No se abrio bien el fichero \n"+e.toString());
}
// Ahora se va a abrir el fichero para leerlo

Página: 55
Programación Orientada a Objetos
BufferedReader entrada=null;
boolean f=false;
try
{
do
{
try
{
f=false;
System.out.println("Teclea nombre del archivo");
fichero=t.nextLine();
entrada=new BufferedReader(new FileReader(fichero));
int i=0;
aux=entrada.readLine();
while(aux!=null)
{
i++;
System.out.println("Y dice "+aux);
aux=entrada.readLine();
}
System.out.println("Trabajo concluido");
}
catch(FileNotFoundException e)
{
System.out.println("Fichero "+" no encontrado");
System.out.println("Valor de f "+f);
f=true;
}
finally
{
if(!f)
entrada.close();
System.out.println("Hace el finally "+f);
// f=true;
}
System.out.println("Valor de f "+f);
}while(f);
}
catch(IOException e)
{
System.out.println("No se abrio bien ekl archivo "+e.toString());
}
// TODO code application logic here
}
}

8.5 Ficheros de Acceso Directo

Cuando se requiere de acceder de una forma rápida y directa a registros de un fichero sin tener
que de leer los datos que le anteceden se utilizan los ficheros de acceso directo o aleatorio. En un
fichero de acceso directo se pueden insertar datos sin necesidad de destruir los demás datos del
fichero. Los datos previaemte almacenados también pueden actualizarse o eliminarse sin tener que
rescribir todo elfichero.

Como Java no impone estructura alguna a los ficheros, la aplicación que tenga que usar ficheros
de acceso directo deberá crear y mantener la estructura necesaria. Se pueden usar diferentes

Página: 56
Programación Orientada a Objetos
técnicas para crear ficheros de acceso directo, pero la mas sencilla es la de exigir que todos los
registros tengan la misma longitud, de esta manera se podrá calcular con facilidad, en función del
tamaño del registro, la posición exacta de cualquier registro relativa al principio del fichero.

8.5.1 Creación de un fichero de acceso directo

Se utiliza la clase “RandomAccessFile” (fichero de acceso aleatorio). Los objetos


RandomAccessFiletienen todas las capacidades de los objetos de clase “DataInputStream” y
“DataOutputStream”. Cuando se asocia un flujo”RandomAccessFile” a un fichero, los datos se leen
o escriben a partir de la posición en que se encuentre en el fichero. Todos los datos se leen o
escriben como tipos de datos primitivos. Por ejemplo, al escribir un valor “int”, se envían 4 bytes al
fichero. Al leer un valor “double”, se recuperan 8 bytes del fichero. El tamaño de los diversos tipos
de datos está garantizado porque Java tiene tamaños fijos para todos los tipos de datos primitivos,
sea cual sea el ordenador o sistema operativo de trabajo.

Para trabajar con un archivo de acceso directo se tiene:

RandomAccessFile nombreCanal;
NombreCanal=new RandomAccessFile(nombreFichero,”modo”);
Donde:

NombreCanal es el nombre del objeto que servirá como canal de comunicación entre el programa y
el fichero.

NombreFichero identifica el nombre del fichero (puede ser una constante o variable de tipo String).

Modo indica el modo de apertura del fichero. Este modo puede ser sólo de lectura (“r”) o lectura-
escritura (“rw”). Si el modo seleccionado es “r”, el fichero ya debe existir, si el modo es “rw”, si el
fichero existe lo abre y si no existe lo crea.

Se debe entender claramente que en Java el fichero es una secuencia lineal de bytes que concluye
en algún tipo de marca de fin de fichero (EOF). Para conocer dónde estamos en el fichero existe
un puntero interno de posición (ver figuta).

EOF

Puntero

Con este esquema en mente se puede comentar algunos de los métodos genéricos de la clase
RandomAccessFile:

void seek(long posición): Se usa para establecer la posición actual del puntero dentro del fichero.
La variable “posición” indica el número de bytes desde el principio del fichero. Después de llamar a
“seek”, la siguiente operación de lectura o escritura se realizará en la nueva posición dentro del
fichero.

long getFilePointer(): Devuelve la posición actual del puntero del fichero (en bytes) a partir del
principio del fichero.

Página: 57
Programación Orientada a Objetos
int skipBytes(int desplazamiento): Mueve el puntero, desde laposición actual, el número de
bytes indicado por “desplazamiento”, hacia delante si el valor es positivo o hacia atrás si el valor es
negativo.

long length(): Devuelve la longitud del fichero en bytes.

Una vez usado el fichero, debemos cerrarlo con el método close().

8.5.2 Escritura en un fichero de acceso directo

Primero se debe abrir el fichero en modo de lectura-escritura (“rw”),

Ejemplo:

RandomAccessFile salida;
salida=new RandomAccessFile(“Ejemplo.dat”,”rw”);

Si el fichero “Ejemplo.dat” no existe, se crea, y si ya existe, se abre y deja el apuntador en el inicio


del fichero.

Al igual que en los ficheros de acceso secuencial se debe capturar la “IOException”. Una vez
abierto el fichero, se podrá escribir en él. Existen varios métodos análogos a los de las clases
DataOutputStream. Algunos de ellos son:

writeInt(entero): Escriben un entero de tipo “int”.

writeDouble(doble): Escriben un dato de tipo “double”.

writeByte(entero): Escriben una entero de tipo “byte”.

writeBytes(cadena): Escriben una cadena como una secuencia de bytes.

writeUTF(cadena): Escriben una cadena usando el formato UTF (Formato Independiente de la


Plataforma) que usa 8 bits para los caracteres normales. Este formato añade 2 bytes al
principio, que indican la cantidad de bytes que conforman la cadena. Por eso, si escribimos “n”
caracteres en UTF, en el fichero se escribirán n bytes + 2 bytes..

Existen métodos para cada uno de los tipos de datos primitivos.

El método close se debe usar para cerrar los ficheros.

8.5.3 Lectura de un fichero de acceso directo

Se debe abrir el fichero de la misma manera que en el punto anterior, con la observación siguiente:
Si sólo se desea tener acceso al fichero para leer los registros, el modo de apertura es “r”. Si
además de leer se va a escribir en el fichero (actualización), el fichero se abre en modo “rw”.

Ejemplo:

RandomAccessFile entrada;
entrada=new RandomAccessFile(“Ejemplo.dat”,”r”);

También se tienen métodos similares a los de la clase DataInputStream. Algunos métodos son:

readInt(), readDouble(), readUTF() y, en resumen, métodos para cada uno de los tipos de datos
primitivos.

Página: 58
Programación Orientada a Objetos
Es importante saber cómo moverse en los ficheros. Para trabajar a nivel de registro lo más útil es
poder saltar registros y no bytes. Eb Java esto se puede hacer conociendo el número de bytes de
cada registro. Suponiendo que en un fichero de acceso directo se almacenan los datos de los
clientes de una empresa, que constan de un número de orden (int), el nombre escrito en formato
UTF con 30 caracteres (30+2 bytes) y un saldo (double). El tamaño del registro, en bytes, es: int (4
bytes) + nombre (30+2 bytes) + double (8 bytes)= 44 bytes.

Se puede asignar a una variable el tamaño del registro:

LongRegistro=44;
Conociendo la longitud del registro (número de bytes por registro) se puede cambiar de unidades:
de registro a bytes o de bytes a registros.

Ejemplo:

import java.io.*;
import java.util.Scanner;
public class Main
{
public static void main(String[] args)
{
Scanner t=new Scanner(System.in);
String fichero,aux,resp="respuesta";
double d;
RandomAccessFile salida;
try
{
System.out.println("Teclea nombre del archivo");
fichero=t.nextLine();
salida=new RandomAccessFile(fichero,"rw"); // modo lectura/escritura
salida.seek(salida.length()); // Se mueve el apuntador al final del fichero
System.out.println("Teclea lineas:");
int i=0;
do
{
i++;
System.out.println("Teclea linea "+i);
aux=t.nextLine();
aux=String.format("%-60s",aux);
System.out.println("Teclea real "+i);
d=t.nextDouble();
t.nextLine(); // Lectura fantasma de una cadena
salida.writeUTF(aux); // Escribe en el fichero
salida.writeDouble(d); // Escribe en el fichero
System.out.println("Otra linea??? <si/no>");
resp=t.nextLine();
}while(resp.toUpperCase().equals("SI"));
System.out.println("Se insertaron "+i+” registros”);
salida.close();
System.out.println("Se escribieron las lineas en "+fichero);
}
catch (IOException e) // captura la Excepcion
{
System.out.println(" No se abrio bien el fichero \n"+e.toString());
}
RandomAccessFile entrada;

Página: 59
Programación Orientada a Objetos
boolean f=false;
do
{
try
{
f=false;
System.out.println("Teclea nombre del archivo");
fichero=t.nextLine();
entrada=new RandomAccessFile(fichero,"r"); // Abre fichero
entrada.seek(0); // Mueve el puntero al inicio del fichero
long i=0;
System.out.println("Tamaño Fichero: "+entrada.length());
System.out.println("\nRegistris: "+entrada.length()/70);
for(long y=0;y<entrada.length();y+=70)
{
aux=entrada.readUTF();
d=entrada.readDouble();
System.out.println(i+"\n"+aux+" real "+d);
}
entrada.close();
System.out.println("Lectura concluida");
}
catch(IOException e)
{
System.out.println("Fichero "+ fichero +" no encontrado"+e.toString());
f=true;
}
finally
{
System.out.println("Hace el finally "+f);
}
}while(f);
}
}

Página: 60

Das könnte Ihnen auch gefallen