Académique Documents
Professionnel Documents
Culture Documents
Facultad de Ingeniería
UDEP
Mgtr. Ernesto Guevara A.
9 de marzo de 2007
Índice general
1. Algorítmica 1
1.1. Complejidad algorítmica . . . . . . . . . . . . . . . . . . . . . . . 1
1.2. Notación O grande . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2.1. Propiedades de la notación O grande. . . . . . . . . . . . 2
1.2.2. Órdenes de Complejidad . . . . . . . . . . . . . . . . . . . 3
1.3. Métodos de resolución de problemas . . . . . . . . . . . . . . . . 4
1.3.1. Divide y vencerás: Divide & Conquer . . . . . . . . . . . . 4
1.3.2. Algoritmos voraces: Greedy . . . . . . . . . . . . . . . . . 5
1.3.3. Esquema de vuelta atrás: Backtracking . . . . . . . . . . 6
1.3.4. Ramicación y poda: Branch & Bound . . . . . . . . . . . 7
1
ÍNDICE GENERAL 2
3. Estructuras de datos 30
3.1. Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.2. Archivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.2.1. Clase File . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.2.2. Flujos de bytes (streams) . . . . . . . . . . . . . . . . . . 34
3.2.3. Flujos de accesos a archivos . . . . . . . . . . . . . . . . . 35
3.2.4. Uso de Streams de Archivos Secuenciales . . . . . . . . . 35
3.2.5. Trabajar con Archivos de Acceso Aleatorio . . . . . . . . 37
3.3. Serialización de Objetos . . . . . . . . . . . . . . . . . . . . . . . 39
3.3.1. Proporcionar Serialización de Objetos . . . . . . . . . . . 40
3.4. Estructuras dinámicas . . . . . . . . . . . . . . . . . . . . . . . . 43
3.4.1. Genéricos . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.4.2. Librería de colecciones . . . . . . . . . . . . . . . . . . . . 44
3.4.2.1. Interfaces Collection e Iterator . . . . . . . . . . 45
3.4.2.2. Clases de la librería . . . . . . . . . . . . . . . . 46
3.4.3. Listas enlazadas . . . . . . . . . . . . . . . . . . . . . . . 46
3.4.3.1. Uso de listas con la librería . . . . . . . . . . . . 50
3.4.4. Pilas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
3.4.4.1. Uso de la librería . . . . . . . . . . . . . . . . . . 52
3.4.5. Colas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.4.5.1. Interfaz . . . . . . . . . . . . . . . . . . . . . . . 53
3.4.5.2. Colas de Prioridad . . . . . . . . . . . . . . . . 53
3.4.6. Tablas Hash . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.4.7. Uso de la la colección para tablas hash . . . . . . . . . . . 55
Algorítmica
1.1. Complejidad algorítmica
La eciencia de un determinado algoritmo depende de la máquina en la
cual se ejecuta el programa, y de otros factores externos al propio diseño. Pa-
ra comparar dos algoritmos sin tener en cuenta estos factores externos se usa
la complejidad. Esta es una media informativa del tiempo de ejecución de un
algoritmo, y depende de varios factores:
S1;
for (int i= 0; i < N; i++) S2;
requiere
1
CAPÍTULO 1. ALGORÍTMICA 2
T(N)= t1 + t2*N
los extremos son habitualmente conocidos como "caso peor" y "caso mejor".
Entre ambos se hallara algún "caso promedio" o más frecuente.
Para muchos programas, el tiempo de ejecución es en realidad una función
de la entrada especica, y no sólo del tamaño de ella. En cualquier caso se dene
T(n) como el tiempo de ejecución del peor caso, es decir, el máximo valor del
tiempo de ejecución para las entradas de tamaño n.
Ejemplo:
T(n) = (n+1)2
n2 +2n+1 <= c-n2
n >= n0
n0 = 1
2 2
Orden n es O(n )
O(c.f(n)) = O(f(n))
CAPÍTULO 1. ALGORÍTMICA 3
O(f(n)+g(n)) = O(máx(f(n),g(n)).
O(f(n))*O(g(n)) = O(f(n)*g(n))
Para los bucles es el orden del cuerpo del bucle sumado tantas veces como
se ejecute el bucle.
2
O(n ): Complejidad cuadrática. Aparece en bucles o ciclos doblemente
anidados. Si n se duplica, el tiempo de ejecución aumenta cuatro veces.
3
O(n ): Complejidad cúbica. Suele darse en bucles con triple anidación. Si
n se duplica, el tiempo de ejecución se multiplica por ocho. Para un valor
grande de n empieza a crecer dramáticamente.
a
O(n ): Complejidad polinómica (a >3). Si a crece, la complejidad del
programa es bastante mala.
n
O(2 ): Complejidad exponencial. No suelen ser muy útiles en la práctica
por el elevadísimo tiempo de ejecución. Se dan en subprogramas recursivos
que contengan dos o más llamadas internas. N
n/c, siendo c>0 una constante natural, denota el tamaño de los datos que recibe
cada uno de los subproblemas en que se descompone.
Estas dos condiciones están caracterizando un algoritmo, generalmente re-
cursivo múltiple, en el que se realizan operaciones para fragmentar el tamaño
de los datos y para combinar los resultados de los diferentes subproblemas re-
sueltos.
Además, es conveniente que el problema satisfaga una serie de condiciones
adicionales para que sea rentable, en términos de eciencia, aplicar esta estra-
tegia de resolución. Algunas de ellas se enumeran a continuación :
4. Hay que evitar generar nuevas llamadas cuando el tamaño de los datos
que recibe el subproblema es sucientemente pequeño.
Como ejemplos muy conocidos de este tipo de algoritmos están los métodos de
ordenamiento Quicksort y Mergesort.
3. existe una función denominada factible que permite averiguar si una se-
cuencia de decisiones, la solución en curso actual, viola o no las restriccio-
nes.
No parece que exista una gran diferencia entre los problemas que se pueden
resolver utilizando un esquema Voraz y los que se pueden resolver aplicando
este nuevo esquema de Vuelta Atrás. Y es que en realidad no existe. La técnica
de resolución que emplean cada uno de ellos es la gran diferencia.
Básicamente, Vuelta Atrás es un esquema que genera TODAS las secuencias
posibles de decisiones. Esta tarea también la lleva a cabo el algoritmo conocido
CAPÍTULO 1. ALGORÍTMICA 7
como de fuerza bruta pero lo hace de una forma menos eciente. Vuelta Atrás,
de forma sistemática y organizada, genera y recorre un espacio que contiene
todas las posibles secuencias de decisiones. Este espacio se denomina el espacio
de búsqueda del problema. Una de las primeras implicaciones de esta forma de
resolver el problema es que, si el problema tiene solución, Vuelta Atrás seguro
que la encuentra.
Programación orientada a
objetos
2.1. Tipos de datos abstractos
2.1.1. Conceptos
Frente a un problema que va a ser resuelto con un programa se debe empezar
con una etapa de modelado del problema.
El modelado se puede realizar de muchas maneras. Cuando se desea hacer
uso de la tecnología de orientación a objetos se debe modelar usando tipos
abstractos de datos.
El modelo dene una perspectiva abstracta del problema. Esto implica que
el modelo se enfoca solamente en aspectos relacionados con el problema y que
trata de denir propiedades del problema. Estas propiedades incluyen
9
CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS 10
1. Exporta un tipo.
El Punto 3 de la denición del TDA dice que para cada acceso a la estructura de
los datos debe haber una operación denida. Los ejemplos de acceso de arriba
parecen contradecir este requisito. ¾Es esto realmente cierto?
Hay que ver otra vez las dos posibilidades para representar números com-
plejos. Considerar únicamente la parte real. En la primera versión, x es igual a
c[0]. En la segunda versión, x es igual a c.r. En ambos casos x es igual a "algo".
Es este "algo" el que diere en la estructura de datos actual que se está usando.
Pero en ambos casos, la operación ejecutada "igual a" tiene el mismo signicado
para declarar que x es igual a la parte real del número complejo c: ambos casos
logran la misma semántica.
Si se piensa en operaciones más complejas, el impacto de desacoplar estruc-
tura de datos y operaciones se hace aún más evidente. Por ejemplo, la suma
de dos números complejos requiere ejecutar una suma para cada parte. Por
consecuencia, se debe accesar el valor de cada parte, el cuál es diferente para
cada versión. Al proveer una operación "suma" tu puedes encapsular estos de-
talles aparte de su uso actual. En el contexto de una aplicación simplemente se
"suma dos números complejos" sin importar cómo se logra en la práctica esta
funcionalidad.
Una vez que se ha creado un TDA para números complejos, llamado Com-
plex, se puede usar de la misma manera que se usan los tipos de datos conocidos
tales como los enteros (integers).
Resumiendo: La separación de las estructuras de los datos y las operaciones
por una parte y la restricción de solamente accesar la estructura de los datos
vía una bien denida interface por la otra, permite escoger estructuras de datos
apropiadas para el ambiente de la aplicación.
2.1.3. Notación
Debido a que los TDAs proveen una perspectiva abstracta para describir
propiedades de conjuntos de entidades, su uso es independiente de un lenguaje
de programación en particular. Toda descripción de un TDA consiste en dos
partes :
Datos: Esta parte describe la estructura de los datos usada en el TDA de una
manera informal.
Operaciones: Esta parte describe las operaciones válidas para este TDA, por
lo tanto, describe su interface. Se usa la operación especial constructor
para describir las acciones que se van a ejecutar una vez que una entidad
de este TDA es creada y destructor para describir las acciones que se van a
efectuar cuando una entidad es destruída. Son dados para cada operación,
los argumentos provistos así como precondiciones y postcondiciones.
Se presenta como ejemplo la descripción del TDA Integer. Sea k una expresión
integer:
TDA Integer es
Operaciones
constructor Crea un nuevo integer.
add(k) Crea un nuevo integer, suma de N y k. Por consecuencia, la postcon-
dición de esta operación es add = N+k.
sub(k) Similar a add, esta operación crea un nuevo integer de la diferencia de
ambos valores integer. Por lo tanto la postcondición para esta operación
es sub = N-k.
set(k) Pone a N lo que vale k. La postcondición para esta operación es N = k.
...
end
La descripción de arriba es una especicación para el TDA Integer. Nótese
por favor, que se usan palabras para nombres de operaciones tales como "add".
Se podría haber usado el signo "+", que es más intuitivo, pero esto podría llevar
a alguna confusión El nombre de la operación es solamente sintaxis ahí donde
la semántica se describe por las pre- y postcondiciones asociadas. Sin embargo,
siempre constituye una buena idea el combinar ambos para hacer que la lectura
de las especicaciones del TDA sea más fácil.
Los lenguajes de programación reales son libres de escoger una implementa-
ción arbitraria para un TDA. Por ejemplo, podrían implementar la operación
add con el operador injo "+" que condujera a una lectura más intuitiva para
la suma de enteros.
Un objeto es una entidad con unos límites bien denidos que encap-
sulan estado y comportamiento. El estado se representa por atributos
y relaciones, el comportamiento es representado por operaciones y
métodos. Object Management Group
2.2.7. Interfaz
Es el aspecto externo del objeto. La parte visible y accesible para el resto de
objetos.
También se le dene como el protocolo de comunicación de un objeto.
Puede estar formado por uno o varios métodos. No todos los métodos de un
objeto tienen que formar parte del interfaz.
CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS 15
Todos los objetos de una clase dada son idénticos en estructura y en com-
portamiento, pero tienen identidad única.
Objeto (instancia):
class Ventana {
string titulo;
int coordx;
int coordy;
int altura;
int anchura;
}
CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS 16
2.3.2.1. Constructores
Para cada clase, pueden denirse uno o más métodos particulares: son los
constructores.
El constructor se llama en el momento de la creación de un objeto. La uti-
lización de new( ) implica la creación física del objeto y la llamada a uno de
sus constructores. Si hubiera más de un constructor, éstos se diferencian por los
parámetros que se pasan en el new( ). Véase el ejemplo siguiente:
CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS 17
import java.io.*;
// una clase que sirve de main
public class Democonstructor {
public static void main (String arg [] ) {
Ejemplo e;
e = new Ejemplo (2);
e = new Ejemplo ("2");
};
}
// una clase que tiene dos constructores diferentes
class Ejemplo {
public Ejemplo (int param) {
System.out.println ("ha llamado al constructor con un entero");
}
public Ejemplo (String param) {
System.out.println ("ha llamado al constructor con una string");
};
};
class Valor {
static public void main ( string [ ] arg ) {
new Reserva (1000);
};
}
class Reserva {
int capacidad = 2; // Valor predeterminado
Esto dará como resultado la visualización del valor que el núcleo le ha dado
al atributo capacidad en la inicialización (2) y posteriormente el valor que le
asigna el constructor (1000).
CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS 18
class Pobre {
int atributo;
void CualquierMetodo();
}
Hacer un new Pobre( ) sería correcto. Pero éste deja de existir si se dene uno,
y por lo tanto no se le puede llamar.
class Demostatic {
static public void main ( String[ ] args ){
participante p1=new participante ( );
participante p2=new participante ( );
p1.modifica ( );
System.out.println ("p1: " + p1.participado + " " +
p1.noparticipado);
System.out.println ("p2: " + p2.participado + " " +
p2.noparticipado);
};
}
class Participante {
static int participado=2;
int noparticipado=2;
void modifica ( ) {
participado=3;
noparticipado=4;
};
}
p1: 3 4
p2: 3 2
CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS 19
class participante {
static int participado;
int noparticipado; ...
el método:
void modifica ( ) {
participado + +;
}
void modifica2 ( ) {
noparticipado + +;
}
así como
void modifica3 ( ) {
modifica2 ( );
};
un método static puede ser llamado por otro método static, algo que no
se sale de lo normal
2.3.3. La encapsulación
Se trata de proteger los datos en una clase. La encapsulación se basa en
la noción de servicios prestados. Una clase proporciona un cierto número de
servicios y los usuarios de esta clase no tienen que conocer la forma como se
prestan estos servicios.
Hay que distinguir en la descripción de la clase dos partes:
class ventana {
class pantalla {
void desplaza10endiagonal (ventana unaV) {
unaV.x1 += 10;
unaV.y1 + = 10;
}
}
Esto resulta muy práctico pero muy imprudente, si más adelante se decide añadir
los atributos siguientes a la clase ventana:
por ejemplo, para no tener que recalcularla cada vez. El método Desplaza10EnDiagonal
debe modicarse para tener en cuenta estos nuevos atributos:
class Pantalla {
void Desplaza10EnDiagonal (Ventana unaV) {
unaV.x1 += 10;
unaV.y1 + = 10;
unaV.x2 + = 10;
unaV.y2 + = 10;
}
}
CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS 21
2.3.5. Herencia
La herencia es una de las nociones importantes del diseño y de la progra-
mación orientada a objetos. Es uno de los factores que permiten la reutilización
del código.
La herencia es una relación entre clases denida por la palabra clave extends.
Si se dice que una clase Hija, hereda de una clase Madre, quiere decir que éste
asimila los atributos y métodos del Madre y que un objeto de la clase Hija es
también de la clase Madre.
Esto último, no es recíproco, es decir, un objeto de la clase Madre no lo es
de la clase Hija.
La herencia, desde este punto de vista supone:
CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS 22
class Madre{
int entero;
void metodo () { ;};
}
class Hija extends Madre { // extends por heredar
void metododeHija () {
// . . .
};
}
class Madre {
int entero;
void metodo () {;};
}
class Hija {
int entero;
void metodo () {;};
}
import java.io.*;
public class redefinicion {
public static void main (String arg [ ]) {
Madre m new Madre ();
m.habla ();
Hija h = new Hija ();
h.habla ();
nieta n = new Nieta ();
n.habla ();
};
}
class Madre {
void habla () {
2.3.8. Polimorsmo
Como se ha dicho, un objeto de la clase Hija es también un objeto de la
clase Madre. Se puede aprovechar esta dualidad gracias a lo que se denomina
polimorsmo.
El polimorsmo es un aprovechamiento de la herencia imposible de realizar
en un lenguaje que no sea orientado a objetos. Consiste en llamar a un método
según el tipo estático de una variable, basándose en el núcleo para llamar a la
versión correcta del método.
Conservando la denición de las clases anteriores, Madre, Hija y Nieta, y
modicando la llamada a los métodos:
Aunque la variable, m, sea siempre de tipo Madre, los objetos designados con
esta variable son sucesivamente del tipo Madre, Hija y Nieta, y se ejecutan los
métodos de sus clases.
Se puede pensar que ésto es una compilación innecesaria, pero no es así, se
trata de una funcionalidad potente de los lenguajes orientados a objetos, la cual
permite suprimir una buena parte de los case y otros switch.
Hasta ahora se ha hablado de ventana Madre y ventana Hija para designar
una ventana que contiene a otra. Esto no signica que la ventana Hija hereda de
la ventana Madre. Una ventana no puede heredar de otra porque son objetos.
Son las clases quienes heredan unas de otras.
En el ejemplo visto, la ventana Hija puede ser de la misma clase que la
ventana Madre, o bien de otra clase distinta. Por el contrario, esa ventana Hija
será sin duda un atributo de la ventana Madre.
CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS 25
class Madre {
void metodo ( ) { };
void metodo (int param) {
// el método está sobrecargado
}
}
class Hija extends Madre{
void metodo ( ) {
// redefinición de metodo () de Madre
}
}
class Madre {
final void metodo () { ;} ;
}
class Hija extends Madre {
void metodo () { };
}
es rechazado por el compilador.
Los atributos también pueden ser nal, lo que permite de hecho denir cons-
tantes. Si un atributo es nal:
import java.io.*;
class herecons {
public static void main (string arg [ ] ) {
new Hija (2);
}
};
class Madre {
int m;
public Madre ( int mm ) {
System.out.println ('madre');
};
}
class Hija extends Madre {
int f;
public Hija (int ff) {
super (ff);
System.out.println("Hija");
};
}
}
abstract void metabstracto { } ; // sin cuerpo
void metvacio ( ) { ; }
Una clase de la que uno de los métodos sea abstracto es a su vez llamada
abstracta. Debe declararse como tal en el programa:
Si se dene una clase como abstracta cuando ninguno de sus métodos lo es, el
compilador no lo considera como un error. Pero la clase es entonces efectivamente
considerada como abstracta.
Si se olvida de añadir abstract delante de la declaración de una clase que
posee métodos abstractos, el compilador considera ésto como un error.
En denitiva, ¾ para qué sirven las clases abstractas ?, para denir conceptos
incompletos que deben ser completados en las subclases de la clase abstracta.
Así,
métodos, incluyendo los que no deben llamarse nunca porque deben ser rede-
nidos en las subclases. Debe tenerse en cuenta que ha de protegerse las clases de
una inesperada llamada. a sus métodos. La otra solución sería no denir vehicu-
lo y copiar y pegar entre las clases autobus y coche. Esto sería muy malo para
el mantenimiento del programa. Además, no se podría utilizar el polimorsmo,
que es posible con las clases abstractas. Una variable que es estáticamente de un
tipo abstracto puede pasarse como parámetro o ser el atributo de una clase. El
compilador sabe que en la ejecución es en realidad un objeto de una subclase que
será el parámetro o atributo, pero que este objeto respetará las especicaciones
de la clase abstracta.
2.4. Interfaz
Una interfaz es una clase:
interface vehiculo{
/* final */ int numruedas = 2;
/* abstract */ void metodo ();
}
Las clases que pertenecen, por ejemplo, al paquete java.awt (que son las grá-
cas):
Como se ve, es fácil crear paquetes. Estos, los paquetes, permiten utilizar los
modos de protección protected y friendly.
Estructuras de datos
Las estructuras de datos siempre se han entendido en la programación como
aquellos elementos que permiten la manipulación de una colección de datos. Su
clasicación típica ha sido la siguiente:
Estructuras estáticas: Aquellas que son creadas con un tamaño jo y que no
pueden cambiar de tamaño durante la ejecución del programa. Por ejemplo
un array.
Las estructuras estáticas son más fáciles de usar, pero evidentemente son más
limitadas. Siempre existe el peligro de declarar el tamaño por exceso o por
defecto en el programa.
El uso de estructuras de datos estáticas ya ha sido visto en la primera parte
del curso y a partir de ahora se verán únicamente las estructuras dinámicas.
Bajo el enfoque de programación orientada a objetos existe una forma orde-
nada de programar, que es la creación de dos clases distintas:
Una clase nodo, que contiene los datos que se quieren manipular. La clase
administradora hará la manipulación de múltiples instancias de la clase
nodo.
3.1. Arrays
En Java los arrays son objetos, por lo tanto deben ser creados usando el
operador new, indicando el tamaño del array en el momento de su creación .
Un ejemplo de declaración de un array:
30
CAPÍTULO 3. ESTRUCTURAS DE DATOS 31
Así se declara que la variable buer es un array de enteros y tendrá espacio para
10 enteros.
Ejemplo:
vector.length( );
import java.io.*;
class Person
{
private String lastName;
private String firstName;
private int age;
//-----------------------------------------------------------
public Person(String last, String first, int a)
{ // constructor
lastName = last;
firstName = first;
age = a;
}
//-----------------------------------------------------------
public void displayPerson()
{
System.out.print(" Apellido : " + lastName);
System.out.print(", Nombre : " + firstName);
System.out.println(", Edad: " + age);
}
//-----------------------------------------------------------
public String getLast() // obtener apellido
{ return lastName; }
CAPÍTULO 3. ESTRUCTURAS DE DATOS 32
}
class ClassDataArray
{
private Person[] a; // referencia
private int nElems; // número de elementos
//-----------------------------------------------------------
public ClassDataArray(int max) // constructor
{
a = new Person[max]; // crear el array
nElems = 0; // sin elementos
}
//-----------------------------------------------------------
public Person find(String searchName)
{ // buscar un valor
int j;
for(j=0; j<nElems; j++)
if( a[j].getLast().equals(searchName) )
break;
if(j == nElems)
return null;
else
return a[j];
}
//-----------------------------------------------------------
// insertar objeto Person
public void insert(String last, String first, int age)
{
a[nElems] = new Person(last, first, age);
nElems++; // incrementar
}
//-----------------------------------------------------------
public boolean delete(String searchName)
{ // eliminar Person
int j;
for(j=0; j<nElems; j++)
if( a[j].getLast().equals(searchName) )
break;
if(j==nElems)
return false;
else
{
for(int k=j; k<nElems; k++)
a[k] = a[k+1];
CAPÍTULO 3. ESTRUCTURAS DE DATOS 33
nElems--;
return true;
}
}
//-----------------------------------------------------------
public void displayA() // imprimir contenido
{
for(int j=0; j<nElems; j++)
a[j].displayPerson();
}
//-----------------------------------------------------------
}
class ClassDataApp
{
public static void main(String[] args)
{
int maxSize = 100;
ClassDataArray arr; // referencia
arr = new ClassDataArray(maxSize); // crear el array
// insertar 10 items
arr.insert("Evans", "Patty", 24);
arr.insert("Smith", "Lorraine", 37);
arr.insert("Yee", "Tom", 43);
arr.insert("Adams", "Henry", 63);
arr.insert("Hashimoto", "Sato", 21);
arr.insert("Stimson", "Henry", 29);
arr.insert("Velasquez", "Jose", 72);
arr.insert("Lamarque", "Henry", 54);
arr.insert("Vang", "Minh", 22);
arr.insert("Creswell", "Lucinda", 18);
arr.displayA(); // ver los items
String searchKey = "Stimson"; // buscar uno
Person found;
found=arr.find(searchKey);
if(found != null)
{
System.out.print("Encontrado ");
found.displayPerson();
}
else
3.2. Archivos
El manejo de archivos en Java implica el uso de dos grupos de clases. Están
las clases que permiten hacer manipulaciones de archivos a nivel de sistema
operativo y las que permiten hacer las operaciones sobre ellos a modo de ujo
de bytes.
Todos los ujos que aparecen en Java englobados en el paquete java.io, per-
tenecen a dos clases abstractas comunes: java.io.InputStream para los ujos de
Entrada (aquellos de los que se puede leer) y java.io.OutputStream para os ujos
de salida (aquellos en los que se puede escribir).
Estos ujos, pueden tener orígenes diversos (un archivo, un socket TCP,
etc.), pero una vez que se tiene una referencia a ellos, se puede trabajar siempre
de la misma forma: leyendo datos mediante los métodos de la familia read() o
escribiendo datos con los métodos write().
Hay formas de manipular ujos como:
import java.io.*;
public class Copy {
public static void main(String[] args) throws IOException {
out.write(c);
in.close();
out.close();
}
}
Este código crea un objeto File que representa el archivo nombrado en el sistema
de archivos nativo. File es una clase de utilidad proporcionada por java.io. Este
programa usa este objeto sólo para construir un FileReader sobre farrago.txt.
Sin embargo, se podría usar inputFile para obtener información sobre farra-
go.txt, como su path completo.
Después de haber ejecutado el programa, se debe encontrar una copia exacta
de farrago.txt en un archivo llamado outagain.txt en el mismo directorio.
Las clases FileReader y FileWriter son las más sencillas pero no son las
únicas para manipular archivos secuenciales.
Para el caso más general, cuando se desea trabajar con otros tipo de datos
que no sean caracter, se usan las clases DataInputStream y DataOutputStream.
Estas clases tienen métodos distintos para hacer cada lectura y escritura sobre
los tipos básicos de datos.
Ejemplo de manipulaciones de lectura:
double precio;
int unidad;
String descripcion;
double total=0.0;
DataInputStream entrada=new DataInputStream(new FileInputStream("pedido.txt"));
try {
while ((descripcion=entrada.readLine())!=null) {
unidad=entrada.readInt();
entrada.readChar(); //lee el carácter tabulador
precio=entrada.readDouble();
System.out.println("has pedido "+unidad+" "+
descripcion+" a "+precio+" soles.");
total=total+unidad*precio;
}
}catch (EOFException e) {}
System.out.println("por un TOTAL de: "+total+" soles");
entrada.close();
Y esta abre el mismo archivo tanto para lectura como para escritura:
Después de haber abierto el archivo, se pueden usar los métodos comunes re-
adXXX o writeXXX para realizar I/O en el archivo. Los métodos para lectura
son:
byte readByte()
int readUnsignedByte()
short readShort()
int readUnsignedShort()
char readChar()
int readInt()
long readLong()
oat readFloat()
double readDouble()
String readLine()
long getFilePointer();
long length();
Devuelve la longitud del chero. La posición length() marca el nal de ese -
chero.
Ejemplo que añade una cadena de caracteres a un archivo existente:
import java.io.*;
// Cada vez que ejecutemos el programa, se incorporara una nueva
// linea al fichero de log que se crea la primera vez que se ejecuta
//
class Log {
RandomAccessFile miRAFile;
String s = "Informacion a incorporar\n";
// Abrimos el fichero de acceso aleatorio
miRAFile = new RandomAccessFile( "C:\\tmp\\java.log","rw" );
// Nos vamos al final del fichero
miRAFile.seek( miRAFile.length() );
try {
try {
try {
CAPÍTULO 3. ESTRUCTURAS DE DATOS 43
listas enlazadas
pilas
colas
tablas hash
árboles
3.4.1. Genéricos
Programación genérica signica escribir código que puede ser reusado por
objetos de tipos distintos.
Una clase genérica es una clase creada con uno o más tipos de variables. Se
coloca el código de una clase Pareja:
private T second;
public Pareja() {
first = null;
second= null;
}
public Pareja(T first, T second)
{
this.first= first;
this.second = second;
}
public T getFirst() {return first;}
public T getSecond() {return second;}
public void setFirst(T nuevo) { first = nuevo;}
public void setSecond(T nuevo) { second = nuevo;}
}
Antes del uso de la programación con genéricos se lograba algo parecido usando
parámetros de tipo Object. Por polimorsmo cualquier objeto siempre se podía
comportar como un objeto de tipo Object. La desventaja de este enfoque es que
no era restrictivo, se podían mezclar objetos distintos tipos en las clases creadas.
Para los ejemplos expuestos el objeto mm solamente puede aceptar parámetros
tipo String para sus métodos y producirá un error si se quieren usar otros tipos
de datos.
Las interfaces poseen los métodos ordinarios para el manejo de las colecciones
de datos: agregar, eliminar, buscar, contar, etc. Además algunos brindan opera-
ciones más avanzadas, para trabajar con varias colecciones a la vez: intersección,
diferencia, unión, etc.
Iterator<E> iterator() devuelve un iterador que puede ser usado para visi-
tar los objetos de la colección
class NodoLista
{
Object elemento;
NodoLista siguiente;
}
NodoLista aux=lista;
aux=aux.siguiente;
Siguiendo con el ejemplo anterior, para insertar un nuevo nodo justo delante del
nodo referenciado por aux se deben modicar las referencias siguiente del nodo
aux y del nodo a insertar.
CAPÍTULO 3. ESTRUCTURAS DE DATOS 48
siguiente=lista.siguiente;
lista.siguiente=anterior;
anterior=lista;
lista=siguiente;
}
}
Una lista circular es aquella en donde la referencia siguiente del último nodo
en vez de ser null apunta al primer nodo de la lista. El concepto se aplica tanto
a listas de enlace simple como doblemente enlazadas.
import java.util.*;
public class LinkedListTest
{
public static void main(String[] args)
{
List<String> a = new LinkedList<String>();
a.add("Amy");
a.add("Carl");
a.add("Erica");
List<String> b = new LinkedList<String>();
b.add("Bob");
b.add("Doug");
b.add("Frances");
b.add("Gloria");
// mezclar las palabras de b en a
ListIterator<String> aIter = a.listIterator();
Iterator<String> bIter = b.iterator();
while (bIter.hasNext())
{
if (aIter.hasNext()) aIter.next();
aIter.add(bIter.next());
}
System.out.println(a);
// eliminar cada segunda palabra de b
bIter = b.iterator();
while (bIter.hasNext())
{
bIter.next(); // saltar un elemento
if (bIter.hasNext())
{
}
System.out.println(b);
// eliminar de a todas las palabras de b
a.removeAll(b);
System.out.println(a);
}
}
3.4.4. Pilas
Una pila es una estructura de datos en la cual el acceso está limitado al
elemento más recientemente insertado y solamente puede crecer y decrecer por
uno de sus extremos.
Las pilas se denominan también estructuras LIFO (Last-In-First-Out), por-
que su característica principal es que el último elemento en llegar es el primero
en salir.
En todo momento, el único elemento visible de la estructura es el último que
se colocó.
Se dene el tope de la pila como el punto donde se encuentra dicho elemento.
En una pila, las tres operaciones naturales de insertar, eliminar y obtener el
dato, se renombran por push, pop y peek.
import java.util.Stack;
public class StackExample {
public static void main(String args[]) {
Stack<String> s = new Stack<String>();
s.push("primero");
s.push("segundo");
s.push("tercero");
System.out.println("Siguiente a salir: " + s.peek());
s.push("cuarto");
System.out.println(s.pop());
CAPÍTULO 3. ESTRUCTURAS DE DATOS 53
s.push(".");
int count = s.search("primero");
while (count != 1 && count > 1) {
s.pop();
count--;
}
System.out.println(s.pop());
System.out.println(s.empty());
}
}
3.4.5. Colas
Una cola es una estructura lineal, en la cual los elementos sólo pueden ser
adicionados por uno de sus extremos y eliminados o consultados por el otro.
Este tipo de estructuras lineales se conocen como estructuras FIFO ( First-
In-First-Out), o sea, el primero en llegar es el primero es salir.
También hay que tener presente, que el único elemento visible en una cola es
el primero y mientras éste no haya salido (eliminado), no es posible tener acceso
al siguiente.
3.4.5.1. Interfaz
No existe en la librería de Java una clase que implmente directamente una
cola. Lo que existe es una interfaz con los métodos propios de una cola. La
interfaz presente lo siguiente:
java.util.Queue<E>
import java.util.*;
public class PriorityQueueTest
{
public static void main(String[] args)
{
PriorityQueue<GregorianCalendar> pq =
new PriorityQueue<GregorianCalendar>();
pq.add(new GregorianCalendar(1906, Calendar.DECEMBER, 9));
pq.add(new GregorianCalendar(1815, Calendar.DECEMBER, 10));
pq.add(new GregorianCalendar(1903, Calendar.DECEMBER, 3));
pq.add(new GregorianCalendar(1910, Calendar.JUNE, 22));
System.out.println("Iterando los elementos...");
for (GregorianCalendar date : pq)
System.out.println(date.get(Calendar.YEAR));
System.out.println("Eliminando elementos...");
while (!pq.isEmpty())
System.out.println(pq.remove().get(Calendar.YEAR));
}
}
acepta una llave y devuelve su código hash o valor hash. Las llaves varían en su
tipo de datos pero los códigos hash son siempre enteros.
Se dice que una tabla hash está directamente direccionada cuando su función
hashing pueda garantizar que no hay dos llaves que puedan generar el mismo
código hash. Esto es lo ideal pero es difícil de lograr en la práctica.
Normalmente el número de entradas en una tabla hash es mucho menor al
universo de los posible valores de llaves. Consecuentemente muchas funciones
hashing mapean algunas llaves a la misma posición en la tabla. Cuando dos
llaves mapean a la misma posición se denomina una colisión. Una buena función
hashing minimiza las colisiones y está lista para poder resolverlas.
Con los métodos put() y get() de la clase HashMap se pueden añadir y eliminar
elementos a la colección basados en los valores de sus llaves.
Se coloca a continuación un programa de ejemplo demostrando el uso posible
del HashMap.
import java.util.*;
public class MapTest
{
public static void main(String[] args)
{
Map<String, Empleado> staff = new HashMap<String, Empleado>();
staff.put("144255464", new Empleado("Amy Lee"));
staff.put("567242546", new Empleado("Harry Hacker"));
staff.put("157627935", new Empleado("Gary Cooper"));
staff.put("456625527", new Empleado("Francesca Cruz"));
// imprimir todos
System.out.println(staff);
// eliminar uno
staff.remove("567242546");
// remplazar un elemento
staff.put("456625527", new Empleado("Francesca Miller"));
// mirar un valor
System.out.println(staff.get("157627935"));
// iterar por todos los elementos
for (Map.Entry<String, Empleado> entry : staff.entrySet())
{
CAPÍTULO 3. ESTRUCTURAS DE DATOS 56
57
CAPÍTULO 4. APLICACIONES CON ORIENTACIÓN A OBJETOS 58
Disponer un contenedor:
• JFrame
• Dialog
• JApplet
Mostrar el contenedor
Ejemplo de una ventana mínima (ventana vacía de 300 por 200 pixels):
import javax.swing.*;
public class SimpleFrameTest
{
public static void main(String[] args)
{
SimpleFrame frame = new SimpleFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
CAPÍTULO 4. APLICACIONES CON ORIENTACIÓN A OBJETOS 59
frame.setVisible(true);
}
}
class SimpleFrame extends JFrame
{
public SimpleFrame()
{
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
}
import javax.swing.*;
import java.awt.*;
public class NotHelloWorld
{
public static void main(String[] args)
{
NotHelloWorldFrame frame = new NotHelloWorldFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class NotHelloWorldFrame extends JFrame
{
public NotHelloWorldFrame()
CAPÍTULO 4. APLICACIONES CON ORIENTACIÓN A OBJETOS 60
{
setTitle("NotHelloWorld");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// añadir el panel al frame
NotHelloWorldPanel panel = new NotHelloWorldPanel();
add(panel);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
}
class NotHelloWorldPanel extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawString("Not a Hello, World program", MESSAGE_X, MESSAGE_Y);
}
public static final int MESSAGE_X = 75;
public static final int MESSAGE_Y = 100;
}
Temporizaciones
ActionEvent
MouseEvent
KeyEvent
WindowEvent
FocusEvent
Otros...
ActionListener
WindowListener
MouseListener
CAPÍTULO 4. APLICACIONES CON ORIENTACIÓN A OBJETOS 61
KeyListener
FocusListener
Otros...
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonTest
{
public static void main(String[] args)
{
ButtonFrame frame = new ButtonFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
/**
Un frame con un button panel
*/
class ButtonFrame extends JFrame
{
public ButtonFrame()
{
setTitle("ButtonTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// añadir el panel al frame
ButtonPanel panel = new ButtonPanel();
add(panel);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
}
/**
Un panel con 3 botones
*/
class ButtonPanel extends JPanel
{
public ButtonPanel()
{
// crear botones
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
// añadir los botones al panel
add(yellowButton);
add(blueButton);
add(redButton);
CAPÍTULO 4. APLICACIONES CON ORIENTACIÓN A OBJETOS 62
De manera ordinaria su utiliza algún tipo de IDE con soporte para GUI
cuando se trabaja en el desarrollo de aplicaciones con interfaz gráca. El IDE
puede tener algún tipo de soporte visual para la creación de las ventanas, de
modo que el programador dibuja la ventana con los controles que necesita y
el IDE crea el código fuente necesario. El programador debe crear siempre el
código para los eventos asociados a los controles.
CAPÍTULO 4. APLICACIONES CON ORIENTACIÓN A OBJETOS 63
código fuente
archivos de conguración
librerías compiladas
otros
diseño de la aplicación
programación
pruebas
entrega
En todo el proyecto solamente puede existir una clase que tenga un método
main(), la cual será la que inicie la aplicación. Usualmente este main() es el que
invoca a la ventana principal donde se inicia la interacción con el usuario.
En la programación se van implementando los elementos que fueron especi-
cados en el diseño. En algunos casos se debe de ir probando el funcionamiento
del código mientras se va desarrollando. Estas pruebas llevan a la depuración,
etapa en la cual se quitan los errores de programación encontrados.
Usualmente en la depuración se trabaja con un IDE que permita la ejecución
del código línea por línea, así como examinar el contenido de memoria para
cualquier variable. Usando esas funcionalidades se puede ver si el programa
tiene el comportamiento esperado mientras se hacen las pruebas.
Para la entrega se deben crear los productos de despliegue. En Java se puede
crear un archivo .jar en el cual se encuentren todas las clases de la aplicación
compiladas y unidas en un único archivo. Esto facilita el despliegue asegurando
que no faltarán archivos de la aplicación cuando se entrega.
Lo único necesario para que la aplicación creada sea ejecutada en el compu-
tador del usuario debe ser que se encuentre instalada la máquina virtual de Java
(JRE). Todas las librerías usadas se deben incluir en el producto de despliegue.
Apéndice A
Lenguaje de programación
Java
A continuación se expone la sintaxis elemental del lenguaje de programación
Java. Es básicamente la mención a sus elementos propios de la programación
estructurada. El alumno ya debe de tener familiaridad con esta sintaxis, propio
de lo que ha aprendido en el curso de Programación Básica.
65
APÉNDICE A. LENGUAJE DE PROGRAMACIÓN JAVA 66
A.1.1.2. REAL
Hay dos tipos de número real en Java.
total y parcial son dos variables en coma otante; total tiene el valor 156 y
parcial 23400.
A.1.1.3. CARÁCTER
Este tipo de dato utiliza un carácter Unicode de 16 bits y se establece me-
diante un valor entre comillas simples. Ejemplo:
A.1.1.4. BOOLEANO
Este tipo de variable puede tomar el valor verdadero o falso.
Ejemplo:
multiplicación * a * b a por b
división / a / b a dividido entre b
suma + a + b b sumado a a
resta - a - b b restado a a
resto de la división % a% b resto de dividir a entre b
entera entre b
El operador % solamente se puede aplicar a datos enteros.
int a = 3, b = 9;
a += b;
int a = 4, b = 11;
b %= int a;
cont ++;
++ cont;
APÉNDICE A. LENGUAJE DE PROGRAMACIÓN JAVA 68
suponiendo que el valor de cont es 12, esta expresión hace que se incremente el
valor en 1, el 13 se almacena en cont y se retorna dicho valor.
Los resultados que se obtienen con estos operadores pueden utilizarse en
otros contextos donde esos valores puedan emplearse, como en asignaciones.
Ejemplo:
int a = 0 , b;
b = a ++;
int a. b, c = 44;
boolean var1, var2;
a = 22; b = 33;
var1 = (a < 10) && (b == 33); // var1 toma el valor falso.
var2 = !var1 && !(b < 22 && a< 23); // var2 toma el valor verdadero.
Al igual que los vectores, las cadenas de caracteres son consideradas como ob-
jetos en Java y tienen métodos asociados.
Ejemplo:
nombre.length( );
int a, b, c;
a = 10;
b = 37;
c = 91;
APÉNDICE A. LENGUAJE DE PROGRAMACIÓN JAVA 70
String identificador;
identificador = "frodo";
nombre = "María";
Esto se debe a que, aunque en Java, las cadenas de caracteres sean consideradas
como objetos existen facilidades para este tipo de datos que son no orientadas
a objetos. Si las secuencias de caracteres fueran objetos puros, la asignación del
ejemplo anterior no estaría permitida. La única forma adecuada para comuni-
carse con un objeto real es a través de un mensaje. Ejemplo:
usuario.setStringValue ("a1065");
Las operaciones que se pueden efectuar con un String son las siguientes:
Reemplazar caracteres.
Ejemplo:
Añadir directamente a una cadena objetos de tipo int, boolean, etc., sin
tener que convertirlos previamente en cadenas de caracteres.
Los StringBuer se diferencian de los Strings en que los objetos del primer tipo
pueden modicarse y se tratan como objetos puros. Ejemplo:
A.2.0.1. If
La estructura if permite que secciones de código sean ejecutadas en función
de si cierta condición es verdadera o falsa. La sintaxis es:
if (condición)
acciones-cond-verdadera( );
else
acciones-cond-falsa( );
La acción de incrementar cont se hará si se cumple que cont sea menor que 10
y que la variable booleana continuar sea verdadera.
A.2.0.2. Switch
Permite establecer una condición múltiple, de forma que después de evaluar
un argumento se lleve a cabo la acción correspondiente a ese caso. El formato
es el siguiente.
switch(argumento) {
case uno: acción1;
case dos: acción2;
...
case n: acción n;
}
switch(variable_entera) {
case (1): accion1( );
case(2): accion2( ); break;
case(3): accion3( );
default: accion_por_defecto( );
}
A.2.0.3. While
La sintaxis es:
while (expresión)
acción;
En acción puede ir una sola acción o un grupo de ellas encerradas entre llaves.
La semántica es :
Mientras la expresión sea verdadera se ejecutan las acciones.
Ejemplo:
int i = 0, total = 0 ;
while (i < 5) {
total+= valores[i];
i++;
}
acción;
while (expresión) {
acción1;
acción2;
acción3;
if (condición) break;
acción4;
...
acciónn;
}
otra_acción;
APÉNDICE A. LENGUAJE DE PROGRAMACIÓN JAVA 74
A.2.0.4. Do while
Es similar al while, se repite un conjunto de acciones mientras cierta condi-
ción sea verdadera. La única diferencia es que, en el do-while se tiene la seguridad
de pasar al menos una vez por el bucle.
La sintaxis es:
do{
acciones;
} while (expresión);
La semántica es:
ejecutar las acciones mientras la expresión de la tercera línea sea verdadera.
Ejemplo:
int i = 1;
do
i++;
while ( !nombres [i.equals (último_nombre));
A.2.0.5. For
La sintaxis es:
import java.util.Scanner;
public class ESEnteros {
public static void main (String[]args){
Scanner s = new Scanner(System.in);
int entero = 0;
do{
entero = s.nextInt();
}while(entero != 9 );
}
}
import java.util.Scanner;
public class ESCadenas{
public static void main (String[] args){
Scanner s = new Scanner (System.in) ;
String cadena = ;
do{
cadena = s.next() ; // devuelve un token
System.out.println(cadena.toUpperCase());
}while (!cadena.equals(exit));
s.close();
}
}