|
|
|
|
|
Publicaciones de Estudiantes
Francisco Javier Pabón Molina
Titulo: Principios de Programación II
Area: Information Systems
Pais:
Perfil:
Programa:
Available for Download:
Yes
Diseminar información, ideas innovadoras y
conocimientos académicos es una función
importante para Atlantic Internacional
University. Publicaremos noticias, artículos,
comentarios y otras publicaciones de
nuestros estudiantes y otros colaboradores.
Si desea contactar al autor por motivos
profesionales favor enviar su petición por
este medio.
|
|
|
|
|
|
1.
Introducción
En este ensayo titulado “Principles
of programming: Object Oriented
Programming”
pretendo dar un recorrido a este
paradigma de programación actual que
es
utilizado por miles de programadores
a nivel mundial, y que se ha
convertido en un
estándar en el desarrollo de
aplicaciones tanto de escritorio,
Web e Internet y
dispositivos móviles.
A lo largo del presente texto se ira
describiendo el origen, evolución,
tendencias
actuales y ciertos patrones de
diseño, así como una vista aunque no
muy profunda
si lo mas fiel posible a un lenguaje
de programación que ha revolucionado
el
presente y porque no decirlo también,
el futuro de la programación actual,
el
lenguaje Java.
¿Por qué Java?, en lo que va de mi
corta pero interesante carrera
profesional en
tecnologías de información he tenido
contacto con un pequeño numero de
lenguajes, desde Pascal (del cual
desarrollé el ensayo de Principles
of
programming I), donde se trató la
programación estructurada, dando una
minúscula exploración a Delphi,
lenguaje C y haciendo uso de
herramientas
propietarias en las cuales he tenido
mas experiencia y profundización
como ser la
suite Microsoft Visual Studio 6.0 en
el que comencé a dar mis primeros
pasos con
este estilo de programación de
objetos y el cual en la actualidad
sigo utilizando,
haciendo un cambio a la plataforma
.Net y lo que se ha aplica
actualmente el
concepto de framework1 de desarrollo,
existiendo varias tendencias o
estilos;
aunque las herramientas antes
mencionadas proveen de cierta
facilidad y
productividad al programador y
desarrollador de aplicaciones,
tienen la limitación
de la plataforma y ser propietarias2
y no en muchas universidades se
encuentran
disponible para la enseñanza, es
aquí donde entra Java.
Como se ira viendo, Java tiene la
ventaja de ser multiplataforma y
detallaré más a
fondo estas características en los
siguientes apartados, y si uno como
profesional
aplica para obtener un trabajo en el
área de la programación, es casi una
obligación desde un punto de vista
personal conocer y desempeñarse en
ambas
plataformas y certificarse3, para
ofrecer y brindarle mayores
soluciones a la
empresa, organización o individuo
que requiera de sus servicios.
También se pretende dar una pequeña
crítica a este estilo de
programación,
porque aunque todo tiene sus
ventajas y desventajas, y como dije
al principio, la
comunidad de desarrolladores es muy
amplia, hay muchos de los cuales aun
no
están del todo conformes y prefieren
todavía otros estilos y metodologías.
1 En desarrollo de software, un
framework es definido como una
estructura de apoyo en la cual otro
proyecto de software
puede ser organizado y desarrollado.
2 El uso de herramientas
propietarias, generalmente ligadas a
tecnologías específicas bajo el
control de determinadas
empresas, contribuye a lograr una
fuerte dependencia del futuro
profesional en las mismas.
3 Actualmente, certificarse es un
requisito en el mercado tecnológico.
La competencia es cada vez mayor y
debes estar
preparado para los cambios del
mercado.
Las prácticas y desarrollos se harán
con el JRE 5.0 Update 6 y como IDE
me he
decidido por Eclipse Project del
cual también detallaré mas adelante.
Agradecer a Atlantic International
University por brindarme la
oportunidad de poder
mostrar mis conocimientos y adquirir
y profundizar los nuevos que he
elegido en
vista a un mejor crecimiento
profesional.
2. Paradigma de la Programación
Un paradigma es una forma de
representar y manipular el
conocimiento.
Representa un enfoque particular o
filosofía para la construcción o
ingeniería de
software. No es mejor uno que otro
sino que cada uno tiene sus ventajas
y
desventajas. También hay situaciones
donde un paradigma es mejor que otro.
Un paradigma provee (y determina) la
vista que el programador tiene de la
ejecución del programa. Por ejemplo,
en un programa orientado a objetos,
los
programadores pueden pensar en un
programa como una colección de
interacción
de objetos, mientras en un programa
funcional un programa puede ser
pensado
como una secuencia de evaluaciones
de función sin estado.
Tal como diferentes grupos en
ingeniería de software abogan por
las diferentes
metodologías, los distintos
lenguajes de programación abogan por
los diferentes
paradigmas de programación. Algunos
lenguajes son diseñados para apoyar
un
paradigma particular (Smalltalk4 y
Java apoyan la programación
orientada a
objetos mientras Haskell5 y Scheme6
apoyan el paradigma de programación
funcional), otros lenguajes de
programación apoyan múltiples
paradigmas.
Algunos ejemplos de paradigmas de
programación:
El paradigma imperativo es
considerado el más común y está
representado, por
ejemplo, por el C7 o por BASIC8.
• El paradigma funcional está
representado por la familia de
lenguajes LISP9,
en particular Scheme.
• El paradigma lógico, un ejemplo es
PROLOG10.
Si bien puede seleccionarse la forma
pura de estos paradigmas al momento
de
programar, en la práctica es
habitual que se mezclen. Tal es el
caso de lenguajes
como C++, Delphi o Visual Basic, los
cuales combinan el paradigma
imperativo
con el orientado a objetos.
Por ejemplo, C++ es diseñado para
apoyar los elementos de programación
procedural, programación basada a
objetos, programación orientada a
objetos, y
programación genérica.
4-10 Lenguajes de programación
pertenecientes a distintos
paradigmas.
Uno puede escribir un programa
puramente procedural en C ++, puede
escribir un
Sin embargo, los diseñadores y
programadores deciden como construir
un
programa que usa aquellos elementos
de un paradigma. programa puramente
orientado a objetos en C ++, o puede
escribir un programa que contiene
los
elementos de ambos paradigmas.
En estos estilos es donde entra la
controversia por parte de muchos
programadores en cuanto a cual es
mejor o cumple sus necesidades, y
con la
evolución de los lenguajes, van
aplicándose nuevos paradigmas, por
ejemplo el de
programación orientada a aspectos,
el cual es reciente y su intención
es permitir
una adecuada modularización y
posibilitar una mejor separación de
conceptos.
Varias tecnologías con nombres
diferentes se encaminan a la
consecución de los
mismos objetivos y así, el término
POA (Programación Orientada a
Aspectos) es
usado para referirse a varias
tecnologías relacionadas.
Muchas veces nos encontramos, a la
hora de programar, con problemas que
no
podemos resolver de una manera
adecuada con las técnicas habituales
usadas en
la programación procedural o en la
programación orientada a objetos. O
que en
otro lenguaje se resuelven de forma
más sencilla y tratamos de evitar en
la
mayoría de las veces varias líneas
de código. Con éstas, nos vemos
forzados a
tomar decisiones de diseño que
repercuten de manera importante en
el desarrollo
de la aplicación y que nos alejan
con frecuencia de otras
posibilidades. Pero,
como se repite en este apartado,
todo tiene sus ventajas y se hará un
énfasis a la
programación orientada a objetos y
sus fortalezas.
Algunos diseñadores de lenguajes han
dado por sentado que la programación
orientada a objetos, de por sí, no
es adecuada para resolver de manera
sencilla
todos los problemas de programación,
y hacen referencia al uso de
lenguajes de
programación multiparadigma.
La descripción de los otros
paradigmas se dejara para un futuro
apartado.
3. Programación orientada a objetos
Aunque existen distintas
terminologías para nombrar los
mismos conceptos y
características importantes de la
programación orientada a objetos,
abreviada
POO, es importante entender la
mayoría de ellos tanto por quien la
desconoce
como por aquel que la conoce.
Las computadoras, más que máquinas,
pueden considerarse como
herramientas
que permiten ampliar la mente ("bicicletas
para la mente", como se enorgullece
de
decir Steve Jobs), además de un
medio de expresión inherentemente
diferente.
Como resultado, las herramientas
empiezan a parecerse menos a
máquinas y
más a partes de nuestra mente, al
igual que ocurre con otros medios de
expresión
como la escritura, la pintura, la
escultura, la animación o la
filmación de películas.
La programación orientada a objetos
(POO) es una parte de este
movimiento
dirigido a utilizar las computadoras
como si de un medio de expresión se
tratara.
La programación orientada a objetos
asevera dar mayor flexibilidad,
facilitando los
cambios a los programas y es
ampliamente popular en la gran
escala de
ingeniería de software. Además, los
que proponen la POO argumentan que
es
mas fácil de aprender para quienes
son nuevos en la programación de
computadoras y que su abordaje a
menudo es más fácil de desarrollar y
mantener
prestándose así mismo más análisis
directo, codificación y
entendimiento de
situaciones complejas y
procedimientos comparado a otros
métodos de
programación.
4. ¿Qué es la POO?
La programación orientada a objetos
es una evolución lógica de la
programación
estructurada, en la que el concepto
de variables locales a un
procedimiento o
función, que no son accesibles a
otros procedimientos y funciones, se
hace
extensible a los propios
subprogramas que acceden a estas
variables. Pero la
programación orientada a objetos va
mucho más allá. En realidad, cambia
la
concepción de la metodología de
diseño de los programas.
En la programación orientada a
objetos, se definen objetos que
conforman una
aplicación. Estos objetos están
formados por una serie de
características y
operaciones que se pueden realizar
sobre los mismos. Estos objetos no
están
aislados en la aplicación, sino que
se comunican entre ellos.
Los programadores que emplean
lenguajes procedurales, escriben
funciones y
después les pasan datos. Los
programadores que emplean lenguajes
orientados a
objetos definen objetos con datos y
métodos y después envían mensajes a
los
objetos diciendo que realicen esos
métodos en sí mismos.
4.1 Conceptos fundamentales
La programación orientada a objetos
hace énfasis en los siguientes
conceptos:
• Abstracción: Cada objeto en el
sistema sirve como modelo de un
"agente"
abstracto que puede realizar
trabajo, informar y cambiar su
estado, y
"comunicarse" con otros objetos en
el sistema sin revelar cómo se
implementan estas características.
Los procesos, las funciones o los
métodos pueden también ser
abstraídos y cuando los están, una
variedad
de técnicas son requeridas para
ampliar una abstracción. Es la
habilidad de
un programa de ignorar los detalles
de un objeto. Por ejemplo “Rin Tin
Tin”
el perro puede ser tratado como
perro la mayoría de las veces, menos
cuando es abstraído apropiadamente
al nivel de canido (superclase de
perro) o carnívoro (superclase de
canido) y así sucesivamente.
• Encapsulamiento: También llamada
"ocultación de la información", esto
asegura que los objetos no pueden
cambiar el estado interno de otros
objetos de maneras inesperadas;
solamente los propios métodos
internos
del objeto pueden acceder a su
estado. Cada tipo de objeto expone
una
interfaz a otros objetos que
especifica cómo otros objetos pueden
interactuar con él. Algunos
lenguajes relajan esto, permitiendo
un acceso
directo a los datos internos del
objeto de una manera controlada y
limitando
el grado de abstracción.
• Polimorfismo: Las referencias y
las colecciones de objetos pueden
contener objetos de diferentes
tipos, y la invocación de un
comportamiento
en una referencia producirá el
comportamiento correcto para el tipo
real del
objeto referenciado. Cuando esto
ocurre en "tiempo de ejecución",
esta
última característica se llama
asignación tardía o asignación
dinámica.
Algunos lenguajes proporcionan
medios más estáticos (en "tiempo de
compilación") de polimorfismo, tales
como las plantillas y la sobrecarga
de
operadores de C++.
• Herencia: Organiza y facilita el
polimorfismo y la encapsulación
permitiendo
a los objetos ser definidos y
creados como tipos especializados de
objetos
preexistentes. Estos pueden
compartir (y extender) su
comportamiento sin
tener que reimplementar su
comportamiento. Esto suele hacerse
habitualmente agrupando los objetos
en clases o Class y las clases en
árboles o enrejados que reflejan un
comportamiento común.
Cada uno de estos conceptos se irá
detallando más a fondo en los
siguientes
apartados.
4.2 ¿Qué es un Objeto?
La respuesta a esta pregunta en
términos ajenos a la programación
parece simple.
Un objeto es una persona, animal o
cosa. Se distingue de otros objetos
por tener
unas determinadas características y
“sirve” para algo, o dicho de otra
forma, se
pueden realizar distintas
operaciones con/sobre ese objeto.
Por ejemplo: Una casa es un objeto.
CARACTERÍSTICAS: Número de pisos,
altura total en metros, color de la
fachada, número de ventanas, número
de puertas, ciudad, calle y número
donde
está ubicada, etc.
OPERACIONES: Construir, destruir,
pintar fachada, modificar alguna de
las
características, como por ejemplo,
abrir una nueva ventana, etc.
Evidentemente, cada objeto puede
definirse en función de multitud de
características y se pueden realizar
innumerables operaciones sobre él.
Ya en
términos de programación, será
misión del programador determinar
qué
características y que operaciones
interesa mantener sobre un objeto.
Por ejemplo,
sobre el objeto casa puede no ser
necesario conocer su ubicación y por
lo tanto,
dichas características no formarán
parte del objeto definido por el
programador. Lo
mismo podría decirse sobre las
operaciones.
Pensar en términos de objetos es muy
parecido a cómo lo haríamos en la
vida
real. Por ejemplo vamos a pensar en
un coche para tratar de modelizarlo
en un
esquema de POO. Diríamos que el
coche es el elemento principal que
tiene una
serie de características, como
podrían ser el color, el modelo o la
marca. Además
tiene una serie de funcionalidades
asociadas, como pueden ser ponerse
en
marcha, parar o aparcar.
En terminología de programación
orientada a objetos, a las
características del
objeto se les denomina ATRIBUTOS y a
las operaciones MÉTODOS. Cada uno de
estos métodos es un procedimiento o
una función perteneciente a un
objeto.
Un objeto está formado por una serie
de características o datos
(atributos) y una
serie de operaciones (métodos). No
puede concebirse únicamente en
función de
los datos o de las operaciones sino
en su conjunto.
4.3 Abstracción
Todos los lenguajes de programación
proporcionan abstracciones. Puede
incluso
afirmarse que la complejidad de los
problemas a resolver es directamente
proporcional a la clase (tipo) y
calidad de las abstracciones a
utilizar, entendiendo
por tipo "clase", aquello que se
desea abstraer. El lenguaje
ensamblador es una
pequeña abstracción de la máquina
subyacente. Muchos de los lenguajes
denominados "imperativos"
desarrollados a continuación del
antes mencionado
ensamblador (como Fortran, BASIC y
C) eran abstracciones a su vez del
lenguaje
citado. Estos lenguajes supusieron
una gran mejora sobre el lenguaje
ensamblador, pero su abstracción
principal aún exigía pensar en
términos de la
estructura del computador más que en
la del problema en sí a resolver. El
programador que haga uso de estos
lenguajes debe establecer una
asociación
entre el modelo de la máquina
(dentro del "espacio de la
solución", que es donde
se modela el problema, por ejemplo,
un computador) y el modelo del
problema
que de hecho trata de resolver (en
el "espacio del problema", que es
donde de
hecho el problema existe).
La abstracción consiste en aislar un
elemento de su contexto o del resto
de los
elementos que lo acompañan. El
término se refiere al énfasis en el
"¿qué hace?"
más que en el "¿cómo lo hace?". El
común denominador en la evolución de
los
lenguajes de programación, desde los
clásicos o imperativos hasta los
orientados
a objetos, ha sido el nivel de
abstracción del que cada uno de
ellos hace uso.
Se sabe que los lenguajes de
programación son las herramientas
mediante las
cuales los diseñadores de lenguajes
pueden implementar los modelos
abstractos.
La abstracción ofrecida por los
lenguajes de programación se puede
dividir en dos
categorías: abstracción de datos
(pertenecientes a los datos) y
abstracción de
control (perteneciente a las
estructuras de control).
La abstracción encarada desde el
punto de vista de la programación
orientada a
objetos expresa las características
esenciales de un objeto, las mismas
distinguen
al objeto de los demás. Además de
distinguir entre los objetos provee
límites
conceptuales. Entonces se puede
decir que la encapsulación separa
las
características esenciales de las no
dentro de un objeto. Si un objeto
tiene mas
características de las necesarias
los mismos resultarán difíciles de
usar, modificar,
construir y comprender.
La misma genera una ilusión de
simplicidad dado a que minimiza la
cantidad de
características que definen a un
objeto.
Y todo es un objeto. Hay que pensar
en cualquier objeto como una
variable:
almacena datos, permite que se le
"hagan peticiones", pidiéndole que
desempeñe
por sí mismo determinadas
operaciones, etc. En teoría, puede
acogerse cualquier
componente conceptual del problema a
resolver (bien sean perros,
edificios,
servicios, etc.) y representarlos
como objetos dentro de un programa.
4.4 Métodos y mensajes
Un término que se utiliza más
frecuentemente en Java y POO es
método, al ser
"una manera de hacer algo". Si se
desea, es posible seguir pensando en
funciones. Verdaderamente sólo hay
una diferencia sintáctica, pero de
ahora en
adelante se usará el término
"método" en lugar del término
"función".
Los métodos son las operaciones que
pueden realizarse sobre el objeto,
que
normalmente estarán incorporados en
forma de programas (código) que el
objeto
es capaz de ejecutar y que también
pone a disposición de sus
descendientes a
través de la herencia.
Las partes fundamentales de un
método son su nombre, sus
parámetros, el tipo
de retorno y el cuerpo.
He aquí su forma básica:
tipoRetorno nombreMetodo ( /* lista
de parámetros */ ) {
/ * Cuerpo del método */
}
El tipo de retorno es el tipo del
valor que surge del método tras ser
invocado. La
lista de parámetros indica los tipos
y nombres de las informaciones que
es
necesario pasar a ese método. Cada
método se identifica unívocamente
mediante
el nombre del método y la lista de
parámetros.
En Java los métodos pueden crearse
como parte de una clase. Es posible
que un
método pueda ser invocado sólo por
un objeto2, y ese objeto debe ser
capaz de
llevar a cabo esa llamada al método.
Si se invoca erróneamente a un
método de
un objeto, se generará un error en
tiempo de compilación. Se invoca a
un método
de un objeto escribiendo el nombre
del objeto seguido de un punto y el
nombre del
método con su lista de argumentos,
como: nombreObjeto.nombreMetodo
(arg1,
arg2, arg3). Por ejemplo, si se
tiene un método f ( ) que no recibe
ningún
parámetro y devuelve un dato de tipo
int, y si se tiene un objeto a para
el que
puede invocarse a f ( ), es posible
escribir:
int x = a.f ();
El tipo del valor de retorno debe
ser compatible con el tipo de x.
Este acto de invocar a un método
suele denominarse envío de un
mensaje a un
objeto. En el ejemplo de arriba, el
mensaje es f ( ) y el objeto es a.
La
programación orientada a objetos
suele resumirse como un simple
"envío de
mensajes a objetos".
Un ejemplo claro sería el siguiente:
. Ejemplo de Clase Mamífero:
Datos Métodos
Color Desplazarse
Tamaño Masticar
Peso Digerir
Cantidad de dientes Respirar
Cuadrúpedo/bípedo Parpadear
Edad Dormir
Ahora veamos un ejemplo más cercano
a la informática: Una ventana. Clase
Ventana:
Datos Métodos
Etiqueta de encabezado
Cerrarse
Abrirse
Tipo de letra encabezado Minimizarse
Maximizarse
Color de Fondo
Color de letra encabezado
Como puede verse, en esencia la POO
lo que nos permite es ampliar el
tipo de
datos con los que se puede trabajar.
Todos los hombres comparten las
características de la clase hombre.
Sin
embargo todos los hombres son
distintos entre sí, en estatura,
pigmentación, etc.
A cada uno de los hombres
particulares se le llama “objetos de
la clase
hombre”. Cada niño que nace es una
instanciación de la clase hombre.
Entonces, los métodos los podemos
considerar sinónimos de
comportamientos,
operaciones, procedimientos.
La aplicación de los métodos dará
distintos resultados dependiendo de
los
valores con los que trabaje.
Para poder mandar mensajes a los
objetos, necesitamos un operador, a
este le
llamamos el operador de envío. Cada
lenguaje puede tener el suyo, pero
es
frecuente que se utilicen los dos
puntos (‘:’) o el punto (‘.’) (En
C++ es el punto
simple, igual que para referirnos a
los elementos de los struct’s).
Así, si queremos enviarle el mensaje
Caminar al objeto Juan de la clase
hombre,
escribiríamos lo siguiente:
El operador de envío hace que se
ejecute la porción del código
agrupada bajo el
nombre del método, y el método
trabajara con los datos propios de
la instancia de
la clase a la que se refiera.
Referencias a sí mismo
Un caso especial ocurre cuando
estamos codificando un método de una
clase y
tenemos que referirnos a un dato o
un método del propio objeto; estamos
creando
la clase, o por así decirlo, dentro
del objeto mismo.
Entonces, para referirse al propio
objeto, en OOP se hace uso de el
mismo, si
mismo, self (en ingles). Self
(algunos lenguajes utilizan “self”,
pero lo mas común
es que utilicen “this”11: así lo
hacen C++ y Java, por ejemplo.) en
OOP se refiere al
propio objeto con el que se esta
trabajando. Por lo tanto, si estamos
escribiendo
un método de una clase y queremos
enviar un mensaje al propio objeto,
escribiríamos:
This.Ocultar ( );
En este punto, puede parecer que un
programa no es más que un montón de
objetos con métodos que toman otros
objetos como parámetros y envían
mensajes a esos otros objetos. Esto
es, sin duda, mucho de lo que está
curriendo,
pero en otros apartados se verá cómo
hacer el trabajo de bajo nivel
detallado,
tomando decisiones dentro de un
método.
4.5 Encapsulado y ocultación de la
información: Clases
El concepto de clase, es simplemente
una abstracción que hacemos de
nuestra
experiencia sensible. El ser humano
tiende a agrupar seres o cosas
–objetos- con
características similares en grupos
–clases-. Así, aun cuando existen
por ejemplo
multitud de vasos diferentes,
podemos reconocer un vaso en cuanto
lo vemos,
incluso aun cuando ese modelo
concreto de vaso no lo hayamos visto
nunca. El
concepto vaso es una abstracción de
nuestra experiencia sensible. Al
igual que
ciertos ejemplos que pusimos en el
apartado anterior.
Quizás el ejemplo mas claro para
exponer esto lo tengamos en las
taxonomías;
los biólogos han dividido a todo ser
(vivo o inerte) sobre la tierra en
clases.
11 ver el apartado 12.5
Ellos, llaman a cada una de estas
parcelas reino, tipo, clase,
especie, orden,
familia, género, etc.; sin embargo,
nosotros a todas las llamaremos del
mismo
modo: clase. Así, hablaremos de la
clase animal, clase vegetal y clase
mineral, o
de la clase félidos y de las clases
leo (león) y tigres (tigre).
Cada clase posee unas cualidades que
la diferencian de las otras. Así,
por
ejemplo, los vegetales se
diferencian de los minerales –entre
otras muchas cosas-
en que los primeros son seres vivos
y los minerales no. De los animales
se
diferencian en que las plantas son
capaces de sintetizar clorofila a
partir de la luz
solar y los animales no.
Como vemos, el ser humano tiende, de
un modo natural a clasificar los
objetos del
mundo que le rodean en clases; son
definiciones estructuralistas de la
naturaleza
al estilo de la escuela francesa de
Saussure12.
Encapsulación
Un método de una clase puede llamar
a otros métodos de su misma clase y
pude
cambiar los valores de los datos de
su misma clase. Es esta la forma
correcta de
hacerse: los datos de una clase solo
deben ser alterados por los métodos
de su
clase; y no de forma directa (que es
como cambiamos los valores de las
variables
en un programa). Esta es una regla
de oro que no debe de olvidarse:
todos los
datos de una clase son privados y se
accede a ellos mediante métodos
públicos.
12 Ferdinand de Saussure. Lingüista
suizo, considerado el fundador de la
lingüística moderna.
Según los dos modos comentados,
tomemos como ejemplo un objeto
perteneciente a la clase marco,
modificaremos su dato nY1
(coordenada superior
izquierda) de dos modos distintos:
directamente y mediante el método
PonerY1 ( ).
Cambio directo: oCajaGeneral.nY1 =
12;
Cambio mediante invocación de
método: oCajaGeneral.PonerY1 (12);
Es más cómodo el primer método, ya
que hay que escribir menos para
cambiar el
valor del dato y además, a la hora
de construir la clase, no es
necesario crear un
método para cambiar cada uno de los
datos del objeto. Sin embargo, y
como se
ha ido comentando, la OOP recomienda
efusivamente que se utilice el
segundo
procedimiento. La razón es bien
simple: una clase debe ser una
estructura
cerrada, no se debe poder acceder a
ella si no es a través de los
métodos
definidos para ella. Si hacemos nY1
público (para que pueda ser accedido
directamente), estamos violando el
principio de encapsulación.
Encapsulación es la característica
de la POO que hace que un objeto sea
una
caja negra. Se le envía un mensaje
al objeto y este responde ejecutando
el
método apropiado.
Un método de una clase puede llamar
a métodos de la misma clase.
Gracias a la encapsulación una clase
que ha sido programada y probada
puede
usarse sin temor a que la
programación de otros objetos
basados en dichas clases
tengan errores.
En lugar de considerar el programa
como una enorme entidad, la
encapsulación
permite dividir un programa en
componentes más pequeños e
independientes.
Cada componente es autónomo y
realiza su labor independientemente
de los
demás componentes.
La encapsulación mantiene esa
independencia ocultando los detalles
internos de
cada componente, mediante una
interfaz externa.
Una interfaz lista los servicios
proporcionados por un componente. La
interfaz
actúa como un contrato con el mundo
exterior que define exactamente lo
que una
entidad externa puede hacer con el
objeto.
Una interfaz es un panel de control
para el objeto. Al definir una
interfaz puede
especificarse cuales métodos serán
definidos como públicos, privados y
protegidos.
Los métodos públicos son aquellos
que están disponibles para todo
aquel que
cree objetos basados en la clase.
Los métodos privados son aquellos
que solo son invocados por otros
métodos de
la clase.
Cuando un comportamiento se desee
poner a disposición del mundo
exterior,
debe tener acceso público. Lo que se
desee ocultar debe tener acceso
protegido o privado.
¿Porqué utilizar encapsulación?
. Independencia: Se puede reutilizar
el objeto en cualquier parte. Cuando
se
encapsulan de forma apropiada los
objetos, estos no están limitados a
un
programa en particular. Para
utilizarlo solo hay que poner en
acción su
interfaz.
. Transparencia: La encapsulación le
permite hacer cambios transparentes
al objeto, en tanto no se altere la
interfaz.
. Autonomía: El uso de un objeto
encapsulado no causará efectos
secundarios inesperados entre el
objeto y el resto del programa.
Puesto
que el objeto es autónomo, no tendrá
ninguna interacción con el programa
más allá de lo establecido por la
interfaz.
Gracias a la encapsulación, una
clase, cuando ha sido programada y
probada
hasta comprobar que no tiene fallos,
podemos usarla sin miedo a que al
programar otros objetos estos puedan
interferir con los primeros
produciendo
efectos colaterales indeseables que
arruinen nuestro trabajo; esto
también permite
depurar (eliminar errores de
programación) con suma facilidad, ya
que si un objeto
falla, el error solo puede estar en
esa clase y no en ninguna otra.
4.6 Modularidad
Un modulo puede definirse
diversamente, pero generalmente debe
ser un
componente de un sistema mas grande,
y opera independientemente dentro de
las funciones de ese sistema con los
otros componentes.
La modularidad es la propiedad que
mide hasta que punto han estado
compuestos de partes separadas
llamadas módulos. Los programas
tienen
muchas relaciones mutuas directas
entre cualquiera de dos partes
aleatorias del
código del programa.
4.7 Polimorfismo
Se denomina polimorfismo a la
capacidad del código de un programa
para ser
utilizado con diferentes tipos de
datos u objetos. También se puede
aplicar a la
propiedad que poseen algunas
operaciones de tener un
comportamiento diferente
dependiendo del objeto (o tipo de
dato) sobre el que se aplican.
El concepto de polimorfismo se puede
aplicar tanto a funciones como a
tipos de
datos. Así nacen los conceptos de
funciones polimórficas y tipos
polimórficos. Las
primeras son aquellas funciones que
pueden evaluarse y/o ser aplicadas a
diferentes tipos de datos de forma
indistinta; los tipos polimórficos,
por su parte,
son aquellos tipos de datos que
contienen al menos un elemento cuyo
tipo no está
especificado.
Se puede clasificar el polimorfismo
en dos grandes clases:
• Polimorfismo dinámico (o
polimorfismo ad hoc) es aquél en el
que el
código no incluye ningún tipo de
especificación sobre el tipo de
datos sobre
el que se trabaja. Así, puede ser
utilizado a todo tipo de datos
compatible.
• Polimorfismo estático (o
polimorfismo paramétrico) es aquél
en el que
los tipos a los que se aplica el
polimorfismo deben ser explicitados
y
declarados uno por uno antes de
poder ser utilizados.
El polimorfismo dinámico unido a la
herencia es lo que en ocasiones se
conoce
como programación genérica.
Pongamos por ejemplo las clase
hombre, vaca y perro, si todos les
damos la
orden –enviamos el mensaje-Come,
cada uno de ellos sabe como hacerlo
y
realizara este comportamiento a su
modo.
Veamos otro ejemplo algo más
ilustrativo. Tomemos las clases
barco, avión y
coche, todas ellas derivadas de la
clase padre vehículo; si les
enviamos el
mensaje Desplázate, cada una de
ellas sabe como hacerlo.
Realmente, y para ser exactos, los
mensajes no se envían a las clases,
sino a
todos o algunos de los objetos
instaciados de las clases. Así, por
ejemplo,
podemos decirle a los objetos Juan
Sebastián el Cano y Kontiqui, de la
clase
barco que se desplacen, con los que
el resto de los objetos de esa clase
permanecerán inmóviles.
Del mismo modo, si tenemos en
pantalla cinco recuadros (marcos) y
tres textos,
podemos decirle a tres de los
recuadros y a dos de los textos que
cambien de
color y no decírselo a los demás
objetos. Todos estos sabrán como
hacerlo
porque hemos redefinido para cada
uno de ellos su método Pintarse que
bien
podría estar en la clase padre
Visual (conjunto de objetos que
pueden visualizarse
en pantalla).
En programación tradicional, debemos
crear un nombre distinto para la
acción de
pintarse, si se trata de un texto o
de un marco; en POO el mismo nombre
nos sirve
para todas las clases creadas si así
lo queremos, lo que suele ser
habitual. El
mismo nombre suele usarse para
realizar acciones similares en
clases diferentes.
Si enviamos el mensaje Imprímete a
objetos de distintas clases, cada
uno se
imprimirá como le corresponda, ya
que todos saben como hacerlo.
El polimorfismo facilita el trabajo,
ya que gracias a él, el número de
nombre de
métodos que tenemos que recordar
disminuye ostensiblemente.
La mayor ventaja la obtendremos en
métodos con igual nombre aplicados a
las
clases que se encuentran próximas a
la raíz del árbol de clases, ya que
estos
métodos afectaran a todas las clases
que de ellas se deriven.
4.8 Jerarquización: Herencia y
Objetos compuestos
Herencia
Esta es la cualidad más importante
de un sistema POO, la que nos dará
mayor
potencia y productividad,
permitiéndonos ahorrar horas y horas
de codificación y
de depuración de errores.
Sería mejor si pudiéramos hacer uso
de una clase ya existente, clonarla,
y
después hacer al "clon" las
adiciones y modificaciones que sean
necesarias.
Efectivamente, esto se logra
mediante la herencia, con la
excepción de que si se
cambia la clase original (denominada
la clase base, clase súper o clase
padre), el
"clon" modificado (denominado clase
derivada, clase heredada, subclase o
clase
hijo) también reflejaría esos
cambios.
Puede haber más de una clase
derivada. Un tipo hace más que
definir los límites
de un conjunto de objetos; también
tiene relaciones con otros tipos.
Dos tipos
pueden tener características y
comportamientos en común, pero un
tipo puede
contener más características que
otro y también puede manipular más
mensajes
(o gestionarlos de manera distinta).
La herencia expresa esta semejanza
entre
tipos haciendo uso del concepto de
tipos base y tipos derivados. Un
tipo base
contiene todas las características y
comportamientos que comparten los
tipos que
de él se derivan. A partir del tipo
base, es posible derivar otros tipos
para expresar
las distintas maneras de llevar a
cabo esta idea.
Por ejemplo, una máquina de
reciclaje de basura clasifica los
desperdicios. El tipo
base es "basura", y cada desperdicio
tiene su propio peso, valor, etc. y
puede ser
fragmentado, derretido o
descompuesto.
Así, se derivan tipos de basura más
específicos que pueden tener
características
adicionales (una botella tiene un
color), o comportamientos (el
aluminio se puede
modelar, una lata de acero tiene
capacidades magnéticas). Además,
algunos
comportamientos pueden ser distintos
(el valor del papel depende de su
tipo y
condición). El uso de la herencia
permite construir una jerarquía de
tipos que
expresa el problema que se trata de
resolver en términos de los propios
tipos.
Un segundo ejemplo es el clásico de
la "figura geométrica" utilizada
generalmente
en sistemas de diseño por computador
o en simulaciones de juegos. El tipo
base
es "figura" y cada una de ellas
tiene un tamaño, color, posición,
etc. Cada figura
puede dibujarse, borrarse, moverse,
colorearse, etc.
A partir de ésta, se pueden derivar
(heredar) figuras específicas:
círculos,
cuadrados, triángulos, etc.,
pudiendo tener cada uno de los
cuales características
y comportamientos adicionales.
Algunos comportamientos pueden ser
distintos,
como pudiera ser el cálculo del área
de los distintos tipos de figuras.
La jerarquía
de tipos engloba tanto las
similitudes como las diferencias
entre las figuras.
Otro ejemplo, la clase león que se
comenta en la figura de la
clasificación
taxonómica en el apartado 4.5,
hereda cualidades –métodos- de todas
las clases
predecesoras –padres- y posee
métodos propios, diferentes a los
del resto de las
clases. Es decir, las clases van
especializándose según se avanza en
el árbol
taxonómico. Cada vez que creamos una
clase heredada de otra (la padre)
añadimos métodos a la clase padre o
modificamos alguno de los métodos de
la
clase padre.
Veamos que hereda la clase león de
sus clases padre:
Clase Que hereda
Vertebrados Espina dorsal
Mamíferos Se alimenta con leche
materna
Carnívoros Al ser adultos se
alimentan de carne
La clase león hereda todos los
métodos de las clases padre y añade
métodos
nuevos que forman su clase
distinguiéndola del resto de las
clases: por ejemplo el
color de su piel.
Pongamos ahora un ejemplo algo más
informático: supongamos que usted ha
construido una clase que le permite
leer números enteros desde teclado
con un
formato determinado, calcular su IVA
y almacenarlos en un archivo. Si
desea
poder hacer lo mismo con números
reales (para que admitan decimales),
solo
deberá crear una nueva subclase para
que herede de la clase padre todos
sus
métodos y redefinirá solo el método
de lectura de teclado. Esta nueva
clase sabe
almacenar y mostrar los números con
formato porque lo sabe su clase
padre.
Las cualidades comunes que comparten
distintas clases, pueden y deben
agruparse para formar una clase
padre. Por ejemplo, usted podría
derivar las
clases presupuesto y factura de la
superclase pedidos, ya que estas
clases
comparten características comunes.
De este modo, la clase padre
poseería los
métodos comunes a todas ellas y solo
tendríamos que añadir aquellos
métodos
propios de cada una de las
subclases, pudiendo reutilizar el
código escrito en la
superclase desde cada una de las
clases derivadas. Así, si enseñamos
a la clase
padre a imprimirse, cada uno de los
objetos de las clases inferiores
sabrán
automáticamente y sin escribir ni
una sola línea más de código
imprimirse.
Es así, que la herencia es la
cualidad mas importante de la POO ya
que le permite
reutilizar todo el código escrito
para las superclases re-escribiendo
solo aquellas
diferencias que existan entre estas
y las subclases.
Muchas veces las clases
–especialmente aquellas que se
encuentran próximas a
la raíz en el árbol de la jerarquía
de clases- son abstractas. Es decir,
solo existen
para proporcionar una base para la
creación de clases mas especificas,
y por lo
tanto no puede instanciarse de
ellas; son las clases virtuales.
Una subclase hereda de su superclase
solo aquellos miembros visibles
desde la
clase hija y por lo tanto solo puede
redefinir estos.
Una subclase tiene forzosamente que
redefinir aquellos métodos que han
sido
definidos como abstractos en la
clase padre o padres.
Es habitual que la herencia suscite
un pequeño debate: ¿debería la
herencia
superponer sólo las funciones de la
clase base (sin añadir nuevas
funciones
miembro que no se encuentren en
ésta)?. Esto significaría que el
tipo derivado sea
exactamente el mismo tipo que el de
la clase base, puesto que tendría
exactamente la misma interfaz.
Consecuentemente, es posible
sustituir un objeto
de la clase derivada por otro de la
clase base. A esto se le puede
considerar
sustitución pura, y a menudo se le
llama el principio de sustitución.
De cierta
forma, ésta es la manera ideal de
tratar la herencia. Habitualmente, a
la relación
entre la clase base y sus derivadas
que sigue esta filosofía se le
denomina
relación es-un, pues es posible
decir que "un círculo es un
polígono". Una manera
de probar la herencia es determinar
si es posible aplicar la relación
es-un a las
clases en liza, y tiene sentido.
Hay veces en las que es necesario
añadir nuevos elementos a la
interfaz del tipo
derivado, extendiendo así la
interfaz y creando un nuevo tipo.
Éste puede ser
también sustituido por el tipo base,
pero la sustitución no es perfecta
pues las
nuevas funciones no serían
accesibles desde el tipo base. Esta
relación puede
describirse como la relación
es-como-un" el nuevo tipo tiene la
interfaz del viejo
pero además contiene otras
funciones, así que no se puede decir
que sean
exactamente iguales. Considérese por
ejemplo un acondicionador de aire.
Supongamos que una casa está
cableada y tiene las botoneras para
refrescarla,
es decir, tiene una interfaz que
permite controlar la temperatura.
Imagínese que se
estropea el acondicionador de aire y
se reemplaza por una bomba de calor
que
puede tanto enfriar como calentar.
La bomba de calor es-como-un
acondicionador
de aire, pero puede hacer más
funciones. Dado que el sistema de
control de la
casa está diseñado exclusivamente
para controlar el enfriado, se
encuentra
restringido a la comunicación con la
parte "enfriadora" del nuevo objeto.
Es
necesario extender la interfaz del
nuevo objeto, y el sistema existente
únicamente
conoce la interfaz original.
Por supuesto, una vez que uno ve
este diseño, está claro que la clase
base
"sistema de enfriado" no es lo
suficientemente general, y debería
renombrarse a
"sistema de control de temperatura"
de manera que también pueda incluir
calentamiento -punto en el que el
principio de sustitución funcionará.
Sin embargo,
este diagrama es un ejemplo de lo
que puede ocurrir en el diseño y en
el mundo
real.
Cuando se ve el principio de
sustitución es fácil sentir que este
principio (la
sustitución pura) es la única manera
de hacer las cosas, y de hecho, es
bueno
para los diseños que funcionen así.
Pero hay veces que está claro que
hay que
añadir nuevas funciones a la
interfaz de la clase derivada.
4.9 Ligadura Dinámica
La ligadura en las llamadas a
métodos
La conexión de una llamada a un
método se denomina ligadura. Cuando
se lleva a
cabo la ligadura antes de ejecutar
el programa (por parte del
compilador y el
montador, cuando lo hay) se denomina
ligadura temprana. Puede que este
término parezca extraño pues nunca
ha sido una opción con los lenguajes
procedurales. Los compiladores de C
tienen un único modo de invocar a un
método utilizando la ligadura
temprana.
La solución es la ligadura tardía,
que implica que la correspondencia
se da en
tiempo de ejecución, basándose en el
tipo de objeto. La ligadura tardía
se
denomina también dinámica o en
tiempo de ejecución.
Cuando un lenguaje implementa la
ligadura tardía, debe haber algún
mecanismo
para determinar el tipo de objeto en
tiempo de ejecución e invocar al
método
adecuado. Es decir, el compilador
sigue sin saber el tipo de objeto,
pero el
mecanismo de llamada a métodos
averigua e invoca al cuerpo de
método
correcto. El mecanismo de la
ligadura tardía varía de un lenguaje
a otro, pero se
puede imaginar que es necesario
instalar algún tipo de información
en los objetos.
Toda ligadura de métodos en Java se
basa en la ligadura tardía a menos
que se
haya declarado un método como
constante. Esto significa que
ordinariamente no
es necesario tomar decisiones sobre
si se dará la ligadura tardía, sino
que esta
decisión se tomará automáticamente.
5. Diseño tradicional vrs. Diseño OO
Como hemos visto hasta ahora, el
Diseño Orientado a Objetos ofrece
muchas
alternativas para la resolución de
problemas al programador, y hemos
mencionado
también los distintos paradigmas de
programación.
Ahora, repasemos puntos clave del
diseño OO:
• El diseño OO se enfoca sobre
objetos y clases basados en el mundo
real.
• Hace un énfasis en el estado,
comportamientos e interacciones de
objetos
• Provee las siguientes ventajas:
o Desarrollo rápido de aplicaciones
(RAD)
o Aumenta la calidad
o Provee un fácil mantenimiento
o Mejora la modificación
o Incrementa el reutilizamiento de
software
Pero como todo en este mundo,
también tiene sus limitaciones e
inconvenientes.
De las primeras no cabe ni hablar,
porque aun cuando sea el sistema mas
apropiado del que se dispone para
programar, todavía se enfrenta con
ciertos
problemas de diseño.
El mayor inconveniente real proviene
de un “error” de planteamiento; y
como casi
siempre, estos son de mucha mayor
dificultad a la hora de
solucionarlos. El
problema, según comenta uno de los
mejores analistas de hoy en día,
Jeff
Duntemann15, en un articulo de la
revista Dr. Dobbs16, es que “la
encapsulación y
la herencia se hallan en esquinas
opuestas de la casa”.
15 Escritor, Editor, Tecnólogo. Más
en www.duntemann.com
16 Dr. Dobbs Journal, más en
www.ddj.com
Es decir, la encapsulación choca
frontalmente con la herencia, y sin
embargo, son
dos piedras angulares de la POO.
Por un lado decimos que los objetos
deben ser totalmente independientes
y
autónomos, y por otro, al heredar
unas clases de otras, estamos
dejando fuera de
un objeto perteneciente a una clase
hija gran parte de la información
que esta
necesita para poder comportarse.
Otro inconveniente que se puede
observar es el que estriba en la
imposibilidad de
utilización conjunta de objetos de
distintos programadores.
Un ejemplo simple, supongamos que se
monta un coche en un garaje, puede
comprar un carburador de cualquier
marca y montarlo en un coche de otra
marca,
unos asientos de uno y montarlos en
la carrocería de otro; esto mismo
puede
hacerse en POO.
Si Microsoft fabrica la clase Menú,
que deriva de la clase Visual y
Borland fabrica
la clase Ventana aunque también
derive de la clase Visual, no puede
conseguir
coger el menú de una y la ventana de
la otra, a menos que todas las
clases
superiores (en este caso solo una)
sean exactamente iguales: tengan el
mismo
nombre, contengan los mismos datos y
los mismos métodos y en estos, todos
los
parámetros deben coincidir en orden
y en tipo.
Este problema por ahora no tiene
solución, y lo peor es que no se
vislumbra que la
tenga en un futuro próximo, ya que
para ello seria necesario normalizar
las clases
(al menos las mas habituales), pero
si no nos ponemos de acuerdo para
utilizar un
mismo HTML ¿Cómo nos vamos a poner
de acuerdo para esto?
Critica
Los taxonomías jerárquicas no
coinciden a menudo con el mundo real
y los
cambios del mundo real según algunos
críticos, y debe evitarse. Sin
embargo,
muchos defensores de POO también
hacen pensar en evitar las
jerarquías y usar
las técnicas de POO en cambio como
la composición.
También, muchos sienten que POO
corre lo opuesto a la filosofía de
planeación
correlativa y base de datos
relacionales, mientras se vuelve a
los arreglos de la
base de datos de navegación de los
años sesenta. No está claro que ésta
es la
falta de POO, desde que la
planeación de bases de datos es
fundamentalmente
basada en las diferentes premisas
que la planeación basada en objetos.
En
cualquier caso, las bases de datos
relacionales trazan a las
asociaciones en los
modelos basados en objetos, y las
diferencias parecen ser
completamente
debidas a las diferencias en el
enfoque.
Hay una historia de
desinterpretación de la relación
entre planear basado en
objetos y en la correlación a lo que
puede enturbiar este problema. Hay
también,
variaciones en las opiniones sobre
los papeles y definiciones de cada
uno.
La desigualdad de impedancia entre
las bases de datos y POO se causa
por la
diferencia de balanza entre
funcionamientos realizados por los
objetos y bases de
datos; las transacciones de la base
de datos, la unidad más pequeña de
trabajo
realizada por las bases de datos,
son mucho más grandes que cualquier
funcionamiento proporcionado por los
objetos de POO.
Mientras se exige que POO es buena
para "aplicaciones grandes”, otros
sienten
que deben reducirse las aplicaciones
grandes en cambio a muchas
aplicaciones
pequeñas, como procedimientos
manejados a eventos que " se
alimentan" fuera
de una base de datos y armazones de
interfaz de usuario basadas en
declaratorias de programación.
Está reconocido que POO
necesariamente no quiere decir falta
de complejidad.
6. Evolución de los Lenguajes
Orientados a Objetos
Los conceptos de la programación
orientada a objetos tienen origen en
Simula 67,
un lenguaje diseñado para hacer
simulaciones, creado por Ole-Johan
Dahl y
Kristen Nygaard del Centro de
Cómputo Noruego en Oslo. Según se
informa, la
historia es que trabajaban en
simulaciones de naves, y fueron
confundidos por la
explosión combinatoria de cómo las
diversas cualidades de diversas
naves podían
afectar unas a las otras. La idea
ocurrió para agrupar los diversos
tipos de naves
en diversas clases de objetos,
siendo responsable cada clase de
objetos de definir
sus propios datos y comportamientos.
Fueron refinados más tarde en
Smalltalk,
que fue desarrollado en Simula en
Xerox PARC(Palo Alto Research
Center) pero
diseñado para ser un sistema
completamente dinámico en el cual
los objetos se
podrían crear y modificar "en
marcha" en lugar de tener un sistema
basado en
programas estáticos.
La programación orientada a objetos
tomó posición como la metodología de
programación dominante a mediados de
los años ochenta, en gran parte
debido a
la influencia de C++, una extensión
del lenguaje de programación C. Su
dominación fue consolidada gracias
al auge de las Interfaces gráficas
de usuario
(GUI por sus siglas en ingles), para
los cuales la programación orientada
a objetos
está particularmente bien adaptada.
En el ETH de Zürich, Niklaus Wirth y
sus colegas tuvieron también
investigaciones
como tópicos de abstracción de datos
y programación modular. El lenguaje
Modula-2 incluye ambos, y su diseño
sucesor, Oberon incluye una
aproximación
distintiva a orientación a objetos,
clases y semejantes. El acercamiento
es
diferente a Smalltalk, y muy
diferente a C++.
Las características de Orientación a
Objetos han sido añadidas a muchos
lenguajes existentes durante el
tiempo, incluyendo Ada, BASIC, Lisp,
Fortran,
Pascal, y otros. Agregando esos
aspectos a lenguajes que no fueron
inicialmente
diseñados para ello condujo a
problemas con la compatibilidad y
mantenimiento
de código. Los lenguajes Orientados
a Objetos “Puros”, por otra parte,
carecían de
aspectos que muchos programadores
han venido dependiendo. Para cruzar
esta
brecha, muchos intentos han sido
hechos para crear nuevos lenguajes
basados en
métodos Orientados a Objetos pero
permitiendo algunos aspectos
procedurales
en formas “seguras”.
El lenguaje Eiffel de Bertrand
Meyer’s17 fue un temprano y
moderadamente exitoso
lenguaje con esos objetivos.
En la década pasada Java ha emergido
ampliamente en uso parcialmente por
su
similitud a C y C++, pero quizás lo
más importante de su implementación
es el uso
de una maquina virtual que es
proyectada a correr código
inalterado sobre muchas
plataformas distintas. Esta ultima
característica lo ha hecho muy
atractivo en
amplias tiendas de desarrollo y
ambientes heterogéneos.
La iniciativa de la plataforma .NET
de Microsoft ha tenido un objetivo
similar e
incluye/soporta varios nuevos
lenguajes, o variantes de algunos
viejos.
Más recientemente, un número de
lenguajes ha surgido para ser
primariamente
orientados a objetos aun compatibles
con la metodología procedural, tales
como
Python y Ruby. Al lado de Java,
probablemente el mas comercial
lenguaje
orientado a objetos reciente es
Visual Basic .NET y C# diseñados
para la
plataforma .NET de Microsoft.
Tal como la programación procedural
lleva a refinamientos de técnicas
como la
programación estructurada, el
software orientado a objetos moderno
diseña
métodos que incluye refinamiento
como el uso de diseño de patrones,
diseño por
convenios, y lenguajes de modelado
(tal es UML, usado en ingeniería de
software).
17 Bertrand Meyer, (nacido en
Francia en 1950). Es uno de los
primeros y vocales más proponentes
de la POO. Sus libros
sobre “Construcción de Software” son
considerados los mejores trabajos
presentes para la presentación de
POO.
Para el aprendizaje
En el pasado, han existido muchas
disputas como cual es el mejor
lenguaje para
comenzar con la programación
orientada a objetos. Hay dos
diferentes enfoques a
tomar cuando se comienza con este
estilo de programación. El primero
es la idea
que lo mejor es empezar con un
lenguaje simple como Java, donde el
aprendizaje
se enfoca sobre la orientación a
objetos y no es perturbado por
semánticas
complejas del lenguaje. Aun así,
otra razón es que lo mejor es
empezar con un
lenguaje más complicado como C++, el
cual soporta con más precisión todas
las
estructuras y capacidades prescritas
por el lenguaje de modelado
unificado (UML),
aunque no hay un lenguaje conocido
el cual esencialmente soporte
totalmente las
capacidades de UML. En la versión
utilizada para el entorno de
desarrollo Eclipse
para este ensayo esta instalado la
versión Eclipse UML 3.10 Free
Edition de
Omondo.
7. Lenguajes orientados a objetos
Entre los lenguajes orientados a
objetos destacan los siguientes:
• Smalltalk
• Objective-C
• C++
• Ada 95
• Java
• Ocaml
• Python
• Delphi
• Lexico (en castellano)
• C#
• Eiffel
• Ruby
• ActionScript
• Visual Basic
• PHP
• PowerBuilder
• Clarion
Estos lenguajes de programación son
muy avanzados en orientación a
objetos.
Al igual que C++ otros lenguajes,
como OOCOBOL, OOLISP, OOPROLOG y
Object REXX, han sido creados
añadiendo extensiones orientadas a
objetos a un
lenguaje de programación clásico.
Un nuevo paso en la abstracción de
paradigmas de programación es la
Programación Orientada a Aspectos
(POA). Aunque es todavía una
metodología
en estado de maduración, cada vez
atrae a más investigadores e incluso
proyectos comerciales en todo el
mundo.
8. Java
8.1 ¿Qué es Java?
Java es un lenguaje de desarrollo de
propósito general, y como tal es
válido para
realizar todo tipo de aplicaciones
profesionales.
Entonces, ¿es simplemente otro
lenguaje más? Definitivamente no.
Incluye una
combinación de características que
lo hacen único y está siendo
adoptado por
multitud de fabricantes como
herramienta básica para el
desarrollo de aplicaciones
comerciales de gran repercusión.
¿Qué lo hace distinto de los demás
lenguajes?
Una de las características más
importantes es que los programas
“ejecutables”,
creados por el compilador de Java,
son independientes de la
arquitectura. Se
ejecutan indistintamente en una gran
variedad de equipos con diferentes
microprocesadores y sistemas
operativos.
• De momento, es público. Puede
conseguirse un JDK (Java Developer's
Kit) o
Kit de desarrollo de aplicaciones
Java gratis. No se sabe si en un
futuro seguirá
siéndolo.
• Permite escribir Applets (pequeños
programas que se insertan en una
página
HTML) y se ejecutan en el ordenador
local.
• Se pueden escribir aplicaciones
para intrarredes, aplicaciones
cliente/servidor,
aplicaciones distribuidas en redes
locales y en Internet.
• Es fácil de aprender y está bien
estructurado.
• Las aplicaciones son fiables.
Puede controlarse su seguridad
frente al acceso a
recursos del sistema y es capaz de
gestionar permisos y criptografía.
También,
según Sun, la seguridad frente a
virus a través de redes locales e
Internet está
garantizada. Aunque al igual que ha
ocurrido con otras tecnologías y
aplicaciones, se han descubierto, y
posteriormente subsanado, “agujeros”
en la
seguridad de Java.
¿Qué se puede programar con Java?
Si tenía preconcebida la idea de que
con Java sólo se programan applets
para
páginas Web, está completamente
equivocado. Ya que Java es un
lenguaje de
propósito general, puede programarse
en él cualquier cosa:
• Aplicaciones independientes. Como
con cualquier otro lenguaje de
propósito
general.
• Applets. Pequeñas aplicaciones que
se ejecutan en un documento HTML,
siempre y cuando el navegador
soporte Java, como ocurre con los
navegadores HotJava y las últimas
versiones de Netscape y el
explorador de
Internet de Microsoft.
Para todo aquel que no conozca la
programación orientada a objetos,
este
lenguaje es ideal para aprender
todos sus conceptos, ya que en cada
paso de su
aprendizaje se va comprobando que
las cosas se hacen en la forma
natural de
hacerlas, sin sorpresas ni
comportamientos extraños de los
programas. A medida
que se va aprendiendo, se va
fomentando en el programador, y sin
esfuerzo, un
buen estilo de programación
orientada a objetos. En realidad, no
puede ser de otra
forma, ya que Java impide “hacer
cosas extrañas” y, además, no
permite
“abandonar” la programación
orientada a objetos, como ocurre con
otros lenguajes
de programación. Esto es bastante
conveniente, de lo contrario, un
programador
que está aprendiendo puede sentir la
tentación de “volver” a lo que
conoce (la
programación tradicional).
8.2 Breve Historia de Java
El lenguaje Java™ fue creado por Sun
Microsystems Inc. en un proceso por
etapas que arranca en 1990, año en
el que Sun creó un grupo de trabajo,
liderado
por James Gosling, para desarrollar
un sistema para controlar
electrodomésticos e
incluso PDAs o Asistentes Personales
(pequeños ordenadores) que, además,
permitiera la conexión a redes de
ordenadores. Se pretendía crear un
hardware
polivalente, con un Sistema
Operativo eficiente (SunOS) y un
lenguaje de
desarrollo denominado Oak (roble),
el precursor de Java. El proyecto
finalizó en
1992 y resultó un completo fracaso
debido al excesivo coste del
producto, con
relación a alternativas similares,
tras lo cual el grupo se disolvió.
Por entonces aparece Mosaic y la
World Wide Web. Después de la
disolución del
grupo de trabajo, únicamente quedaba
del proyecto el lenguaje Oak.
Gracias a
una acertada decisión de distribuir
libremente el lenguaje por la Red de
Redes y al
auge y la facilidad de acceso a
Internet, propiciado por la WWW, el
lenguaje se
popularizó y se consiguió que una
gran cantidad de programadores lo
depurasen y
terminasen de perfilar la forma y
usos del mismo. A partir de este
momento, el
lenguaje se difunde a una velocidad
vertiginosa, añadiéndosele numerosas
clases
y funcionalidad para TCP/IP. El
nombre del lenguaje tuvo que ser
cambiado ya
que existía otro llamado Oak. El
nombre “Java” surgió en una de las
sesiones de
“brainstorming” celebradas por el
equipo de desarrollo del lenguaje.
Buscaban un
nombre que evocara la esencia de la
tecnología (viveza, animación,
rapidez,
interactividad…). Java fue elegido
de entre muchísimas propuestas. No
es un
acrónimo, sino únicamente algo
humeante, caliente y que a muchos
programadores les gusta beber en
grandes cantidades: una taza de café
(Java en
argot Inglés americano). De esta
forma, Sun lanzó las primeras
versiones de Java
a principios de 1995.
Desde entonces, Sun ha sabido
manejar inteligentemente el éxito
obtenido por su
lenguaje, concediéndose licencias a
cualquiera sin ningún problema,
fomentando
su uso entre la comunidad
informática y extendiendo las
especificaciones y
funcionalidad del lenguaje.
8.3 Características de Java
• Es intrínsecamente orientado a
objetos.
• Funciona perfectamente en red.
• Aprovecha características de la
mayoría de los lenguajes modernos
evitando
sus inconvenientes. En particular
los del C++.
• Tiene una gran funcionalidad
gracias a sus librerías (clases).
• NO tiene punteros manejables por
el programador, aunque los maneja
interna
y transparentemente.
• El manejo de la memoria no es un
problema, la gestiona el propio
lenguaje y
no el programador.
• Genera aplicaciones con pocos
errores posibles.
• Incorpora Multi-Threading (para
permitir la ejecución de tareas
concurrentes
dentro de un mismo programa).
El lenguaje Java puede considerarse
como una evolución del C++. La
sintaxis es
parecida a la de este lenguaje, por
lo que se hace referencia a dicho
lenguaje. A
pesar de que puede considerarse como
una evolución del C++ no acarrea los
inconvenientes del mismo, ya que
Java fue diseñado “partiendo de
cero”, es decir,
un lenguaje “puro” y no necesitaba
ser compatible con versiones
anteriores de
ningún lenguaje como ocurre con C++
y C.
Gracias a que fue diseñado
“partiendo de cero” ha conseguido
convertirse en un
lenguaje orientado a objetos puro,
limpio y práctico. No permite
programar
mediante otra técnica que no sea la
programación orientada a objetos y,
una vez
superado el aprendizaje de la
programación orientada a objetos, es
realmente
sencillo aprender Java.
¿El lenguaje es Compilado o
Interpretado? Ni una cosa ni la
otra. Aunque
estrictamente hablando es
interpretado, necesita de un proceso
previo de
compilación. Una vez “compilado” el
programa, se crea un archivo que
almacena
lo que se denomina bytecodes o
j_code (seudo código prácticamente
al nivel de
código máquina). Para ejecutarlo, es
necesario un “intérprete”, la JVM
(Java
Virtual Machine) máquina virtual
Java. De esta forma, es posible
compilar el
programa en una estación UNIX y
ejecutarlo en otra con Windows
utilizando la
máquina virtual Java para Windows.
Esta JVM se encarga de leer los
bytecodes y
traducirlos a instrucciones
ejecutables directamente en un
determinado
microprocesador, de una forma
bastante eficiente.
Que el programa deba ser
“interpretado” no hace que sea poco
eficiente en cuanto
a velocidad, ya que la
interpretación se hace prácticamente
al nivel de código
máquina. Por ejemplo, es mucho más
rápido que cualquier otro programa
interpretado como por ejemplo Visual
Basic, aunque es más lento que el
mismo
programa escrito en C++. Esta
deficiencia en cuanto a la
velocidad, puede ser
aminorada por los compiladores
Just-In-Time (JIT). Un compilador
JIT transforma
los bytecodes de un programa o un
applet en código nativo de la
plataforma donde
se ejecuta, por lo que es más
rápido. Suelen ser incorporados por
los
navegadores, como Netscape o
Internet Explorer.
El lenguaje Java es robusto. Las
aplicaciones creadas en este
lenguaje son
susceptibles de contener pocos
errores, principalmente porque la
gestión de
memoria y punteros es realizada por
el propio lenguaje y no por el
programador.
Bien es sabido que la mayoría de los
errores en las aplicaciones vienen
producidos por fallos en la gestión
de punteros o la asignación y
liberación de
memoria. Además, el lenguaje
contiene estructuras para la
detección de
excepciones (errores de ejecución
previstos) y permite obligar al
programador a
escribir código fiable mediante la
declaración de excepciones posibles
para una
determinada clase reutilizable.
La Máquina Virtual Java (JVM)
La máquina virtual Java es la idea
revolucionaria18 del lenguaje. Es la
entidad que
proporciona la independencia de
plataforma para los programas Java
“compilados”
en byte-code.
18 Otros sistemas en el pasado, como
por ejemplo el Pascal UCSD
compilaban a un código intermedio
(p-code) que luego
era interpretado al ejecutar el
programa.
Un mismo programa fuente compilado
en distintas plataformas o sistemas
operativos, genera el mismo archivo
en byte-code. Esto es lógico, ya que
se
supone que el compilador de Java
traduce el archivo fuente a código
ejecutable
por una máquina que únicamente
existe en forma virtual (aunque se
trabaja en la
construcción de microprocesadores
que ejecuten directamente el
byte-code).
Evidentemente, si un mismo programa
en byte-code puede ser ejecutado en
distintas plataformas es porque
existe un traductor de ese byte-code
a código
nativo de la máquina sobre la que se
ejecuta. Esta tarea es realizada por
la JVM.
Existe una versión distinta de esta
JVM para cada plataforma. Esta JVM
se carga
en memoria y va traduciendo “al
vuelo”, los byte-codes a código
máquina. La JVM
no ocupa mucho espacio en memoria,
piénsese que fue diseñada para poder
ejecutarse sobre pequeños
electrodomésticos como teléfonos,
televisores, etc.
8.4 Novedades en la versión 1.5
1. Introducción
En este documento se resumen las
principales novedades que ofrece la
versión
1.5 de Java, separándolas en
diferentes áreas. Para una
explicación más
detallada, consultar la página de
Sun:
https://java.sun.com/j2se/1.5.0/docs/relnotes/features.html
2. Novedades en la máquina virtual
Autoajuste de memoria mejorado
La capacidad de autoajustar la
cantidad de memoria necesaria (pila,
recolector de
basura, etc.) se ve mejorada en esta
versión.
Compartir clases
Al instalar la máquina virtual, se
cargan en memoria un conjunto de
clases del
sistema, en forma de representación
interna, de forma que las siguientes
llamadas
a la máquina virtual ya encuentren
estas clases mapeadas en memoria, y
se
permita que los datos de estas
clases se compartan entre múltiples
procesos
dentro de la JVM.
Ajuste del recolector de basura
Relacionado con el autoajuste de
memoria, el recolector de basura
también se
autoadapta a las necesidades de
memoria de la aplicación, para
evitar que el
usuario tenga que ajustar su tamaño
y características desde línea de
comandos.
Tratamiento de errores fatales
El mecanismo de listado de errores
fatales se ha mejorado de forma que
se
obtengan mejores diagnósticos a
partir de las salidas generadas por
dichos
errores.
3. Novedades en el lenguaje
Tipos de datos parametrizados
(generics)
Esta mejora permite tener
colecciones de tipos concretos de
datos, lo que permite
asegurar que los datos que se van a
almacenar van a ser compatibles con
un
determinado tipo o tipos. Por
ejemplo, podemos crear un Vector que
sólo
almacene Strings, o una HashMap que
tome como claves Integers y como
valores
Vectors. Además, con esto nos
ahorramos las conversiones cast al
tipo que
deseemos, puesto que la colección ya
se asume que será de dicho tipo.
Ejemplo
// Vector de cadenas
Vector<String> v = new
Vector<String>();
v.addElement("Hola");
String s = v.getElementAt(0);
v.addElement(new Integer(20)); //
Daría error!!
// HashMap con claves enteras y
valores de vectores
HashMap<Integer, Vector> hm = new
HashMap<Integer, Vector>();
hm.put(1, v);
Vector v2 = hm.get(1);
Autoboxing
Esta nueva característica evita al
programador tener que establecer
correspondencias manuales entre los
tipos simples (int, double, etc.) y
sus
correspondientes wrappers o tipos
complejos (Integer, Double, etc.).
Podremos
utilizar un int donde se espere un
objeto complejo (Integer), y
viceversa.
Ejemplo
Vector<Integer> v = new
Vector<Integer>();
v.addElement(30);
Integer n = v.getElementAt(0);
n = n+1;
Mejoras en Ciclos
Se mejoran las posibilidades de
recorrer colecciones y arrays,
previniendo índices
fuera de rango, y pudiendo recorrer
colecciones sin necesidad de acceder
a sus
iteradores (Iterator).
Ejemplo
// Recorre e imprime todos los
elementos de un array
int[] arrayInt = {1, 20, 30, 2, 3,
5};
for(int elemento: arrayInt)
System.out.println (elemento);
// Recorre e imprime todos los
elementos de un Vector
Vector<String> v = new
Vector<String>();
for(String cadena: v)
System.out.println (cadena);
Tipo enum
El tipo enum que se introduce
permite definir un conjunto de
posibles valores o
estados, que luego podremos utilizar
donde queramos:
Ejemplo
// Define una lista de 3 valores y
luego comprueba en un switch// cuál
es el valor que tiene un objeto de
ese tipo
enum EstadoCivil {soltero, casado,
divorciado};
EstadoCivil ec = EstadoCivil.casado;
ec = EstadoCivil.soltero;
switch(ec)
{
case soltero: System.out.println("Es
soltero");
break;
case casado: System.out.println("Es
casado");
break;
case
divorciado:System.out.println("Es
divorciado");
break;
}
Imports estáticos
Los imports estáticos permiten
importar los elementos estáticos de
una clase, de
forma que para referenciarlos no
tengamos que poner siempre como
prefijo el
nombre de la clase. Por ejemplo,
podemos utilizar las constantes de
color de la
clase java.awt.Color, o bien los
métodos matemáticos de la case Math.
Ejemplo
import static java.awt.Color;
import static java.lang.Math;
public class...
{
...
JLabel lbl = new JLabel();
lbl.setBackground(white); // Antes
sería
Color.white
...
double raiz = sqrt(1252.2); // Antes
sería
Math.sqrt(...)
}
Argumentos variables
Ahora Java permite pasar un número
variable de argumentos a una función
(como
sucede con funciones como printf en
C). Esto se consigue mediante la
expresión
"..." a partir del momento en que
queramos tener un número variable de
argumentos.
Ejemplo
// Funcion que tiene un parámetro
String obligatorio
// y n parámetros int opcionales
public void miFunc(String param,
int... args)
{
...
// Una forma de procesar n
parametros variables
for (int argumento: args)
{
...
}
...
}
...
miFunc("Hola", 1, 20, 30, 2);
miFunc("Adios");
Metainformación
Se tiene la posibilidad de añadir
ciertas anotaciones en campos,
métodos, clases y
otros elementos, que permitan a las
herramientas de desarrollo o de
despliegue
leerlas y realizar ciertas tareas.
Por ejemplo, generar archivos
fuentes, archivos
XML, o un Stub de métodos para
utilizar remotamente con RMI.
Un ejemplo más claro lo tenemos en
las anotaciones que ya se utilizan
para la
herramienta Javadoc. Las marcas
@deprecated no afectan al
comportamiento de
los métodos que las llevan, pero
previenen al compilador para que
muestre una
advertencia indicando que el método
que se utiliza está desaconsejado.
También
se tienen otras marcas @param,
@return, @see, etc., que utiliza
Javadoc para
generar las páginas de documentación
y las relaciones entre ellas.
Ejemplo
// Definición de una interfaz
mediante metainformacion
public @interface MiInterfaz
{
int metodo1();
String metodo2();
}
4. Novedades en librerías
principales
Red
Se han añadido cambios y mejoras
para el trabajo en red, como:
• Soporte para IPv6 en Windows XP y
2003
• Establecimiento de timeouts para
conectar y leer
• API para lanzar aplicaciones RMI a
través de inetd
• La clase InetAddress permite
testear si una URL es alcanzable
(utilidad
ping)
• Otras mejoras en el tratamiento de
cookies, servidores proxy,
tratamiento
de URLs, etc.
Seguridad
Hay bastantes mejoras en seguridad.
Se da soporte a más estándares de
seguridad (SASL, OCSP, TSP, etc.),
hay mejoras en la escalabilidad a
través de
SSLEngine, en criptografía, etc.
Internacionalización
Mediante la internacionalización
podemos construir una aplicación que
se adapte
a varios idiomas, formatos
monetarios, de fecha, etc., sin
tener que reprogramarla.
En este aspecto, se añaden mejoras
en la versión 1.5, relacionadas con:
• La gestión de juegos de caracteres
se basa en el formato Unicode 4.0,
lo
que afecta a las clases Character y
String, entre otras.
• La clase DecimalFormat se ha
modificado para poder procesar
elementos
de tipo BigDecimal o BigInteger sin
perder precisión
• Se han añadido nuevos Locales
soportados, como el vietnamita.
Formateador
La clase Formatter permite dar
formato (justificación y
alineamiento, formatos
numéricos, de fecha, etc.) a las
cadenas y otros tipos de datos,
siguiendo un estilo
parecido al printf de C. También se
permite mediante la interfaz
Formattable dar
formatos (limitados) a tipos creados
por el usuario.
Ejemplo
// Uso de formatter para construir
cadenas formateadas
StringBuilder sb = new
StringBuilder();
Formatter f = new Formatter(sb,
Locale.US);
f.format("Hola, %1$2s, esta es tu
visita numero %2$d", "Pepe",
20);
// Resultaría: "Hola, Pepe, esta es
tu visita numero 20"
// También se tienen unos métodos
predefinidos en ciertas clases
System.out.format("Hola, %1$2s, esta
es tu visita numero %2$d",
"Pepe", 20);
System.err.printf("Hola, %1$2s, esta
es tu visita numero %2$d",
"Pepe", 20);
String s = String.format("Hola,
%1$2s", "Pepe");
Escaneador
La clase Scanner permite parsear un
flujo de entrada (archivo, cadena de
texto,
stream de datos, etc.), y extraer
tokens siguiendo un determinado
patrón o tipo de
datos. También se permite trabajar
con expresiones regulares para
indicar qué
patrones se deben buscar.
Ejemplo
// Lectura de enteros de la entrada
estándar
Scanner sc =
Scanner.create(System.in);
int n = sc.nextInt();
// Lectura de todos los doubles de
un archivo
Scanner sc = Scanner.create(new
File("miFich.txt"));
while (sc.hasNextDouble())
double d = sc.nextDouble();
// Uso de otros delimitadores
String s = "Esto hola es hola 1 hola
ejemplo";
Scanner sc =
Scanner.create(s).useDelimiter("\\s*hola\\s*");
System.out.println(sc.next());
System.out.println(sc.next());
System.out.println(sc.next());
// Sacaría Esto \n es \n 1
Arquitectura de JavaBeans
La arquitectura de JavaBeans se
encuentra dentro del paquete
java.beans. Se ha
añadido una nueva clase,
IndexedPropertyChangeEvent, subclase
de
PropertyChangeEvent, para dar
soporte a eventos que respondan a
cambios en
propiedades indexadas (propiedades
que utilicen un índice para
identificar la parte
del bean que haya cambiado). También
se han añadido mejoras a la hora de
crear
editores de propiedades de beans
(PropertyEditor), como el método
createPropertyEditor(...), y
constructores públicos para la clase
PropertyEditorSupport.
Arquitectura de colecciones
Como se ha comentado antes, las
colecciones (estructura que contiene
clases e
interfaces como Collection, Vector,
ArrayList, etc.) dan soporte a
nuevas
características del lenguaje, como
el autoboxing, mejoras en los Ciclos
(for) , y el
uso de generics. Además, se han
añadido interfaces nuevos, como
Queue,
BlockingQueue y ConcurrentMap, con
algunas implementaciones concretas
de
dichas interfaces. Además, se tienen
nuevas implementaciones de List, Set
y Map.
Manipulación de bits
Los wrappers de tipos simples (es
decir, clases como Integer, Long,
Double, Char)
soportan operaciones de bits, como
highestOneBit, lowestOneBit,
rotateLeft,
rotateRight, reverse, etc.
Elementos matemáticos
El paquete java.math también ha
sufrido modificaciones, como la
adición en la
clase BigDecimal de soporte para
cálculo en coma flotante de
precisión fija.
Clases como Math o StrictMath además
incluyen soporte para senos y
cosenos
hiperbólicos, raíces cúbicas o
logaritmos en base 10. Por último,
también se da
soporte al uso de números
hexadecimales con coma flotante.
Serialización
Además de corregir errores previos
en la serialización, se da soporte
para
serializar el nuevo tipo enum,
aunque la forma de serializarlo
difiere ligeramente
de los tipos tradicionales.
Hilos
Se han añadido los paquetes
java.util.concurrent,
java.util.concurrent.atomic y
java.util.concurrent.locks, que
permiten crear diversas
infraestructuras de hilos,
como pooling de hilos o colas de
bloqueos, liberando al programador
de tener que
controlar todas estas estructuras "a
mano". En definitiva, se da soporte
para
automatizar la programación
concurrente.
Además, a la clase Thread se le han
añadido los métodos getStackTrace y
getAllStackTraces para obtener la
traza de los hilos en un momento
dado.
Monitorización y gestión
Se tienen mejoras en el control o la
monitorización tanto de las
aplicaciones Java
como de la máquina virtual (JVM), a
través de la API de JMX.
5. Novedades en librerías
adicionales
RMI
Se tienen dos mejoras fundamentales
en RMI:
• Generación dinámica de Stubs: se
generan en tiempo de ejecución, sin
necesidad de utilizar rmic
previamente. Sólo tendremos que
utilizarlo para
generar Stubs para clientes que
funcionen con versiones anteriores
de
Java.
• Lanzamiento de rmid o un servidor
RMI Java desde inetd/xinetd.
JDBC
En Java 1.4 se introdujo la interfaz
RowSet, que permitía pasar datos
entre
componentes. En Java 1.5 se han
desarrollado 5 implementaciones de
dicha
interfaz, para cubrir 5 posibles
casos de uso:
• JdbcRowSet: para encapsular un
ResultSet o un driver que utilice
tecnología JDBC
• CachedRowSet: desconecta de la
fuente de datos y trabaja
independientemente, salvo cuando
esté obteniendo datos de dicha
fuente o
volcando datos en ella.
• FilteredRowSet: hereda de la
anterior, y se utiliza para obtener
un
subconjunto de datos
• JoinRowSet: hereda de CachedRowSet
y se emplea para unir múltiples
objetos RowSet.
• WebRowSet: hereda de CachedRowSet
para procesar datos XML.
JNDI
• Mejoras en la clase
javax.naming.NameClassPair para
poder acceder al
nombre completo del servicio de
directorio.
• Soporte para controles estándar de
LDAP
• Soporte para manipular nombres de
LDAP
6. Novedades en la interfaz de
usuario
Internacionalización
Para permitir renderizar texto
multilingüe utilizando fuentes
lógicas, se tienen en
cuenta tanto las fuentes del cliente
como las del locale que tengamos
instalado.
Además, AWT utiliza APIs Unicode en
Windows 2000 y XP, con lo que se
puede
manejar texto sin estar limitado a
la configuración de locale de
Windows.
Java Sound
Para el tratamiento del sonido desde
Java, se tienen también los
siguientes
cambios, principalmente:
• Se permite el acceso a puertos en
todas las plataformas
• También está disponible la
entrada/salida MIDI
Java 2D
• Se cachean en memoria todas las
imágenes construidas a partir de
BufferedImage
• Métodos para controlar la
aceleración hardware en imágenes
• Añadido soporte para aceleración
hardware con OpenGL en Solaris y
Linux.
• Creación de fuentes a partir de
archivos y flujos de entrada
• Se ha mejorado la renderización de
texto.
Flujos de imágenes
Se permite, además de los formatos
de imágenes ya soportados (PNG, JPG,
etc.),
leer y escribir imágenes BMP y WBMP.
AWT
Las novedades más significativas
son:
• La clase MouseInfo almacena
información sobre la posición del
ratón en el
escritorio
• Se tienen nuevos métodos en la
clase Window para especificar la
posición
para las nuevas ventanas que se
creen. También se tienen métodos
para
asegurar que una ventana permanezca
siempre en primer lugar (aunque
esta característica no funciona bien
en algunas versiones de Solaris o
Linux)
• Nuevos métodos para acceder y
notificar sobre el contenido del
portapapeles.
• Se han corregido errores
existentes en los layout managers:
GridBagLayout
y FlowLayout.
• Cambios y correcciones en eventos
de teclado (nuevos mapeos de teclas,
redefinición de métodos, etc.), y de
acción (correcciones en cuanto al
uso
de Enter como evento de acción).
Swing
Entre otras, se tienen las
novedades:
• Se proporcionan nuevos look &
feels (uno de ellos, Synth, es
skinnable, es
decir, se puede personalizar el skin
que muestra)
• Se añade soporte para imprimir
JTables.
• Se permite definir menús
contextuales (popup menus) en
componentes,
para que muestren las opciones que
queramos. Para ello se tiene la
clase
JPopupMenu.
• Se han añadido mejoras en
JTextArea para facilitar el scroll
por su
contenido, y la actualización cuando
se modifique su texto.
• Podremos utilizar el método
JFrame.add(...), en lugar de
JFrame.getContentPane().add(...)
// Antes
JFrame f = new JFrame();
f.getContentPane().add(new
JButton("Hola"));
// Ahora
JFrame f = new JFrame();
f.add(new JButton("Hola"));
7. Novedades en despliegue de
aplicaciones
Despliegue general
Se tienen, entre otras, las
siguientes novedades en cuanto a
despliegue de
aplicaciones:
• Se ha unido gran parte de la
funcionalidad entre el Java Plug-in
y Java Web
Start, de modo que ahora tienen un
Java Control Panel común para ambos
• Las aplicaciones desplegadas se
pueden configurar mediante un
archivo de
configuración, que puede ser
accedido a través de una URL, y
establecer
ciertos parámetros de la aplicación.
• El formato de compresión Pack200
permite tener archivos JAR
ultracomprimidos
(hasta 1/8 de su tamaño original),
reduciendo los tiempos de
descarga, o de despliegue mediante
Java Web Start.
• Inclusión de marcas de tiempo
(timestamps) en los archivos JAR
firmados,
de forma que se sabe cuándo se
concedió el certificado para el
archivo, y
se previene así el uso de JARs cuyo
permiso ya caducó.
Java Web Start
Hay también cambios específicos de
Java Web Start, como son:
• Supresión del Application Manager,
cuya mayor funcionalidad se ha
encapsulado dentro del nuevo Java
Control Panel, y el JNLP Cache
Viewer,
una nueva herramienta que permite
gestionar las aplicaciones
descargadas.
• Supersión del Developer Bundle, ya
que Java Web Start está
completamente integrado con JRE y
SDK, y los elementos que contenía
dicho bundle ahora están integrados
en Java.
• Se tiene una caché de sistema, de
forma que el administrador del
sistema
puede cargar al inicio programas en
la caché de sistema, de forma que
sean compartidas y accesibles por
múltiples usuarios.
• Hay también una facilidad de
importación, de forma que se
facilita la
instalación desde CDs, donde el
código se carga primero desde un
lugar y
luego se actualiza desde otro.
También permite preinstalar
aplicaciones y
librerías en caché, sin ejecutarlas.
8. Novedades en herramientas
JPDA
JPDA es un conjunto de interfaces
utilizadas para depurar en entornos
y sistemas
de desarrollo. En la versión 1.5 se
tienen nuevas mejoras y
funcionalidades de
esta herramienta. Muchas de estas
nuevas funcionalidades se han
añadido para
adaptar las nuevas características
de la versión 1.5 de Java, como los
argumentos
variables, imports estáticos, etc.
JVMTI
JVMTI (Java Virtual Machine Tool
Interface) es una nueva interfaz de
programación nativa para utilizar en
herramientas de desarrollo y
monitorización.
Forma parte de las interfaces
contenidas en JPDA para depuración.
Permite
chequear el estado y controlar la
ejecución de una aplicación.
Compilador (javac)
Se han añadido algunas
características a la hora de
utilizar el compilador, como
son los parámetros:
• - source 1.5: habilita las nuevas
características de la versión 1.5 de
Java,
para ser tenidas en cuenta por el
compilador. Lleva implícito también
un
parámetro -target 1.5
• -target 1.5: permite al compilador
utilizar características específicas
de Java
1.5 en las librerías y la máquina
virtual.
• -Xlint: permite producir warnings
sobre código que, aunque es
correcto,
puede ser problemático debido a su
construcción.
Herramienta javadoc
Se tienen nuevas funcionalidades en
la herramienta javadoc. Como en
otros
elementos, muchos de los cambios dan
soporte a características nuevas de
Java
1.5 (generics, tipo enum, etc.). Se
tienen también marcas nuevas, como
regression para indicar
funcionalidades que se
desaconsejaron en versiones
anteriores, pero que han sido
corregidas en la nueva. También se
da soporte a
anotaciones para generar
metainformación que utilizar en
otros programas.
8.5 El Entorno de Desarrollo de Java
La herramienta básica para empezar a
desarrollar aplicaciones o applets
en Java
es el JDK (Java Developer’s Kit) o
Kit de Desarrollo Java, que
consiste,
básicamente, en un compilador y un
intérprete (JVM) para la línea de
comandos.
No dispone de un entorno de
desarrollo integrado (IDE), pero
este puede ser
descargado de varias fuentes en
Internet (Netbeans, Eclipse en mi
caso, etc.)
suficiente para aprender el lenguaje
y desarrollar pequeñas aplicaciones.
¿Dónde conseguirlo?
El Kit de desarrollo puede obtenerse
en las direcciones siguientes:
· https://www.sun.com
· https://www.javasoft.com
El entorno para Windows9x/NT está
formado por un archivo ejecutable
que realiza
la instalación, creando toda la
estructura de directorios. El kit
contiene
básicamente:
· El compilador: javac.exe
· El depurador: jdb.exe
· El intérprete: java.exe y
javaw.exe
· El visualizador de applets:
appletviewer.exe
· El generador de documentación:
javadoc.exe
· Un desensamblador de clases:
javap.exe
· El generador de archivos fuentes y
de cabecera (.c y .h) para clases
nativas en C: javah.exe
8.6 Herramientas para trabajar en
Java
Existen distintas “ediciones” de
Java para el desarrollo de
aplicaciones en distintos
ámbitos:
• Aplicaciones de propósito general
(J2SE)
• Aplicaciones de gestión en
entornos empresariales (J2EE)
• Aplicaciones para teléfonos
móviles, PDAs y otros dispositivos
electrónicos que
permitan aplicaciones empotradas
(J2ME)
La más utilizada es sin duda la
edición estándar (J2SE).
J2SE incluye bibliotecas muy
extensas y completas, que permiten
la
implementación de casi cualquier
tipo de aplicación:
• Seguridad
• EEDDs
• Componentes (JavaBeans)
• Internacionalización
• E/S
• XML
• Redes y acceso a Internet
• Programación distribuida (RMI,
CORBA)
• Matemática de precisión arbitraria
• Sonido
• Interfaz de usuario (AWT, SWING)
• Gráficos 2D
• Manipulación, carga y descarga de
imágenes
• Impresión
• Acceso a bases de datos (JDBC)
• Gestión de preferencias y
configuraciones
Varias de estas características se
verifican en la sección de
novedades, apartado
8.4.
Entonces, en los entornos de
desarrollo podemos encontrar:
NetBeans. Entorno integrado de
desarrollo Java de Sun, realizado
íntegramente
en Java (y por tanto
multiplataforma). Consume bastantes
recursos. Permite
diseñar ventanas, escribir código,
compilar, ejecutar etc. Requiere un
JDK
instalado. Puede obtenerse
gratuitamente de www.netbeans.org
Borland JBuilder. Excelente entorno
integrado de desarrollo Java de
Borland. Al
igual que Netbeans, también está
realizado íntegramente en Java.
Existen
versiones limitadas que pueden
bajarse de www.borland.com.
Microsoft Visual J++. Uno de los más
populares, aunque las aplicaciones
obtenidas pueden presentar problemas
de compatibilidad con el SDK oficial
de
Java, por el uso de librerías
especificas de Microsoft. Permite
construir
aplicaciones Java dentro de la
plataforma .NET.
Otros IDE son Eclipse, el cual se
detallará en el apartado 15, IBM
WebSphere,
Oracle JDeveloper, etc.
Java vrs otros lenguajes OO
9. Tipos de datos, variables y
matrices
9.1 Tipos de datos
En Java existen dos tipos
principales de datos:
1) Tipos de datos simples.
2) Referencias a objetos.
Los tipos de datos simples son
aquellos que pueden utilizarse
directamente en un
programa, sin necesidad del uso de
clases (POO). Estos tipos son:
El segundo tipo está formado por
todos los demás. Se les llama
referencias
porque en realidad lo que se
almacena en los mismos son punteros
a zonas de
memoria donde se encuentran
almacenadas las estructuras de datos
que los
soportan. Dentro de este grupo se
encuentran las clases (objetos) y
también se
incluyen las interfaces, los
vectores y los Strings.
Pueden realizarse conversiones entre
los distintos tipos de datos
(incluso entre
simples y referenciales), bien de
forma implícita o de forma
explícita.
9.2 Tipos de datos simples
Los tipos de datos simples
soportados por Java son los
siguientes:
No existen más datos simples en
Java. Incluso éstos que se enumeran
son
envueltos por clases equivalentes
(java.lang.Integer,
java.lang.Double,
java.lang.Byte, etc.), que pueden
tratarlos como si fueran objetos en
lugar de
datos simples.
A diferencia de otros lenguajes de
programación como el C, en Java los
tipos de
datos simples no dependen de la
plataforma ni del sistema operativo.
Un entero de
tipo int siempre tendrá 4 bytes, por
lo que no tendremos sorpresas al
migrar un
programa de un sistema operativo a
otro. Es más, ni siquiera hay que
volverlo a
compilar.
Eso sí, Java no realiza una
comprobación de los rangos. Por
ejemplo: si a una
variable de tipo short con el valor
32.767 se le suma 1, el resultado
será
-32.768 y no se producirá ningún
error de ejecución.
Nota: A diferencia de otros
lenguajes de programación, los
Strings en Java no son
un tipo simple de datos sino un
objeto. Los valores de tipo String
van entre
comillas dobles (“Hola”), mientras
que los de tipo char van entre
comillas simples
(‘K’).
Los valores que pueden asignarse a
variables y que pueden ser
utilizados en
expresiones directamente reciben el
nombre de literales.
9.3 Tipos de datos referenciales
El resto de tipos de datos que no
son simples, son considerados
referenciales.
Estos tipos son básicamente las
clases, en las que se basa la
programación
orientada a objetos.
Al declarar un objeto perteneciente
a una determinada clase, se está
reservando
una zona de memoria19 donde se
almacenarán los atributos y otros
datos
pertenecientes a dicho objeto. Lo
que se almacena en el objeto en sí,
es un
puntero (referencia) a dicha zona de
memoria.
Dentro de estos tipos pueden
considerarse las interfaces, los
Strings y los
vectores, que son unas clases un
tanto especiales, y que se verán en
detalle
posteriormente.
Existe un tipo referencial especial
nominado por la palabra reservada
null que
puede ser asignado a cualquier
variable de cualquier clase y que
indica que el
puntero no tiene referencia a
ninguna zona de memoria (el objeto
no está
inicializado).
Además, todos los tipos de datos
simples vistos en el punto anterior
pueden ser
declarados como referenciales
(objetos), ya que existen clases que
los engloban.
Estas clases son:
19 En realidad, el momento en el que
se realiza la reserva física del
espacio de memoria es cuando se
instancia el objeto
realizando la llamada a su
constructor, y no en el momento de
la declaración.
Clases que Engloban datos simples
9.4 Identificadores y variables
Los identificadores son los nombres
que se les da a las variables,
clases,
interfaces, atributos y métodos de
un programa.
Reglas para la creación de
identificadores:
1. Java hace distinción entre
mayúsculas y minúsculas, por lo
tanto, nombres o
identificadores como var1, Var1 y
VAR1 son distintos.
2. Pueden estar formados por
cualquiera de los caracteres del
código Unicode, por
lo tanto, se pueden declarar
variables con el nombre:
añoDeCreación, raïm, etc.
(se acabó la época de los nombres de
variable como ano_de_creacion),
aunque
eso sí, el primer carácter no puede
ser un dígito numérico y no pueden
utilizarse
espacios en blanco ni símbolos
coincidentes con operadores.
3. La longitud máxima de los
identificadores es prácticamente
ilimitada.
4. No puede ser una palabra
reservada del lenguaje ni los
valores lógicos true o
false.
5. No pueden ser iguales a otro
identificador declarado en el mismo
ámbito.
6. Por convenio, los nombres de las
variables y los métodos deberían
empezar por
una letra minúscula y los de las
clases por mayúscula.
Además, si el identificador está
formado por varias palabras la
primera se escribe
en minúsculas (excepto para las
clases) y el resto de palabras se
hace empezar
por mayúscula (por ejemplo:
añoDeCreación). Estas reglas no son
obligatorias,
pero son convenientes ya que ayudan
al proceso de codificación de un
programa,
así como a su legibilidad. Es más
sencillo distinguir entre clases y
métodos o
variables.
Ya que el lenguaje permite
identificadores todo lo largos que
se desee, es
aconsejable crearlos de forma que
tengan sentido y faciliten su
interpretación. El
nombre ideal para un identificador
es aquel que no se excede en
longitud (lo más
corto posible) siempre que califique
claramente el concepto que intenta
representar. Siempre dentro de unos
límites; por ejemplo, no sería muy
adecuado
utilizar un identificador de un
índice de un Ciclo como
indiceDeTalCiclo en lugar
de simplemente i.
Hay que evitar identificadores como
a1, a2, a3, a4, va1, xc32, xc21,
xsda, … y en
general todos aquellos
identificadores que no “signifiquen”
nada.
Declaración de variables
La declaración de una variable se
realiza de la misma forma que en C.
Siempre
contiene el nombre (identificador de
la variable) y el tipo de dato al
que pertenece.
El ámbito de la variable depende de
la localización en el programa donde
es
declarada.
Ejemplo:
int x;
Las variables pueden ser
inicializadas en el momento de su
declaración, siempre
que el valor que se les asigne
coincida con el tipo de dato de la
variable.
Ejemplo:
int x = 0;
Ámbito de una variable
El ámbito de una variable es la
porción de programa donde dicha
variable es
visible para el código del programa
y, por tanto, referenciable.
El ámbito de una variable depende
del lugar del programa donde es
declarada,
pudiendo pertenecer a cuatro
categorías distintas.
· Variable local.
· Atributo.
· Parámetro de un método.
· Parámetro de un tratador de
excepciones.
Como puede observarse, NO existen
las variables globales. Esto no es
un
“defecto” del lenguaje sino todo lo
contrario. La utilización de
variables globales
resulta peligrosa, ya que puede ser
modificada en cualquier parte del
programa y
por cualquier procedimiento.
Además, a la hora de utilizarlas hay
que buscar dónde están declaradas
para
conocerlas y dónde son modificadas
para evitar sorpresas en los valores
que
pueden contener.
Los ámbitos de las variables u
objetos en Java siguen los criterios
“clásicos”, al
igual que en la mayoría de los
lenguajes de programación como
Pascal, C++, etc.
No existen sorpresas.
Si una variable no ha sido
inicializada, tiene un valor
asignado por defecto. Este
valor es, para las variables de tipo
referencial (objetos), el valor
null. Para las
variables de tipo numérico, el valor
por defecto es cero (0), las
variables de tipo
char, el valor ‘\u0000’ y las
variables de tipo boolean, el valor
false.
Variables locales
Una variable local se declara dentro
del cuerpo de un método de una clase
y es
visible únicamente dentro de dicho
método.
Se puede declarar en cualquier lugar
del cuerpo, incluso después de
instrucciones
ejecutables, aunque es una buena
costumbre declararlas justo al
principio.
También pueden declararse variables
dentro de un bloque patentizado por
llaves
{…}. En ese caso, sólo serán
“visibles” dentro de dicho bloque.
class Caracter {
char ch;
public Caracter(char c) {
ch=c;
}
public void repetir(int num) {
int i;
for (i=0;i<num;i++)
System.out.println(ch);
}
}
class Ej1 {
public static void main(String
argumentos[]) {
Caracter caracter;
caracter = new Caracter('H');
caracter.repetir(20);
}
}
En este ejemplo existe una variable
local: int i; definida en el método
repetirde la clase Caracter, por lo
tanto, únicamente es visible dentro
del método
repetir.
También existe una variable local en
el método main. En este caso, la
variable local
es un objeto:
Caracter caracter; .. que sólo será
visible dentro del método en el que
está
declarada (main).
Es importante hacer notar que una
declaración como la anterior le
indica al
compilador el tipo de la variable
caracter pero no crea un objeto. El
operador que
crea el objeto es new, que necesita
como único parámetro el nombre del
constructor (que será el
procedimiento que asigna valor a ese
objeto recién
instanciado).
Cuando se pretende declarar
múltiples variables del mismo tipo
pueden
declararse, en forma de lista,
separadas por comas:
Ejemplo:
int x,y,z;
· Declara tres variables x, y, z de
tipo entero.
· Podrían haberse inicializado en su
declaración de la forma:
int x=0,y=0,z=3;
No es necesario que se declaren al
principio del método. Puede hacerse
en
cualquier lugar del mismo, incluso
de la siguiente forma:
public void repetir(int num) {
for (int i=0;i<num;i++)
System.out.println(ch);
}
En el caso de las variables locales,
éstas no se inicializan con un valor
por
defecto, como se ha comentado en el
punto anterior, sino que es
necesario
asignarles algún valor antes de
poder utilizarlas en cualquier
instrucción, de lo
contrario el compilador genera un
error, de tal forma que es imposible
hacer uso
de una variable local no
inicializada sin que se percate de
ello el compilador.
Las variables locales pueden ser
antecedidas por la palabra reservada
final. En
ese caso, sólo permiten que se les
asigne un valor una única vez.
Ejemplo:
final int x=0;
No permitirá que a x se le asigne
ningún otro valor. Siempre contendrá
0.
No es necesario que el valor se le
asigne en el momento de la
declaración, podría
haberse inicializado en cualquier
otro lugar, pero una sola vez:
Ejemplo:
final int x;
…
x=y+2;
Después de la asignación x=y+2, no
se permitirá asignar ningún otro
valor a x.
Atributos
Los atributos de una clase son las
características que se van a tener
en cuenta
sobre un objeto y por lo tanto su
ámbito está circunscrito, en
principio, dentro de la
clase a la cual caracterizan. Se
declaran de la misma forma que las
variables
locales pero, además, pueden tener
algunos modificadores que afectan al
ámbito
de los mismos.
En el ejemplo anterior, ch es un
atributo de la clase Caracter y por
lo tanto es
“manipulable” en cualquier método de
dicha clase, como de hecho ocurre en
los
método repetir () y Carácter ().
Para acceder a un atributo de un
objeto desde algún método
perteneciente a otra
clase u objeto se antepone el nombre
del objeto y un punto al nombre de
dicho
atributo. Por ejemplo:
caracter.ch
Con los nombres de los métodos se
hace lo mismo.
Ejemplo:
caracter.repetir(20);
Parámetros de un método
Los parámetros se declaran en la
cabecera del método de la siguiente
forma:
[Modificadores_de_método]
Tipo_devuelto Nombre_de_método
(lista_de_parámetros)
{
…
}
La lista_de_parámetros consiste en
una serie de variables, separadas
por comas y
declarando el tipo al que
pertenecen.
Ejemplo:
public static void miMétodo(int v1,
int v2, float v3, Stringv4,
ClaseObjeto v5);
Nótese que aunque existan varios
parámetros pertenecientes al mismo
tipo o
clase, no pueden declararse
abreviadamente, como ocurre con las
variables
locales y los atributos, indicando
el tipo y a continuación la lista de
parámetros
separados por comas. Así, es ilegal
la siguiente declaración del método
anterior:
public static void miMétodo(int v1,
v2, float v3, String v4,
ClaseObjeto v5); .. (ILEGAL)
La declaración de un parámetro puede
ir antecedida, como ocurre con las
variables locales, por la palabra
reservada final. En ese caso, el
valor de dicho
parámetro no podrá ser modificado en
el cuerpo del método.
Los parámetros de un método pueden
ser de dos tipos:
• Variables de tipo simple de datos:
En este caso, el paso de parámetros
se
realiza siempre por valor. Es decir,
el valor del parámetro de llamada no
puede
ser modificado en el cuerpo del
método (El método trabaja con una
copia del
valor utilizado en la llamada).
• Variables de tipo objeto
(referencias): En este caso, lo que
realmente se
pasa al método es un puntero al
objeto y, por lo tanto, el valor del
parámetro de
llamada sí que puede ser modificado
dentro del método (El método trabaja
directamente con el valor utilizado
en la llamada), a no ser que se
anteponga la
palabra reservada final.
La salida del programa sería la
siguiente:
Valor del objeto = 1
Valor de la variable = 2
-Después de llamar a modifica ( )-
Valor del objeto = 2
Valor de la variable = 2
Como puede verse, después de la
llamada, el valor del objeto obj sí
ha sido
modificado (se ha realizado un pase
de parámetro por referencia),
mientras que el
valor de la variable var1 no ha sido
modificado (se ha realizado un paso
de
parámetro por valor).
9.5 Conversión de Tipos
La palabra conversión se utiliza con
el sentido de "convertir a un
molde". Java
convertirá automáticamente un tipo
de datos en otro cuando sea
adecuado. Por
ejemplo, si se asigna un valor
entero a una variable de coma
flotante, el
compilador convertirá
automáticamente el int en float. La
conversión permite llevar
a cabo estas conversiones de tipos
de forma explícita, o forzarlas
cuando no se
diesen por defecto.
Para llevar a cabo una conversión,
se pone el tipo de datos deseado
(incluidos
todos los modificadores) entre
paréntesis a la izquierda de
cualquier valor. He aquí
un ejemplo:
void conversiones ( ) {
int i = 200;
long 1 = (long) i;
long 12 = (lon9)2OO;
}
Como puede verse, es posible llevar
a cabo una conversión, tanto con un
valor
numérico, como con una variable. En
las dos conversiones mostradas, la
conversión es innecesaria, dado que
el compilador convertirá un valor
int en long
cuando sea necesario. No obstante,
se permite usar conversiones
innecesarias
para hacer el código más limpio. En
otras situaciones, puede ser
esencial una
conversión para lograr que el código
compile.
En C y C++, las conversiones pueden
conllevar quebraderos de cabeza. En
Java,
la conversión de tipos es segura,
con la excepción de que al llevar a
cabo una de
las denominadas conversiones
reductoras(es decir, cuando se va de
un tipo de
datos que puede mantener más
información a otro que no puede
contener tanta)
se corre el riesgo de perder
información. En estos casos, el
compilador fuerza a
hacer una conversión explícita,
diciendo, de hecho, "esto puede ser
algo peligroso
de hacer -si se quiere que lo haga
de todas formas, tiene que hacer la
conversión
de forma explícita". Con una
conversión extensora no es necesaria
una
conversión explícita porque el nuevo
tipo es capaz de albergar la
información del
viejo tipo sin que se pierda nunca
ningún bit.
Java permite convertir cualquier
tipo primitivo en cualquier otro
tipo, excepto
boolean, que no permite ninguna
conversión. Los tipos clase no
permiten ninguna
conversión. Para convertir una a
otra debe utilizar métodos
especiales (String es
un caso especial y se verá más
adelante en este libro que los
objetos pueden
convertirse en una familia de tipos;
un Roble puede convertirse en Árbol
y
viceversa, pero esto no puede
hacerse con un tipo foráneo como
Roca.)
Toda expresión en Java tiene un tipo
de dato asociado, ya sea simple o
referencial, y puede ser deducido de
la estructura de la expresión y los
literales
que pudiera contener.
Las conversiones de tipos pueden ser
apropiadas para cambiar el tipo de
una
determinada expresión que no se
corresponda con el tipo necesario.
Tipos de conversiones de tipos:
Conversión por ampliación: Consiste
en cambiar el tipo de dato por otro
cuyo rango es mayor y por lo tanto,
contiene al primero. En este tipo de
conversión no se pierde información
(aunque puede perderse precisión en
la conversión de datos enteros a
reales).
Conversión por reducción: En este
tipo de conversión sí que puede
perderse información debido a que se
pretende almacenar más información
de la que “cabe” en el tipo de
destino. Al ser una conversión
“semidestructiva”, es necesario
indicar que se pretende realizar la
misma
explícitamente para evitar pérdidas
de información por conversiones por
reducción inadvertidas.
Conversión por ampliación de tipos
de datos simples
En estos casos no se pierde
información sobre la magnitud de los
valores
numéricos.
Tipo a Tipo resultante
convertir
byte short
int
long
float
double
short int
long
float
double
char int
long
float
double
int long
float
double
long float
double
float double
Para los casos de asignación no es
necesario realizar ninguna
conversión explícita
de tipos. Por ejemplo, se puede
asignar un valor de tipo byte a una
variable de tipo
short, int, long, float o double.
También se puede asignar un carácter
(char) a una variable de tipo int,
long, float
o double.
int i = ’A’;
Los únicos casos en los que no se
preserva necesariamente la exactitud
entre los
valores del tipo a convertir y el
tipo resultante, ya que puede
perderse precisión
son:
Tipo a Tipo resultante
convertir
int float
long float
double
En estos casos de pérdida de
precisión, se redondea al valor más
cercano.
Ejemplo de pérdida de precisión:
Produce la siguiente salida:
f=1.23456794E9
-46
Esto es debido a que el tipo float
sólo es preciso hasta 8 dígitos
significativos.
Conversión por reducción de tipos de
datos simples
En estos casos puede perderse
información ya que los rangos de
representación
son distintos, no estando el del
tipo de dato a convertir incluido en
el rango del tipo
de destino.
La forma de convertir de un tipo a
otro es trasladando los n bits menos
significativos a la zona de memoria
de destino con capacidad para esos n
bits. De
esta forma puede perderse incluso el
signo de valores numéricos
(representados
en complemento a dos).
Tipo a
convertir
Tipo
resultante
Tipo a
convertir
Tipo
resultante
byte char
short byte
char
char byte
short
int byte
short
char
long byte
short
char
int
float byte
short
char
int
long
double byte
short
char
Int
Long
float
Reducción de datos simples
En estos casos, en que puede
perderse información en el proceso
de conversión
de tipos, es necesario indicarlo
explícitamente para evitar que esta
pérdida se
produzca de forma accidental, o por
lo menos de forma inadvertida por el
programador.
La forma de realizar la conversión
de tipo por reducción es mediante la
anteposición a la expresión a
convertir, el tipo de destino
encerrado entre
paréntesis.
Salida por pantalla:
f1=1.234567 i1=1
f2=7.654321 i2=7
i=1234567 s=-10617
Conversión por ampliación de tipos
de datos referenciales
Al igual que ocurre con los tipos de
datos simples, también para los
objetos
pueden realizarse conversiones tanto
por ampliación como por reducción.
Las conversiones por ampliación
permitidas son:
Tipo a convertir Tipo resultante
La clase null - Cualquier clase,
interfase o vector
Cualquier clase C - Cualquier clase
R siempre que C sea
subclase de R
- Cualquier interfase I si C
implementa I
- La clase Object
Cualquier interfase I - Cualquier
interfase K siempre que I sea
subinterfaz de K
- La clase Object
Cualquier vector A - La clase Object
- La clase Cloneable
- Cualquier vector R siempre que el
tipo de los
elementos de A y R sean datos
referenciales y
exista una conversión por ampliación
entre ellos.
En estos casos no es necesaria
ninguna conversión explícita.
Produce la siguiente salda por
pantalla:
Clase c2
En el ejemplo, se ha declarado un
objeto c1 de la clase C1, que es
subclase de
C2. A c1 se le asigna un objeto de
la clase C2 (al realizar la llamada
al constructor
de C2). Esto puede hacerse sin
ninguna conversión explícita ya que
C2 es
subclase de C1. Casi gráficamente
puede entenderse mediante un ejemplo
de la
siguiente forma: imaginemos una
clase Ave y una subclase Pato; si se
dispone de
una variable de la clase Ave, a
dicha variable se le puede asignar
un “Pato”, ya
que un pato es, efectivamente, un
ave. Por lo tanto, a una variable de
la clase Ave,
se le puede asignar un objeto “Pato”
sin ninguna conversión explícita ya
que es un
proceso “natural”. El caso contrario
no siempre es cierto. Si se dispone
de una
variable de la clase Pato, no se le
puede asignar un objeto de la clase
“Ave”, ya
que cualquier ave no es un pato.
Si se dispone de una variable de la
clase Pato y se le desea asignar un
objeto de
la clase Ave, habrá que realizar una
conversión explícita (ver punto
siguiente) para
“asegurar” que el ave que asignamos
es “realmente” un pato.
Conversión por reducción de tipos de
datos referenciales
Las conversiones por reducción
permitidas son:
Tipo a convertir Tipo resultante
La clase Object - Cualquier vector
- Cualquier interfase
- Cualquier clase
Cualquier clase C - Cualquier clase
R siempre que C sea
superclase de R
- Cualquier interfase I si C no es
final y
no implementa I
Cualquier interfase I - Cualquier
clase C que no sea final
- Cualquier clase C que sea final
siempre que C implemente I
- Cualquier interfase K siempre que
I no
sea subinterface de K y no tengan
métodos con el mismo nombre pero
distinto tipo de dato de retorno.
Cualquier vector A - Cualquier
vector R siempre que el tipo
de los elementos de A y R sean datos
referenciales y exista una
conversión
por reducción entre ellos.
En estos casos es necesario
indicarlo explícitamente.
La forma de realizar la conversión
de tipo por reducción es mediante la
anteposición a la expresión a
convertir, el tipo de destino
encerrado entre
paréntesis.
El siguiente ejemplo muestra una
conversión no permitida y genera un
mensaje de
error por el compilador:
class C1 {
public String clase;
public C1() {
clase = "clase c1";
}
}
class C2 extends C1{
public C2() {
clase = "clase c2";
}
}
class Conversion4 {
public static void main(String
arg[]) {
C1 c1 = new C1();
C2 c2 = c1;
System.out.println(c2.clase);
}
}
Conversion4.java:16: Incompatible
type for declaration. Explicit
cast needed to convert C1 to C2.
C2 c2 = c1;
^
1 error
La conversión de un objeto de la
clase C1 a la clase C2 necesita del
programador
la indicación de que se sabe que se
está asignando un valor de la clase
C1 a una
variable de la clase C2, pero que en
realidad c1 no contiene un objeto de
la clase
C1 sino un objeto de la clase C2.
Para realizar la conversión en el
ejemplo anterior se antepone la
clase a convertir
encerrada entre paréntesis de la
siguiente manera:
class Conversion4 {
public static void main(String
arg[]) {
C1 c1 = new C1();
// Conversión por reducción de tipo
de
// dato referencial
C2 c2 = (C2) c1;
System.out.println(c2.clase);
}
}
Con esta conversión explícita se
indica que el objeto almacenado en
c1 es, en
realidad, de la clase C2 (lo cuál no
es cierto). Ahora el compilador no
genera
ningún error, pero al ejecutar el
programa se genera una excepción:
java.lang.ClassCastException:
at
Conversion5.main(Conversion5.java:17)
No se habría generado ninguna
excepción si en c1 hubiera un objeto
de la clase
C2 de la siguiente forma:
class Conversion4 {
public static void main(String
arg[]) {
C1 c1 = new C2(); // Conversión por
ampliación
// Conversión por reducción de tipo
de dato
// referencial
C2 c2 = (C2) c1;
System.out.println(c2.clase);
}
}
La salida por pantalla sería:
Clase c2
Conversiones de tipos no permitidas
Tipo a convertir Tipo resultante
Tipo referencial Tipo simple
Tipo simple Tipo referencial
(Excepto al tipo String)
Null Tipo simple
Cualquier tipo Tipo boolean
Tipo boolean Cualquier otro tipo
(Excepto al tipo
String)
Cualquier clase C - Cualquier clase
R siempre que C no
sea
subclase de R y R no sea subclase de
C
(Excepto a la clase String)
- Cualquier interfase I si C es
“final” y no
implementa I
- Cualquier vector (Excepto si C es
de
la clase Object)
Cualquier interfase I - Cualquier
clase C (Excepto si C es
String) si es “final” y no
implementa I
- Cualquier interfase K si I y K
declaran
métodos con la misma cabecera y
distinto tipo de dato de retorno.
Cualquier vector - Cualquier
interfase excepto al
interfase
Cloneable.
-Cualquier otro vector si no hay
conversión entre los tipos de los
elementos (excepto la conversión a
String).
9.6 Vectores o Matrices
Para manejar colecciones de objetos
del mismo tipo estructurados en una
sola
variable se utilizan los vectores20.
En Java, los vectores son en
realidad objetos y
por lo tanto se puede llamar a sus
métodos (como se verá en el capítulo
siguiente).
Existen dos formas equivalentes de
declarar vectores en Java:
1) tipo nombreDelVector [ ];
2) tipo [ ] nombreDelVector;
Ejemplo:
int vector1[], vector2[], entero;
//entero no es un vector
int[] otroVector;
También pueden utilizarse vectores
de más de una dimensión:
Ejemplo:
int matriz [] [];
int [] [] otraMatriz;
Los vectores, al igual que las demás
variables pueden ser inicializados
en el
momento de su declaración. En este
caso, no es necesario especificar el
número
de elementos máximo reservado. Se
reserva el espacio justo para
almacenar los
elementos añadidos en la
declaración.
Ejemplo:
String Días []=
{”Lunes”,”Martes”,”Miércoles”,”Jueves”,
”Viernes”,”Sábado”,”Domingo”};
Una simple declaración de un vector
no reserva espacio en memoria, a
excepción
del caso anterior, en el que sus
elementos obtienen la memoria
necesaria para ser
almacenados. Para reservar la
memoria hay que llamar
explícitamente a un
constructor new21 de la siguiente
forma:
new tipoElemento[ numElementos] ;
20 También llamados arrays, matrices
o tablas.
21 Véase la sección 12.4
Ejemplo:
int matriz[][];
matriz = new int[4][7];
También se puede indicar el número
de elementos durante su declaración:
Ejemplo:
int vector[] = new int[5];
Para hacer referencia a los
elementos particulares del vector,
se utiliza el
identificador del vector junto con
el índice del elemento entre
corchetes. El índice
del primer elemento es el cero (0) y
el del último, el número de
elementos menos
uno.
Ejemplo:
j = vector[0]; vector[4] =
matriz[2][3];
El intento de acceder a un elemento
fuera del rango de la matriz, a
diferencia de lo
que ocurre en C, provoca una
excepción (error) que, de no ser
manejado por el
programa, será el compilador quien
aborte la operación.
Para obtener el número de elementos
de un vector en tiempo de ejecución
se
accede al atributo de la clase
vector length. No olvidemos que los
vectores en
Java son tratados como un objeto.
class Array1 {
public static void main (String
argumentos[]) {
String colores[] =
{"Rojo","Verde","Azul",
"Amarillo","Negro"};
int i;
for (i=0;i<colores.length;i++)
System.out.println(colores[i]);
}
}
El ejemplo anterior produce la
siguiente salida por pantalla:
Rojo
Verde
Azul
Amarillo
Negro
Arrays multidimensionales
Java permite crear fácilmente arrays
multidimensionales:
/ / :
c04:ArrayMultidimensional.java/ /
creando arrays multidimensionales.
import java.util.*;
public class ArrayMultidimensional {
static Random aleatorio = new
Random();
static int pAleatorio (int modulo) {
return Math. abs (aleatorio. nextInt
( ) ) % modulo + 1;
}
static void visualizar (String S) {
System.out.println (S);
}
public static void main (String[]
args) {
int[l [ ] al = {
{ 1, 2, 3}
{ 4, 5 , 6}
};
for(int i = O; i < al.length; i++)
for (int j = O; j < al [i] . length;
j++)
visualizar ("al [ " + i + "1 [ " + j
+
“j = “ + al[i] [j]);
/ / array 3-D de longitud fija:
int [] [] [] a2 = new int [2] [2]
[4];
for (int i = O; i < a2.length; i+t)
for (int j = O; j < a2 [i] . length;
jt+)
for(int k = O; k < a2[i] [j]
.length;
k++)
visualizar ("a2 [ " + i + "1 [ " + j
+ "][" + k +
“J = “+ a2 [i][j][k]);
/ / array 3-D con vectores de
longitud variable:
int [] [ ] [] a3 = new int
[pAleatorio (7) ] [] [] ;
for (int i = O; i < a3.length; i++)
{
a3 [i] = new int [pAleatorio (5) ]
[] ;
for(int j = O; j < a3[i] .length;
j++)
a3[il [jl = new int[pAleatorio(5)1;
}
for (int i = O; i < a3.length; i++)
for (int j = O; j < a3 [i] .length;
j++)
for(int k = O; k < a3[i] [j]
.length;
k++)
visualizar ("a3 [ " + i + "1 [ " + j
+ "][" + k +
“J = “ + a3 [il [jl [kl ) ;
/ / Array de objetos no primitivos:
Integer [] [] a4 = {
{ new Integer (1) , new Integer (2)
} ,
{ new Integer (3) , new Integer ( 4
) },
{ new Integer (S), new Integer (6) }
,
};
for(int i = O; i < a4.length; i++)
for (int j = O; j < a4 [i] . length;
j++)
visualizar("a4[" + i + "1 [ " + j +
" J = " + a4 [i] [j]);
Integer [] [ ] a5;
a5 = new Integer [3] [] ;
for(int i = O; i < a5.length; i++) {
a5[i] = new Integer[3];
for (int j = O; j < a5 [i] .length;
j++)
a5 [i] [ j ] = new Integer (i* j) ;
}
for(int i = O; i < a5.length; i++)
for(int j = O; j < a5[i] .length;
j++)
visualizar ("a5 [ " + i + "1 [ " + j
+
“ J = “J + a5[i] [j]);
}
} / / / : -
El código utilizado para imprimir
utiliza el método length, de forma
que no
depende de tamaños fijos de array.
El primer ejemplo muestra un array
multidimensional de tipos
primitivos. Se puede
delimitar cada vector del array por
llaves:
Cada conjunto de corchetes nos
introduce en el siguiente nivel del
array.
El segundo ejemplo muestra un array
de tres dimensiones asignado con
new.
Aquí, se asigna de una sola vez todo
el array:
int [] [] [] a2 = new int [2] [2]
[4] ;
Pero el tercer ejemplo muestra que
cada vector en los arrays que
conforman la
matriz puede ser de cualquier
longitud:
El primer new crea un array con un
primer elemento de longitud
aleatoria, y el
resto, indeterminados. El segundo
new de dentro del Ciclo for rellena
los
elementos pero deja el tercer índice
indeterminado hasta que se acometa
el tercer
new.
Se verá en la salida que los valores
del array que se inicializan
automáticamente a
cero si no se les da un valor de
inicialización explícito.
Se puede tratar con arrays de
objetos no primitivos de forma
similar, lo que se
muestra en el cuarto ejemplo, que
demuestra la habilidad de englobar
muchas
expresiones new entre llaves:
El quinto ejemplo muestra cómo se
puede construir pieza a pieza un
array de
objetos no primitivos:
El i*j es simplemente para poner
algún valor interesante en el
Integer.
10 Operadores
Los operadores son partes
indispensables en la construcción de
expresiones.
Existen muchas definiciones técnicas
para el término expresión. Puede
decirse
que una expresión es una combinación
de operandos ligados mediante
operadores.
Los operandos pueden ser variables,
constantes, funciones, literales,
etc. y los
operadores todos los enumerados en
este punto.
10.1 Operadores aritméticos:
Operador Formato Descripción
+ op1 + op2 Suma aritmética de dos
operandos
-op1 - op2
-op1
Resta aritmética de dos
operandos
Cambio de signo
* op1 * op2 Multiplicación de dos
operandos
/ op1 / op2 División entera de dos
operandos
% op1 % op2 Resto de la división
)
++
--op1
op1 --
entera ( o módulo++op1
op1++
Incremento unitario
Decremento unitario
El operador - puede utilizarse en su
versión unaria (- op1) y la
operación que
realiza es la de invertir el signo
del operando.
Como en C, los operadores unarios ++
y -- realizan un incremento y un
decremento respectivamente. Estos
operadores admiten notación prefija
y postfija.
• ++op1: En primer lugar realiza un
incremento (en una unidad) de op1 y
después ejecuta la instrucción en la
cual está inmerso.
• op1++: En primer lugar ejecuta la
instrucción en la cual está inmerso
y
después realiza un incremento (en
una unidad) de op1.
• --op1: En primer lugar realiza un
decremento (en una unidad) de op1 y
después ejecuta la instrucción en la
cuál está inmerso.
• op1--: En primer lugar ejecuta la
instrucción en la cual está inmerso
y
después realiza un decremento (en
una unidad) de op1.
La diferencia entre la notación
prefija y la postfija no tiene
importancia en
expresiones en las que únicamente
existe dicha operación:
++contador; es equivalente a:
contador++;
--contador; contador--;
Es importante no emplear estos
operadores en expresiones que
contengan más
de una referencia a la variable
incrementada, puesto que esta
práctica puede
generar resultados erróneos
fácilmente.
La diferencia es apreciable en
instrucciones en las cuáles están
incluidas otras
operaciones.
Por ejemplo:
cont = 1; cont = 1;
do { do {
…} …}
while ( cont++ < 3 ); while ( ++cont
< 3 );
En el primer caso, el Ciclo se
ejecutará 3 veces, mientras que en
el segundo se
ejecutará 2 veces.
Otro ejemplo:
a = 1; a = 1;
b = 2 + a++; b = 2 + ++a;
• En el primer caso, después de las
operaciones, b tendrá el valor 3 y a
el valor
2.
• En el segundo caso, después de las
operaciones, b tendrá el valor 4 y a
el
valor 2.
10.2 Operadores relacionales
Operador Formato Descripción
> op1 > op2 Devuelve true (cierto)
si op1
es mayor que op2
< op1 < op2 Devuelve true (cierto)
si op1
es menor que op2
>= op1 >= op2 Devuelve true (cierto)
si op1
es mayor o igual que op2
<= op1<= op2 Devuelve true (cierto)
si op1
es menor o igual que op2
== op1 == op2 Devuelve true (cierto)
si op1
es igual a op2
!= op1 != op2 Devuelve true (cierto)
si op1
es distinto de
op2
Los operadores relacionales actúan
sobre valores enteros, reales y
caracteres
(char); y devuelven un valor del
tipo boolean (true o false).
Class Relacional {
public static void main(String
arg[]) {
double op1,op2;
op1=1.34;
op2=1.35;
System.out.println("op1="+op1+"
op2="+op2);
System.out.println("op1>op2 =
"+(op1>op2));
System.out.println("op1<op2 =
"+(op1<op2));
System.out.println("op1==op2 =
"+(op1==op2));
System.out.println("op1!=op2 =
"+(op1!=op2));
char op3,op4;
op3='a'; op4='b';
System.out.println("'a'>'b' =
"+(op3>op4));
}
}
Salida por pantalla:
op1=1.34 op2=1.35
op1>op2 = false
op1<op2 = true
op1==op2 = false
op1!=op2 = true
'a'>'b' = false
Los operadores == y != actúan
también sobre valores lógicos
(boolean).
10.3 Operadores lógicos
Operador Formato Descripción
&& op1 && op2 Y lógico. Devuelve
true
(cierto) si son ciertos op1 y
op2
½½ op1 ½½ op2 O lógico. Devuelve
true
(cierto) si son ciertos op1 o
op2
! ! op1 Negación lógica. Devuelve
true (cierto) si es false op1.
Estos operadores actúan sobre
operadores o expresiones lógicas, es
decir,
aquellos que se evalúan a cierto o
falso (true / false).
class Bool {
public static void main ( String
argumentos[] ) {
boolean a=true;
boolean b=true;
boolean c=false;
boolean d=false;
System.out.println("true Y true = "
+ (a && b) );
System.out.println("true Y false = "
+ (a && c) );
System.out.println("false Y false =
" + (c && d) );
System.out.println("true O true = "
+ (a || b) );
System.out.println("true O false = "
+ (a || c) );
System.out.println("false O false =
" + (c || d) );
System.out.println("NO true = " +
!a);
System.out.println("NO false = " +
!c);
System.out.println("(3 > 4) Y true =
" + ((3 >4) && a) );
}
}
Produciría la siguiente salida por
pantalla:
true Y true = true
true Y false = false
false Y false = false
true O true = true
true O false = true
false O false = false
NO true = false
NO false = true
(3 > 4) Y true = false
10.4 Operadores de asignación
El operador de asignación es el
símbolo igual (=).
op1 = Expresión
Asigna el resultado de evaluar la
expresión de la derecha a op1.
.
Resto del documento disponible para descarga...
|
|
|
dd |
|
|
|
|
|
|
|
|
|
|