Beruflich Dokumente
Kultur Dokumente
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:
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.
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
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
Produce applets
Java puede ser usado para crear dos tipos de programas: aplicaciones independientes y
applets.
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 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.
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.
Página: 5
Programación Orientada a Objetos
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 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:
Página: 6
Programación Orientada a Objetos
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.
El ocultamiento de los datos de el TDA y sólo proveer de una interfaz bien definida se conoce
como encapsulación.
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.
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.
Ejemplo:
CLASE: Coche
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 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:
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
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
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.
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+")");
}
}
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.
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.
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:
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.
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:
d=3.1416;
Página: 15
Programación Orientada a Objetos
d
3.1416
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á:
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).
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.
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.
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'};
Se definen igual que los vectores excepto que se requiere un índice por cada dimensión.
Su sintaxis es la siguiente:
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.
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:
numeros[0][0]=1 numeros[0][1]=2
numeros[1][0]=3 numeros[1][1]=4
numeros[2][0]=5 numeros[2][1]=6
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();
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();
Para hacer referencia a algún atributo o método se realiza como x. seguido de lo correspondiente
(nombre del atributo o método)
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:
Página: 21
Programación Orientada a Objetos
Gráficamente tendríamos:
vec
nombre nroCtrl semestre edad
0
2
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
2
nombre nroCtrl semestre edad
Unidad 4: Sobrecarga.
4.1 Conversión de tipos.
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".
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.
(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.
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.
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:
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:
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.
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 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 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);
}
}
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...
El resultado es:
Sobreescritura de Métodos
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.
Ahora que hemos visto ambas formas de reescribir métodos, revisemos las reglas y diferencias
entre ambos tipos de reescritura:
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.
Unidad 5: Herencia.
5.1 Introducción a la herencia.
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.
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.
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:
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:
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.
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:
Página: 30
Programación Orientada a Objetos
SegmentoOrientado s=new SegmentoOrientado(11.0,2.0,3.0,7.0,-1);
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:
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
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{....}
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.
A diferencia de lo que ocurre en C++ no hay herencia múltiple, es decir, cada clase tiene a lo más
una superclase.
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).
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();
}
}
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.
Al identificador del método y lista de parámetros con sus tipos, se le conoce como firma del
método.
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
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
Padre polimorfico;
polimorfico=padre; // hace referencia a un objeto padre
polimorfico.onToy();
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";
}
}
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.
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:
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.
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:
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
interface nombre_interface
{
tipo_retorno nombre_metodo ( lista_argumentos ) ;
...
}
Por ejemplo:
interface Figura
{
int area();
}
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:
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:
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:
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:
Página: 41
Programación Orientada a Objetos
Esto puede usarse simplemente:
System.out.println(Meses.NOMBRES_MESES[ENERO]);
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.
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.
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;
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.
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.
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;
Paquete.Subpaquetes1.Subpaquete2.Clase_o_Interfaz.elemento
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.
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
si algoSaleMal
encargarse del problema
otro
situación normal
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
Página: 44
Programación Orientada a Objetos
Las siguientes son las excepciones predefinidas más frecuentes que se pueden encontrar:
int i = 12 / 0;
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.
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.
}
...
}
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;
}
}
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.
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.
Excepciones verificadas: El compilador obliga a verificarlas. Son todas las que son lanzadas
explicitamente por objetos de usuario.
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:
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.
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:
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.
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:
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:
Página: 50
Programación Orientada a Objetos
Se encierra el código que puede lanzar la excepción en un bloque try / catch.
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.
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.
Ejemplo:
Página: 52
Programación Orientada a Objetos
public static void main(String[] args)
{
double resul;
System.out.println(“Tecleados numeros: “);
Ejemplo
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.
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.
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.
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.
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));
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:
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
}
}
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.
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.
Ejemplo:
RandomAccessFile salida;
salida=new RandomAccessFile(“Ejemplo.dat”,”rw”);
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:
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.
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