Home Map Index Search News Archives Links About LF
[Top Bar]
[Bottom Bar]
[Photo of the Author]
Jose M. Fernández
Acerca del Author: Diplomado en informática, en la especialidad de planificación y control de sistemas informáticos por la universidad de Málaga a finales de los años 80. Desde entonces trabajó primero como programador y después como analista de aplicaciones en empresas siempre relacionadas con la Administración. Siempre he trabajado con grandes sistemas (y profesionalmente sigo haciéndolo) pero hace año y medio por casualidad tropecé con Linux y descubrí que en este mundo la libertad se escribe con letras MAYÚSCULAS.

Escribe al autor

Contenidos:
Cuestiones Lexicas
Tipos de datos, variables, operadores
Sentencias de control
Clases, Métodos, Herencia

Java. Parte II

[Ilustration]

Resumen:

Esta serie de artículos tiene como modelo los libros clásicos de programación por lo que intentaremos seguir su estructura en la forma de presentar los contenidos. Una vez realizada una introducción y definidas las características del lenguaje en el primer artículo, continuaremos en este segundo con los tipos de datos, variables, sentencias de control etc., hasta llegar a las clases, el tema central y más importante de toda la serie de artículos. Las clases son la base de este lenguaje de programación. Para poder centrarnos en el tema de las clases la primera parte la haremos lo más esquemática posible ya que al fin y al cabo no varía mucho respecto al resto de lenguajes de programación.


Cuestiones Léxicas

Formalmente un programa en Java está compuesto por un conjunto de comentarios, identificadores, literales, separadores, espacios en blanco y palabras clave.

El compilador recoge código escrito en formato Unicode expandiendo el número de bytes de los 8 del ASCII a 16 y por consiguiente amplia el juego de caracteres para adaptarse a los caracteres de los idiomas no latinos. Los espacios en blanco, tabuladores y los saltos de líneas son eliminados por el compilador ya que no son parte del conjunto de símbolos, esto hace que los programas en Java se puedan formatear de la forma que más nos guste.

Hay tres estilos de comentarios en Java:

// comentario
Se ignoran los caracteres desde // hasta el final de la línea.
/* comentario */
Se ignoran todos los caracteres entres /* y */. Estos comentarios pueden tener una extensión de varias líneas.
/** comentario **/
Igual que los comentarios /* */, pero sólo deberían utilizarse estos comentarios antes de las declaraciones ya que la herramienta javadoc los utiliza para generar documentación automática.

Los identificadores son los nombres que se dan a las variables, clases y métodos. Pueden ser cualquier secuencia de letras en mayúsculas y minúsculas, números y los caracteres subrayado y signo dólar($). No pueden comenzar por un número.

En Java hay algunos caracteres que se utilizan como separadores. El separador utilizado con más frecuencia es el (;) pero además encontraremos:

Símbolo
Descripción
()

Contiene listas de parámetros en la definición y llamada a los métodos. Se utiliza asimismo para precedencia en expresiones, contener expresiones en sentencias de control y en conversiones de tipos.

{}

Para contener los valores de las matrices inicializadas automáticamente. Para definir un bloque de código, para clases, métodos, y ámbitos locales.

[]

Para definir tipos matriz. Para referencia valores de una matriz.

;

Separador de sentencias.

,

Separa identificadores consecutivos en la declaración de variables. Encadena sentencias dentro de una sentencia for.

.

Para separar nombres de paquetes, subpaquetes y clases. Para separar una variable o método de una variable de referencia.

Las palabras claves son identificadores utilizados por el lenguaje Java y no pueden utilizarse de ninguna otra forma que la que está definida por el mismo. En el cuadro siguiente mostramos todas las palabras claves de Java:

abstract double int super
boolean else interface switch
break extends long synchronized
byte false native this
byvalue final new threadsafe
case finally null throw
catch float package transient
char for private true
class goto protected try
const if public void
continue implements return while
default import short  
do instanceof static  

Tipos de datos, Variables, Operadores.

En este punto hay que volver a insistir en que Java es un lenguaje fuertemente tipado, cada variable tiene un tipo y cada tipo está definido estrictamente. En todas las asignaciones, bien sean explícitas o a través de paso de parámetros en llamadas a métodos, se comprueba la compatibilidad de los tipos. No hay conversión automática entre tipos. El compilador comprueba todas las expresiones y parámetros para asegurar que los tipos son compatibles.

En él artículo anterior comentamos que Java era un lenguaje totalmente orientado a objetos, pues bien, por razones de eficiencia Java define ocho tipos de datos "simples" que no son objetos. Además por razones de portabilidad todos los tipos de datos tienen un rango definido estrictamente.

Los tipos de datos simples los podemos distribuir en cuatro grupos:

TIPO
NOMBRE
TAMAÑO
RANGO
Entero long 64 -9.223.372.036.854.775.808
a
9.223.372.036.854.775.807
  int 32 -2.147.483.648
a
2.147.483.647
  short 16 -32.768 a 37.767
  byte 8 -128 a 127
Coma flotante float 32 3.4 e-038 a 3.4 e+038
  double 64 1.7 e-308 a 1.7 e+308
Caracter char 16 Unicode
Boleano boolean   true o false

Variables:

Una variable se define por la combinación de un identificador y un tipo. Opcionalmente podemos inicializar la variable al mismo tiempo que la declaramos. Toda variable tiene un ámbito y un tiempo de vida. Todas las variables tienen que ser declaradas antes de ser usadas. Se pueden declarar en cualquier parte del programa, incluso en el mismo momento de ser utilizada por primera vez.

La forma general de la declaración de una variable es:

tipo identificador [=valor] [,identificador [=valor]......];

donde tipo puede ser un tipo básico de Java, una clase o interfaces. Si inicializamos la variable, la expresión de inicialización debe se del mismo tipo o compatible con el especificado en la variable.

Ejemplos :


         int  a = 4, b, c=7;
	 char c;
	 miclase clase;

Como regla general, la variable definida dentro de un ámbito no podrá ser accedida desde fuera de éste (se define como "ámbito" como la porción de código delimitado entre llaves {}). Una variable no mantendrá su valor una vez que se ha salido de su ámbito.

La mayoría de los lenguajes de programación definen dos categorías de ámbitos: global y local. Estos ámbitos tradicionales no encajan con el modelo orientado a objetos de Java. En éste los dos principales ámbitos son los definidos por una clase y por un método.

Conversión de tipos:

Java permite, aunque en la teoría se niegue, asignar un valor de un tipo a una variable de otro tipo, si los dos tipos son compatibles se realiza una conversión automática y si no lo son se puede realizar una conversión explícita entre tipos incompatibles. Para realizar una conversión automática de tipos se debe cumplir las siguientes condiciones:

Por ejemplo, un tipo int es lo suficientemente grande como para almacenar un tipo byte por lo que no necesita una conversión explícita. Los tipos numéricos no son compatibles con char o boolean y estos no son compatibles con el resto. Si quisiéramos asignar un valor int a un byte tendríamos que utilizar una conversión explícita que tiene el siguiente formato:


(tipo) valor
	    

donde tipo indica el tipo al que se va a convertir. Ejemplo :


         int a;
         char b;
         a=(int) b;
	    

Con la conversión automática de tipos hay que tener un poco de cuidado ya que al realizar la conversión de puede perder información. Por ejemplo:

La conversión de tipos en coma flotante a enteros pierde la parte decimal:


         int a;
         double d= 125.43;
         a=(int) d;
	    

la variable a tiene el valor 125.

         byte   b;
         int i=257;
         b=(byte) i;
	    

la variable b tiene el valor 1, que es el resultado de dividir 257 entre 256 donde 256 es el rango del tipo byte.

         byte b;
         double d= 340.123;
         b=(byte) d;
	    

donde b tendrá el valor de 84;

Todas estas conversiones se realizan con el beneplácito del compilador y del interprete que no ponen ningún tipo de reparo a ello.

Operadores:

Existe un amplio conjunto de operadores, los cuales podemos agrupar en cuatro categorías: aritméticos, a nivel de bit, relacionales y lógicos. Como norma general funcionarán exactamente igual que en otros lenguajes pero hay algunas pequeñas diferencias que iremos matizando sobre la marcha.

Operadores Aritméticos:
Operador
Descripción
+

Suma

-

Resta

*

Multiplicación

/

División

%

Modulo(resto de la división)

++

Incremento

+=

Suma y asignación

-=

Resta y asignación

*=

Multiplicación y asignación

/=

División y asignación

%=

Módulo y asignación

--

Decremento

El operador módulo se puede aplicar tanto a valores enteros como en coma flotante. Por ejemplo:


	 int     a= 38;
	 double  d= 41.95;
	 int     c= a % 10;
	 double  e= d % 10;

La variable c contiene el valor 8. La variable e contiene el valor 1.95;

Los operadores con asignación son útiles en construcciones del tipo:

a = a + 4; es equivalente a a += 4;

a = a % 2; es equivalente a a %= 2;

En general podemos indicar que las sentencias de la forma:

var=var op expresión;

se pueden sustituir por:

var op= expresión;

Operadores a nivel de Bit:

Se definen algunos operadores a nivel de bit que se pueden aplicar a tipos entero: long, int, short, char y byte que pueden modificar los bits de sus operandos.

Operador
Descripción
~

NOT unário a nivel de bit

&

AND a nivel de bit

|

OR a nivel de bit

/\

OR exclusivo a nivel de bit

>>

Desplazamiento a la derecha

>>>>

Desplazamiento a la derecha rellenando con ceros

<<

Desplazamiento a la izquierda

&=

AND a nivel de bit y asignación

|=

OR a nivel de bit y asignación

/\=

OR exclusivo a nivel de bit y asignación

>>=

Desplazamiento a la derecha y asignación

>>>>=

Desplazamiento a la derecha rellenado con ceros y asignación

<<=

Desplazamiento a la izquierda y asignación

Operadores relacionales:

Determinan la relación que un operando tiene con otro, específicamente igualando y ordenando. El resultado de la operación es un valor booleano.

Operador
Descripción
= =

Igual a

!=

Distinto de

>

Mayor que

<

Menor que

>=

Mayor o igual que

<=

Menor o igual que

A diferencia de algunos lenguajes(C/C++) los valores booleanos son True y False, valores no numéricos.

Operadores lógicos booleanos:

Operan únicamente con operandos booleanos y dan como resultado otro operador booleano.

Operador
Descripción
&

AND lógico

|

OR lógico

/\

XOR lógico(OR exclusivo)

||

OR en cortocircuito

&&

AND en cortocircuito

!

NOT unario lógico

&=

Asignación AND

|=

Asignación OR

/\=

Asignación XOR

= =

Igual a

!=

Distinto de

?:

If-then-else ternario

El operador OR en cortocircuito tiene como resultado verdadero cuando el primer operador es verdadero independientemente del segundo operador. De forma similar el operador AND en cortocircuito es falso cuando el primer operador es falso independientemente del valor del segundo operador.

El formato general del operador ternario es:

Expesion1 ? expesion2 : expresion3

Si Expresion1 es true se ejecutará la expresion2, si es false se ejecutará la expresion3.

Precedencia de operadores:

Alta
( )
[ ]
.
 
++
--
~
!
*
/
%
 
+
-
   
>>
>>>>
<<
 
>
>=
<
<=
= =
!=
   
&
     
'
     
|
     
&&
     
||
     
?:
     
=
Op=
   
Baja

Sentencias de control

Las sentencias de control el Java se pueden dividir en tres categorías: Selección, Iteración y Salto.

Grupo
Sentencia
Descripción

Selección
if
if ( condición ) 
  sentencia1;
else  sentencia2; 
	    
 
if múltiple
if ( condición ) 
  sentencia;
else if ( condición) 
  sentencia;
else if ( condición) 
sentencia;
.
.
else 
  sentencia;
 
switch
switch ( expresión){
  case valor1:
     sentencia;
     break;
  case valor2:
     sentencia;
     break;
.
.
.
.
  default:
     sentencia;
}
Iteración
while
while ( condición) {
  sentencias;
}
 
do while
do {
   sentencias;
} while (condición)
 
for
for (iniciación, condición, iteración) {
    sentencias;
}
	    
Salto
break
Para escapar de un switch.
Para escapar de un bucle
 
continue
Escapa de la iteración del bucle pero continua en el mismo bucle
 
return
Volver explícitamente desde un método

Clases, Métodos, Herencia

Como indicamos en el primer artículo Java es un lenguaje que se diseñó partiendo de cero y como resultado de esto fue una aproximación limpia y útil a la programación orientada a objetos. De hecho todos los programas Java son orientados a objetos. Como puede comprender no intente buscar en este artículo y los siguientes algo para ampliar sus conocimientos sobre la programación orientada a objetos, para ello hay literatura abundante y amplia para profundizar en su conocimiento tanto como se quiera. Pero es tan importante la programación orientada a objetos para Java que es necesario entender sus principios básicos antes de empezar a programar en este lenguaje. No nos queda más remedio que manejar y utilizar elementos principios y características de este paradigma aunque siempre seguiremos la nomenclatura de Java e intentaremos de la forma más breve y clara posible definir los elementos que hacen de Java un lenguaje totalmente orientado a objetos.

CLASES :

Es el núcleo de Java. Define la forma y naturaleza de un objeto, constituye la base de la programación orientada a objetos. Una clase define un nuevo tipo de dato, este nuevo tipo se puede utilizar para crear objetos de este tipo.

Una clase es un modelo (plantilla) para un objeto y un objeto es una instancia de una clase. Java no soporta funciones o variables globales todas las acciones (métodos) de los programas se definen dentro de una clase.

Una clase se define utilizando la palabra clave class. La forma general de la definición de una clase es:

Class  nombre_de_clase {
  Tipo variable_de_instancia1;
  Tipo variable_de_instancia2;
  .
  .
  ...
  tipo variable_de_instancia;

  tipo nombre_de_metodo1(lista_de_parametros){
    //cuerpo del método
  }
  .
  .
  ..
  tipo nombre_de_metodo(lista_de_parametros){
    //cuerpo del método
  }
}
	    

Los datos o variables definidos en una clase se llaman variables de instancias. El código está contenido en los métodos, estos son los que determinan como se pueden utilizar los datos de una clase.

Para obtener objetos de una clase es necesario realizar dos pasos:

  1. declarar una variable del tipo de la clase. Esta variable no define un objeto, es una variable con la que podemos referenciar un objeto.
  2. Asignar dinámicamente memoria para el objeto y obtener una referencia del mismo.

Este segunda paso se realiza utilizando el operador new, su formato general es:


variable = new nonbre_de_clase();
	    

Donde variable es una variable de la clase que se quiere crear y nombre_de_clase es el nombre de la clase que está siendo instanciado. Gráficamente podíamos representar el creación de un objeto de la forma siguiente:

MÉTODOS:

El formato general de un método es:


tipo nombre_del_metodo (lista_de_parametros) {
  //cuerpo del método
}    

tipo indica el tipo de dato devuelto por el método, puede ser cualquier tipo valido incluido los tipos de clase o no puede devolver ningún valor (void).

La lista de parámetros es una secuencia de parejas de tipo-identificador separados por coma. Los parámetros son variables que reciben el valor de los argumentos que se pasan al método, si el método no tiene parámetros entonces la lista estará vacía.

Los métodos que devuelven un valor distinto de void a la rutina que lo llama utiliza:


return valor;	    

Donde valor es el valor devuelto.

Java da mucho poder y flexibilidad a los métodos por lo que iremos, desde aquí, hasta el final del artículo, describiendo cada uno de los aspectos más importantes de los métodos.

Aunque antes de seguir avanzando repasaremos todo lo visto hasta ahora viendo un ejemplo simple. Crearemos una clase que calcule la capacidad de un recipiente rectangular(como una especie de piscina):

Código
Comentarios
class capacidad {
  double  largo;
  double  ancho;
  double  alto;

  void  CalcVolumen () {

    double volumen ;
    volumen= largo*ancho*alto;
    System.out.println("Volumen:" 
                       + volumen);

  }
}	    

Como vemos, hemos definido una clase que hemos llamado capacidad en la que definimos tres variables de instancia: largo, ancho y alto. También se define un método que calcula el volumen del recipiente. El archivo fuente lo llamaremos capacidad.java, al compilarlo creará una clase llamada capacidad.class

Esta clase por sí sola no realiza ninguna acción ya que no es un "applet" ni tiene el método main() para ejecutarlo desde la línea de comando. Hemos creado un modelo o plantilla para crear objetos o instancias de esta clase. Para poder ejecutarla crearemos una clase que se pueda ejecutar desde la línea de comandos.

Código
Comentarios
class ejemplo {
  public static void main(String Arg[]){
    capacidad p1=new capacidad();
    capacidad p2=new capacidad();
    p1.largo = 16;
    p1.ancho=3;
    p1.alto=2;
    //
    p2.largo = 25;
    p2.ancho=6;
    p2.alto=3;
    //
    p1.CalcVolumen();
    //
    p2.CalcVolumen();
  }
} 

Declaramos dos variables del tipo capacidad p1,p2. Con el operador new creamos dos objetos del tipo capacidad a los cuales podemos referenciarnos a través de las variables p1, p2.

A continuación asignamos valores a las variables de cada uno de los objetos creados.

Llamamos al método CalcVolumen del objeto referenciado en p1 y como resultado tendremos por pantalla:

"el volumen es 80".

Idem para el objeto referenciado en p2. Obtenemos como resultado:

"el volumen es 450"

Cuando se ejecuta p1.CalcVolumen(), el interprete de Java transfiere el control al código definido dentro de CalcVolumen(), una vez que se ejecuta todas las sentencias el control vuelve a la rutina llamante y la ejecución continua en la línea siguiente a la llamada.

Métodos con parámetros. Devolución de valores.

La mayoría de los métodos se utilizan con parámetros que permiten generalizarlos. Además los métodos pueden a su vez devolver valores por lo que podemos construir métodos que puedan trabajar con diversidad de datos y puedan ser utilizados en diferentes situaciones.

Nuestro ejemplo lo mejoraremos:

Código
Comentarios
class capacidad {

  double  CalcularVolumen (double l, 
                           double a,
                           double p){
    double volumen=l*a*p ;
    return volumen;
  }
}

El método CalcVolumen ha sido modificado para que reciba tres parámetros. Además se define para que devuelva un tipo double, acción que realiza con la instrucción return volumen.

class ejemplo {
  public static void main(String Arg[]) {
    capacidad p1=new capacidad();
    capacidad p2=new capacidad();
    double vol;
    vol=p1.CalcVolumen(10,3,2);
    System.out.println("Volumen:" + vol);
    //
    vol=p2.CalcVolumen(25,5,2);
    System.out.println("Volumen:" + vol);
  }
}

La llamada al método se realiza enviando los parámetros deseados. Además devuelve el valor del método en la variable vol, que debe ser del mismo tipo que el método.

Un aspecto importante de las clases son los constructores. Estos definen qué ocurre cuando se crea un objeto de una clase. La mayoría de las clases definen explícitamente sus propios constructores dentro de la definición de la clase, pero si no se especifica, entonces Java utiliza un constructor por defecto (esto es lo que ocurre en nuestro ejemplo).

Un constructor incluido en una clase tiene exactamente el mismo nombre que la clase, sintácticamente es similar a un método. Es ejecutado automáticamente después de crear el objeto, antes de que termine el operador new.

Los constructores no devuelven ningún tipo, ya que implícitamente devuelve el propio tipo de la clase. La tarea del constructor es inicializar todo el estado interno de un objeto para que el código que crea una instancia tenga el objeto integro y utilizable inmediatamente. Los constructores por defecto inicializan las variables de instancias por defecto. Como ocurre con los métodos, los constructores pueden tener parámetros con lo que hacen a estos muchos más útiles. Volveremos a modificar nuestro ejemplo para ver estos nuevos aspectos.

Código
Comentarios
Class capacidad {
  double  largo;
  double  ancho;
  double  alto;
  //
  capacidad(double l, 
            double a, 
            double p){
    largo=l;
    ancho=a;
    altao=p;
  }
  //
  void  CalcularVolumen () {

    double volumen ;
    volumen= largo*ancho*alto;
    return volumen;
  }
}

A la clase se le añade un constructor, que tiene aspecto de un método con el nombre igual a la clase pero sin tipo. Este constructor inicializa las variables de instancias declaradas con los argumentos pasados.

class ejemplo {
  public static void main(String Arg[]) {
    capacidad p1=new capacidad(10,5,2);
    capacidad p2=new capacidad(25,6,2);
    double vol;
    vol=p1.CalcVolumen();
    System.out.println("Volumen:" + vol);
    //
    vol=p2.CalcVolumen();
    System.out.println("Volumen:" + vol);
}
	  

El operador new crea las instancias de la clase, pasando los parámetros necesarios al constructor de dicha clase.

Cuando no hay ninguna referencia a un objeto se asume que este objeto no se va a utilizar más y la memoria utilizada por dicho objeto se libera. No hay necesidad de destruir explícitamente a los objetos, este proceso se hace de forma esporádica y automática durante la ejecución del programa.

No obstante para añadir un finalizador a una clase se realiza añadiendo un método finalice(), el interprete ejecutará este método cuando destruya al objeto. En este método incluiremos aquellas acciones que se quieran realizar antes de destruir al objeto.

Sobrecarga de Métodos.

El polimorfismo (una interfaz múltiples métodos) es uno de los pilares básicos de la programación orientada a objetos. Java implementa el polimorfismo por medio de la sobrecarga de métodos.

En una clase se pueden definir varios métodos con el mismo nombre, pero con lista de parámetros distinta o tipo de valores devuelto. Cuando se invoca al método sobrecargado el interprete de Java utiliza el tipo y/o los argumentos con los que se invoca para determinar la versión del método que debe ejecutar.

Cada versión del método sobrecargado puede realizar tareas distintas aunque esto hace totalmente imposible el polimorfismo, por lo que es muy recomendable que la sobrecarga de métodos implique una relación. De la misma forma que se sobrecargan los métodos se pueden sobrecargas los constructores.

Paso de Argumentos.

En general los lenguajes de programación permiten pasar los argumentos de dos formas:

Por valor:
Copia el valor del argumento en la varible-parametro de la rutina.
Por referencia:
Se pasa la referencia (la dirección de memoria) donde está el valor, en este caso si la rutina modifica el valor también se modifica en el proceso que lo llama.

En Java existen también dos medios para pasar parámetros a los métodos. Si el argumento es un tipo simple éste se pasa por valor, pero cuando el argumento es un objeto se pasa por referencia.

Control de Acceso.

Otro pilar básico de la programación orientada a objeto es la encapsulación. Ésta relaciona los datos con el código que la utiliza. Además la encapsulación proporciona el control de acceso, es decir que parte del programa puede acceder a los miembros de una clase.

Las especificaciones de acceso en Java son public, private y protected. Cuando un método o variable de instancia se define como public entonces puede ser accedido por cualquier parte del programa. Si se define como private sólo puede ser accedido por otros miembros de su clase. Por defecto todos los métodos o variables de instancia (miembros) son public (para su propio paquete). El especificador protected se utiliza cuando se trabaja con herencias que veremos a continuación.

HERENCIA.

Los tres elementos que define la programación orientada objetos son el polimorfismo, la encapsulación y por fin la herencia, por este mecanismo podemos definir una clase general (superclase) con unas características comunes y esta superclase puede ser heredada por otras clases más especificas añadiendo cada una de estas cosas particulares.

Para que una clase B herede las características de una superclase A, en la definición de la clase B haremos:


Class B extends A {
  // defincion de la clase
}

La clase B incluye todos los miembros de la superclase A, ésta última es también una clase autónoma y puede ser utilizada libremente, además B puede se una superclase de otra clase.

Java no permite una herencia múltiple de varias superclases en una subclase.

Si en la superclase hay miembros definidos como private no pueden ser accedidos por las clases herederas. Cuando una subclase necesite referenciar a una superclase inmediata se puede utilizar la palabra reservada super. Con esto podemos llamar al constructor o a miembros de la superclase que han sido ocultados por la subclase.

Cuando un método de una subclase tiene el mismo nombre y tipo que un método de la superclase se dice que el método está sobrescrito. Esto constituye la base de unos de los aspectos más poderosos de Java que es la "Selección de método dinámica", es decir, es en tiempo de ejecución cuando se resuelve qué método es el llamado, es el tipo del objeto que está siendo referenciado y no el tipo de la variable de referencia el que determina el método a ejecutar.

En el siguiente artículo veremos en toda su potencia la herencia con las clases abstractas, interfaces etc.

Referencias


Texto original en Castellano


Páginas web mantenidas por Miguel Ángel Sepúlveda
© Jose M. Fernandez 1998
LinuxFocus 1998