Introducción a la Programación en Java: Características, Ventajas y Sintaxis
Introducción a Java
¿Qué es Java?
Java es un lenguaje de programación desarrollado por Sun Microsystems® a principios de los años 90 con la siguiente filosofía:
- Debería usar la metodología de la programación orientada a objetos para implementar cualquier proceso.
- Debería permitir la ejecución de un mismo programa en múltiples sistemas operativos.
- Debería incluir por defecto soporte para trabajo en red.
- Debería diseñarse para ejecutar código en sistemas remotos de forma segura.
- Debería ser fácil de usar y tomar lo mejor de otros lenguajes orientados a objetos, como C++.
- Debería ser de libre acceso y uso (software libre)
Características Novedosas de Java
Java surge con la idea de romper con los conceptos existentes hasta el momento sobre la programación. Como todo cambio, para ser aceptado tuvo que ofrecer mejoras ostensibles respecto a la programación estructurada.
- Sencillez: Todas las clases, sean propias del diseñador o del sistema, se implementan de forma análoga. No existen punteros. La memoria dinámica se gestiona de forma automática.
- Multiplataforma: El desarrollo y ejecución son independientes de la arquitectura y del S.O. al 100%.
- Ideal para sistemas heterogéneos: Java es un lenguaje ideal para comunicar sistemas heterogéneos.
- Multi-threading: Permite la gestión de procesos de forma paralela.
- Integración web y modularidad: Permite desarrollar pequeñas aplicaciones (Applets) que pueden ser integradas en páginas web, así como elementos con una “vida” propia que pueden ser incrustados en otras aplicaciones (JavaBeans).
- Seguridad: Es seguro a nivel local (captura de excepciones, control de índices…) y remoto (autenticación, firmas electrónicas, encriptación). Esto último dirigido al e-commerce e implementado en las librerías java.net y java.security.
- Modularidad: Es capaz de crecer con el tiempo y adaptarse a nuevas necesidades.
Este “espíritu Java” ha hecho que el lenguaje haya ido con el tiempo aumentando su potencia y prestaciones en concordancia con la propia evolución que las tecnologías de la información han tenido. Hoy en día Java está reconocido como el lenguaje ideal para:
- Elaborar portales web.
- Desarrollar aplicaciones J2ME para dispositivos móviles (teléfonos, PDAs, Blackberries…).
- Manipular, programar y transformar estructuras de información XML.
- Programar aplicaciones de E/S de datos para sensores (detectores de movimiento o presencia, temperatura, etc.) empleados en las áreas de seguridad y domótica.
- Aplicaciones de video o audio streaming y de radiodifusión (broadcast) en las que se requiere un flujo continuo, fiable y en tiempo real de gran cantidad de información.
- Programación de Servicios Web de integración de plataformas o de colaboración empresarial.
- Desarrollo de ERPs para la gestión empresarial como OpenBravo.
- Aplicaciones para microdispositivos (aparatos médicos, reproductores MP3, tarjetas inteligentes, sistemas de frenado de automóviles y etiquetas RFID, entre otros productos) gracias al proyecto Squawk VM.
La Máquina Virtual de Java (JVM)
El compilador de Java produce un código llamado bytecode que puede ser interpretado por cualquier máquina independientemente del Sistema Operativo sobre el que está instalado o del tipo de recursos que consume (Sistema de Ficheros, Bases de Datos, Interfaz Gráfico…).
La Máquina Virtual Java (JVM) es la especificación de una máquina (ordenador) capaz de ejecutar bytecode Java.
Una implementación de la máquina virtual para una plataforma concreta es lo que llamamos Java Run Time System o intérprete Java que se compone de:
- Verificador de código
- Cargador de clases
- Gestor de seguridad
Los entornos de ejecución JAVA se denominan JSDKs.
Ventajas e Inconvenientes de Java
Ventajas
- Pequeño: Los programas se componen del paso de mensajes entre las clases implementadas.
- Eficiente: Tienen mucha potencia. La relación entre recursos utilizados y tiempo de respuesta es óptimo.
- Portable: Independiente de la plataforma.
- Sencillo de programar.
- Modular: Los programas se componen de piezas que pueden crecer, reutilizarse en otros programas, adaptarse a especializaciones, etc.
- Seguro.
Inconvenientes
- Interpretado: Necesitamos cargar en nuestro sistema un entorno de desarrollo (JSDK) que permita entender los bytecodes generados en otra máquina o sistema.
- Compatibilidad de los JSDK: A lo largo de los años los entornos de desarrollo se han ido mejorando, incluyendo nuevas clases, nuevas funciones… Es posible que algo desarrollado, por ejemplo, en un JSDK 1.4 no funcione correctamente en uno inferior (p.e. JSDK 1.1). En sentido inverso (pasando a una versión más ‘moderna’ jamás habrá esos problemas).
JSDK, JDK, JRE y API
Un JSDK (Java Standard Development Kit) es el entorno de desarrollo y ejecución de Java. Para que una aplicación Java pueda “correr” en nuestro sistema es imprescindible instalar una versión del JSDK que es el que proporciona el JRE (Java Run Enviroment).
Los JSDK contienen las herramientas necesarias para compilar y ejecutar aplicaciones, los paquetes de librerías con los que trabajar, un entorno gráfico (según la versión), generadores de informes, visor de sucesos, visor de applets…
Actualmente los JDK y los JSDK son lo mismo.
Programación Orientada a Objetos
Programación Tradicional vs. Programación O.O.
Programación tradicional (estructurada)
- Modelo “top-down”.
- Un programa se compone de subprogramas.
- Las subtareas se ejecutan “secuencialmente”.
Programación Orientada a Objetos
- Modelo objetos: implementa las técnicas UML de análisis y diseño.
- El programa se compone de objetos o clases.
- Los objetos colaboran para llevar a cabo la tarea a través del paso de mensajes.
Las Clases
Una clase es un conjunto o agrupación de características y comportamientos que definen e identifican de forma clara un “tipo” de elemento de nuestra aplicación.
A veces las clases de un programa imitan objetos reales:
- Clase: Persona
- Características: edad, talla, peso, nombre, ocupación…
- Comportamientos: andar, hablar, coger, mirar…
Otras veces representan conceptos abstractos pero igualmente útiles en nuestro programa:
- Clase: Número Complejo
- Características: parte real, parte imaginaria
- Comportamientos: sumar, restar, multiplicar, dividir
Las características, que a partir de ahora denominaremos atributos, se implementan utilizando variables y los comportamientos a través de métodos. Más adelante veremos cuál es la estructura que adopta una clase, con sus atributos y métodos, dentro de una aplicación JAVA. La abstracción consiste en determinar, de todas aquellas características ‘naturales’ de una clase, cuáles son aquellas que realmente van a ser útiles o necesarias en el programa, desechando las demás.
Objetos e Instancias
Mientras que una clase es un “molde” para crear objetos del mismo tipo, las instancias de una clase se generan al rellenar los datos de las plantillas (los atributos) con valores concretos haciendo así funcionales sus métodos.
Por tanto, a través de la instanciación creamos objetos diferentes de una misma clase que tienen las mismas características (con valores distintos) y comportamientos muy similares (en función de los mencionados valores).
Para crear una instancia utilizaremos uno de los constructores de la clase. Instanciar una clase implica: 1. Cargar los atributos de la clase (con su inicialización si se ha definido) y 2. ‘insertarlo’ en el sistema (ámbito de la aplicación) para que sea útil.
Mensajes
Los objetos colaboran para llevar a cabo una tarea ya que muchas veces quien desencadena la acción no puede realizar por sí mismo una parte del proceso. Para ello los objetos se comunican mediante el envío de mensajes.
No es necesario que dos objetos formen parte del mismo proceso, ni siquiera que estén en la misma máquina, para que puedan enviarse mensajes.
La forma de enviar mensajes consiste en usar los métodos implementados de los objetos que interactúan. Un mensaje desencadena la ejecución de un método en el objeto receptor.
Características de la POO
La programación orientada a objetos tiene tres pilares fundamentales sobre los que se sustenta pero que no obliga a implementar. Lo que se nos asegura es que, en caso de usarlos, tendremos algún beneficio:
- Herencia: Para hacer aplicaciones más modulares y reutilizar al máximo clases ya implementadas.
- Sobrecarga y polimorfismo: Aumenta la funcionalidad de las aplicaciones permitiendo crear alternativas de proceso.
- Encapsulamiento: Asegura la integridad de los datos minimizando el riesgo de tener en ejecución objetos con valores erróneos.
Herencia
- Las clases pueden estar relacionadas de forma jerárquica (padres-hijos) heredando las propiedades y los comportamientos de sus ascendientes.
- Consiste en definir una clase en función de otra con lo que conseguimos modularizar al máximo.
- Una clase hija es una especialización de la clase padre bien porque añade atributos o métodos, o porque los redefine.
- Una clase puede tener N-hijos, pero un hijo sólo podrá heredar de un padre. En Java no se contempla la herencia múltiple como tal; sin embargo, se intenta aprovechar las ventajas de esta técnica a través de las interfaces.
- La forma de implementar en Java la herencia entre clases es a través de la palabra reservada
extends
.
Ya no sólo a nivel de concepto sino de implementación podemos afirmar que una clase padre puede albergar a cualquier tipo de sus hijos, pero jamás en sentido inverso. MedioTransporte medio = new Familiar();
En este caso concreto se debe a la siguiente aseveración lógica: “Todo vehículo familiar es un medio de transporte”. El aserto inverso, sin embargo, no se cumple en todos los casos: “Todo medio de transporte no es familiar” y por ello jamás podremos hacer: Familiar cocheFam = new MedioTransporte();
En Java, todos los objetos heredan de una superclase genérica que es la que asegura el idéntico comportamiento de las diferentes clases dentro de la máquina virtual. Esta superclase se llama Object
y por lo que acabamos de ver unas líneas más arriba podemos asegurar que: Object miObjeto = new CualquierTipo();
Polimorfismo, Sobrecarga y Sobreescritura
El polimorfismo hace referencia a la posibilidad de que métodos con mismo nombre, mismo número y mismo tipo de argumentos, coexistan implementados en clases distintas.
- Los objetos pueden responder de distinta forma frente al mismo mensaje.
- El método que se va a ejecutar depende del objeto, no del mensaje. P.e. Supongamos el método
mover()
. No es lo mismo mover un coche, que mover una pieza de ajedrez y, más allá, no es lo mismo mover en el tablero un alfil que una torre.
Cuando mezclamos este concepto con el de herencia comentado en el apartado anterior, surge un nuevo concepto denominado sobrescritura.
Ya sabemos que cuando una clase hereda de otra, adquiere automáticamente todos sus comportamientos (métodos). Sin embargo, es posible que por la propia especialización que exige la subclase, sea necesario redefinir un método del padre para evitar comportamientos erróneos.
La redefinición o sobrescritura exige que el método sobrescrito tenga el mismo nombre, el mismo número y orden de argumentos y el mismo tipo devuelto.
Cuando en una misma clase coexisten dos o más métodos con el mismo nombre pero con distinto tipo o número de argumentos, decimos que lo que existe es una sobrecarga de métodos. La forma que tiene la máquina virtual de diferenciar a cuál de los métodos se está invocando será, precisamente, la lista de argumentos invocada:
P.e. coche.mover()
=> desplazar un coche hasta pulsar parar();coche.mover(int mts)
=> desplazar un coche los metros indicados por parámetro
Encapsulamiento
En ocasiones, al describir las características de una clase y ver su comportamiento, el diseñador se da cuenta de que existen atributos “críticos” que deben ser protegidos para cuidar de su integridad y de un correcto uso. La forma de proteger estos atributos es “encerrarlos” (encapsularlos) en la propia clase para que sólo ella pueda acceder a su información.
Las demás clases no podrán acceder directamente a los atributos protegidos, y ni tan siquiera los objetos de la propia clase podrán verlos. Sin embargo, sí podrán hacer uso de los métodos implementados que controlan la forma de acceder a la información.
Características del encapsulamiento:
- Equivalente a Tipos Abstractos de Datos.
- Modularidad.
- Ocultamiento de información.
El primer paso, más allá del propio análisis, consistirá en declarar los atributos seleccionados como private
de forma que se hacen inaccesibles. A partir de este instante cualquier referencia directa al atributo se considera un error en tiempo de compilación. A continuación deberemos implementar los métodos GET/SET que implementen las condiciones especiales o restricciones de acceso a esas variables. Su estructura siempre es idéntica:
public void setAtributo (tipo_atrib nuevo_valor){ //condiciones… }
public tipo_atrib getAtributo () {// condiciones… return atributo;}
La carga de valores en el atributo usará las condiciones del SET y la recuperación de dicho valor las del GET.
Sintaxis de Java
Organización de las Aplicaciones Java
Las aplicaciones en Java se organizan a nivel de proyectos. Una aplicación en Java es un conjunto de procesos (algunos independientes y otros colaborativos) que sirven a un propósito empresarial.
Generalmente las clases se agrupan por colaboraciones en esos procesos. Éstos conjuntos o packs de clases se agrupan en paquetes (packages) que determinarán la visibilidad, accesibilidad y forma de colaboración en la aplicación. Un paquete puede estar organizado, a su vez, de N subpaquetes.
Las aplicaciones pueden necesitar de la colaboración de otras aplicaciones o proyectos desarrollados con anterioridad. Estos proyectos suelen estar comprimidos en archivos JAR que pueden ser importados y utilizados como si fueran propios de nuestro proyecto.
Estructura de un Programa
Un programa Java es la definición de una clase que tiene la posibilidad de lanzar un hilo de ejecución con el que poner en comunicación a las distintas clases que se encuentren definidas o que sean invocadas desde el proyecto.
El comienzo de la ejecución consiste en crear un objeto de la clase y enviarle un mensaje inicial que provoca la ejecución del método main()
.
Las clases de un mismo proceso se agrupan en los denominados paquetes (package
). Si queremos referenciar o usar una clase que tenemos definida en otro paquete o una que se encuentra ya implementada en el API de Java que tenemos instalado, deberemos hacer uso de la importación de las mismas en el espacio que se tiene reservado a los import
.
Las Clases
Una clase es la definición de los objetos que pertenecen a ella, es decir, una plantilla en la que se define su estructura (variables miembro o atributos) y su comportamiento (métodos).
Una vez definida una clase, ésta es “un nuevo tipo de dato”. Podemos declarar variables usando el nombre de la clase como tipo y estas variables serán referencias a objetos de la clase.
La declaración básica de una clase tiene la siguiente estructura:
package proyecto;
import librerías;
class nombre {
//declaración de variables globales (atributos)
//declaración de constructores
//declaración de métodos
}
Atributos y Variables Globales
La declaración de una variable es de la siguiente forma:
[Tipo_visibilidad] tipo_de_dato identificador_de_variable [= expresión];
El tipo de visibilidad hace referencia a los lugares (proyectos y/o clases) desde los que las variables pueden ser accedidas.
El tipo puede ser primitivo (enteros, byte, caracteres, decimales…), o bien un identificador de una clase previamente definida (tipo referencia que apunta a un objeto de la clase). Cada instancia de la clase (cada objeto) tendrá sus propias variables miembro que ocupan espacio en memoria y para acceder a ellas se utilizará el operador punto.
Las variables se clasifican en 5 grupos según su ámbito:
- Variable miembro de una clase.
- Variable local de un método.
- Parámetro de un método.
- Variable de flujo.
- Parámetro de un gestor de excepción.
Constructores
Un constructor es un método que inicializa un objeto en el momento de ser creado (cuando se ejecuta el operador new
). Siempre debería ser público, no tiene tipo de retorno y tiene el mismo nombre que la clase. Su estructura es la siguiente:
public nombre_clase ( [lista_argumentos]){…}
Si no se define un constructor explícitamente, la clase tiene el constructor por defecto, sin parámetros, que no inicializa las variables.
Los constructores tienen una doble función: por un lado la posibilidad de inicializar a los objetos con sus valores y por otro la de “prepararlos” para el sistema en el que van a trabajar. No me serviría de nada crear perfectamente un objeto “peón” sin posicionarlo en un tablero de una partida de ajedrez, o crear un “delantero” sin ponerlo a jugar en un partido…
Es posible sobrecargar el constructor de una clase de forma que, según el número o tipo de los argumentos pasados, las operaciones a realizar (inicialización, carga, visualización…) en el momento de instanciar sean unas u otras. La sobrecarga de constructores es un mecanismo de especialización en la carga de atributos de una clase.
El constructor por defecto se hereda de la clase Object
, superclase de todas las clases Java, que es el que utiliza para implementar el mecanismo de herencia.
Vamos a poder referenciar a constructores de la propia clase y a constructores ya implementados en una clase padre haciendo uso de los objetos this
y super
, reutilizando así al máximo el procedimiento de carga y las condiciones idóneas de inicialización o inserción en el proceso. En un proceso real, los constructores de carga reciben como argumentos o parámetros, información persistente del sistema (registros de bases de datos, ficheros de texto, etc).
Métodos
Los métodos de una clase son funciones declaradas en el cuerpo de la clase y representan los comportamientos de la misma. La declaración básica de un método sigue la siguiente estructura:
[tipo_visibilidad] tipo_devuelto nombre ( [lista_de_parámetros] ) {
conjunto de sentencias;
}
Los métodos son código, de modo que no hay necesidad de que cada objeto tenga una copia de ellos, pero una llamada siempre debe estar asociada con un objeto concreto. Se hace una llamada a un método en un objeto utilizando el operador punto. Esto se conoce como enviar un mensaje a un objeto.
Existen métodos que son importantes por su proceso y que, por tanto, no necesitan devolver un resultado. El tipo devuelto de estos métodos es el denominado void
.
Sin embargo, existen otros métodos que deben devolver un resultado a aquel objeto que haya pedido su colaboración a través del mensaje. Estos métodos declararán el tipo de la devolución (puede ser un “dato” o un objeto) y como última línea de código deberán indicar un return
de una variable u objeto del tipo indicado.
Método main
Toda aplicación debe tener un método main
, puesto que es el método que invocará el JRTS (Java RunTime System) tras cargar el bytecode.
El método main
no devuelve nada (void
) y debe tener un único parámetro (un array de String
) que permitirá a la aplicación aceptar argumentos de la línea de comando. Además, el método main
debe ser public
(accesible desde cualquier parte del programa) y static
(idéntico para todos los hilos de ejecución).
public static void main(String[] args) {…}
Un programa es una clase, pero no todas las clases son programas. El método main
no es obligatorio en todas las clases y en un mismo proyecto puede haber más de una clase con método main
.
Comentarios
Son partes del programa fuente que serán ignoradas por el compilador. Java soporta 3 tipos de comentarios:
- Comentario de una línea (
// comentario
). - Comentario de varias líneas (
/* comentario */
). - Comentario de documentación: este tipo de comentarios se incluyen en la documentación generada por javadoc (documentador de Java incluido en toda máquina virtual).
Palabras Reservadas
Las palabras reservadas Java no deben utilizarse como identificadores de variables, métodos o clases.
abstract, double, int, static, boolean, else, interface, super, break, extends, long, switch, byte, final, native, synchronized, case, finally, new, this, catch, float, null, throw, char, for, package, throws, class, goto, private, transient, const, if, protected, try, continue, implements, public, void, default, import, return, volatile, do, instanceof, short, while
En los IDE (entornos de desarrollo) estas palabras aparecen en negrita dando a entender la exclusividad de su uso.
Tipos de Datos
Los tipos de datos de Java se dividen en dos grupos: primitivos y referencia.
- Tipos Primitivos: Una variable de un tipo primitivo contiene un valor del tipo correspondiente. Un tipo primitivo como tal es una dirección de almacenamiento, no una referencia a un objeto, y por ello, los tipos primitivos no tienen asociados ni atributos ni métodos. Sin embargo, si el programador quisiera aplicar a un tipo primitivo la potencia de los objetos, puede usar su tipo referencia asociado: p.e. el tipo primitivo
int
dispone de su correspondiente tipo referenciaInteger
que tiene implementados numerosos métodos para trabajar con enteros. Todo tipo primitivo tiene un tipo referencia asociado. - Tipos Referencia: Una variable de tipo referencia es una referencia al valor o conjunto de valores (objeto) que representa la variable. Hace referencia a la dirección de memoria donde se almacena el valor. No se maneja como un puntero de C. Una vez definido un objeto de esta forma, tiene automática disponibles todos los atributos y métodos implementados en el objeto correspondiente.
Literales
Representación de valores constantes.
- Los números enteros se pueden representar en decimal, octal o hexadecimal.
- Los números reales se pueden representar en notación estándar o científica.
- Los números decimales pueden ser simples (
float
) o de doble precisión (double
). - Los literales booleanos son
true
yfalse
, no tienen representación ni equivalencia numérica. - Los caracteres se representan entre comillas simples. Los caracteres que no se pueden teclear se representan mediante secuencias de escape.
- Las cadenas de caracteres se representan entre comillas dobles.
Visibilidad y Acceso
La declaración de una clase puede ir precedida del modificador de acceso public
, lo cual indica que cualquier otra clase puede utilizarla (crear objetos de esa clase). Por defecto, sólo las clases del mismo paquete pueden utilizarla si no hay imports
de por medio. A esta forma de trabajar se le denomina visibilidad.
Dentro de una misma clase todas la variables y los métodos son accesibles y visibles entre ellos. La clase encierra a sus miembros y controla el acceso desde las otras clases. En este sentido las otras clases se dividen en tres grupos:
- Subclases (pueden estar en el mismo paquete o en otro).
- Clases del mismo paquete (pueden ser subclases o no).
- Clases de otros paquetes que no son subclases.
La declaración de una variable miembro o un método puede ir precedida por los siguientes modificadores de acceso que determinan el nivel de acceso permitido para la variable o método:
private
: sólo la propia clase tiene acceso.package
: sólo las clases del mismo paquete pueden acceder. Es la visibilidad o acceso por defecto, lo cual quiere decir que en caso de no especificar ningún modificador en una variable, atributo, método o clase, sólo las demás clases del mismo proyecto podrán usarlos.protected
: además de las hijas, también las clases del mismo paquete tienen acceso. Las subclases de otros paquetes, sin embargo, tienen un acceso especial* (las heredan como propias pero no las verían en objetos de su clase padre).public
: cualquier clase tiene acceso.
Clases, Métodos y Variables Finales
El modificador final
se puede aplicar a una clase, un método o una variable miembro. En cada contexto tiene su significado.
- Para evitar que posibles subclases sobreescriban un método, éstos se declaran con el modificador
final
. - Una variable o atributo
final
es una constante y no ocupa memoria en cada uno de los objetos, sólo hay una copia para todos ellos. Se crea al cargar la clase en el JRTS. Por convención los nombres de las constantes son de letras mayúsculas. - El modificador
final
aplicado a una clase impide que se definan subclases de ella.
Métodos y Variables Estáticos
Las variables y métodos estáticos son la forma de implementar variables y funciones “globales” en Java; no están asociados a objetos particulares sino a la clase en general. Se declaran con el modificador static
y se hace referencia a ellos mediante el operador punto utilizando el nombre de la clase, no de un objeto.
Al igual que en las variables final
, sólo hay una copia de las variables static
de la clase, pero no son constantes. Para inicializar las variables static
se puede definir un bloque de código static
que será ejecutado al cargarse la clase.
Los métodos estáticos sólo pueden hacer llamadas directas (sin operador punto) a otros métodos estáticos y no pueden utilizar las referencias this
ni super
. Además, los métodos estáticos no pueden ser sobrescritos.
Los métodos estáticos se heredan cuando se da el caso de manera que una subclase puede usar todos ellos como si estuvieran declarados en la propia clase.
Superclase:… public static String test(String s){ …}
Subclase:… desde un contexto estático p.e. un main
System.out.println(test(args[0]));
Objetos Predefinidos this y super
this
La variable ‘this
’ está accesible desde cualquier ámbito no estático de una clase y representa a cualquier objeto de dicha clase una vez esté creado. Se puede referenciar a él de dos maneras:
- Para acceder y ejecutar un constructor de la propia clase: para ello usaremos
this
seguido de una lista de argumentos que coincida en número, orden y tipo con una de uno de sus constructores, ejecutando todo el código contenido en él Æ this([lista_args]). La llamada athis()
ejecutaría el constructor vacío de la clase. - Para acceder a los métodos y atributos accesibles de la clase en ese punto: para ello usaremos el operador punto desplegándose automáticamente las características y comportamientos de los objetos de la clase, no sólo los propiamente implementados en la clase, sino también todos los heredados Æ
this.miMetodo()
óthis.atributo
super
La variable ‘super
’ está accesible desde cualquier ámbito no estático de una clase que herede de otra, de forma que la invocación a dicha variable provoca una ‘propagación del ámbito’. Se trata de ceder el control a la clase padre para que sea ésta la que ejecute uno de sus constructores super(lista_args)
o uno de los métodos que tuviera éste implementado (y que podría haber sido sobrescrito) super.métodoPadre()
.
Siempre que se haga llamada al objeto ‘super
’, ésta deberá ser la primera línea de código del método desde el que se invoque; de otro modo se producirá un error en tiempo de compilación.
Sentencias de Control de Flujo
Condicionales
- Condicional simple:
if (expresion_booleana) { conjunto de sentencias } [else { conjunto de sentencias }]
- Condicional múltiple:
switch(expresión evaluable) { case valor1: conjunto de sentencias; break; … case valorN: conjunto de sentencias; break; [default: sentencia] }
Cuando en una función el valor devuelto depende de una condicional simple o múltiple, hemos de asegurarnos de que se retorna un resultado por cada rama de dicha condicional.
Bucles
for ([inicialización]; [mientras condición]; [acción en cada vuelta]) { //conjunto de sentencias }
bucle con contador. Las sentencias de inicialización, condición y acción pueden ser múltiples, separadas por comas. No es obligatorio codificar los tres parámetros; sin embargo, siempre tienen que aparecer las separaciones con “;”.while (condición) { //conjunto de sentencias }
bucle de 0 a N veces. Ya que primero se evalúa la condición y luego se entra en el cuerpo, con lo que la primera comprobación puede ser ya falsa.do { //conjunto de sentencias } while (condición)
bucle de 1 a N veces. En este caso primero se ejecuta el cuerpo y al finalizar la iteración se evalúa la condición para realizar la siguiente pasada.
Rupturas de Control de Flujo
Las rupturas se suelen utilizar para romper un proceso recursivo (bucle) bajo ciertas condiciones (if (condición) Æ ruptura
). Existen varias formas de romper el flujo:
break[etiqueta]
: sin etiqueta provoca un salto al final del bloque en el que se encuentra; con etiqueta provoca un salto al final del bloque indicado por la etiqueta. El bloque indicado debe englobar la sentenciabreak
.continue[etiqueta]
: sin etiqueta provoca un salto al comienzo del bucle en el que se encuentra; con etiqueta provoca un salto al comienzo del bloque indicado por la etiqueta. El bloque indicado debe englobar la sentenciacontinue
.return[expresion]
: provoca el retorno al método llamador con resultado opcional.
Manipulación de Datos
Casting
Java es un lenguaje fuertemente tipado, es decir, sólo permite hacer asignaciones entre variables de distintos tipos cuando el tipo de la variable destino tiene capacidad para almacenar cualquier posible valor del tipo de la variable asignada. En este caso, en el que no se pierde información, se produce una conversión de tipo o casting automático llamado “promoción” o “ampliación” (promotion or widening):
byte Æ short Æ int Æ long Æ float Æ double
El caso contrario sería una “reducción” (narrowing) y es necesario hacer un casting explícito. Si el valor asignado no está en el rango del tipo destino se truncará.
Tipos de Casting
• Transformación de cualquier tipo primitivo a String: este casting se puede realizar de
forma inmediata sinnecesidad de utilizar ningúnmétodo asociado a laclase.Basta con
concatenar la variable a un subcadena (incluida la vacía).
• Transformación de una variable de tipo primitivo A a su correspondiente Clase A : hay
ocasiones enlas que el propio uso de algunos métodos implementados, nosobliga a
trabajar con tipos referencia en lugar de con tipos primitivos. Cuando sea necesario
transformar untipo primitivo A ensutipo referencia asignado,deberemos utilizar los
constructores de la clase.
• Transformación de un tipo referencia A en su correspondiente tipo primitivo: a pesar de
la potencia de los tiposreferencia,la mayor parte de los operadores nosonsoportados
directamente por ellos (suma,resta, comparación…).Es decir, nunca podremos por
ejemplo sumar directamente dos Integer, perosí sus correspondientes valores int.
Para obtener el tipo primitivo de un tipo referencia, deberemos usar siempre uno de los
métodos definidos sobre la instancia de la clase de partida.
• Transformación de un tipo A en un tipo B: esta transformación sigue los mismos pasos
bien se trate de variables de tipo primitivo o de tipo referencia. Si nos encontráramos
en este caso debemos preguntarnos en primer lugar siempre lo mismo: “¿Se puede
realizar la transformaciónpara cualquier valor que tome la variable que deseamos
transformar?”
Si la respuesta fuera afirmativa, podremos usar el llamado casting directo que vamos a
ver con un ejemplo:
Supongamosque queremos transformar unavariable i de tipo inta float.¿Podemos
realizar la transformación de un entero a decimal sea cual sea el valor del decimal?. La
respuesta obvia es sí, por lo que estamos enel caso que nos ocupa. En este caso
aplicamos el casting directo de la siguiente forma:
float a = (float) i ;
Por tanto elcasting directo sigue la estructura: tipo_B= (tipo_B) variable_ tipoA; Lo
mismo es aplicable a las clasescomo veremos en un ejercicio.
Retomemos ahora ladisyuntiva de lapregunta y sigamos por larama alternativa: el
caso en el que no siempre se pueda realizarla transformación, pero síen algún rango
de valores. ¿Podemos a caso transformar unstring enun entero?. Obviamente no
podemos obtener un valor numérico de la cadena “pepe”pero sí de la cadena“645”,
luego este sería un ejemplo válidoa estudiar.
En estos casos la forma de lograr la transformación será “acudir a algunode los
métodos estáticos definidos en la clase correspondiente al tipodestino.” Enel ejemplo
queremos pasar de un String a unint. La clase del tipo destino es, por tanto, el tipo
referencia Integer. Puestal y como dice lapremisa, debería existir un método en ella
que pueda realizar esta transformación, como así es: Integer.parseInt (String s) ;
2. MANIPULACIÓN DE CADENAS
Las cadenas de caracteres en JAVAvienen definidas por el tiporeferenciaString que,como
tal, dispone de un amplio abanico de métodos para manipularlas. Sin embargo, el hecho de
que las cadenas sean objetos, nos obliga a tener presente una serie de factores:
• Aunque es cierto que las variablesde tipo cadena pueden inicializarse como el resto de
los tipos primitivos [Stringcadena=“Me llamoIban”; ] el hecho de ser tratadas como
objetos tambiénnos permite inicializarlas A.)usandolos constructores de la clase:
String cad1 = new String ();
String cad2 = new String (“Ésta es otra cadena”);
B.)Usando métodos estáticos de la clase
String cad3 = String.valueOf (1.23);
C.)Inicializándola a null para reservar una direcciónde memoria:
String cad4 = null;
• Los operadores no tienen cabida a la hora de usar cadenas, yaque éstos están
orientados a los tipos primitivos dedatos.La comparación de cadenas se tiene que
realizar a través del método equals(), los tamaños a través del length()… Elúnico
operador que se puede usar es el “+”pero susignificado es completamente distinto:no
se trata de sumar valores sino deconcatenar cadenas. Recordemos que cualquier tipo
primitivo que se concatene a unStringseconvierte, automáticamente, en cadena.
• Las cadenas de tipo String son estáticas: estono significa que no puedan cambiar de
valor, ni incrementarse a través de la concatenación. Significa que una cadenaes un
arrayde caracteres y,como tal, noexistentécnicas de inserción,desplazamiento,
borrado o manipulación de los mismos.
3. COLECCIONES
Las colecciones son objetos que pueden contener otros objetos. Algunos de los tipos
simples de datos en JAVA son colecciones comoes el casode los arrays, los vectores, las pilas
o las tablas indexadas, aunque existen estructuras de datos mucho más complejas y potentes
como los árboles o los mapas.
La mayoría de estas colecciones se incrementanautomáticamente por loque no es
necesario decir de antemano cuálva a ser el tamaño necesario paraubicar loselementos de
los que disponemos, máxime cuando muchas veces desconocemos este dato.
3.1. ARRAYS
Java permite asociargrupos de valores en un objeto array. Los arrays no son variables
sinoobjetos,las variables que utilizamos para manejarlos sonvariables de tipo referencia.
La declaración de un array indica su tipo (el tipo de los elementos que contiene) y su
nombre (el nombre de la variable que lo referencia). Nose puede declarar un array genérico.
Ladeclaraciónnoreserva memoria para contener los elementos.Antes de utilizarlo, el
programa debe instanciarlo. Para ello seutiliza eloperador new bien en la misma
declaracióno posteriormente. Existen varias formas de declarar un array:
(1) Tipo_array[] nombre_array = new Tipo_array [capacidad];
(2) Tipo_array[] nombrearray ={valor1, valor2,…,valorN};
(3) Tipo_array[] nombrearray;
Se pueden declarar arrays tanto detipo primitivos de datos como de tipos referencia
como de objetos definidos por el usuario o por el API deJAVA: int[], Integer[],TextField[] o
Cliente[];
Para saber la capacidad de un array disponemos de la propiedad length.
Para referenciar unelementose utiliza elnombre del arrayseguido del índice entre
corchetes.Elíndicevade 0 a longitud-1.Es posibledeclarar,instanciar e inicializar unarray en
una sola instrucción perohay que tener siempre mucho cuidado en la definición del tamaño ya
que los arrays no pueden crecer dinámicamente. Esta operación se podría“simular” con un
volcado de datos en un array de mayor longitud pero no es muy frecuente. Cuando no se sabe
nisiquiera con aproximación elnúmero de elementos que va a tener quealmacenar la
estructura, se suelen utilizar los Vectores, que veremos a continuación.
EnJava noexistenlos arraysmultidimensionales,perose puedendeclarar arraysde
arrays, lo cual surte casi el mismo efecto. La única diferencia es que nose almacenan
necesariamente en un único bloquede memoria.
3.2. VECTORES
Así como para la utilizaciónde arrays nos es indispensable conocer el número de
elementos que éste va a albergar para, de estaforma, reservar espacio de memoria, cuando
este dato nos es desconocido JAVAnos ofrece la posibilidad de trabajarcon vectores.
Unvectoresunaestructuradedatosque puede contener un número indeterminado de
Objetos, y sólo objetos, y que trata su tamaño de forma dinámica. La forma enlaque están
implementados los vectores nos facilita mucho las cosas a la hora de manipular los datos:
siempre se añade, busca, elimina oinserta de la misma manera independientementede estar
almacenando Clientes o Strings.
Sin embargo, esta forma de implementación acarrea con alguna incomodidad respecto a
los arrays que comentábamos en el aparatado anterior.Enprimer lugarenunvectornose
pueden almacenar tipos primitivos de datos(int,long, char…)aunqueciertamente sípodemos
almacenar sus correspondientes tipos referencia haciendo uso del casting.Por otro lado a la
hora de extraer cualquier elemento de un vector, se ha dispuesto que éste sea del supertipo
Object perdiendo en primera instancia el tipo referencia del objeto original; luego,por ejemplo,
insertamos Integers pero recuperamos Objects siendo necesario usar otro caso decasting.
Además,la propia definición de cualquier colecciónentre las que incluimos a losvectores
hace que,a diferencia de los arrays,en ellas puedan almacenarse objetos de tipos diferentes
con el peligro que ello acarrearíaa lahora de su transformación. Para solventar este problema,
a partir de la versión 5 de los SDKs, se ha incluido la posibilidadde tipificar vectores; es
decir, determinar en el momento de su creaciónel tipo exclusivo de elementos que va a poder
contener. Su estructura esla siguiente: Vector mivector = new Vector();
Como hemos mencionado, la clase Vectortiene implementados una ampliaserie de
métodos que permitenlocalizar elementos,añadirlos al final de la cola,interponerlos entre dos
posiciones dadas de forma sencillay universal, etc.
Para saber el número de elementos que posee en un momento dado un vector, existe el
método size()indispensable a la hora de trabajar con estas colecciones ya que, un acceso
fuera de rangos, nos ocasionará un error en tiempo de ejecución (tal y como sucedía con los
arrays). Elrecorrido de los vectores va de 0a size()-1;
3.3. ENUMERACIONES
Existen muchos métodos de clases de colecciones que devuelven todo su contenido en un
formato especialllamado Enumeration y que se utilizanpara hacer unrecorrido secuencial de
dicha información haciendo elmínimo uso de memoria. Este procedimiento es universalpara
cualquier colección definida en el API de JAVA.
El recorrido de estas colecciones sólo se puede realizar en una dirección y sólo se puede
hacer una vez ya queuna vez seiteracon un elemento contenido, este desaparece dela
memoria eliminado por el recolectorde basura.
La forma de obtener los elementos es a través del método nextElement y laforma de
saber si hay información o no por leer se realiza por medio de hasMoreElements.
3.4. TABLAS INDEXADAS
Se trata de un tipo de colección en la que puedes buscar un objeto basándote en el
valor de otro objeto que se convierte en la clave de acceso. Se trata, por tanto, de una
colección en la que se pueden hacer búsqueda dirigidas mucho máseficaces.
Cuando se añade un elemento a una HashTablese debe indicar no sólo el objeto a
insertar sino también la clave por la que va a ser referenciado de forma única. Para hacernos
una idea, escomo trabajar con tablas en las que existe un campo de clave primaria.
La clave puede ser cualquier tipo de objeto por lo que al igual que enlos vectores,
no podemos usartipos primitivos de datos. Estose debea que la búsquedaa través de su
método principal hashCode()usa internamente el método equals(), definido en exclusiva para
los objetos. Si un programador decidiera crear sus propias clases de clave, debería sobrescribir
los métodosque acabamos de nombrar. Parallevar a cabo las búsquedas y la manipulación de
los datos son muy útiles, entre otros, los métodos get(), isEmpty(), remove(), keys() y
elements().
INTERFACES Y CLASESABSTRACTAS
1. CLASES ABSTRACTAS
Hay clases que por su generalidad, nunca van a ser instanciadas. Loimportante de estas
clases va a ser la agrupaciónde variables y métodos genéricos que sí van a poder ser
heredados por clases hijas.
Este mecanismo aporta, por tanto, dos de las características más importantes de los
lenguajes orientados a objetos:
• Por unlado la modularidad: que permite “subir de nivel” métodos que caractericen
a unao varias subclases.
• Por otro lado la seguridad: ya que sólo cuando las subclases implementen los
citados métodos, podrán ser consideradas clases “normales” y por tantoser
instanciadas.
Una clase abstracta, por tanto, define características y comportamientos abstractos (sin
implementarlos) y nos servirá como base para las subclases que tendrán sus propias variables
e implementaránsupropia versiónde los métodos abstractos.
Ya a nivel decodificación diremos que 1basta con que uno de los métodos de una clase
sea abstracto para que la clase tambiénlosea yque 2sólo cuando todoslos métodos
abstractos heredados sean implementados, la clase podrá ser instanciada.
Que las clases abstractas no se puedan instanciar significa que nunca podremos usar
uno de susconstructores para crear objetos, pero no por elloestos constructores dejan de
tener sentido en estetipo de clases. A través de los mecanismos de herencia, cualquier
subclase que implemente todoslos métodos abstractos podrállamar en sus propios
constructores a los de su superclasea través de la variable super.
RECORDAR:Nose pueden crear objetos de una clase abstracta.
2. LA HERENCIA MÚLTIPLE
La herenciamúltiple hace referenciaa aquellas situaciones en la que una clase “hija”
hereda de dos o más clases “padre” adquiriendo, por tanto, todas las característicasy
comportamientos de éstas.
Aunque esta técnica esempleada por otros lenguajes orientados a objetos, la forma en
que está pensado y diseñado el lenguaje JAVA hace que esta ideaorigine problemas de dos
tipos:
A nivel de atributos: yaque puede heredar de sus padres atributos con el mismo
nombre pero delmismo o diferentes tipos…
A nivel de métodos: puede heredar métodos con el mismo nombre pero con
funcionalidades distintas.
En ambos casos el compilador no va poder tomar decisiones y ante la duda, provocará
un error en tiempo de compilación impidiendo continuarel proyecto.Por ello en JAVA no se
puede implementar la herencia múltiple comotal.
Sin embargo es de sobra conocida la utilidad deeste mecanismo y porello JAVA intenta
simular los beneficios de la herencia múltiple através de la Interfaces.
3. INTERFACES
Un interfaz lleva hasta el extremo el concepto de clase abstracta. Un interfaz podrá
verse simplemente como una plantilla de comportamientos yenellas sólo se permite declarar
nombres demétodos (no código), listas de argumentos, tipos de retorno y adicionalmente
miembros datos (los cuales podrán ser únicamente tipos básicos y serán tomados como
constantes en tiempo de compilación, es decir, static y final).
Para poder entender mejor este concepto tan abstracto, podemos pensar en otroque,
como símil,puede aclararnos las cosas: el concepto de “pertenecer a un clan”. Declarar una
interfaz es decidir qué es lo que tiene que saber hacer una clase para poder ser parte del ‘clan’
que representa esa interfaz.
Para debatir un poco enclase, podemos analizar y decidir,por ejemplo: ¿Qué es lo que
debería saber hacer una persona para poder ser un futbolista?
Nos daremos cuenta de que esos requerimientos son funcionales más que descriptivos;
nos es muy fácil encontrar acciones pero bastante más complicado encontrar atributos. ¿Es
necesario ser zurdo odiestro para ser futbolista?, ¿o tener undisparo de una potencia
determinada? Cómo mucho igual podemos definir algún rango (máximo o mínimo) por ejemplo
para la edad, pero sin dudaestosería muy discutible…
Por todo ello hemos definido elinterfaz como un conjunto de declaraciones de métodos
sin implementación y variables comunes y constantes. Una clase puede implementar una o
varias interfaces definiendo cadauno de los métodos declarados en ellas.
Volviendo a nuestro concepto de pertenencia, podemos tener definido que es lo que
tiene que cumplir una clase para ser una ‘Aeronave’ (avión, helicóptero, globo, zeppelín…) y
por otro lado lo que debe cumplirse paraser ‘Espacial’(satélite, estación,plataforma…).
Bueno, pueses posible que queramos definir clases como trasbordador espacial que deba
cumplir las características de ‘Aeronave’ y de ‘Espacial’.
Las interfaces de Java implementan las ventajas de la herencia múltiplesinla
complejidadque conllevaésta.Laventaja principal del uso de interfaces es que una clase
interfacepuede ser implementada por cualquier número de clases, permitiendo a cada una
compartir el interfaz de programación sin tener que ser conscientede la implementaciónque
hagan las otras.